Fix rc_logid in RecentChange::loadFromCurRow
[lhc/web/wiklou.git] / includes / User.php
index 531123b..e210eba 100644 (file)
@@ -123,6 +123,7 @@ class User {
                'deleterevision',
                'edit',
                'editinterface',
+               'editprotected',
                'editusercssjs', #deprecated
                'editusercss',
                'edituserjs',
@@ -140,12 +141,15 @@ class User {
                'nominornewtalk',
                'noratelimit',
                'override-export-depth',
+               'passwordreset',
                'patrol',
+               'patrolmarks',
                'protect',
                'proxyunbannable',
                'purge',
                'read',
                'reupload',
+               'reupload-own',
                'reupload-shared',
                'rollback',
                'sendemail',
@@ -282,7 +286,10 @@ class User {
                                $this->loadFromId();
                                break;
                        case 'session':
-                               $this->loadFromSession();
+                               if( !$this->loadFromSession() ) {
+                                       // Loading from session failed. Load defaults.
+                                       $this->loadDefaults();
+                               }
                                wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) );
                                break;
                        default:
@@ -448,11 +455,12 @@ class User {
         * will be loaded once more from the database when accessing them.
         *
         * @param $row Array A row from the user table
+        * @param $data Array Further data to load into the object (see User::loadFromRow for valid keys)
         * @return User
         */
-       public static function newFromRow( $row ) {
+       public static function newFromRow( $row, $data = null ) {
                $user = new User;
-               $user->loadFromRow( $row );
+               $user->loadFromRow( $row, $data );
                return $user;
        }
 
@@ -464,8 +472,7 @@ class User {
         * @return String|bool The corresponding username
         */
        public static function whoIs( $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
-               return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), __METHOD__ );
+               return UserCache::singleton()->getProp( $id, 'name' );
        }
 
        /**
@@ -475,8 +482,7 @@ class User {
         * @return String|bool The corresponding user's real name
         */
        public static function whoIsReal( $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
-               return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), __METHOD__ );
+               return UserCache::singleton()->getProp( $id, 'real_name' );
        }
 
        /**
@@ -622,7 +628,7 @@ class User {
                // Certain names may be reserved for batch processes.
                foreach ( $reservedUsernames as $reserved ) {
                        if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
-                               $reserved = wfMsgForContent( substr( $reserved, 4 ) );
+                               $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
                        }
                        if ( $reserved == $name ) {
                                return false;
@@ -760,6 +766,7 @@ class User {
         *                - 'usable'     Valid for batch processes and login
         *                - 'creatable'  Valid for batch processes, login and account creation
         *
+        * @throws MWException
         * @return bool|string
         */
        public static function getCanonicalName( $name, $validate = 'valid' ) {
@@ -811,39 +818,16 @@ class User {
 
        /**
         * Count the number of edits of a user
-        * @todo It should not be static and some day should be merged as proper member function / deprecated -- domas
         *
         * @param $uid Int User ID to check
         * @return Int the user's edit count
+        *
+        * @deprecated since 1.21 in favour of User::getEditCount
         */
        public static function edits( $uid ) {
-               wfProfileIn( __METHOD__ );
-               $dbr = wfGetDB( DB_SLAVE );
-               // check if the user_editcount field has been initialized
-               $field = $dbr->selectField(
-                       'user', 'user_editcount',
-                       array( 'user_id' => $uid ),
-                       __METHOD__
-               );
-
-               if( $field === null ) { // it has not been initialized. do so.
-                       $dbw = wfGetDB( DB_MASTER );
-                       $count = $dbr->selectField(
-                               'revision', 'count(*)',
-                               array( 'rev_user' => $uid ),
-                               __METHOD__
-                       );
-                       $dbw->update(
-                               'user',
-                               array( 'user_editcount' => $count ),
-                               array( 'user_id' => $uid ),
-                               __METHOD__
-                       );
-               } else {
-                       $count = $field;
-               }
-               wfProfileOut( __METHOD__ );
-               return $count;
+               wfDeprecated( __METHOD__, '1.21' );
+               $user = self::newFromId( $uid );
+               return $user->getEditCount();
        }
 
        /**
@@ -887,7 +871,7 @@ class User {
                if( $loggedOut !== null ) {
                        $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
                } else {
-                       $this->mTouched = '0'; # Allow any pages to be cached
+                       $this->mTouched = '1'; # Allow any pages to be cached
                }
 
                $this->mToken = null; // Don't run cryptographic functions till we need a token
@@ -931,8 +915,7 @@ class User {
        }
 
        /**
-        * Load user data from the session or login cookie. If there are no valid
-        * credentials, initialises the user as an anonymous user.
+        * Load user data from the session or login cookie.
         * @return Bool True if the user is logged in, false otherwise.
         */
        private function loadFromSession() {
@@ -960,7 +943,6 @@ class User {
                if ( $cookieId !== null ) {
                        $sId = intval( $cookieId );
                        if( $sessId !== null && $cookieId != $sessId ) {
-                               $this->loadDefaults(); // Possible collision!
                                wfDebugLog( 'loginSessions', "Session user ID ($sessId) and
                                        cookie user ID ($sId) don't match!" );
                                return false;
@@ -969,7 +951,6 @@ class User {
                } elseif ( $sessId !== null && $sessId != 0 ) {
                        $sId = $sessId;
                } else {
-                       $this->loadDefaults();
                        return false;
                }
 
@@ -979,21 +960,18 @@ class User {
                        $sName = $request->getCookie( 'UserName' );
                        $request->setSessionData( 'wsUserName', $sName );
                } else {
-                       $this->loadDefaults();
                        return false;
                }
 
                $proposedUser = User::newFromId( $sId );
                if ( !$proposedUser->isLoggedIn() ) {
                        # Not a valid ID
-                       $this->loadDefaults();
                        return false;
                }
 
                global $wgBlockDisablesLogin;
                if( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
                        # User blocked and we've disabled blocked user logins
-                       $this->loadDefaults();
                        return false;
                }
 
@@ -1005,7 +983,6 @@ class User {
                        $from = 'cookie';
                } else {
                        # No session or persistent login cookie
-                       $this->loadDefaults();
                        return false;
                }
 
@@ -1017,7 +994,6 @@ class User {
                } else {
                        # Invalid credentials
                        wfDebug( "User: can't log in from $from, invalid credentials\n" );
-                       $this->loadDefaults();
                        return false;
                }
        }
@@ -1061,8 +1037,12 @@ class User {
         * Initialize this object from a row from the user table.
         *
         * @param $row Array Row from the user table to load.
+        * @param $data Array Further user data to load into the object
+        *
+        *      user_groups             Array with groups out of the user_groups table
+        *      user_properties         Array with properties out of the user_properties table
         */
-       public function loadFromRow( $row ) {
+       public function loadFromRow( $row, $data = null ) {
                $all = true;
 
                $this->mGroups = null; // deferred
@@ -1120,6 +1100,15 @@ class User {
                if ( $all ) {
                        $this->mLoadedItems = true;
                }
+
+               if ( is_array( $data ) ) {
+                       if ( is_array( $data['user_groups'] ) ) {
+                               $this->mGroups = $data['user_groups'];
+                       }
+                       if ( is_array( $data['user_properties'] ) ) {
+                               $this->loadOptions( $data['user_properties'] );
+                       }
+               }
        }
 
        /**
@@ -1193,7 +1182,9 @@ class User {
        }
 
        /**
-        * Clear various cached data stored in this object.
+        * Clear various cached data stored in this object. The cache of the user table
+        * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
+        *
         * @param $reloadFrom bool|String Reload user and user_groups table data from a
         *   given source. May be "name", "id", "defaults", "session", or false for
         *   no reload.
@@ -1207,6 +1198,8 @@ class User {
                $this->mEffectiveGroups = null;
                $this->mImplicitGroups = null;
                $this->mOptions = null;
+               $this->mOptionsLoaded = false;
+               $this->mEditCount = null;
 
                if ( $reloadFrom ) {
                        $this->mLoadedItems = array();
@@ -1225,9 +1218,8 @@ class User {
 
                $defOpt = $wgDefaultUserOptions;
                # default language setting
-               $variant = $wgContLang->getDefaultVariant();
-               $defOpt['variant'] = $variant;
-               $defOpt['language'] = $variant;
+               $defOpt['variant'] = $wgContLang->getCode();
+               $defOpt['language'] = $wgContLang->getCode();
                foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
                        $defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
                }
@@ -1303,13 +1295,13 @@ class User {
                        # Local list
                        if ( self::isLocallyBlockedProxy( $ip ) ) {
                                $block = new Block;
-                               $block->setBlocker( wfMsg( 'proxyblocker' ) );
-                               $block->mReason = wfMsg( 'proxyblockreason' );
+                               $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
+                               $block->mReason = wfMessage( 'proxyblockreason' )->text();
                                $block->setTarget( $ip );
                        } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
                                $block = new Block;
-                               $block->setBlocker( wfMsg( 'sorbs' ) );
-                               $block->mReason = wfMsg( 'sorbsreason' );
+                               $block->setBlocker( wfMessage( 'sorbs' )->text() );
+                               $block->mReason = wfMessage( 'sorbsreason' )->text();
                                $block->setTarget( $ip );
                        }
                }
@@ -1388,11 +1380,11 @@ class User {
                                $ipList = gethostbynamel( $host );
 
                                if( $ipList ) {
-                                       wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
+                                       wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
                                        $found = true;
                                        break;
                                } else {
-                                       wfDebug( "Requested $host, not found in $base.\n" );
+                                       wfDebugLog( 'dnsblacklist', "Requested $host, not found in $base.\n" );
                                }
                        }
                }
@@ -1764,16 +1756,22 @@ class User {
                        # Check memcached separately for anons, who have no
                        # entire User object stored in there.
                        if( !$this->mId ) {
-                               global $wgMemc;
-                               $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
-                               $newtalk = $wgMemc->get( $key );
-                               if( strval( $newtalk ) !== '' ) {
-                                       $this->mNewtalk = (bool)$newtalk;
+                               global $wgDisableAnonTalk;
+                               if( $wgDisableAnonTalk ) {
+                                       // Anon newtalk disabled by configuration.
+                                       $this->mNewtalk = false;
                                } else {
-                                       // Since we are caching this, make sure it is up to date by getting it
-                                       // from the master
-                                       $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true );
-                                       $wgMemc->set( $key, (int)$this->mNewtalk, 1800 );
+                                       global $wgMemc;
+                                       $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
+                                       $newtalk = $wgMemc->get( $key );
+                                       if( strval( $newtalk ) !== '' ) {
+                                               $this->mNewtalk = (bool)$newtalk;
+                                       } else {
+                                               // Since we are caching this, make sure it is up to date by getting it
+                                               // from the master
+                                               $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true );
+                                               $wgMemc->set( $key, (int)$this->mNewtalk, 1800 );
+                                       }
                                }
                        } else {
                                $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
@@ -1789,14 +1787,20 @@ class User {
         */
        public function getNewMessageLinks() {
                $talks = array();
-               if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) )
+               if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
                        return $talks;
-
-               if( !$this->getNewtalk() )
+               } elseif( !$this->getNewtalk() ) {
                        return array();
-               $up = $this->getUserPage();
-               $utp = $up->getTalkPage();
-               return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL() ) );
+               }
+               $utp = $this->getTalkPage();
+               $dbr = wfGetDB( DB_SLAVE );
+               // Get the "last viewed rev" timestamp from the oldest message notification
+               $timestamp = $dbr->selectField( 'user_newtalk',
+                       'MIN(user_last_timestamp)',
+                       $this->isAnon() ? array( 'user_ip' => $this->getName() ) : array( 'user_id' => $this->getID() ),
+                       __METHOD__ );
+               $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
+               return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ) );
        }
 
        /**
@@ -1823,12 +1827,17 @@ class User {
         * Add or update the new messages flag
         * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
         * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+        * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null.
         * @return Bool True if successful, false otherwise
         */
-       protected function updateNewtalk( $field, $id ) {
+       protected function updateNewtalk( $field, $id, $curRev = null ) {
+               // Get timestamp of the talk page revision prior to the current one
+               $prevRev = $curRev ? $curRev->getPrevious() : false;
+               $ts = $prevRev ? $prevRev->getTimestamp() : null;
+               // Mark the user as having new messages since this revision
                $dbw = wfGetDB( DB_MASTER );
                $dbw->insert( 'user_newtalk',
-                       array( $field => $id ),
+                       array( $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ),
                        __METHOD__,
                        'IGNORE' );
                if ( $dbw->affectedRows() ) {
@@ -1863,8 +1872,9 @@ class User {
        /**
         * Update the 'You have new messages!' status.
         * @param $val Bool Whether the user has new messages
+        * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null or !$val.
         */
-       public function setNewtalk( $val ) {
+       public function setNewtalk( $val, $curRev = null ) {
                if( wfReadOnly() ) {
                        return;
                }
@@ -1882,7 +1892,7 @@ class User {
                global $wgMemc;
 
                if( $val ) {
-                       $changed = $this->updateNewtalk( $field, $id );
+                       $changed = $this->updateNewtalk( $field, $id, $curRev );
                } else {
                        $changed = $this->deleteNewtalk( $field, $id );
                }
@@ -1996,7 +2006,7 @@ class User {
 
                if( $str !== null ) {
                        if( !$wgAuth->allowPasswordChange() ) {
-                               throw new PasswordError( wfMsg( 'password-change-forbidden' ) );
+                               throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() );
                        }
 
                        if( !$this->isValidPassword( $str ) ) {
@@ -2009,12 +2019,12 @@ class User {
                                        $message = $valid;
                                        $params = array( $wgMinimalPasswordLength );
                                }
-                               throw new PasswordError( wfMsgExt( $message, array( 'parsemag' ), $params ) );
+                               throw new PasswordError( wfMessage( $message, $params )->text() );
                        }
                }
 
                if( !$wgAuth->setPassword( $this, $str ) ) {
-                       throw new PasswordError( wfMsg( 'externaldberror' ) );
+                       throw new PasswordError( wfMessage( 'externaldberror' )->text() );
                }
 
                $this->setInternalPassword( $str );
@@ -2450,7 +2460,21 @@ class User {
                if( $this->getId() ) {
                        if ( !isset( $this->mEditCount ) ) {
                                /* Populate the count, if it has not been populated yet */
-                               $this->mEditCount = User::edits( $this->mId );
+                               wfProfileIn( __METHOD__ );
+                               $dbr = wfGetDB( DB_SLAVE );
+                               // check if the user_editcount field has been initialized
+                               $count = $dbr->selectField(
+                                       'user', 'user_editcount',
+                                       array( 'user_id' => $this->mId ),
+                                       __METHOD__
+                               );
+
+                               if( $count === null ) {
+                                       // it has not been initialized. do so.
+                                       $count = $this->initEditCount();
+                               }
+                               wfProfileOut( __METHOD__ );
+                               $this->mEditCount = $count;
                        }
                        return $this->mEditCount;
                } else {
@@ -2778,9 +2802,13 @@ class User {
         * @param $value String Value to set
         * @param $exp Int Expiration time, as a UNIX time value;
         *                   if 0 or not specified, use the default $wgCookieExpiration
+        * @param $secure Bool
+        *  true: Force setting the secure attribute when setting the cookie
+        *  false: Force NOT setting the secure attribute when setting the cookie
+        *  null (default): Use the default ($wgCookieSecure) to set the secure attribute
         */
-       protected function setCookie( $name, $value, $exp = 0 ) {
-               $this->getRequest()->response()->setcookie( $name, $value, $exp );
+       protected function setCookie( $name, $value, $exp = 0, $secure = null ) {
+               $this->getRequest()->response()->setcookie( $name, $value, $exp, null, null, $secure );
        }
 
        /**
@@ -2796,8 +2824,9 @@ class User {
         *
         * @param $request WebRequest object to use; $wgRequest will be used if null
         *        is passed.
+        * @param $secure Whether to force secure/insecure cookies or use default
         */
-       public function setCookies( $request = null ) {
+       public function setCookies( $request = null, $secure = null ) {
                if ( $request === null ) {
                        $request = $this->getRequest();
                }
@@ -2836,9 +2865,18 @@ class User {
                        if ( $value === false ) {
                                $this->clearCookie( $name );
                        } else {
-                               $this->setCookie( $name, $value );
+                               $this->setCookie( $name, $value, 0, $secure );
                        }
                }
+
+               /**
+                * If wpStickHTTPS was selected, also set an insecure cookie that
+                * will cause the site to redirect the user to HTTPS, if they access
+                * it over HTTP. Bug 29898.
+                */
+               if ( $request->getCheck( 'wpStickHTTPS' ) ) {
+                       $this->setCookie( 'forceHTTPS', 'true', time() + 2592000, false ); //30 days
+               }
        }
 
        /**
@@ -2861,6 +2899,7 @@ class User {
 
                $this->clearCookie( 'UserID' );
                $this->clearCookie( 'Token' );
+               $this->clearCookie( 'forceHTTPS' );
 
                # Remember when user logged out, to prevent seeing cached pages
                $this->setCookie( 'LoggedOut', wfTimestampNow(), time() + 86400 );
@@ -2871,11 +2910,16 @@ class User {
         * @todo Only rarely do all these fields need to be set!
         */
        public function saveSettings() {
+               global $wgAuth;
+
                $this->load();
                if ( wfReadOnly() ) { return; }
                if ( 0 == $this->mId ) { return; }
 
                $this->mTouched = self::newTouchedTimestamp();
+               if ( !$wgAuth->allowSetLocalPassword() ) {
+                       $this->mPassword = '';
+               }
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->update( 'user',
@@ -2972,7 +3016,29 @@ class User {
        }
 
        /**
-        * Add this existing user object to the database
+        * Add this existing user object to the database. If the user already
+        * exists, a fatal status object is returned, and the user object is
+        * initialised with the data from the database.
+        *
+        * Previously, this function generated a DB error due to a key conflict
+        * if the user already existed. Many extension callers use this function
+        * in code along the lines of:
+        *
+        *   $user = User::newFromName( $name );
+        *   if ( !$user->isLoggedIn() ) {
+        *       $user->addToDatabase();
+        *   }
+        *   // do something with $user...
+        *
+        * However, this was vulnerable to a race condition (bug 16020). By
+        * initialising the user object if the user exists, we aim to support this
+        * calling sequence as far as possible.
+        *
+        * Note that if the user exists, this function will acquire a write lock,
+        * so it is still advisable to make the call conditional on isLoggedIn(),
+        * and to commit the transaction after calling.
+        *
+        * @return Status
         */
        public function addToDatabase() {
                $this->load();
@@ -2995,14 +3061,31 @@ class User {
                                'user_registration' => $dbw->timestamp( $this->mRegistration ),
                                'user_editcount' => 0,
                                'user_touched' => $dbw->timestamp( $this->mTouched ),
-                       ), __METHOD__
+                       ), __METHOD__,
+                       array( 'IGNORE' )
                );
+               if ( !$dbw->affectedRows() ) {
+                       $this->mId = $dbw->selectField( 'user', 'user_id',
+                               array( 'user_name' => $this->mName ), __METHOD__ );
+                       $loaded = false;
+                       if ( $this->mId ) {
+                               if ( $this->loadFromDatabase() ) {
+                                       $loaded = true;
+                               }
+                       }
+                       if ( !$loaded ) {
+                               throw new MWException( __METHOD__. ": hit a key conflict attempting " .
+                                       "to insert a user row, but then it doesn't exist when we select it!" );
+                       }
+                       return Status::newFatal( 'userexists' );
+               }
                $this->mId = $dbw->insertId();
 
                // Clear instance cache other than user table data, which is already accurate
                $this->clearInstanceCache();
 
                $this->saveOptions();
+               return Status::newGood();
        }
 
        /**
@@ -3333,15 +3416,15 @@ class User {
                        $message = 'confirmemail_body_' . $type;
                }
 
-               return $this->sendMail( wfMsg( 'confirmemail_subject' ),
-                       wfMsg( $message,
+               return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
+                       wfMessage( $message,
                                $this->getRequest()->getIP(),
                                $this->getName(),
                                $url,
                                $wgLang->timeanddate( $expiration, false ),
                                $invalidateURL,
                                $wgLang->date( $expiration, false ),
-                               $wgLang->time( $expiration, false ) ) );
+                               $wgLang->time( $expiration, false ) )->text() );
        }
 
        /**
@@ -3604,14 +3687,27 @@ class User {
        public static function getGroupsWithPermission( $role ) {
                global $wgGroupPermissions;
                $allowedGroups = array();
-               foreach ( $wgGroupPermissions as $group => $rights ) {
-                       if ( isset( $rights[$role] ) && $rights[$role] ) {
+               foreach ( array_keys( $wgGroupPermissions ) as $group ) {
+                       if ( self::groupHasPermission( $group, $role ) ) {
                                $allowedGroups[] = $group;
                        }
                }
                return $allowedGroups;
        }
 
+       /**
+        * Check, if the given group has the given permission
+        *
+        * @param $group String Group to check
+        * @param $role String Role to check
+        * @return bool
+        */
+       public static function groupHasPermission( $group, $role ) {
+               global $wgGroupPermissions, $wgRevokePermissions;
+               return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
+                       && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+       }
+
        /**
         * Get the localized descriptive name for a group, if it exists
         *
@@ -3850,43 +3946,63 @@ class User {
        public function incEditCount() {
                if( !$this->isAnon() ) {
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'user',
+                       $dbw->update(
+                               'user',
                                array( 'user_editcount=user_editcount+1' ),
                                array( 'user_id' => $this->getId() ),
-                               __METHOD__ );
+                               __METHOD__
+                       );
 
                        // Lazy initialization check...
                        if( $dbw->affectedRows() == 0 ) {
-                               // Pull from a slave to be less cruel to servers
-                               // Accuracy isn't the point anyway here
-                               $dbr = wfGetDB( DB_SLAVE );
-                               $count = $dbr->selectField( 'revision',
-                                       'COUNT(rev_user)',
-                                       array( 'rev_user' => $this->getId() ),
-                                       __METHOD__ );
-
                                // Now here's a goddamn hack...
+                               $dbr = wfGetDB( DB_SLAVE );
                                if( $dbr !== $dbw ) {
                                        // If we actually have a slave server, the count is
                                        // at least one behind because the current transaction
                                        // has not been committed and replicated.
-                                       $count++;
+                                       $this->initEditCount( 1 );
                                } else {
                                        // But if DB_SLAVE is selecting the master, then the
                                        // count we just read includes the revision that was
                                        // just added in the working transaction.
+                                       $this->initEditCount();
                                }
-
-                               $dbw->update( 'user',
-                                       array( 'user_editcount' => $count ),
-                                       array( 'user_id' => $this->getId() ),
-                                       __METHOD__ );
                        }
                }
                // edit count in user cache too
                $this->invalidateCache();
        }
 
+       /**
+        * Initialize user_editcount from data out of the revision table
+        *
+        * @param $add Integer Edits to add to the count from the revision table
+        * @return Integer Number of edits
+        */
+       protected function initEditCount( $add = 0 ) {
+               // Pull from a slave to be less cruel to servers
+               // Accuracy isn't the point anyway here
+               $dbr = wfGetDB( DB_SLAVE );
+               $count = $dbr->selectField(
+                       'revision',
+                       'COUNT(rev_user)',
+                       array( 'rev_user' => $this->getId() ),
+                       __METHOD__
+               );
+               $count = $count + $add;
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->update(
+                       'user',
+                       array( 'user_editcount' => $count ),
+                       array( 'user_id' => $this->getId() ),
+                       __METHOD__
+               );
+
+               return $count;
+       }
+
        /**
         * Get the description of a given right
         *
@@ -3993,10 +4109,10 @@ class User {
                        $action = 'create2';
                        if ( $byEmail ) {
                                if ( $reason === '' ) {
-                                       $reason = wfMsgForContent( 'newuserlog-byemail' );
+                                       $reason = wfMessage( 'newuserlog-byemail' )->inContentLanguage()->text();
                                } else {
                                        $reason = $wgContLang->commaList( array(
-                                               $reason, wfMsgForContent( 'newuserlog-byemail' ) ) );
+                                               $reason, wfMessage( 'newuserlog-byemail' )->inContentLanguage()->text() ) );
                                }
                        }
                }
@@ -4026,15 +4142,33 @@ class User {
        }
 
        /**
-        * @todo document
+        * Load the user options either from cache, the database or an array
+        *
+        * @param $data Rows for the current user out of the user_properties table
         */
-       protected function loadOptions() {
+       protected function loadOptions( $data = null ) {
+               global $wgContLang;
+
                $this->load();
-               if ( $this->mOptionsLoaded || !$this->getId() )
+
+               if ( $this->mOptionsLoaded ) {
                        return;
+               }
 
                $this->mOptions = self::getDefaultOptions();
 
+               if ( !$this->getId() ) {
+                       // For unlogged-in users, load language/variant options from request.
+                       // There's no need to do it for logged-in users: they can set preferences,
+                       // and handling of page content is done by $pageLang->getPreferredVariant() and such,
+                       // so don't override user's choice (especially when the user chooses site default).
+                       $variant = $wgContLang->getDefaultVariant();
+                       $this->mOptions['variant'] = $variant;
+                       $this->mOptions['language'] = $variant;
+                       $this->mOptionsLoaded = true;
+                       return;
+               }
+
                // Maybe load from the object
                if ( !is_null( $this->mOptionOverrides ) ) {
                        wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
@@ -4042,21 +4176,27 @@ class User {
                                $this->mOptions[$key] = $value;
                        }
                } else {
-                       wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
-                       // Load from database
-                       $dbr = wfGetDB( DB_SLAVE );
-
-                       $res = $dbr->select(
-                               'user_properties',
-                               array( 'up_property', 'up_value' ),
-                               array( 'up_user' => $this->getId() ),
-                               __METHOD__
-                       );
+                       if( !is_array( $data ) ) {
+                               wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
+                               // Load from database
+                               $dbr = wfGetDB( DB_SLAVE );
 
-                       $this->mOptionOverrides = array();
-                       foreach ( $res as $row ) {
-                               $this->mOptionOverrides[$row->up_property] = $row->up_value;
-                               $this->mOptions[$row->up_property] = $row->up_value;
+                               $res = $dbr->select(
+                                       'user_properties',
+                                       array( 'up_property', 'up_value' ),
+                                       array( 'up_user' => $this->getId() ),
+                                       __METHOD__
+                               );
+
+                               $this->mOptionOverrides = array();
+                               $data = array();
+                               foreach ( $res as $row ) {
+                                       $data[$row->up_property] = $row->up_value;
+                               }
+                       }
+                       foreach ( $data as $property => $value ) {
+                               $this->mOptionOverrides[$property] = $value;
+                               $this->mOptions[$property] = $value;
                        }
                }
 
@@ -4165,8 +4305,8 @@ class User {
                /*
                if ( $wgMinimalPasswordLength > 1 ) {
                        $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
-                       $ret['title'] = wfMsgExt( 'passwordtooshort', 'parsemag',
-                               $wgMinimalPasswordLength );
+                       $ret['title'] = wfMessage( 'passwordtooshort' )
+                               ->numParams( $wgMinimalPasswordLength )->text();
                }
                */