(bug 13015, bug 18347, bug 18996, bug 20473, bug 23669, bug 28244) separate the passw...
authorHappy-melon <happy-melon@users.mediawiki.org>
Wed, 20 Apr 2011 15:27:09 +0000 (15:27 +0000)
committerHappy-melon <happy-melon@users.mediawiki.org>
Wed, 20 Apr 2011 15:27:09 +0000 (15:27 +0000)
* Refactor with all the latest bells and whistles
* Allow wikis to enable resettting by entering an email address (bug 13015).  This is currently an unindexed query, but it is disabled by default so no immediate problem.
* Allow resetting to be disabled entirely (bug 20473).
* Don't send registered users' IP addresses in the emails (bug 18347)
* Check that a user is not globally blocked before letting them send messages (bug 23669)
* Display a more useful error message when an account exists globally but not locally (bug 18996).

includes/AutoLoader.php
includes/DefaultSettings.php
includes/SpecialPage.php
includes/SpecialPageFactory.php
includes/specials/SpecialPasswordReset.php [new file with mode: 0644]
includes/specials/SpecialUserlogin.php
includes/templates/Userlogin.php
languages/messages/MessagesEn.php
maintenance/language/messages.inc

index 8c88bdc..edae895 100644 (file)
@@ -91,6 +91,7 @@ $wgAutoloadLocalClasses = array(
        'FileRevertForm' => 'includes/FileRevertForm.php',
        'ForkController' => 'includes/ForkController.php',
        'FormOptions' => 'includes/FormOptions.php',
+       'FormSpecialPage' => 'includes/SpecialPage.php',
        'GenderCache' => 'includes/GenderCache.php',
        'GlobalDependency' => 'includes/CacheDependency.php',
        'HashtableReplacer' => 'includes/StringUtils.php',
@@ -714,6 +715,7 @@ $wgAutoloadLocalClasses = array(
        'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
        'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php',
        'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
+       'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php',
        'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
        'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
        'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
index 852b59a..6bc9437 100644 (file)
@@ -3004,6 +3004,17 @@ $wgMinimalPasswordLength = 1;
  */
 $wgLivePasswordStrengthChecks = false;
 
+/**
+ * Whether to allow password resets ("enter some identifying data, and we'll send an email
+ * with a temporary password you can use to get back into the account") identified by
+ * various bits of data.  Setting all of these to false (or the whole variable to false)
+ * has the effect of disabling password resets entirely
+ */
+$wgPasswordResetRoutes = array(
+       'username' => true,
+       'email' => false, // Warning: enabling this will be *very* slow on large wikis
+);
+
 /**
  * Maximum number of Unicode characters in signature
  */
index f375fc0..866cbec 100644 (file)
@@ -659,6 +659,138 @@ class SpecialPage {
        }
 }
 
+/**
+ * Special page which uses an HTMLForm to handle processing.  This is mostly a
+ * clone of FormAction.  More special pages should be built this way; maybe this could be
+ * a new structure for SpecialPages
+ */
+abstract class FormSpecialPage extends SpecialPage {
+
+       /**
+        * Get an HTMLForm descriptor array
+        * @return Array
+        */
+       protected abstract function getFormFields();
+
+       /**
+        * Add pre- or post-text to the form
+        * @return String HTML which will be sent to $form->addPreText()
+        */
+       protected function preText() { return ''; }
+       protected function postText() { return ''; }
+
+       /**
+        * Play with the HTMLForm if you need to more substantially
+        * @param $form HTMLForm
+        */
+       protected function alterForm( HTMLForm $form ) {}
+
+       /**
+        * Get the HTMLForm to control behaviour
+        * @return HTMLForm|null
+        */
+       protected function getForm() {
+               $this->fields = $this->getFormFields();
+
+               // Give hooks a chance to alter the form, adding extra fields or text etc
+               wfRunHooks( "Special{$this->getName()}ModifyFormFields", array( &$this->fields ) );
+
+               $form = new HTMLForm( $this->fields, $this->getContext() );
+               $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+               $form->setWrapperLegend( wfMessage( strtolower( $this->getName() ) . '-legend' ) );
+               $form->addHeaderText( wfMessage( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
+
+               // Retain query parameters (uselang etc)
+               $params = array_diff_key( $this->getRequest()->getQueryValues(), array( 'title' => null ) );
+               $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
+
+               $form->addPreText( $this->preText() );
+               $form->addPostText( $this->postText() );
+               $this->alterForm( $form );
+
+               // Give hooks a chance to alter the form, adding extra fields or text etc
+               wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) );
+
+               return $form;
+       }
+
+       /**
+        * Process the form on POST submission.
+        * @param  $data Array
+        * @return Bool|Array true for success, false for didn't-try, array of errors on failure
+        */
+       public abstract function onSubmit( array $data );
+
+       /**
+        * Do something exciting on successful processing of the form, most likely to show a
+        * confirmation message
+        */
+       public abstract function onSuccess();
+
+       /**
+        * Basic SpecialPage workflow: get a form, send it to the user; get some data back,
+        */
+       public function execute( $par ) {
+               $this->setParameter( $par );
+               $this->setHeaders();
+
+               // This will throw exceptions if there's a problem
+               $this->userCanExecute( $this->getUser() );
+
+               $form = $this->getForm();
+               if ( $form->show() ) {
+                       $this->onSuccess();
+               }
+       }
+
+       /**
+        * Maybe do something interesting with the subpage parameter
+        * @param $par String
+        */
+       protected function setParameter( $par ){}
+
+       /**
+        * Checks if the given user (identified by an object) can perform this action.  Can be
+        * overridden by sub-classes with more complicated permissions schemes.  Failures here
+        * must throw subclasses of ErrorPageError
+        *
+        * @param $user User: the user to check, or null to use the context user
+        * @throws ErrorPageError
+        */
+       public function userCanExecute( User $user ) {
+               if ( $this->requiresWrite() && wfReadOnly() ) {
+                       throw new ReadOnlyError();
+               }
+
+               if ( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ) {
+                       throw new PermissionsError( $this->getRestriction() );
+               }
+
+               if ( $this->requiresUnblock() && $user->isBlocked() ) {
+                       $block = $user->mBlock;
+                       throw new UserBlockedError( $block );
+               }
+
+               return true;
+       }
+
+       /**
+        * Whether this action requires the wiki not to be locked
+        * @return Bool
+        */
+       public function requiresWrite() {
+               return true;
+       }
+
+       /**
+        * Whether this action cannot be executed by a blocked user
+        * @return Bool
+        */
+       public function requiresUnblock() {
+               return true;
+       }
+}
+
 /**
  * Shortcut to construct a special page which is unlisted by default
  * @ingroup SpecialPage
index ebe3001..2a74186 100644 (file)
@@ -73,6 +73,7 @@ class SpecialPageFactory {
                'Unblock'                   => 'SpecialUnblock',
                'BlockList'                 => 'SpecialBlockList',
                'ChangePassword'            => 'SpecialChangePassword',
+               'PasswordReset'             => 'SpecialPasswordReset',
                'DeletedContributions'      => 'DeletedContributionsPage',
                'Preferences'               => 'SpecialPreferences',
                'Contributions'             => 'SpecialContributions',
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
new file mode 100644 (file)
index 0000000..fe9d924
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Implements Special:Blankpage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Special page for requesting a password reset email
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPasswordReset extends FormSpecialPage {
+
+       public function __construct() {
+               parent::__construct( 'PasswordReset' );
+       }
+
+       public function userCanExecute( User $user ) {
+               global $wgPasswordResetRoutes, $wgAuth;
+
+               // Maybe password resets are disabled, or there are no allowable routes
+               if ( !is_array( $wgPasswordResetRoutes )
+                       || !in_array( true, array_values( $wgPasswordResetRoutes ) ) )
+               {
+                       throw new ErrorPageError( 'internalerror', 'passwordreset-disabled' );
+               }
+
+               // Maybe the external auth plugin won't allow local password changes
+               if ( !$wgAuth->allowPasswordChange() ) {
+                       throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
+               }
+
+               // Maybe the user is blocked (check this here rather than relying on the parent
+               // method as we have a more specific error message to use here
+               if ( $user->isBlocked() ) {
+                       throw new ErrorPageError( 'internalerror', 'blocked-mailpassword' );
+               }
+
+               return parent::userCanExecute( $user );
+       }
+
+       protected function getFormFields() {
+               global $wgPasswordResetRoutes;
+               $a = array();
+               if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+                       $a['Username'] = array(
+                               'type' => 'text',
+                               'label-message' => 'passwordreset-username',
+                       );
+               }
+
+               if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+                       $a['Email'] = array(
+                               'type' => 'email',
+                               'label-message' => 'passwordreset-email',
+                       );
+               }
+
+               return $a;
+       }
+
+       protected function preText() {
+               global $wgPasswordResetRoutes;
+               $i = 0;
+               if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+                       $i++;
+               }
+               if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+                       $i++;
+               }
+               return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock();
+       }
+
+       /**
+        * Process the form.  At this point we know that the user passes all the criteria in
+        * userCanExecute(), and if the data array contains 'Username', etc, then Username
+        * resets are allowed.
+        * @param $data array
+        * @return Bool|Array
+        */
+       public function onSubmit( array $data ) {
+
+               if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
+                       $method = 'username';
+                       $users = array( User::newFromName( $data['Username'] ) );
+               } elseif ( isset( $data['Email'] )
+                       && $data['Email'] !== ''
+                       && Sanitizer::validateEmail( $data['Email'] ) )
+               {
+                       $method = 'email';
+
+                       // FIXME: this is an unindexed query
+                       $res = wfGetDB( DB_SLAVE )->select(
+                               'user',
+                               '*',
+                               array( 'user_email' => $data['Email'] ),
+                               __METHOD__
+                       );
+                       if ( $res ) {
+                               $users = array();
+                               foreach( $res as $row ){
+                                       $users[] = User::newFromRow( $row );
+                               }
+                       } else {
+                               // Some sort of database error, probably unreachable
+                               throw new MWException( 'Unknown database error in ' . __METHOD__ );
+                       }
+               } else {
+                       // The user didn't supply any data
+                       return false;
+               }
+
+               // Check for hooks (captcha etc), and allow them to modify the users list
+               $error = array();
+               if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
+                       return array( $error );
+               }
+
+               if( count( $users ) == 0 ){
+                       if( $method == 'email' ){
+                               // Don't reveal whether or not an email address is in use
+                               return true;
+                       } else {
+                               return array( 'noname' );
+                       }
+               }
+
+               $firstUser = $users[0];
+               
+               if ( !$firstUser instanceof User || !$firstUser->getID() ) {
+                       return array( array( 'nosuchuser', $data['Username'] ) );
+               }
+
+               // Check against the rate limiter
+               if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
+                       throw new ThrottledError;
+               }
+
+               // Check against password throttle
+               foreach ( $users as $user ) {
+                       if ( $user->isPasswordReminderThrottled() ) {
+                               global $wgPasswordReminderResendTime;
+                               # Round the time in hours to 3 d.p., in case someone is specifying
+                               # minutes or seconds.
+                               return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) );
+                       }
+               }
+
+               global $wgServer, $wgScript, $wgNewPasswordExpiry;
+
+               // All the users will have the same email address
+               if ( $firstUser->getEmail() == '' ) {
+                       // This won't be reachable from the email route, so safe to expose the username
+                       return array( array( 'noemail', $firstUser->getName() ) );
+               }
+
+               // We need to have a valid IP address for the hook, but per bug 18347, we should
+               // send the user's name if they're logged in.
+               $ip = wfGetIP();
+               if ( !$ip ) {
+                       return array( 'badipaddress' );
+               }
+               $caller = $this->getUser();
+               wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
+               $username = $caller->getName();
+               $msg = IP::isValid( $username )
+                       ? 'passwordreset-emailtext-ip'
+                       : 'passwordreset-emailtext-user';
+
+               $passwords = array();
+               foreach ( $users as $user ) {
+                       $password = $user->randomPassword();
+                       $user->setNewpassword( $password );
+                       $user->saveSettings();
+                       $passwords[] = wfMessage( 'passwordreset-emailelement', $user->getName(), $password );
+               }
+               $passwordBlock = implode( "\n\n", $passwords );
+
+               // Send in the user's language; which should hopefully be the same
+               $userLanguage = $firstUser->getOption( 'language' );
+
+               $body = wfMessage( $msg )->inLanguage( $userLanguage );
+               $body->params(
+                       $username,
+                       $passwordBlock,
+                       count( $passwords ),
+                       $wgServer . $wgScript,
+                       round( $wgNewPasswordExpiry / 86400 )
+               );
+
+               $title = wfMessage( 'passwordreset-emailtitle' );
+
+               $result = $firstUser->sendMail( $title->text(), $body->text() );
+
+               if ( $result->isGood() ) {
+                       return true;
+               } else {
+                       // FIXME: The email didn't send, but we have already set the password throttle
+                       // timestamp, so they won't be able to try again until it expires...  :(
+                       return array( array( 'mailerror', $result->getMessage() ) );
+               }
+       }
+
+       public function onSuccess() {
+               $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' );
+               $this->getOutput()->returnToMain();
+       }
+}
index 119e11d..25fc6f9 100644 (file)
@@ -44,7 +44,7 @@ class LoginForm extends SpecialPage {
        const WRONG_TOKEN = 13;
 
        var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
-       var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
+       var $mAction, $mCreateaccount, $mCreateaccountMail;
        var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
        var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
        var $mType, $mReason, $mRealName;
@@ -90,8 +90,6 @@ class LoginForm extends SpecialPage {
                $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
                $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
                                                                        && $wgEnableEmail;
-               $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' )
-                                                                && $wgEnableEmail;
                $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
                $this->mAction = $request->getVal( 'action' );
                $this->mRemember = $request->getCheck( 'wpRemember' );
@@ -146,8 +144,6 @@ class LoginForm extends SpecialPage {
                                return $this->addNewAccount();
                        } elseif ( $this->mCreateaccountMail ) {
                                return $this->addNewAccountMailPassword();
-                       } elseif ( $this->mMailmypassword ) {
-                               return $this->mailPassword();
                        } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
                                return $this->processLogin();
                        }
@@ -739,95 +735,6 @@ class LoginForm extends SpecialPage {
                $reset->execute( null );
        }
 
-       /**
-        * @private
-        */
-       function mailPassword() {
-               global $wgUser, $wgOut, $wgAuth;
-
-               if ( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
-                       return false;
-               }
-
-               if( !$wgAuth->allowPasswordChange() ) {
-                       $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) );
-                       return;
-               }
-
-               # Check against blocked IPs so blocked users can't flood admins
-               # with password resets
-               if( $wgUser->isBlocked() ) {
-                       $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
-                       return;
-               }
-
-               # Check for hooks
-               $error = null;
-               if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mUsername, &$error ) ) ) {
-                       $this->mainLoginForm( $error );
-                       return;
-               }
-
-               # If the user doesn't have a login token yet, set one.
-               if ( !self::getLoginToken() ) {
-                       self::setLoginToken();
-                       $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
-                       return;
-               }
-
-               # If the user didn't pass a login token, tell them we need one
-               if ( !$this->mToken ) {
-                       $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
-                       return;
-               }
-
-               # Check against the rate limiter
-               if( $wgUser->pingLimiter( 'mailpassword' ) ) {
-                       $wgOut->rateLimited();
-                       return;
-               }
-
-               if ( $this->mUsername == '' ) {
-                       $this->mainLoginForm( wfMsg( 'noname' ) );
-                       return;
-               }
-               $u = User::newFromName( $this->mUsername );
-               if( !$u instanceof User ) {
-                       $this->mainLoginForm( wfMsg( 'noname' ) );
-                       return;
-               }
-               if ( 0 == $u->getID() ) {
-                       $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', $u->getName() ) );
-                       return;
-               }
-
-               # Validate the login token
-               if ( $this->mToken !== self::getLoginToken() ) {
-                       $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
-                       return;
-               }
-
-               # Check against password throttle
-               if ( $u->isPasswordReminderThrottled() ) {
-                       global $wgPasswordReminderResendTime;
-                       # Round the time in hours to 3 d.p., in case someone is specifying
-                       # minutes or seconds.
-                       $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ),
-                               round( $wgPasswordReminderResendTime, 3 ) ) );
-                       return;
-               }
-
-               $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' );
-               if( $result->isGood() ) {
-                       $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' );
-                       self::clearLoginToken();
-               } else {
-                       $this->mainLoginForm( $result->getWikiText( 'mailerror' ) );
-               }
-       }
-
-
        /**
         * @param $u User object
         * @param $throttle Boolean
@@ -977,7 +884,7 @@ class LoginForm extends SpecialPage {
                global $wgEnableEmail, $wgEnableUserEmail;
                global $wgRequest, $wgLoginLanguageSelector;
                global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
-               global $wgSecureLogin;
+               global $wgSecureLogin, $wgPasswordResetRoutes;
 
                $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
 
@@ -1043,6 +950,10 @@ class LoginForm extends SpecialPage {
                        $template->set( 'link', '' );
                }
 
+               $resetLink = $this->mType == 'signup'
+                       ? null
+                       : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
+
                $template->set( 'header', '' );
                $template->set( 'name', $this->mUsername );
                $template->set( 'password', $this->mPassword );
@@ -1061,6 +972,7 @@ class LoginForm extends SpecialPage {
                $template->set( 'emailrequired', $wgEmailConfirmToEdit );
                $template->set( 'emailothers', $wgEnableUserEmail );
                $template->set( 'canreset', $wgAuth->allowPasswordChange() );
+               $template->set( 'resetlink', $resetLink );
                $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
                $template->set( 'usereason', $wgUser->isLoggedIn() );
                $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember );
index e87168d..bcc0224 100644 (file)
@@ -130,11 +130,19 @@ class UserloginTemplate extends QuickTemplate {
                        'tabindex' => '9'
                ) );
                if ( $this->data['useemail'] && $this->data['canreset'] ) {
-                       echo '&#160;';
-                       echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array(
-                               'id' => 'wpMailmypassword',
-                               'tabindex' => '10'
-                       ) );
+                       if( $this->data['resetlink'] === true ){
+                               echo '&#160;';
+                               echo Linker::link(
+                                       SpecialPage::getTitleFor( 'PasswordReset' ),
+                                       wfMessage( 'userlogin-resetlink' )
+                               );
+                       } elseif( $this->data['resetlink'] === null ) {
+                               echo '&#160;';
+                               echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array(
+                                       'id' => 'wpMailmypassword',
+                                       'tabindex' => '10'
+                               ) );
+                       }
                } ?>
 
                        </td>
index 3a097e7..5d8c521 100644 (file)
@@ -420,6 +420,7 @@ $specialPageAliases = array(
        'Myuploads'                 => array( 'MyUploads' ),
        'Newimages'                 => array( 'NewFiles', 'NewImages' ),
        'Newpages'                  => array( 'NewPages' ),
+       'PasswordReset'             => array( 'PasswordReset' ),
        'PermanentLink'             => array( 'PermanentLink', 'PermaLink' ),
        'Popularpages'              => array( 'PopularPages' ),
        'Preferences'               => array( 'Preferences' ),
@@ -1062,6 +1063,7 @@ Do not forget to change your [[Special:Preferences|{{SITENAME}} preferences]].',
 'createaccount'              => 'Create account',
 'gotaccount'                 => 'Already have an account? $1.',
 'gotaccountlink'             => 'Log in',
+'userlogin-resetlink'        => 'Forgotten your login details?',
 'createaccountmail'          => 'By e-mail',
 'createaccountreason'        => 'Reason:',
 'badretype'                  => 'The passwords you entered do not match.',
@@ -1156,7 +1158,7 @@ Please wait before trying again.',
 'php-mail-error'         => '$1', # do not translate or duplicate this message to other languages
 'php-mail-error-unknown' => "Unknown error in PHP's mail() function",
 
-# Password reset dialog
+# Change Password dialog
 'resetpass'                 => 'Change password',
 'resetpass_announce'        => 'You logged in with a temporary e-mailed code.
 To finish logging in, you must set a new password here:',
@@ -1176,6 +1178,41 @@ Now logging you in...',
 You may have already successfully changed your password or requested a new temporary password.',
 'resetpass-temp-password'   => 'Temporary password:',
 
+# Special:PasswordReset
+'passwordreset'                => 'Reset password',
+'passwordreset-text'           => 'Complete this form to receive an email reminder of your account details.',
+'passwordreset-legend'         => 'Reset password',
+'passwordreset-disabled'       => 'Password resets have been disabled on this wiki.',
+'passwordreset-pretext'        => '{{PLURAL:$1||Enter one of the pieces of data below}}',
+'passwordreset-username'       => 'Username:',
+'passwordreset-email'          => 'Email:',
+'passwordreset-emailtitle'     => 'Account details on {{SITENAME}}',
+'passwordreset-emailtext-ip'   => '
+Someone (probably you, from IP address $1) requested a reminder of your
+account details for {{SITENAME}} ($4). The following user {{PLURAL:$3|account is|accounts are}}
+associated with this email address:
+
+$2
+
+{{PLURAL:$3|This temporary password|These temporary passwords}} will expire in {{PLURAL:$5|one day|$5 days}}.
+You should log in and choose a new password now. If someone else made this
+request, or if you have remembered your original password, and you no longer
+wish to change it, you may ignore this message and continue using your old
+password.',
+'passwordreset-emailtext-user'   => '
+User $1 on {{SITENAME}} requested a reminder of your account details for {{SITENAME}}
+($4). The following user {{PLURAL:$3|account is|accounts are}} associated with this email address:
+
+$2
+
+{{PLURAL:$3|This temporary password|These temporary passwords}} will expire in {{PLURAL:$5|one day|$5 days}}.
+You should log in and choose a new password now. If someone else made this
+request, or if you have remembered your original password, and you no longer
+wish to change it, you may ignore this message and continue using your old
+password.',
+'passwordreset-emailelement'     => "\tUsername:           $1\n\tTemporary password: $2",
+'passwordreset-emailsent'        => 'A reminder email has been sent.',
+
 # Edit page toolbar
 'bold_sample'     => 'Bold text',
 'bold_tip'        => 'Bold text',
index 10ad919..bf10adb 100644 (file)
@@ -508,6 +508,20 @@ $wgMessageStructure = array(
                'resetpass-wrong-oldpass',
                'resetpass-temp-password',
        ),
+       'passwordreset' => array(
+               'passwordreset',
+               'passwordreset-text',
+               'passwordreset-legend',
+               'passwordreset-disabled',
+               'passwordreset-pretext',
+               'passwordreset-username',
+               'passwordreset-email',
+               'passwordreset-emailtitle',
+               'passwordreset-emailtext-ip',
+               'passwordreset-emailtext-user',
+               'passwordreset-emailelement',
+               'passwordreset-emailsent',
+       ),
        'toolbar' => array(
                'bold_sample',
                'bold_tip',