In User:
authorTim Starling <tstarling@users.mediawiki.org>
Tue, 15 Apr 2008 09:04:45 +0000 (09:04 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Tue, 15 Apr 2008 09:04:45 +0000 (09:04 +0000)
* Defer load of groups data
* Introduce newFromRow()/loadFromRow() to allow bulk loading of user objects from a result set
* Hook email and email authentication save/load to allow CentralAuth to provide a global email address
* Defer save of user data after confirmEmail() and invalidateEmail(). Caller must now also call saveSettings(). This reduces the master query count in some code paths.

Elsewhere:
* Introduce UserArray class, for bulk loading of user objects. Immediately useful in email notification, potentially useful for proposed user alias feature.
* In Special:Confirmemail, remove useless handling for impossible false return from confirmEmail()/invalidateEmail().

includes/AutoLoader.php
includes/SpecialConfirmemail.php
includes/SpecialPreferences.php
includes/SpecialUserlogin.php
includes/User.php
includes/UserArray.php [new file with mode: 0644]
includes/UserMailer.php

index db7cb4c..1215b60 100644 (file)
@@ -266,6 +266,7 @@ function __autoload($className) {
                'UploadForm' => 'includes/SpecialUpload.php',
                'UploadFormMogile' => 'includes/SpecialUploadMogile.php',
                'User' => 'includes/User.php',
+               'UserArray' => 'includes/UserArray.php',
                'UserMailer' => 'includes/UserMailer.php',
                'UserrightsPage' => 'includes/SpecialUserrights.php',
                'UserRightsProxy' => 'includes/UserRightsProxy.php',
index 6a35da1..d26876a 100644 (file)
@@ -84,15 +84,13 @@ class EmailConfirmation extends UnlistedSpecialPage {
                global $wgUser, $wgOut;
                $user = User::newFromConfirmationCode( $code );
                if( is_object( $user ) ) {
-                       if( $user->confirmEmail() ) {
-                               $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
-                               $wgOut->addWikiMsg( $message );
-                               if( !$wgUser->isLoggedIn() ) {
-                                       $title = SpecialPage::getTitleFor( 'Userlogin' );
-                                       $wgOut->returnToMain( true, $title->getPrefixedText() );
-                               }
-                       } else {
-                               $wgOut->addWikiMsg( 'confirmemail_error' );
+                       $user->confirmEmail();
+                       $user->saveSettings();
+                       $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
+                       $wgOut->addWikiMsg( $message );
+                       if( !$wgUser->isLoggedIn() ) {
+                               $title = SpecialPage::getTitleFor( 'Userlogin' );
+                               $wgOut->returnToMain( true, $title->getPrefixedText() );
                        }
                } else {
                        $wgOut->addWikiMsg( 'confirmemail_invalid' );
@@ -129,13 +127,10 @@ class EmailInvalidation extends UnlistedSpecialPage {
                $user = User::newFromConfirmationCode( $code );
                if( is_object( $user ) ) {
                        $user->invalidateEmail();
-                       if( $user->invalidateEmail() ) {
-                               $wgOut->addWikiMsg( 'confirmemail_invalidated' );
-                               if( !$wgUser->isLoggedIn() ) {
-                                       $wgOut->returnToMain();
-                               }
-                       } else {
-                               $wgOut->addWikiMsg( 'confirmemail_error' );
+                       $user->saveSettings();
+                       $wgOut->addWikiMsg( 'confirmemail_invalidated' );
+                       if( !$wgUser->isLoggedIn() ) {
+                               $wgOut->returnToMain();
                        }
                } else {
                        $wgOut->addWikiMsg( 'confirmemail_invalid' );
index cecc71f..4b0f3d0 100644 (file)
@@ -310,8 +310,10 @@ class PreferencesForm {
                        if( ($newadr != '') && ($newadr != $oldadr) ) {
                                # the user has supplied a new email address on the login page
                                if( $wgUser->isValidEmailAddr( $newadr ) ) {
-                                       $wgUser->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
-                                       $wgUser->mEmailAuthenticated = null; # but flag as "dirty" = unauthenticated
+                                       # new behaviour: set this new emailaddr from login-page into user database record
+                                       $wgUser->setEmail( $newadr );
+                                       # but flag as "dirty" = unauthenticated
+                                       $wgUser->invalidateEmail();
                                        if ($wgEmailAuthentication) {
                                                # Mail a temporary password to the dirty address.
                                                # User can come back through the confirmation URL to re-enable email.
index ba5f01a..9cf8de4 100644 (file)
@@ -158,8 +158,7 @@ class LoginForm {
                if( $wgLoginLanguageSelector && $this->mLanguage )
                        $u->setOption( 'language', $this->mLanguage );
 
-               # Save user settings and send out an email authentication message if needed
-               $u->saveSettings();
+               # Send out an email authentication message if needed
                if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) {
                        global $wgOut;
                        $error = $u->sendConfirmationMail();
@@ -170,6 +169,9 @@ class LoginForm {
                        }
                }
 
+               # Save settings (including confirmation token)
+               $u->saveSettings();
+
                # If not logged in, assume the new account as the current one and set session cookies
                # then show a "welcome" message or a "need cookies" message as needed
                if( $wgUser->isAnon() ) {
@@ -420,6 +422,7 @@ class LoginForm {
                                //
                                if( !$u->isEmailConfirmed() ) {
                                        $u->confirmEmail();
+                                       $u->saveSettings();
                                }
 
                                // At this point we just return an appropriate code
index 686a8f8..43b552c 100644 (file)
@@ -206,7 +206,6 @@ class User {
                                # Can't load from ID, user is anonymous
                                return false;
                        }
-
                        $this->saveToCache();
                } else {
                        wfDebug( "Got user {$this->mId} from cache\n" );
@@ -223,6 +222,7 @@ class User {
         */
        function saveToCache() {
                $this->load();
+               $this->loadGroups();
                if ( $this->isAnon() ) {
                        // Anonymous users are uncached
                        return;
@@ -313,6 +313,16 @@ class User {
                return $user;
        }
 
+       /**
+        * Create a new user object from a user row.
+        * The row should have all fields from the user table in it.
+        */
+       static function newFromRow( $row ) {
+               $user = new User;
+               $user->loadFromRow( $row );
+               return $user;
+       }
+
        /**
         * Get username given an id.
         * @param integer $id Database user id
@@ -788,23 +798,50 @@ class User {
 
                if ( $s !== false ) {
                        # Initialise user table data
-                       $this->mName = $s->user_name;
-                       $this->mRealName = $s->user_real_name;
-                       $this->mPassword = $s->user_password;
-                       $this->mNewpassword = $s->user_newpassword;
-                       $this->mNewpassTime = wfTimestampOrNull( TS_MW, $s->user_newpass_time );
-                       $this->mEmail = $s->user_email;
-                       $this->decodeOptions( $s->user_options );
-                       $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
-                       $this->mToken = $s->user_token;
-                       $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
-                       $this->mEmailToken = $s->user_email_token;
-                       $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $s->user_email_token_expires );
-                       $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
-                       $this->mEditCount = $s->user_editcount;
+                       $this->loadFromRow( $s );
+                       $this->mGroups = null; // deferred
                        $this->getEditCount(); // revalidation for nulls
+                       return true;
+               } else {
+                       # Invalid user_id
+                       $this->mId = 0;
+                       $this->loadDefaults();
+                       return false;
+               }
+       }
+
+       /**
+        * Initialise the user object from a row from the user table
+        */
+       function loadFromRow( $row ) {
+               $this->mDataLoaded = true;
+
+               if ( isset( $row->user_id ) ) {
+                       $this->mId = $row->user_id;
+               }
+               $this->mName = $row->user_name;
+               $this->mRealName = $row->user_real_name;
+               $this->mPassword = $row->user_password;
+               $this->mNewpassword = $row->user_newpassword;
+               $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
+               $this->mEmail = $row->user_email;
+               $this->decodeOptions( $row->user_options );
+               $this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
+               $this->mToken = $row->user_token;
+               $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
+               $this->mEmailToken = $row->user_email_token;
+               $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
+               $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
+               $this->mEditCount = $row->user_editcount; 
+       }
 
-                       # Load group data
+       /**
+        * Load the groups from the database if they aren't already loaded
+        * @private
+        */
+       function loadGroups() {
+               if ( is_null( $this->mGroups ) ) {
+                       $dbr = wfGetDB( DB_MASTER );
                        $res = $dbr->select( 'user_groups',
                                array( 'ug_group' ),
                                array( 'ug_user' => $this->mId ),
@@ -813,12 +850,6 @@ class User {
                        while( $row = $dbr->fetchObject( $res ) ) {
                                $this->mGroups[] = $row->ug_group;
                        }
-                       return true;
-               } else {
-                       # Invalid user_id
-                       $this->mId = 0;
-                       $this->loadDefaults();
-                       return false;
                }
        }
 
@@ -1529,17 +1560,20 @@ class User {
 
        function getEmail() {
                $this->load();
+               wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) );
                return $this->mEmail;
        }
 
        function getEmailAuthenticationTimestamp() {
                $this->load();
+               wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
                return $this->mEmailAuthenticated;
        }
 
        function setEmail( $str ) {
                $this->load();
                $this->mEmail = $str;
+               wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
        }
 
        function getRealName() {
@@ -1657,10 +1691,9 @@ class User {
         */
        function getEffectiveGroups( $recache = false ) {
                if ( $recache || is_null( $this->mEffectiveGroups ) ) {
-                       $this->load();
-                       $this->mEffectiveGroups = $this->mGroups;
+                       $this->mEffectiveGroups = $this->getGroups();
                        $this->mEffectiveGroups[] = '*';
-                       if( $this->mId ) {
+                       if( $this->getId() ) {
                                $this->mEffectiveGroups[] = 'user';
 
                                $this->mEffectiveGroups = array_unique( array_merge(
@@ -1695,7 +1728,6 @@ class User {
         * @param string $group
         */
        function addGroup( $group ) {
-               $this->load();
                $dbw = wfGetDB( DB_MASTER );
                if( $this->getId() ) {
                        $dbw->insert( 'user_groups',
@@ -1707,6 +1739,7 @@ class User {
                                array( 'IGNORE' ) );
                }
 
+               $this->loadGroups();
                $this->mGroups[] = $group;
                $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
 
@@ -1728,6 +1761,7 @@ class User {
                        ),
                        'User::removeGroup' );
 
+               $this->loadGroups();
                $this->mGroups = array_diff( $this->mGroups, array( $group ) );
                $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
 
@@ -2044,10 +2078,10 @@ class User {
                                'user_id' => $this->mId
                        ), __METHOD__
                );
+               wfRunHooks( 'UserSaveSettings', array( $this ) );
                $this->clearSharedCache();
        }
 
-
        /**
         * Checks if a user with the given name exists, returns the ID.
         */
@@ -2398,6 +2432,9 @@ class User {
         * Generate a new e-mail confirmation token and send a confirmation/invalidation
         * mail to the user's given address.
         *
+        * Call saveSettings() after calling this function to commit the confirmation
+        * token to the database.
+        *
         * @return mixed True on success, a WikiError object on failure.
         */
        function sendConfirmationMail() {
@@ -2438,6 +2475,10 @@ class User {
        /**
         * Generate, store, and return a new e-mail confirmation code.
         * A hash (unsalted since it's used as a key) is stored.
+        *
+        * Call saveSettings() after calling this function to commit
+        * this change to the database.
+        *
         * @param &$expiration mixed output: accepts the expiration time
         * @return string
         * @private
@@ -2451,7 +2492,6 @@ class User {
                $this->load();
                $this->mEmailToken = $hash;
                $this->mEmailTokenExpires = $expiration;
-               $this->saveSettings();
                return $token;
        }
 
@@ -2477,28 +2517,35 @@ class User {
        }
 
        /**
-        * Mark the e-mail address confirmed and save.
+        * Mark the e-mail address confirmed.
+        *
+        * Call saveSettings() after calling this function to commit the change.
         */
        function confirmEmail() {
-               $this->load();
-               $this->mEmailAuthenticated = wfTimestampNow();
-               $this->saveSettings();
+               $this->setEmailAuthenticationTimestamp( wfTimestampNow() );
                return true;
        }
 
        /**
         * Invalidate the user's email confirmation, unauthenticate the email
-        * if it was already confirmed and save.
+        * if it was already confirmed.
+        *
+        * Call saveSettings() after calling this function to commit the change.
         */
        function invalidateEmail() {
                $this->load();
                $this->mEmailToken = null;
                $this->mEmailTokenExpires = null;
-               $this->mEmailAuthenticated = null;
-               $this->saveSettings();
+               $this->setEmailAuthenticationTimestamp( null );
                return true;
        }
 
+       function setEmailAuthenticationTimestamp( $timestamp ) {
+               $this->load();
+               $this->mEmailAuthenticated = $timestamp;
+               wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
+       }
+
        /**
         * Is this user allowed to send e-mails within limits of current
         * site configuration?
diff --git a/includes/UserArray.php b/includes/UserArray.php
new file mode 100644 (file)
index 0000000..27847e6
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+abstract class UserArray implements Iterator {
+       static function newFromResult( $res ) {
+               $userArray = null;
+               if ( !wfRunHooks( 'UserArrayFromResult', array( &$userArray, $res ) ) ) {
+                       return null;
+               }
+               if ( $userArray === null ) {
+                       $userArray = self::newFromResult_internal( $res );
+               }
+               return $userArray;
+       }
+
+       protected static function newFromResult_internal( $res ) {
+               $userArray = new UserArrayFromResult( $res );
+               return $userArray;
+       }
+}
+
+class UserArrayFromResult extends UserArray {
+       var $res;
+       var $key, $current;
+
+       function __construct( $res ) {
+               $this->res = $res;
+               $this->key = 0;
+               $this->setCurrent( $this->res->current() );
+       }
+
+       protected function setCurrent( $row ) {
+               if ( $row === false ) {
+                       $this->current = false;
+               } else {
+                       $this->current = User::newFromRow( $row );
+               }
+       }
+
+       function current() {
+               return $this->current;
+       }
+
+       function key() {
+               return $this->key;
+       }
+
+       function next() {
+               $row = $this->res->next();
+               $this->setCurrent( $row );
+               $this->key++;
+       }
+
+       function rewind() {
+               $this->res->rewind();
+               $this->key = 0;
+               $this->setCurrent( $this->res->current() );
+       }
+
+       function valid() {
+               return $this->current !== false;
+       }
+}
index 3105855..546f0c7 100644 (file)
@@ -362,16 +362,17 @@ class EmailNotification {
                                }
                                $dbr = wfGetDB( DB_SLAVE );
 
-                               $res = $dbr->select( 'watchlist', array( 'wl_user' ),
+                               $res = $dbr->select( array( 'watchlist', 'user' ), array( 'user.*' ),
                                        array(
+                                               'wl_user=user_id',
                                                'wl_title' => $title->getDBkey(),
                                                'wl_namespace' => $title->getNamespace(),
                                                $userCondition,
                                                'wl_notificationtimestamp IS NULL',
                                        ), __METHOD__ );
+                               $userArray = UserArray::newFromResult( $res );
 
-                               foreach ( $res as $row ) {
-                                       $watchingUser = User::newFromId( $row->wl_user );
+                               foreach ( $userArray as $watchingUser ) {
                                        if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
                                                ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
                                                $watchingUser->isEmailConfirmed() )