Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -508,6 +508,20 @@ |
509 | 509 | 'resetpass-wrong-oldpass', |
510 | 510 | 'resetpass-temp-password', |
511 | 511 | ), |
| 512 | + 'passwordreset' => array( |
| 513 | + 'passwordreset', |
| 514 | + 'passwordreset-text', |
| 515 | + 'passwordreset-legend', |
| 516 | + 'passwordreset-disabled', |
| 517 | + 'passwordreset-pretext', |
| 518 | + 'passwordreset-username', |
| 519 | + 'passwordreset-email', |
| 520 | + 'passwordreset-emailtitle', |
| 521 | + 'passwordreset-emailtext-ip', |
| 522 | + 'passwordreset-emailtext-user', |
| 523 | + 'passwordreset-emailelement', |
| 524 | + 'passwordreset-emailsent', |
| 525 | + ), |
512 | 526 | 'toolbar' => array( |
513 | 527 | 'bold_sample', |
514 | 528 | 'bold_tip', |
Index: trunk/phase3/includes/SpecialPageFactory.php |
— | — | @@ -73,6 +73,7 @@ |
74 | 74 | 'Unblock' => 'SpecialUnblock', |
75 | 75 | 'BlockList' => 'SpecialBlockList', |
76 | 76 | 'ChangePassword' => 'SpecialChangePassword', |
| 77 | + 'PasswordReset' => 'SpecialPasswordReset', |
77 | 78 | 'DeletedContributions' => 'DeletedContributionsPage', |
78 | 79 | 'Preferences' => 'SpecialPreferences', |
79 | 80 | 'Contributions' => 'SpecialContributions', |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -91,6 +91,7 @@ |
92 | 92 | 'FileRevertForm' => 'includes/FileRevertForm.php', |
93 | 93 | 'ForkController' => 'includes/ForkController.php', |
94 | 94 | 'FormOptions' => 'includes/FormOptions.php', |
| 95 | + 'FormSpecialPage' => 'includes/SpecialPage.php', |
95 | 96 | 'GenderCache' => 'includes/GenderCache.php', |
96 | 97 | 'GlobalDependency' => 'includes/CacheDependency.php', |
97 | 98 | 'HashtableReplacer' => 'includes/StringUtils.php', |
— | — | @@ -714,6 +715,7 @@ |
715 | 716 | 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php', |
716 | 717 | 'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php', |
717 | 718 | 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', |
| 719 | + 'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php', |
718 | 720 | 'SpecialPreferences' => 'includes/specials/SpecialPreferences.php', |
719 | 721 | 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', |
720 | 722 | 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php', |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -3005,6 +3005,17 @@ |
3006 | 3006 | $wgLivePasswordStrengthChecks = false; |
3007 | 3007 | |
3008 | 3008 | /** |
| 3009 | + * Whether to allow password resets ("enter some identifying data, and we'll send an email |
| 3010 | + * with a temporary password you can use to get back into the account") identified by |
| 3011 | + * various bits of data. Setting all of these to false (or the whole variable to false) |
| 3012 | + * has the effect of disabling password resets entirely |
| 3013 | + */ |
| 3014 | +$wgPasswordResetRoutes = array( |
| 3015 | + 'username' => true, |
| 3016 | + 'email' => false, // Warning: enabling this will be *very* slow on large wikis |
| 3017 | +); |
| 3018 | + |
| 3019 | +/** |
3009 | 3020 | * Maximum number of Unicode characters in signature |
3010 | 3021 | */ |
3011 | 3022 | $wgMaxSigChars = 255; |
Index: trunk/phase3/includes/specials/SpecialPasswordReset.php |
— | — | @@ -0,0 +1,225 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Implements Special:Blankpage |
| 5 | + * |
| 6 | + * This program is free software; you can redistribute it and/or modify |
| 7 | + * it under the terms of the GNU General Public License as published by |
| 8 | + * the Free Software Foundation; either version 2 of the License, or |
| 9 | + * (at your option) any later version. |
| 10 | + * |
| 11 | + * This program is distributed in the hope that it will be useful, |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + * GNU General Public License for more details. |
| 15 | + * |
| 16 | + * You should have received a copy of the GNU General Public License along |
| 17 | + * with this program; if not, write to the Free Software Foundation, Inc., |
| 18 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 19 | + * http://www.gnu.org/copyleft/gpl.html |
| 20 | + * |
| 21 | + * @file |
| 22 | + * @ingroup SpecialPage |
| 23 | + */ |
| 24 | + |
| 25 | +/** |
| 26 | + * Special page for requesting a password reset email |
| 27 | + * |
| 28 | + * @ingroup SpecialPage |
| 29 | + */ |
| 30 | +class SpecialPasswordReset extends FormSpecialPage { |
| 31 | + |
| 32 | + public function __construct() { |
| 33 | + parent::__construct( 'PasswordReset' ); |
| 34 | + } |
| 35 | + |
| 36 | + public function userCanExecute( User $user ) { |
| 37 | + global $wgPasswordResetRoutes, $wgAuth; |
| 38 | + |
| 39 | + // Maybe password resets are disabled, or there are no allowable routes |
| 40 | + if ( !is_array( $wgPasswordResetRoutes ) |
| 41 | + || !in_array( true, array_values( $wgPasswordResetRoutes ) ) ) |
| 42 | + { |
| 43 | + throw new ErrorPageError( 'internalerror', 'passwordreset-disabled' ); |
| 44 | + } |
| 45 | + |
| 46 | + // Maybe the external auth plugin won't allow local password changes |
| 47 | + if ( !$wgAuth->allowPasswordChange() ) { |
| 48 | + throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' ); |
| 49 | + } |
| 50 | + |
| 51 | + // Maybe the user is blocked (check this here rather than relying on the parent |
| 52 | + // method as we have a more specific error message to use here |
| 53 | + if ( $user->isBlocked() ) { |
| 54 | + throw new ErrorPageError( 'internalerror', 'blocked-mailpassword' ); |
| 55 | + } |
| 56 | + |
| 57 | + return parent::userCanExecute( $user ); |
| 58 | + } |
| 59 | + |
| 60 | + protected function getFormFields() { |
| 61 | + global $wgPasswordResetRoutes; |
| 62 | + $a = array(); |
| 63 | + if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { |
| 64 | + $a['Username'] = array( |
| 65 | + 'type' => 'text', |
| 66 | + 'label-message' => 'passwordreset-username', |
| 67 | + ); |
| 68 | + } |
| 69 | + |
| 70 | + if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) { |
| 71 | + $a['Email'] = array( |
| 72 | + 'type' => 'email', |
| 73 | + 'label-message' => 'passwordreset-email', |
| 74 | + ); |
| 75 | + } |
| 76 | + |
| 77 | + return $a; |
| 78 | + } |
| 79 | + |
| 80 | + protected function preText() { |
| 81 | + global $wgPasswordResetRoutes; |
| 82 | + $i = 0; |
| 83 | + if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { |
| 84 | + $i++; |
| 85 | + } |
| 86 | + if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) { |
| 87 | + $i++; |
| 88 | + } |
| 89 | + return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock(); |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Process the form. At this point we know that the user passes all the criteria in |
| 94 | + * userCanExecute(), and if the data array contains 'Username', etc, then Username |
| 95 | + * resets are allowed. |
| 96 | + * @param $data array |
| 97 | + * @return Bool|Array |
| 98 | + */ |
| 99 | + public function onSubmit( array $data ) { |
| 100 | + |
| 101 | + if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { |
| 102 | + $method = 'username'; |
| 103 | + $users = array( User::newFromName( $data['Username'] ) ); |
| 104 | + } elseif ( isset( $data['Email'] ) |
| 105 | + && $data['Email'] !== '' |
| 106 | + && Sanitizer::validateEmail( $data['Email'] ) ) |
| 107 | + { |
| 108 | + $method = 'email'; |
| 109 | + |
| 110 | + // FIXME: this is an unindexed query |
| 111 | + $res = wfGetDB( DB_SLAVE )->select( |
| 112 | + 'user', |
| 113 | + '*', |
| 114 | + array( 'user_email' => $data['Email'] ), |
| 115 | + __METHOD__ |
| 116 | + ); |
| 117 | + if ( $res ) { |
| 118 | + $users = array(); |
| 119 | + foreach( $res as $row ){ |
| 120 | + $users[] = User::newFromRow( $row ); |
| 121 | + } |
| 122 | + } else { |
| 123 | + // Some sort of database error, probably unreachable |
| 124 | + throw new MWException( 'Unknown database error in ' . __METHOD__ ); |
| 125 | + } |
| 126 | + } else { |
| 127 | + // The user didn't supply any data |
| 128 | + return false; |
| 129 | + } |
| 130 | + |
| 131 | + // Check for hooks (captcha etc), and allow them to modify the users list |
| 132 | + $error = array(); |
| 133 | + if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) { |
| 134 | + return array( $error ); |
| 135 | + } |
| 136 | + |
| 137 | + if( count( $users ) == 0 ){ |
| 138 | + if( $method == 'email' ){ |
| 139 | + // Don't reveal whether or not an email address is in use |
| 140 | + return true; |
| 141 | + } else { |
| 142 | + return array( 'noname' ); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + $firstUser = $users[0]; |
| 147 | + |
| 148 | + if ( !$firstUser instanceof User || !$firstUser->getID() ) { |
| 149 | + return array( array( 'nosuchuser', $data['Username'] ) ); |
| 150 | + } |
| 151 | + |
| 152 | + // Check against the rate limiter |
| 153 | + if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { |
| 154 | + throw new ThrottledError; |
| 155 | + } |
| 156 | + |
| 157 | + // Check against password throttle |
| 158 | + foreach ( $users as $user ) { |
| 159 | + if ( $user->isPasswordReminderThrottled() ) { |
| 160 | + global $wgPasswordReminderResendTime; |
| 161 | + # Round the time in hours to 3 d.p., in case someone is specifying |
| 162 | + # minutes or seconds. |
| 163 | + return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) ); |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + global $wgServer, $wgScript, $wgNewPasswordExpiry; |
| 168 | + |
| 169 | + // All the users will have the same email address |
| 170 | + if ( $firstUser->getEmail() == '' ) { |
| 171 | + // This won't be reachable from the email route, so safe to expose the username |
| 172 | + return array( array( 'noemail', $firstUser->getName() ) ); |
| 173 | + } |
| 174 | + |
| 175 | + // We need to have a valid IP address for the hook, but per bug 18347, we should |
| 176 | + // send the user's name if they're logged in. |
| 177 | + $ip = wfGetIP(); |
| 178 | + if ( !$ip ) { |
| 179 | + return array( 'badipaddress' ); |
| 180 | + } |
| 181 | + $caller = $this->getUser(); |
| 182 | + wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) ); |
| 183 | + $username = $caller->getName(); |
| 184 | + $msg = IP::isValid( $username ) |
| 185 | + ? 'passwordreset-emailtext-ip' |
| 186 | + : 'passwordreset-emailtext-user'; |
| 187 | + |
| 188 | + $passwords = array(); |
| 189 | + foreach ( $users as $user ) { |
| 190 | + $password = $user->randomPassword(); |
| 191 | + $user->setNewpassword( $password ); |
| 192 | + $user->saveSettings(); |
| 193 | + $passwords[] = wfMessage( 'passwordreset-emailelement', $user->getName(), $password ); |
| 194 | + } |
| 195 | + $passwordBlock = implode( "\n\n", $passwords ); |
| 196 | + |
| 197 | + // Send in the user's language; which should hopefully be the same |
| 198 | + $userLanguage = $firstUser->getOption( 'language' ); |
| 199 | + |
| 200 | + $body = wfMessage( $msg )->inLanguage( $userLanguage ); |
| 201 | + $body->params( |
| 202 | + $username, |
| 203 | + $passwordBlock, |
| 204 | + count( $passwords ), |
| 205 | + $wgServer . $wgScript, |
| 206 | + round( $wgNewPasswordExpiry / 86400 ) |
| 207 | + ); |
| 208 | + |
| 209 | + $title = wfMessage( 'passwordreset-emailtitle' ); |
| 210 | + |
| 211 | + $result = $firstUser->sendMail( $title->text(), $body->text() ); |
| 212 | + |
| 213 | + if ( $result->isGood() ) { |
| 214 | + return true; |
| 215 | + } else { |
| 216 | + // FIXME: The email didn't send, but we have already set the password throttle |
| 217 | + // timestamp, so they won't be able to try again until it expires... :( |
| 218 | + return array( array( 'mailerror', $result->getMessage() ) ); |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + public function onSuccess() { |
| 223 | + $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' ); |
| 224 | + $this->getOutput()->returnToMain(); |
| 225 | + } |
| 226 | +} |
Property changes on: trunk/phase3/includes/specials/SpecialPasswordReset.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 227 | + native |
Index: trunk/phase3/includes/specials/SpecialUserlogin.php |
— | — | @@ -44,7 +44,7 @@ |
45 | 45 | const WRONG_TOKEN = 13; |
46 | 46 | |
47 | 47 | var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; |
48 | | - var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; |
| 48 | + var $mAction, $mCreateaccount, $mCreateaccountMail; |
49 | 49 | var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; |
50 | 50 | var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; |
51 | 51 | var $mType, $mReason, $mRealName; |
— | — | @@ -90,8 +90,6 @@ |
91 | 91 | $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); |
92 | 92 | $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) |
93 | 93 | && $wgEnableEmail; |
94 | | - $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' ) |
95 | | - && $wgEnableEmail; |
96 | 94 | $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); |
97 | 95 | $this->mAction = $request->getVal( 'action' ); |
98 | 96 | $this->mRemember = $request->getCheck( 'wpRemember' ); |
— | — | @@ -146,8 +144,6 @@ |
147 | 145 | return $this->addNewAccount(); |
148 | 146 | } elseif ( $this->mCreateaccountMail ) { |
149 | 147 | return $this->addNewAccountMailPassword(); |
150 | | - } elseif ( $this->mMailmypassword ) { |
151 | | - return $this->mailPassword(); |
152 | 148 | } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { |
153 | 149 | return $this->processLogin(); |
154 | 150 | } |
— | — | @@ -740,95 +736,6 @@ |
741 | 737 | } |
742 | 738 | |
743 | 739 | /** |
744 | | - * @private |
745 | | - */ |
746 | | - function mailPassword() { |
747 | | - global $wgUser, $wgOut, $wgAuth; |
748 | | - |
749 | | - if ( wfReadOnly() ) { |
750 | | - $wgOut->readOnlyPage(); |
751 | | - return false; |
752 | | - } |
753 | | - |
754 | | - if( !$wgAuth->allowPasswordChange() ) { |
755 | | - $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); |
756 | | - return; |
757 | | - } |
758 | | - |
759 | | - # Check against blocked IPs so blocked users can't flood admins |
760 | | - # with password resets |
761 | | - if( $wgUser->isBlocked() ) { |
762 | | - $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); |
763 | | - return; |
764 | | - } |
765 | | - |
766 | | - # Check for hooks |
767 | | - $error = null; |
768 | | - if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mUsername, &$error ) ) ) { |
769 | | - $this->mainLoginForm( $error ); |
770 | | - return; |
771 | | - } |
772 | | - |
773 | | - # If the user doesn't have a login token yet, set one. |
774 | | - if ( !self::getLoginToken() ) { |
775 | | - self::setLoginToken(); |
776 | | - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
777 | | - return; |
778 | | - } |
779 | | - |
780 | | - # If the user didn't pass a login token, tell them we need one |
781 | | - if ( !$this->mToken ) { |
782 | | - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
783 | | - return; |
784 | | - } |
785 | | - |
786 | | - # Check against the rate limiter |
787 | | - if( $wgUser->pingLimiter( 'mailpassword' ) ) { |
788 | | - $wgOut->rateLimited(); |
789 | | - return; |
790 | | - } |
791 | | - |
792 | | - if ( $this->mUsername == '' ) { |
793 | | - $this->mainLoginForm( wfMsg( 'noname' ) ); |
794 | | - return; |
795 | | - } |
796 | | - $u = User::newFromName( $this->mUsername ); |
797 | | - if( !$u instanceof User ) { |
798 | | - $this->mainLoginForm( wfMsg( 'noname' ) ); |
799 | | - return; |
800 | | - } |
801 | | - if ( 0 == $u->getID() ) { |
802 | | - $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', $u->getName() ) ); |
803 | | - return; |
804 | | - } |
805 | | - |
806 | | - # Validate the login token |
807 | | - if ( $this->mToken !== self::getLoginToken() ) { |
808 | | - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); |
809 | | - return; |
810 | | - } |
811 | | - |
812 | | - # Check against password throttle |
813 | | - if ( $u->isPasswordReminderThrottled() ) { |
814 | | - global $wgPasswordReminderResendTime; |
815 | | - # Round the time in hours to 3 d.p., in case someone is specifying |
816 | | - # minutes or seconds. |
817 | | - $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ), |
818 | | - round( $wgPasswordReminderResendTime, 3 ) ) ); |
819 | | - return; |
820 | | - } |
821 | | - |
822 | | - $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); |
823 | | - if( $result->isGood() ) { |
824 | | - $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); |
825 | | - self::clearLoginToken(); |
826 | | - } else { |
827 | | - $this->mainLoginForm( $result->getWikiText( 'mailerror' ) ); |
828 | | - } |
829 | | - } |
830 | | - |
831 | | - |
832 | | - /** |
833 | 740 | * @param $u User object |
834 | 741 | * @param $throttle Boolean |
835 | 742 | * @param $emailTitle String: message name of email title |
— | — | @@ -977,7 +884,7 @@ |
978 | 885 | global $wgEnableEmail, $wgEnableUserEmail; |
979 | 886 | global $wgRequest, $wgLoginLanguageSelector; |
980 | 887 | global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; |
981 | | - global $wgSecureLogin; |
| 888 | + global $wgSecureLogin, $wgPasswordResetRoutes; |
982 | 889 | |
983 | 890 | $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); |
984 | 891 | |
— | — | @@ -1043,6 +950,10 @@ |
1044 | 951 | $template->set( 'link', '' ); |
1045 | 952 | } |
1046 | 953 | |
| 954 | + $resetLink = $this->mType == 'signup' |
| 955 | + ? null |
| 956 | + : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); |
| 957 | + |
1047 | 958 | $template->set( 'header', '' ); |
1048 | 959 | $template->set( 'name', $this->mUsername ); |
1049 | 960 | $template->set( 'password', $this->mPassword ); |
— | — | @@ -1061,6 +972,7 @@ |
1062 | 973 | $template->set( 'emailrequired', $wgEmailConfirmToEdit ); |
1063 | 974 | $template->set( 'emailothers', $wgEnableUserEmail ); |
1064 | 975 | $template->set( 'canreset', $wgAuth->allowPasswordChange() ); |
| 976 | + $template->set( 'resetlink', $resetLink ); |
1065 | 977 | $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); |
1066 | 978 | $template->set( 'usereason', $wgUser->isLoggedIn() ); |
1067 | 979 | $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ); |
Index: trunk/phase3/includes/SpecialPage.php |
— | — | @@ -660,6 +660,138 @@ |
661 | 661 | } |
662 | 662 | |
663 | 663 | /** |
| 664 | + * Special page which uses an HTMLForm to handle processing. This is mostly a |
| 665 | + * clone of FormAction. More special pages should be built this way; maybe this could be |
| 666 | + * a new structure for SpecialPages |
| 667 | + */ |
| 668 | +abstract class FormSpecialPage extends SpecialPage { |
| 669 | + |
| 670 | + /** |
| 671 | + * Get an HTMLForm descriptor array |
| 672 | + * @return Array |
| 673 | + */ |
| 674 | + protected abstract function getFormFields(); |
| 675 | + |
| 676 | + /** |
| 677 | + * Add pre- or post-text to the form |
| 678 | + * @return String HTML which will be sent to $form->addPreText() |
| 679 | + */ |
| 680 | + protected function preText() { return ''; } |
| 681 | + protected function postText() { return ''; } |
| 682 | + |
| 683 | + /** |
| 684 | + * Play with the HTMLForm if you need to more substantially |
| 685 | + * @param $form HTMLForm |
| 686 | + */ |
| 687 | + protected function alterForm( HTMLForm $form ) {} |
| 688 | + |
| 689 | + /** |
| 690 | + * Get the HTMLForm to control behaviour |
| 691 | + * @return HTMLForm|null |
| 692 | + */ |
| 693 | + protected function getForm() { |
| 694 | + $this->fields = $this->getFormFields(); |
| 695 | + |
| 696 | + // Give hooks a chance to alter the form, adding extra fields or text etc |
| 697 | + wfRunHooks( "Special{$this->getName()}ModifyFormFields", array( &$this->fields ) ); |
| 698 | + |
| 699 | + $form = new HTMLForm( $this->fields, $this->getContext() ); |
| 700 | + $form->setSubmitCallback( array( $this, 'onSubmit' ) ); |
| 701 | + $form->setWrapperLegend( wfMessage( strtolower( $this->getName() ) . '-legend' ) ); |
| 702 | + $form->addHeaderText( wfMessage( strtolower( $this->getName() ) . '-text' )->parseAsBlock() ); |
| 703 | + |
| 704 | + // Retain query parameters (uselang etc) |
| 705 | + $params = array_diff_key( $this->getRequest()->getQueryValues(), array( 'title' => null ) ); |
| 706 | + $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) ); |
| 707 | + |
| 708 | + $form->addPreText( $this->preText() ); |
| 709 | + $form->addPostText( $this->postText() ); |
| 710 | + $this->alterForm( $form ); |
| 711 | + |
| 712 | + // Give hooks a chance to alter the form, adding extra fields or text etc |
| 713 | + wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) ); |
| 714 | + |
| 715 | + return $form; |
| 716 | + } |
| 717 | + |
| 718 | + /** |
| 719 | + * Process the form on POST submission. |
| 720 | + * @param $data Array |
| 721 | + * @return Bool|Array true for success, false for didn't-try, array of errors on failure |
| 722 | + */ |
| 723 | + public abstract function onSubmit( array $data ); |
| 724 | + |
| 725 | + /** |
| 726 | + * Do something exciting on successful processing of the form, most likely to show a |
| 727 | + * confirmation message |
| 728 | + */ |
| 729 | + public abstract function onSuccess(); |
| 730 | + |
| 731 | + /** |
| 732 | + * Basic SpecialPage workflow: get a form, send it to the user; get some data back, |
| 733 | + */ |
| 734 | + public function execute( $par ) { |
| 735 | + $this->setParameter( $par ); |
| 736 | + $this->setHeaders(); |
| 737 | + |
| 738 | + // This will throw exceptions if there's a problem |
| 739 | + $this->userCanExecute( $this->getUser() ); |
| 740 | + |
| 741 | + $form = $this->getForm(); |
| 742 | + if ( $form->show() ) { |
| 743 | + $this->onSuccess(); |
| 744 | + } |
| 745 | + } |
| 746 | + |
| 747 | + /** |
| 748 | + * Maybe do something interesting with the subpage parameter |
| 749 | + * @param $par String |
| 750 | + */ |
| 751 | + protected function setParameter( $par ){} |
| 752 | + |
| 753 | + /** |
| 754 | + * Checks if the given user (identified by an object) can perform this action. Can be |
| 755 | + * overridden by sub-classes with more complicated permissions schemes. Failures here |
| 756 | + * must throw subclasses of ErrorPageError |
| 757 | + * |
| 758 | + * @param $user User: the user to check, or null to use the context user |
| 759 | + * @throws ErrorPageError |
| 760 | + */ |
| 761 | + public function userCanExecute( User $user ) { |
| 762 | + if ( $this->requiresWrite() && wfReadOnly() ) { |
| 763 | + throw new ReadOnlyError(); |
| 764 | + } |
| 765 | + |
| 766 | + if ( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ) { |
| 767 | + throw new PermissionsError( $this->getRestriction() ); |
| 768 | + } |
| 769 | + |
| 770 | + if ( $this->requiresUnblock() && $user->isBlocked() ) { |
| 771 | + $block = $user->mBlock; |
| 772 | + throw new UserBlockedError( $block ); |
| 773 | + } |
| 774 | + |
| 775 | + return true; |
| 776 | + } |
| 777 | + |
| 778 | + /** |
| 779 | + * Whether this action requires the wiki not to be locked |
| 780 | + * @return Bool |
| 781 | + */ |
| 782 | + public function requiresWrite() { |
| 783 | + return true; |
| 784 | + } |
| 785 | + |
| 786 | + /** |
| 787 | + * Whether this action cannot be executed by a blocked user |
| 788 | + * @return Bool |
| 789 | + */ |
| 790 | + public function requiresUnblock() { |
| 791 | + return true; |
| 792 | + } |
| 793 | +} |
| 794 | + |
| 795 | +/** |
664 | 796 | * Shortcut to construct a special page which is unlisted by default |
665 | 797 | * @ingroup SpecialPage |
666 | 798 | */ |
Index: trunk/phase3/includes/templates/Userlogin.php |
— | — | @@ -130,11 +130,19 @@ |
131 | 131 | 'tabindex' => '9' |
132 | 132 | ) ); |
133 | 133 | if ( $this->data['useemail'] && $this->data['canreset'] ) { |
134 | | - echo ' '; |
135 | | - echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array( |
136 | | - 'id' => 'wpMailmypassword', |
137 | | - 'tabindex' => '10' |
138 | | - ) ); |
| 134 | + if( $this->data['resetlink'] === true ){ |
| 135 | + echo ' '; |
| 136 | + echo Linker::link( |
| 137 | + SpecialPage::getTitleFor( 'PasswordReset' ), |
| 138 | + wfMessage( 'userlogin-resetlink' ) |
| 139 | + ); |
| 140 | + } elseif( $this->data['resetlink'] === null ) { |
| 141 | + echo ' '; |
| 142 | + echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array( |
| 143 | + 'id' => 'wpMailmypassword', |
| 144 | + 'tabindex' => '10' |
| 145 | + ) ); |
| 146 | + } |
139 | 147 | } ?> |
140 | 148 | |
141 | 149 | </td> |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -420,6 +420,7 @@ |
421 | 421 | 'Myuploads' => array( 'MyUploads' ), |
422 | 422 | 'Newimages' => array( 'NewFiles', 'NewImages' ), |
423 | 423 | 'Newpages' => array( 'NewPages' ), |
| 424 | + 'PasswordReset' => array( 'PasswordReset' ), |
424 | 425 | 'PermanentLink' => array( 'PermanentLink', 'PermaLink' ), |
425 | 426 | 'Popularpages' => array( 'PopularPages' ), |
426 | 427 | 'Preferences' => array( 'Preferences' ), |
— | — | @@ -1062,6 +1063,7 @@ |
1063 | 1064 | 'createaccount' => 'Create account', |
1064 | 1065 | 'gotaccount' => 'Already have an account? $1.', |
1065 | 1066 | 'gotaccountlink' => 'Log in', |
| 1067 | +'userlogin-resetlink' => 'Forgotten your login details?', |
1066 | 1068 | 'createaccountmail' => 'By e-mail', |
1067 | 1069 | 'createaccountreason' => 'Reason:', |
1068 | 1070 | 'badretype' => 'The passwords you entered do not match.', |
— | — | @@ -1156,7 +1158,7 @@ |
1157 | 1159 | 'php-mail-error' => '$1', # do not translate or duplicate this message to other languages |
1158 | 1160 | 'php-mail-error-unknown' => "Unknown error in PHP's mail() function", |
1159 | 1161 | |
1160 | | -# Password reset dialog |
| 1162 | +# Change Password dialog |
1161 | 1163 | 'resetpass' => 'Change password', |
1162 | 1164 | 'resetpass_announce' => 'You logged in with a temporary e-mailed code. |
1163 | 1165 | To finish logging in, you must set a new password here:', |
— | — | @@ -1176,6 +1178,41 @@ |
1177 | 1179 | You may have already successfully changed your password or requested a new temporary password.', |
1178 | 1180 | 'resetpass-temp-password' => 'Temporary password:', |
1179 | 1181 | |
| 1182 | +# Special:PasswordReset |
| 1183 | +'passwordreset' => 'Reset password', |
| 1184 | +'passwordreset-text' => 'Complete this form to receive an email reminder of your account details.', |
| 1185 | +'passwordreset-legend' => 'Reset password', |
| 1186 | +'passwordreset-disabled' => 'Password resets have been disabled on this wiki.', |
| 1187 | +'passwordreset-pretext' => '{{PLURAL:$1||Enter one of the pieces of data below}}', |
| 1188 | +'passwordreset-username' => 'Username:', |
| 1189 | +'passwordreset-email' => 'Email:', |
| 1190 | +'passwordreset-emailtitle' => 'Account details on {{SITENAME}}', |
| 1191 | +'passwordreset-emailtext-ip' => ' |
| 1192 | +Someone (probably you, from IP address $1) requested a reminder of your |
| 1193 | +account details for {{SITENAME}} ($4). The following user {{PLURAL:$3|account is|accounts are}} |
| 1194 | +associated with this email address: |
| 1195 | + |
| 1196 | +$2 |
| 1197 | + |
| 1198 | +{{PLURAL:$3|This temporary password|These temporary passwords}} will expire in {{PLURAL:$5|one day|$5 days}}. |
| 1199 | +You should log in and choose a new password now. If someone else made this |
| 1200 | +request, or if you have remembered your original password, and you no longer |
| 1201 | +wish to change it, you may ignore this message and continue using your old |
| 1202 | +password.', |
| 1203 | +'passwordreset-emailtext-user' => ' |
| 1204 | +User $1 on {{SITENAME}} requested a reminder of your account details for {{SITENAME}} |
| 1205 | +($4). The following user {{PLURAL:$3|account is|accounts are}} associated with this email address: |
| 1206 | + |
| 1207 | +$2 |
| 1208 | + |
| 1209 | +{{PLURAL:$3|This temporary password|These temporary passwords}} will expire in {{PLURAL:$5|one day|$5 days}}. |
| 1210 | +You should log in and choose a new password now. If someone else made this |
| 1211 | +request, or if you have remembered your original password, and you no longer |
| 1212 | +wish to change it, you may ignore this message and continue using your old |
| 1213 | +password.', |
| 1214 | +'passwordreset-emailelement' => "\tUsername: $1\n\tTemporary password: $2", |
| 1215 | +'passwordreset-emailsent' => 'A reminder email has been sent.', |
| 1216 | + |
1180 | 1217 | # Edit page toolbar |
1181 | 1218 | 'bold_sample' => 'Bold text', |
1182 | 1219 | 'bold_tip' => 'Bold text', |
Index: trunk/extensions/GlobalBlocking/GlobalBlocking.php |
— | — | @@ -29,7 +29,7 @@ |
30 | 30 | |
31 | 31 | $wgHooks['getUserPermissionsErrorsExpensive'][] = 'GlobalBlocking::getUserPermissionsErrors'; |
32 | 32 | $wgHooks['UserIsBlockedGlobally'][] = 'GlobalBlocking::isBlockedGlobally'; |
33 | | -$wgHooks['UserLoginMailPassword'][] = 'GlobalBlocking::onMailPassword'; |
| 33 | +$wgHooks['SpecialPasswordResetOnSubmit'][] = 'GlobalBlocking::onSpecialPasswordResetOnSubmit'; |
34 | 34 | $wgHooks['OtherBlockLogLink'][] = 'GlobalBlocking::getBlockLogLink'; |
35 | 35 | |
36 | 36 | $wgAutoloadClasses['SpecialGlobalBlock'] = "$dir/SpecialGlobalBlock.php"; |
Index: trunk/extensions/GlobalBlocking/GlobalBlocking.class.php |
— | — | @@ -289,7 +289,7 @@ |
290 | 290 | return array(); |
291 | 291 | } |
292 | 292 | |
293 | | - static function onMailPassword( $name, &$error ) { |
| 293 | + static function onSpecialPasswordResetOnSubmit( &$users, $data, &$error ) { |
294 | 294 | global $wgUser; |
295 | 295 | |
296 | 296 | if ( GlobalBlocking::getUserBlockErrors( $wgUser, wfGetIp() ) ) { |
Index: trunk/extensions/CentralAuth/CentralAuth.php |
— | — | @@ -187,6 +187,7 @@ |
188 | 188 | $wgHooks['UserLoadDefaults'][] = 'CentralAuthHooks::onUserLoadDefaults'; |
189 | 189 | $wgHooks['getUserPermissionsErrorsExpensive'][] = 'CentralAuthHooks::onGetUserPermissionsErrorsExpensive'; |
190 | 190 | $wgHooks['MakeGlobalVariablesScript'][] = 'CentralAuthHooks::onMakeGlobalVariablesScript'; |
| 191 | +$wgHooks['SpecialPasswordResetOnSubmit'] = 'CentralAuthHooks::onSpecialPasswordResetOnSubmit'; |
191 | 192 | |
192 | 193 | // For interaction with the Special:Renameuser extension |
193 | 194 | $wgHooks['RenameUserWarning'][] = 'CentralAuthHooks::onRenameUserWarning'; |
Index: trunk/extensions/CentralAuth/CentralAuthHooks.php |
— | — | @@ -98,6 +98,32 @@ |
99 | 99 | return true; |
100 | 100 | } |
101 | 101 | |
| 102 | + /** |
| 103 | + * Show a nicer error when the user account does not exist on the local wiki, but |
| 104 | + * does exist globally |
| 105 | + * @param $users Array |
| 106 | + * @param $data Array |
| 107 | + * @param $abortError String |
| 108 | + * @return bool |
| 109 | + */ |
| 110 | + static function onSpecialPasswordResetOnSubmit( &$users, $data, &$abortError ) { |
| 111 | + if ( count( $users ) == 0 || !$users[0] instanceof User ){ |
| 112 | + // We can't handle this |
| 113 | + return true; |
| 114 | + } |
| 115 | + |
| 116 | + $firstUser = $users[0]; |
| 117 | + if( !$firstUser->getID() ) { |
| 118 | + $centralUser = CentralAuthUser::getInstance( $firstUser ); |
| 119 | + if ( $centralUser->exists() ) { |
| 120 | + $abortError = array( 'centralauth-account-exists-reset' ); |
| 121 | + return false; |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + return true; |
| 126 | + } |
| 127 | + |
102 | 128 | static function onUserLoginComplete( &$user, &$inject_html ) { |
103 | 129 | global $wgCentralAuthCookies, $wgRequest; |
104 | 130 | if ( !$wgCentralAuthCookies ) { |
Index: trunk/extensions/CentralAuth/CentralAuth.i18n.php |
— | — | @@ -241,6 +241,7 @@ |
242 | 242 | // Other messages |
243 | 243 | 'centralauth-invalid-wiki' => 'No such wiki database: $1', |
244 | 244 | 'centralauth-account-exists' => 'Cannot create account: the requested username is already taken in the unified login system.', |
| 245 | + 'centralauth-account-exists-reset' => 'The username $1 is not registered on this wiki, but it does exist in the unified login system.', |
245 | 246 | 'centralauth-login-progress' => 'Logging you in to projects of {{int:Centralauth-groupname}}:', # This message supports {{GENDER}} |
246 | 247 | 'centralauth-logout-progress' => 'Logging you out from other projects of {{int:Centralauth-groupname}}:', |
247 | 248 | 'centralauth-login-no-others' => 'You have been automatically logged into other projects of {{int:Centralauth-groupname}}.', |
Index: trunk/extensions/Translate/tag/PageTranslationHooks.php |
— | — | @@ -210,6 +210,7 @@ |
211 | 211 | |
212 | 212 | $options = $parser->getOptions(); |
213 | 213 | |
| 214 | + $sk = $options->getSkin(); |
214 | 215 | if ( method_exists( $options, 'getUserLang' ) ) { |
215 | 216 | $userLangCode = $options->getUserLang(); |
216 | 217 | } else { |
— | — | @@ -246,9 +247,9 @@ |
247 | 248 | if ( $parser->getTitle()->getText() === $_title->getText() ) { |
248 | 249 | $languages[] = Html::rawElement( 'b', null, "*$name* $percent" ); |
249 | 250 | } elseif ( $code === $userLangCode ) { |
250 | | - $languages[] = Linker::linkKnown( $_title, Html::rawElement( 'b', null, "$name $percent" ) ); |
| 251 | + $languages[] = $sk->linkKnown( $_title, Html::rawElement( 'b', null, "$name $percent" ) ); |
251 | 252 | } else { |
252 | | - $languages[] = Linker::linkKnown( $_title, "$name $percent" ); |
| 253 | + $languages[] = $sk->linkKnown( $_title, "$name $percent" ); |
253 | 254 | } |
254 | 255 | } |
255 | 256 | |