3 * SpecialPage for logging users into the wiki
7 class SpecialUserLogin
extends SpecialPage
{
9 var $mUsername, $mPassword, $mReturnTo, $mCookieCheck, $mPosted;
10 var $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
11 var $mRemember, $mDomain, $mLanguage;
12 var $mSkipCookieCheck, $mReturnToQuery;
14 public $mDomains = array();
16 public $mFormHeader = ''; # Can be filled by hooks etc
17 public $mFormFields = array(
20 'label-message' => 'yourname',
28 'label-message' => 'yourpassword',
30 'id' => 'wpPassword1',
35 'label-message' => 'yourdomainname',
41 'label-message' => 'remembermypassword',
46 protected $mLogin; # Login object
48 public function __construct(){
49 parent
::__construct( 'Userlogin' );
52 function execute( $par ) {
55 $this->mLogin
= new Login();
57 # Redirect out for account creation, for B/C
58 $type = ( $par == 'signup' ) ?
$par : $wgRequest->getText( 'type' );
59 if( $type == 'signup' ){
60 $sp = new SpecialCreateAccount();
65 if ( !is_null( $this->mCookieCheck
) ) {
66 $this->onCookieRedirectCheck();
68 } else if( $this->mPosted
) {
69 if ( $this->mMailmypassword
) {
70 return $this->showMailPage();
72 return $this->processLogin();
75 $this->mainLoginForm( '' );
80 * Load member variables from the HTTP request data
81 * @param $par String the fragment passed to execute()
83 protected function loadQuery(){
84 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
86 $this->mUsername
= $wgRequest->getText( 'wpName' );
87 $this->mPassword
= $wgRequest->getText( 'wpPassword' );
88 $this->mDomain
= $wgRequest->getText( 'wpDomain' );
89 $this->mLanguage
= $wgRequest->getText( 'uselang' );
91 $this->mReturnTo
= $wgRequest->getVal( 'returnto' );
92 $this->mReturnToQuery
= $wgRequest->getVal( 'returntoquery' );
93 $this->mCookieCheck
= $wgRequest->getVal( 'wpCookieCheck' );
95 $this->mMailmypassword
= $wgRequest->getCheck( 'wpMailmypassword' )
97 $this->mRemember
= $wgRequest->getCheck( 'wpRemember' );
98 $this->mSkipCookieCheck
= $wgRequest->getCheck( 'wpSkipCookieCheck' );
99 $this->mPosted
= $wgRequest->wasPosted();
101 if ( $wgRedirectOnLogin ) {
102 $this->mReturnTo
= $wgRedirectOnLogin;
103 $this->mReturnToQuery
= '';
106 if( !$wgAuth->validDomain( $this->mDomain
) ) {
107 $this->mDomain
= 'invaliddomain';
109 $wgAuth->setDomain( $this->mDomain
);
111 # When switching accounts, it sucks to get automatically logged out
112 $returnToTitle = Title
::newFromText( $this->mReturnTo
);
113 if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
114 $this->mReturnTo
= '';
115 $this->mReturnToQuery
= '';
120 * Show the main login form
121 * @param $msg String a message key for a warning/error message
122 * that may have been generated on a previous iteration
124 protected function mainLoginForm( $msg, $msgtype = 'error' ) {
125 global $wgUser, $wgOut, $wgEnableEmail;
126 global $wgCookiePrefix, $wgLoginLanguageSelector;
127 global $wgAuth, $wgCookieExpiration;
129 # Preload the name field with something if we can
130 if ( '' == $this->mUsername
) {
131 if ( $wgUser->isLoggedIn() ) {
132 $this->mUsername
= $wgUser->getName();
133 } elseif( isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ) {
134 $this->mUsername
= $_COOKIE[$wgCookiePrefix.'UserName'];
137 if( $this->mUsername
){
138 $this->mFormFields
['Name']['default'] = $this->mUsername
;
139 $this->mFormFields
['Password']['autofocus'] = '1';
141 $this->mFormFields
['Name']['autofocus'] = '1';
144 # Parse the error message if we got one
146 if( $msgtype == 'error' ){
147 $msg = wfMsg( 'loginerror' ) . ' ' . $msg;
149 $msg = Html
::rawElement(
151 array( 'class' => $msgtype . 'box' ),
158 # Make sure the returnTo strings don't get lost if the
159 # user changes language, etc
161 if ( !empty( $this->mReturnTo
) ) {
162 $linkq['returnto'] = wfUrlencode( $this->mReturnTo
);
163 if ( !empty( $this->mReturnToQuery
) )
164 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery
);
167 # Pass any language selection on to the mode switch link
168 if( $wgLoginLanguageSelector && $this->mLanguage
)
169 $linkq['uselang'] = $this->mLanguage
;
171 $skin = $wgUser->getSkin();
173 SpecialPage
::getTitleFor( 'CreateAccount' ),
174 wfMsgHtml( 'nologinlink' ),
178 # Don't show a "create account" link if the user can't
179 $link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
180 ?
wfMsgWikiHtml( 'nologin', $link )
183 # Prepare language selection links as needed
184 $langSelector = $wgLoginLanguageSelector
187 array( 'id' => 'languagelinks' ),
188 self
::makeLanguageSelector( $this->getTitle(), $this->mReturnTo
) )
191 # Add a 'mail reset' button if available
193 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
194 $buttons = Html
::element(
198 'name' => 'wpMailmypassword',
199 'value' => wfMsg( 'mailmypassword' ),
200 'id' => 'wpMailmypassword',
205 # Give authentication and captcha plugins a chance to
206 # modify the form, by hook or by using $wgAuth
207 $wgAuth->modifyUITemplate( $this, 'login' );
208 wfRunHooks( 'UserLoginForm', array( &$this ) );
210 # The most likely use of the hook is to enable domains;
211 # check that now, and add fields if necessary
212 if( $this->mDomains
){
213 $this->mFormFields
['Domain']['options'] = $this->mDomains
;
214 $this->mFormFields
['Domain']['default'] = $this->mDomain
;
216 unset( $this->mFormFields
['Domain'] );
219 # Or to tweak the 'remember my password' checkbox
220 if( !($wgCookieExpiration > 0) ){
221 # Remove it altogether
222 unset( $this->mFormFields
['Remember'] );
223 } elseif( $wgUser->getOption( 'rememberpassword' ) ||
$this->mRemember
){
224 # Or check it by default
225 # FIXME: this doesn't always work?
226 $this->mFormFields
['Remember']['checked'] = '1';
229 $form = new HTMLForm( $this->mFormFields
, '' );
230 $form->setTitle( $this->getTitle() );
231 $form->setSubmitText( wfMsg( 'login' ) );
232 $form->setSubmitId( 'wpLoginAttempt' );
233 $form->suppressReset();
237 . Html
::rawElement( 'p', array( 'id' => 'userloginlink' ),
239 . Html
::rawElement( 'div', array( 'id' => 'userloginprompt' ),
240 wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
244 . $form->getButtons()
246 . Xml
::hidden( 'returnto', $this->mReturnTo
)
247 . Xml
::hidden( 'returntoquery', $this->mReturnToQuery
)
250 $wgOut->setPageTitle( wfMsg( 'login' ) );
251 $wgOut->setRobotPolicy( 'noindex,nofollow' );
252 $wgOut->setArticleRelated( false );
253 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
258 array( 'id' => 'loginstart' ),
259 wfMsgExt( 'loginstart', array( 'parseinline' ) )
264 array( 'id' => 'userloginForm' ),
265 $form->wrapForm( $formContents )
269 array( 'id' => 'loginend' ),
270 wfMsgExt( 'loginend', array( 'parseinline' ) )
277 * Check if a session cookie is present.
279 * This will not pick up a cookie set during _this_ request, but is meant
280 * to ensure that the client is returning the cookie which was set on a
281 * previous pass through the system.
285 protected function hasSessionCookie() {
286 global $wgDisableCookieCheck, $wgRequest;
287 return $wgDisableCookieCheck ?
true : $wgRequest->checkSessionCookie();
291 * Do a redirect back to the same page, so we can check any
292 * new session cookies.
294 protected function cookieRedirectCheck() {
297 $query = array( 'wpCookieCheck' => '1');
298 if ( $this->mReturnTo
) $query['returnto'] = $this->mReturnTo
;
299 $check = $this->getTitle()->getFullURL( $query );
301 return $wgOut->redirect( $check );
305 * Check the cookies and show errors if they're not enabled.
306 * @param $type String action being performed
308 protected function onCookieRedirectCheck() {
309 if ( !$this->hasSessionCookie() ) {
310 return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
312 return self
::successfulLogin( 'loginsuccess', $this->mReturnTo
, $this->mReturnToQuery
);
317 * Produce a bar of links which allow the user to select another language
318 * during login/registration but retain "returnto"
319 * @param $title Title to use in the link
320 * @param $returnTo query string to append
321 * @return String HTML for bar
323 public static function makeLanguageSelector( $title, $returnTo=false ) {
326 $msg = wfMsgForContent( 'loginlanguagelinks' );
327 if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
328 $langs = explode( "\n", $msg );
330 foreach( $langs as $lang ) {
331 $lang = trim( $lang, '* ' );
332 $parts = explode( '|', $lang );
333 if (count($parts) >= 2) {
334 $links[] = SpecialUserLogin
::makeLanguageSelectorLink(
335 $parts[0], $parts[1], $title, $returnTo );
338 return count( $links ) > 0 ?
wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
345 * Create a language selector link for a particular language
346 * Links back to this page preserving type and returnto
347 * @param $text Link text
348 * @param $lang Language code
349 * @param $title Title to link to
350 * @param $returnTo String returnto query
352 public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
354 $attr = array( 'uselang' => $lang );
356 $attr['returnto'] = $returnTo;
357 $skin = $wgUser->getSkin();
358 return $skin->linkKnown(
360 htmlspecialchars( $text ),
367 * Display a "login successful" page.
368 * @param $msgname String message key to display
369 * @param $html String HTML to optionally add
370 * @param $returnto Title to returnto
371 * @param $returntoQuery String query string for returnto link
373 public static function displaySuccessfulLogin( $msgname, $injected_html='', $returnto=false, $returntoQuery=false ) {
374 global $wgOut, $wgUser;
376 $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
377 $wgOut->setRobotPolicy( 'noindex,nofollow' );
378 $wgOut->setArticleRelated( false );
379 $wgOut->addWikiMsg( $msgname, $wgUser->getName() );
380 $wgOut->addHTML( $injected_html );
383 $wgOut->returnToMain( null, $returnto, $this->mReturnToQuery
);
385 $wgOut->returnToMain( null );
390 * Run any hooks registered for logins, then HTTP redirect to
391 * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
392 * nice message here, but that's really not as useful as just being sent to
393 * wherever you logged in from. It should be clear that the action was
394 * successful, given the lack of error messages plus the appearance of your
395 * name in the upper right.
397 public static function successfulLogin( $message, $returnTo='', $returnToQuery='' ) {
398 global $wgUser, $wgOut;
400 # Run any hooks; display injected HTML if any, else redirect
402 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
404 if( $injected_html !== '' ) {
405 SpecialUserLogin
::displaySuccessfulLogin( $message, $injected_html );
407 $titleObj = Title
::newFromText( $returnTo );
408 if ( !$titleObj instanceof Title
) {
409 $titleObj = Title
::newMainPage();
411 $wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
416 protected function processLogin(){
417 global $wgUser, $wgAuth;
418 $result = $this->mLogin
->attemptLogin();
421 if( $this->hasSessionCookie() ||
$this->mSkipCookieCheck
) {
422 # Replace the language object to provide user interface in
423 # correct language immediately on this first page load.
424 global $wgLang, $wgRequest;
425 $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
426 $wgLang = Language
::factory( $code );
427 return self
::successfulLogin( 'loginsuccess', $this->mReturnTo
, $this->mReturnToQuery
);
429 # Do a redirect check to ensure that the cookies are
430 # being retained by the user's browser.
431 return $this->cookieRedirectCheck();
437 $this->mainLoginForm( wfMsg( 'noname' ) );
439 case Login
::WRONG_PLUGIN_PASS
:
440 $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
442 case Login
::NOT_EXISTS
:
443 if( $wgUser->isAllowed( 'createaccount' ) ){
444 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName
) ) );
446 $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName
) ) );
449 case Login
::WRONG_PASS
:
450 $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
452 case Login
::EMPTY_PASS
:
453 $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) );
455 case Login
::RESET_PASS
:
456 $this->resetLoginForm( wfMsg( 'resetpass_announce' ) );
458 case Login
::CREATE_BLOCKED
:
459 $this->userBlockedMessage();
461 case Login
::THROTTLED
:
462 $this->mainLoginForm( wfMsg( 'login-throttled' ) );
465 throw new MWException( "Unhandled case value" );
470 * 'Shell out' to Special:ResetPass to get the user to
471 * set a new permanent password from a temporary one.
472 * @param $error String message
474 function resetLoginForm( $error ) {
476 $wgOut->addHTML( Xml
::element('p', array( 'class' => 'error' ), $error ) );
477 $reset = new SpecialResetpass();
478 $reset->execute( null );
482 * Attempt to send the user a password-reset mail, and display
483 * the results (good, bad or ugly).
484 * @return unknown_type
486 protected function showMailPage(){
488 $result = $this->mLogin
->mailPassword();
491 case Login
::MAIL_READ_ONLY
:
492 $wgOut->readOnlyPage();
494 case Login
::MAIL_PASSCHANGE_FORBIDDEN
:
495 $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) );
497 case Login
::MAIL_BLOCKED
:
498 $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
500 case Login
::MAIL_PING_THROTTLED
:
501 $wgOut->rateLimited();
503 case Login
::MAIL_PASS_THROTTLED
:
504 global $wgPasswordReminderResendTime;
505 # Round the time in hours to 3 d.p., in case someone
506 # is specifying minutes or seconds.
507 $this->mainLoginForm( wfMsgExt(
508 'throttled-mailpassword',
510 round( $wgPasswordReminderResendTime, 3 )
514 $this->mainLoginForm( wfMsg( 'noname' ) );
516 case Login
::NOT_EXISTS
:
517 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin
->mUser
->getName() ) ) );
519 case Login
::MAIL_EMPTY_EMAIL
:
520 $this->mainLoginForm( wfMsg( 'noemail', $this->mLogin
->mUser
->getName() ) );
522 case Login
::MAIL_BAD_IP
:
523 $this->mainLoginForm( wfMsg( 'badipaddress' ) );
525 case Login
::MAIL_ERROR
:
526 $this->mainLoginForm( wfMsg( 'mailerror', $this->mLogin
->mMailResult
->getMessage() ) );
529 $this->mainLoginForm( wfMsg( 'passwordsent', $this->mLogin
->mUser
->getName() ), 'success' );
535 * Since the UserLoginForm hook was changed to pass a SpecialPage
536 * instead of a QuickTemplate derivative, old extensions might
537 * easily try calling this method expecing it to exist. Tempting
538 * though it is to let them have the fatal error, let's at least
542 public function set(){
543 wfDeprecated( __METHOD__
);