Merge "Add language Doteli (dty)"
[lhc/web/wiklou.git] / includes / User.php
index 7902119..1ee8173 100644 (file)
@@ -330,7 +330,7 @@ class User implements IDBAccessObject {
         *
         * @param integer $flags User::READ_* constant bitfield
         */
-       public function load( $flags = self::READ_LATEST ) {
+       public function load( $flags = self::READ_NORMAL ) {
                if ( $this->mLoadedItems === true ) {
                        return;
                }
@@ -344,9 +344,13 @@ class User implements IDBAccessObject {
                                $this->loadDefaults();
                                break;
                        case 'name':
-                               // @TODO: this gets the ID from a slave, assuming renames
-                               // are rare. This should be controllable and more consistent.
-                               $this->mId = self::idFromName( $this->mName );
+                               // Make sure this thread sees its own changes
+                               if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
+                                       $flags |= self::READ_LATEST;
+                                       $this->queryFlagsUsed = $flags;
+                               }
+
+                               $this->mId = self::idFromName( $this->mName, $flags );
                                if ( !$this->mId ) {
                                        // Nonexistent user placeholder object
                                        $this->loadDefaults( $this->mName );
@@ -380,21 +384,19 @@ class User implements IDBAccessObject {
                        return false;
                }
 
-               // Try cache
-               $cache = $this->loadFromCache();
-               if ( !$cache ) {
+               // Try cache (unless this needs to lock the DB).
+               // NOTE: if this thread called saveSettings(), the cache was cleared.
+               if ( ( $flags & self::READ_LOCKING ) || !$this->loadFromCache() ) {
                        wfDebug( "User: cache miss for user {$this->mId}\n" );
-                       // Load from DB
+                       // Load from DB (make sure this thread sees its own changes)
+                       if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
+                               $flags |= self::READ_LATEST;
+                       }
                        if ( !$this->loadFromDatabase( $flags ) ) {
                                // Can't load from ID, user is anonymous
                                return false;
                        }
-                       if ( $flags & self::READ_LATEST ) {
-                               // Only save master data back to the cache to keep it consistent.
-                               // @TODO: save it anyway and have callers specifiy $flags and have
-                               // load() called as needed. That requires updating MANY callers...
-                               $this->saveToCache();
-                       }
+                       $this->saveToCache();
                }
 
                $this->mLoadedItems = true;
@@ -415,9 +417,8 @@ class User implements IDBAccessObject {
                        return false;
                }
 
-               $cache = ObjectCache::getMainWANInstance();
                $key = wfMemcKey( 'user', 'id', $this->mId );
-               $data = $cache->get( $key );
+               $data = ObjectCache::getMainWANInstance()->get( $key );
                if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
                        // Object is expired
                        return false;
@@ -448,15 +449,6 @@ class User implements IDBAccessObject {
                        return;
                }
 
-               $cache = ObjectCache::getMainWANInstance();
-
-               // The cache needs good consistency due to its high TTL, so the user
-               // should have been loaded from the master to avoid lag amplification.
-               if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
-                       wfWarn( "Cannot cache slave-loaded User object with ID '{$this->mId}'." );
-                       return;
-               }
-
                $data = array();
                foreach ( self::$mCacheVars as $name ) {
                        $data[$name] = $this->$name;
@@ -464,7 +456,7 @@ class User implements IDBAccessObject {
                $data['mVersion'] = self::VERSION;
                $key = wfMemcKey( 'user', 'id', $this->mId );
 
-               $cache->set( $key, $data );
+               ObjectCache::getMainWANInstance()->set( $key, $data, 3600 );
        }
 
        /** @name newFrom*() static factory methods */
@@ -598,9 +590,10 @@ class User implements IDBAccessObject {
        /**
         * Get database id given a user name
         * @param string $name Username
+        * @param integer $flags User::READ_* constant bitfield
         * @return int|null The corresponding user's ID, or null if user is nonexistent
         */
-       public static function idFromName( $name ) {
+       public static function idFromName( $name, $flags = self::READ_NORMAL ) {
                $nt = Title::makeTitleSafe( NS_USER, $name );
                if ( is_null( $nt ) ) {
                        // Illegal name
@@ -611,8 +604,11 @@ class User implements IDBAccessObject {
                        return self::$idCacheByName[$name];
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
-               $s = $dbr->selectRow(
+               $db = ( $flags & self::READ_LATEST )
+                       ? wfGetDB( DB_MASTER )
+                       : wfGetDB( DB_SLAVE );
+
+               $s = $db->selectRow(
                        'user',
                        array( 'user_id' ),
                        array( 'user_name' => $nt->getText() ),
@@ -844,15 +840,14 @@ class User implements IDBAccessObject {
         * @since 1.23
         */
        public function checkPasswordValidity( $password ) {
-               global $wgMinimalPasswordLength, $wgMaximalPasswordLength, $wgContLang;
+               global $wgPasswordPolicy;
 
-               static $blockedLogins = array(
-                       'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
-                       'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
+               $upp = new UserPasswordPolicy(
+                       $wgPasswordPolicy['policies'],
+                       $wgPasswordPolicy['checks']
                );
 
                $status = Status::newGood();
-
                $result = false; //init $result to false for the internal checks
 
                if ( !Hooks::run( 'isValidPassword', array( $password, &$result, $this ) ) ) {
@@ -861,28 +856,8 @@ class User implements IDBAccessObject {
                }
 
                if ( $result === false ) {
-                       if ( strlen( $password ) < $wgMinimalPasswordLength ) {
-                               $status->error( 'passwordtooshort', $wgMinimalPasswordLength );
-                               return $status;
-                       } elseif ( strlen( $password ) > $wgMaximalPasswordLength ) {
-                               // T64685: Password too long, might cause DoS attack
-                               $status->fatal( 'passwordtoolong', $wgMaximalPasswordLength );
-                               return $status;
-                       } elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
-                               $status->error( 'password-name-match' );
-                               return $status;
-                       } elseif ( isset( $blockedLogins[$this->getName()] )
-                               && $password == $blockedLogins[$this->getName()]
-                       ) {
-                               $status->error( 'password-login-forbidden' );
-                               return $status;
-                       } else {
-                               //it seems weird returning a Good status here, but this is because of the
-                               //initialization of $result to false above. If the hook is never run or it
-                               //doesn't modify $result, then we will likely get down into this if with
-                               //a valid password.
-                               return $status;
-                       }
+                       $status->merge( $upp->checkUserPassword( $this, $password ) );
+                       return $status;
                } elseif ( $result === true ) {
                        return $status;
                } else {
@@ -1162,7 +1137,6 @@ class User implements IDBAccessObject {
                }
 
                $proposedUser = User::newFromId( $sId );
-               $proposedUser->load( self::READ_LATEST );
                if ( !$proposedUser->isLoggedIn() ) {
                        // Not a valid ID
                        return false;
@@ -1229,7 +1203,7 @@ class User implements IDBAccessObject {
                        self::selectFields(),
                        array( 'user_id' => $this->mId ),
                        __METHOD__,
-                       ( $flags & self::READ_LOCKING == self::READ_LOCKING )
+                       ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                                ? array( 'LOCK IN SHARE MODE' )
                                : array()
                );
@@ -2301,11 +2275,10 @@ class User implements IDBAccessObject {
         * Called implicitly from invalidateCache() and saveSettings().
         */
        public function clearSharedCache() {
-               $this->load();
-               if ( $this->mId ) {
-                       $cache = ObjectCache::getMainWANInstance();
-
-                       $cache->delete( wfMemcKey( 'user', 'id', $this->mId ) );
+               $id = $this->getId();
+               if ( $id ) {
+                       $key = wfMemcKey( 'user', 'id', $id );
+                       ObjectCache::getMainWANInstance()->delete( $key );
                }
        }
 
@@ -2332,14 +2305,11 @@ class User implements IDBAccessObject {
         * @since 1.25
         */
        public function touch() {
-               $this->load();
-
-               if ( $this->mId ) {
-                       $this->mQuickTouched = $this->newTouchedTimestamp();
-
-                       $cache = ObjectCache::getMainWANInstance();
-                       $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
-                       $cache->touchCheckKey( $key );
+               $id = $this->getId();
+               if ( $id ) {
+                       $key = wfMemcKey( 'user-quicktouched', 'id', $id );
+                       ObjectCache::getMainWANInstance()->touchCheckKey( $key );
+                       $this->mQuickTouched = null;
                }
        }
 
@@ -2361,12 +2331,11 @@ class User implements IDBAccessObject {
 
                if ( $this->mId ) {
                        if ( $this->mQuickTouched === null ) {
-                               $cache = ObjectCache::getMainWANInstance();
                                $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
 
-                               $timestamp = $cache->getCheckKeyTime( $key );
+                               $timestamp = ObjectCache::getMainWANInstance()->getCheckKeyTime( $key );
                                if ( $timestamp ) {
-                                       $this->mQuickTouched = wfTimestamp( TS_MW, $timestamp );
+                                       $this->mQuickTouched = wfTimestamp( TS_MW, (int)$timestamp );
                                } else {
                                        # Set the timestamp to get HTTP 304 cache hits
                                        $this->touch();
@@ -2384,7 +2353,7 @@ class User implements IDBAccessObject {
         * @return string TS_MW Timestamp
         * @since 1.26
         */
-       protected function getDBTouched() {
+       public function getDBTouched() {
                $this->load();
 
                return $this->mTouched;
@@ -3522,6 +3491,31 @@ class User implements IDBAccessObject {
                $this->setCookie( $name, '', time() - 86400, $secure, $params );
        }
 
+       /**
+        * Set an extended login cookie on the user's client. The expiry of the cookie
+        * is controlled by the $wgExtendedLoginCookieExpiration configuration
+        * variable.
+        *
+        * @see User::setCookie
+        *
+        * @param string $name Name of the cookie to set
+        * @param string $value Value to set
+        * @param bool $secure
+        *  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 setExtendedLoginCookie( $name, $value, $secure ) {
+               global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
+
+               $exp = time();
+               $exp += $wgExtendedLoginCookieExpiration !== null
+                       ? $wgExtendedLoginCookieExpiration
+                       : $wgCookieExpiration;
+
+               $this->setCookie( $name, $value, $exp, $secure );
+       }
+
        /**
         * Set the default cookies for this session on the user's client.
         *
@@ -3531,6 +3525,8 @@ class User implements IDBAccessObject {
         * @param bool $rememberMe Whether to add a Token cookie for elongated sessions
         */
        public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
+               global $wgExtendedLoginCookies;
+
                if ( $request === null ) {
                        $request = $this->getRequest();
                }
@@ -3572,6 +3568,8 @@ class User implements IDBAccessObject {
                foreach ( $cookies as $name => $value ) {
                        if ( $value === false ) {
                                $this->clearCookie( $name );
+                       } elseif ( $rememberMe && in_array( $name, $wgExtendedLoginCookies ) ) {
+                               $this->setExtendedLoginCookie( $name, $value, $secure );
                        } else {
                                $this->setCookie( $name, $value, 0, $secure, array(), $request );
                        }
@@ -3644,12 +3642,6 @@ class User implements IDBAccessObject {
                        return; // anon
                }
 
-               // This method is for updating existing users, so the user should
-               // have been loaded from the master to begin with to avoid problems.
-               if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
-                       wfWarn( "Attempting to save slave-loaded User object with ID '{$this->mId}'." );
-               }
-
                // Get a new user_touched that is higher than the old one.
                // This will be used for a CAS check as a last-resort safety
                // check against race conditions and slave lag.
@@ -3685,8 +3677,9 @@ class User implements IDBAccessObject {
                        // Maybe the problem was a missed cache update; clear it to be safe
                        $this->clearSharedCache();
                        // User was changed in the meantime or loaded with stale data
+                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
                        MWExceptionHandler::logException( new MWException(
-                               "CAS update failed on user_touched for user ID '{$this->mId}';" .
+                               "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
                                "the version of the user to be saved is older than the current version."
                        ) );