From 41d48826812b3574f8f7efbadb44694af3e502ac Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Tue, 15 Apr 2008 09:04:45 +0000 Subject: [PATCH] In User: * 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 | 1 + includes/SpecialConfirmemail.php | 27 +++---- includes/SpecialPreferences.php | 6 +- includes/SpecialUserlogin.php | 7 +- includes/User.php | 117 ++++++++++++++++++++++--------- includes/UserArray.php | 62 ++++++++++++++++ includes/UserMailer.php | 7 +- 7 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 includes/UserArray.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index db7cb4c91a..1215b6081c 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -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', diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php index 6a35da144b..d26876a2f1 100644 --- a/includes/SpecialConfirmemail.php +++ b/includes/SpecialConfirmemail.php @@ -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' ); diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php index cecc71f031..4b0f3d0576 100644 --- a/includes/SpecialPreferences.php +++ b/includes/SpecialPreferences.php @@ -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. diff --git a/includes/SpecialUserlogin.php b/includes/SpecialUserlogin.php index ba5f01a4ab..9cf8de4a7f 100644 --- a/includes/SpecialUserlogin.php +++ b/includes/SpecialUserlogin.php @@ -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 diff --git a/includes/User.php b/includes/User.php index 686a8f8506..43b552c879 100644 --- a/includes/User.php +++ b/includes/User.php @@ -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 index 0000000000..27847e6fe2 --- /dev/null +++ b/includes/UserArray.php @@ -0,0 +1,62 @@ +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; + } +} diff --git a/includes/UserMailer.php b/includes/UserMailer.php index 3105855e23..546f0c747d 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -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() ) -- 2.20.1