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 # Redirect out for account creation, for B/C
56 $type = ( $par == 'signup' ) ?
$par : $wgRequest->getText( 'type' );
57 if( $type == 'signup' ){
58 $sp = new SpecialCreateAccount();
63 # Because we're transitioning from logged-out, who might not
64 # have a session, to logged-in, who always do, we need to make
65 # sure that we *always* have a session...
66 if( session_id() == '' ) {
71 $this->mLogin
= new Login();
73 if ( $wgRequest->getCheck( 'wpCookieCheck' ) ) {
74 $this->onCookieRedirectCheck();
76 } else if( $wgRequest->wasPosted() ) {
77 if ( $this->mMailmypassword
) {
78 return $this->showMailPage();
80 return $this->processLogin();
83 $this->mainLoginForm( '' );
88 * Load member variables from the HTTP request data
89 * @param $par String the fragment passed to execute()
91 protected function loadQuery(){
92 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
94 $this->mUsername
= $wgRequest->getText( 'wpName' );
95 $this->mPassword
= $wgRequest->getText( 'wpPassword' );
96 $this->mDomain
= $wgRequest->getText( 'wpDomain' );
97 $this->mLanguage
= $wgRequest->getText( 'uselang' );
99 $this->mReturnTo
= $wgRequest->getVal( 'returnto' );
100 $this->mReturnToQuery
= $wgRequest->getVal( 'returntoquery' );
102 $this->mMailmypassword
= $wgRequest->getCheck( 'wpMailmypassword' )
104 $this->mRemember
= $wgRequest->getCheck( 'wpRemember' );
105 $this->mSkipCookieCheck
= $wgRequest->getCheck( 'wpSkipCookieCheck' );
107 if( !$wgAuth->validDomain( $this->mDomain
) ) {
108 $this->mDomain
= 'invaliddomain';
110 $wgAuth->setDomain( $this->mDomain
);
112 if ( $wgRedirectOnLogin ) {
113 $this->mReturnTo
= $wgRedirectOnLogin;
114 $this->mReturnToQuery
= '';
116 # When switching accounts, it sucks to get automatically logged out
117 $returnToTitle = Title
::newFromText( $this->mReturnTo
);
118 if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
119 $this->mReturnTo
= '';
120 $this->mReturnToQuery
= '';
125 * Show the main login form
126 * @param $msg String a message key for a warning/error message
127 * that may have been generated on a previous iteration
129 protected function mainLoginForm( $msg, $msgtype = 'error' ) {
130 global $wgUser, $wgOut, $wgEnableEmail;
131 global $wgCookiePrefix, $wgLoginLanguageSelector;
132 global $wgAuth, $wgCookieExpiration;
134 # Preload the name field with something if we can
135 if ( '' == $this->mUsername
) {
136 if ( $wgUser->isLoggedIn() ) {
137 $this->mUsername
= $wgUser->getName();
138 } elseif( isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ) {
139 $this->mUsername
= $_COOKIE[$wgCookiePrefix.'UserName'];
142 if( $this->mUsername
){
143 $this->mFormFields
['Name']['default'] = $this->mUsername
;
144 $this->mFormFields
['Password']['autofocus'] = '1';
146 $this->mFormFields
['Name']['autofocus'] = '1';
149 # Parse the error message if we got one
151 if( $msgtype == 'error' ){
152 $msg = wfMsgExt( 'loginerror', 'parseinline' ) . ' ' . $msg;
154 $msg = Html
::rawElement(
156 array( 'class' => $msgtype . 'box' ),
163 # Make sure the returnTo strings don't get lost if the
164 # user changes language, etc
166 if ( !empty( $this->mReturnTo
) ) {
167 $linkq['returnto'] = wfUrlencode( $this->mReturnTo
);
168 if ( !empty( $this->mReturnToQuery
) )
169 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery
);
172 # Pass any language selection on to the mode switch link
173 if( $wgLoginLanguageSelector && $this->mLanguage
)
174 $linkq['uselang'] = $this->mLanguage
;
176 $skin = $wgUser->getSkin();
178 SpecialPage
::getTitleFor( 'CreateAccount' ),
179 wfMsgHtml( 'nologinlink' ),
183 # Don't show a "create account" link if the user can't
184 $link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
185 ?
wfMsgWikiHtml( 'nologin', $link )
188 # Prepare language selection links as needed
189 $langSelector = $wgLoginLanguageSelector
192 array( 'id' => 'languagelinks' ),
193 self
::makeLanguageSelector( $this->getTitle(), $this->mReturnTo
) )
196 # Add a 'mail reset' button if available
198 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
199 $buttons = Html
::element(
203 'name' => 'wpMailmypassword',
204 'value' => wfMsg( 'mailmypassword' ),
205 'id' => 'wpMailmypassword',
210 # Give authentication and captcha plugins a chance to
211 # modify the form, by hook or by using $wgAuth
212 $wgAuth->modifyUITemplate( $this, 'login' );
213 wfRunHooks( 'UserLoginForm', array( &$this ) );
215 # The most likely use of the hook is to enable domains;
216 # check that now, and add fields if necessary
217 if( $this->mDomains
){
218 $this->mFormFields
['Domain']['options'] = $this->mDomains
;
219 $this->mFormFields
['Domain']['default'] = $this->mDomain
;
221 unset( $this->mFormFields
['Domain'] );
224 # Or to tweak the 'remember my password' checkbox
225 if( !($wgCookieExpiration > 0) ){
226 # Remove it altogether
227 unset( $this->mFormFields
['Remember'] );
228 } elseif( $wgUser->getOption( 'rememberpassword' ) ||
$this->mRemember
){
229 # Or check it by default
230 # FIXME: this doesn't always work?
231 $this->mFormFields
['Remember']['checked'] = '1';
234 $form = new HTMLForm( $this->mFormFields
, '' );
235 $form->setTitle( $this->getTitle() );
236 $form->setSubmitText( wfMsg( 'login' ) );
237 $form->setSubmitId( 'wpLoginAttempt' );
238 $form->suppressReset();
242 . Html
::rawElement( 'p', array( 'id' => 'userloginlink' ),
244 . Html
::rawElement( 'div', array( 'id' => 'userloginprompt' ),
245 wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
249 . $form->getButtons()
251 . Xml
::hidden( 'returnto', $this->mReturnTo
)
252 . Xml
::hidden( 'returntoquery', $this->mReturnToQuery
)
255 $wgOut->setPageTitle( wfMsg( 'login' ) );
256 $wgOut->setRobotPolicy( 'noindex,nofollow' );
257 $wgOut->setArticleRelated( false );
258 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
263 array( 'id' => 'loginstart' ),
264 wfMsgExt( 'loginstart', array( 'parseinline' ) )
269 array( 'id' => 'userloginForm' ),
270 $form->wrapForm( $formContents )
274 array( 'id' => 'loginend' ),
275 wfMsgExt( 'loginend', array( 'parseinline' ) )
281 * Check if a session cookie is present.
283 * This will not pick up a cookie set during _this_ request, but is meant
284 * to ensure that the client is returning the cookie which was set on a
285 * previous pass through the system.
289 protected function hasSessionCookie() {
290 global $wgDisableCookieCheck, $wgRequest;
291 return $wgDisableCookieCheck ||
$wgRequest->checkSessionCookie();
295 * Do a redirect back to the same page, so we can check any
296 * new session cookies.
298 protected function cookieRedirectCheck() {
301 $query = array( 'wpCookieCheck' => '1');
302 if ( $this->mReturnTo
) $query['returnto'] = $this->mReturnTo
;
303 $check = $this->getTitle()->getFullURL( $query );
305 return $wgOut->redirect( $check );
309 * Check the cookies and show errors if they're not enabled.
310 * @param $type String action being performed
312 protected function onCookieRedirectCheck() {
313 if ( $this->hasSessionCookie() ) {
314 return self
::successfulLogin(
317 $this->mReturnToQuery
320 return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
325 * Produce a bar of links which allow the user to select another language
326 * during login/registration but retain "returnto"
327 * @param $title Title to use in the link
328 * @param $returnTo query string to append
329 * @return String HTML for bar
331 public static function makeLanguageSelector( $title, $returnTo=false ) {
334 $msg = wfMsgForContent( 'loginlanguagelinks' );
335 if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
336 $langs = explode( "\n", $msg );
338 foreach( $langs as $lang ) {
339 $lang = trim( $lang, '* ' );
340 $parts = explode( '|', $lang );
341 if (count($parts) >= 2) {
342 $links[] = SpecialUserLogin
::makeLanguageSelectorLink(
343 $parts[0], $parts[1], $title, $returnTo );
346 return count( $links ) > 0 ?
wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
353 * Create a language selector link for a particular language
354 * Links back to this page preserving type and returnto
355 * @param $text Link text
356 * @param $lang Language code
357 * @param $title Title to link to
358 * @param $returnTo String returnto query
360 public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
362 $attr = array( 'uselang' => $lang );
364 $attr['returnto'] = $returnTo;
365 $skin = $wgUser->getSkin();
366 return $skin->linkKnown(
368 htmlspecialchars( $text ),
375 * Display a "login successful" page.
376 * @param $message String message key of main message to display
377 * @param $html String HTML to optionally add
378 * @param $returnto Title to returnto
379 * @param $returntoQuery String query string for returnto link
381 public static function displaySuccessfulLogin( $message, $html='', $returnTo=false, $returnToQuery=false ) {
382 global $wgOut, $wgUser;
384 $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
385 $wgOut->setRobotPolicy( 'noindex,nofollow' );
386 $wgOut->setArticleRelated( false );
387 $wgOut->addWikiMsg( $message, $wgUser->getName() );
388 $wgOut->addHTML( $html );
391 $wgOut->returnToMain( null, $returnTo, $returnToQuery );
393 $wgOut->returnToMain( null );
398 * Display any messages generated by hooks, or HTTP redirect to
399 * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
400 * nice message here, but that's not as useful as just being sent to
401 * wherever you logged in from. It should be clear that the action was
402 * successful, given the lack of error messages plus the appearance of your
403 * name in the upper right.
405 * Remember that this function can be accessed from a variety of
406 * places, such as Special:ResetPass, or Special:CreateAccount.
407 * @param $message String message key of a message to display if
409 * @param $returnTo String title of page to redirect to
410 * @param $returnToQuery String query string to add to the redirect.
411 * @param $html String empty string to go straight
412 * to the redirect, or valid HTML to add underneath the text.
414 public static function successfulLogin( $message, $returnTo='', $returnToQuery='', $html='' ) {
415 global $wgUser, $wgOut;
418 $titleObj = Title
::newFromText( $returnTo );
419 if ( !$titleObj instanceof Title
) {
420 $titleObj = Title
::newMainPage();
422 $wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
424 SpecialUserLogin
::displaySuccessfulLogin( $message, $html, $returnTo, $returnToQuery );
429 protected function processLogin(){
430 global $wgUser, $wgAuth;
431 $result = $this->mLogin
->attemptLogin();
434 if( $this->hasSessionCookie() ||
$this->mSkipCookieCheck
) {
435 # Replace the language object to provide user interface in
436 # correct language immediately on this first page load.
437 global $wgLang, $wgRequest;
438 $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
439 $wgLang = Language
::factory( $code );
440 return self
::successfulLogin(
443 $this->mReturnToQuery
,
444 $this->mLogin
->mLoginResult
);
446 # Do a redirect check to ensure that the cookies are
447 # being retained by the user's browser.
448 return $this->cookieRedirectCheck();
454 case Login
::WRONG_PLUGIN_PASS
:
455 case Login
::WRONG_PASS
:
456 case Login
::EMPTY_PASS
:
457 case Login
::THROTTLED
:
458 $this->mainLoginForm( wfMsgExt( $this->mLogin
->mLoginResult
, 'parseinline' ) );
461 case Login
::NOT_EXISTS
:
462 if( $wgUser->isAllowed( 'createaccount' ) ){
463 $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $this->mName
) ) );
465 $this->mainLoginForm( wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $this->mName
) ) );
469 case Login
::RESET_PASS
:
470 # 'Shell out' to Special:ResetPass to get the user to
471 # set a new permanent password from a temporary one.
472 $reset = new SpecialResetpass();
473 $reset->mHeaderMsg
= 'resetpass_announce';
474 $reset->mHeaderMsgType
= 'success';
475 $reset->execute( null );
478 case Login
::CREATE_BLOCKED
:
479 $this->userBlockedMessage();
483 $msg = $this->mLogin
->mLoginResult ?
$this->mLogin
->mLoginResult
: $this->mLogin
->mCreateResult
;
484 $this->mainLoginForm( wfMsgExt( $msg, 'parseinline' ) );
488 throw new MWException( "Unhandled case value: $result" );
493 * Attempt to send the user a password-reset mail, and display
494 * the results (good, bad or ugly).
496 protected function showMailPage(){
498 $result = $this->mLogin
->mailPassword();
501 case Login
::READ_ONLY
:
502 $wgOut->readOnlyPage();
504 case Login
::MAIL_PASSCHANGE_FORBIDDEN
:
505 $this->mainLoginForm( wfMsgExt( 'resetpass_forbidden', 'parseinline' ) );
507 case Login
::MAIL_BLOCKED
:
508 $this->mainLoginForm( wfMsgExt( 'blocked-mailpassword', 'parseinline' ) );
510 case Login
::MAIL_PING_THROTTLED
:
511 $wgOut->rateLimited();
513 case Login
::MAIL_PASS_THROTTLED
:
514 global $wgPasswordReminderResendTime;
515 # Round the time in hours to 3 d.p., in case someone
516 # is specifying minutes or seconds.
517 $this->mainLoginForm( wfMsgExt(
518 'throttled-mailpassword',
520 round( $wgPasswordReminderResendTime, 3 )
524 $this->mainLoginForm( wfMsgExt( 'noname', 'parseinline' ) );
526 case Login
::NOT_EXISTS
:
527 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin
->mUser
->getName() ) ) );
529 case Login
::MAIL_EMPTY_EMAIL
:
530 $this->mainLoginForm( wfMsgExt( 'noemail', 'parseinline', $this->mLogin
->mUser
->getName() ) );
532 case Login
::MAIL_BAD_IP
:
533 $this->mainLoginForm( wfMsgExt( 'badipaddress', 'parseinline' ) );
535 case Login
::MAIL_ERROR
:
536 $this->mainLoginForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin
->mMailResult
->getMessage() ) );
539 $this->mainLoginForm( wfMsgExt( 'passwordsent', 'parseinline', $this->mLogin
->mUser
->getName() ), 'success' );
545 * Add text to the header. Only write to $mFormHeader directly
546 * if you're determined to overwrite anything that other
547 * extensions might have added.
548 * @param $text String HTML
550 public function addFormHeader( $text ){
551 $this->mFormHeader
.= $text;
555 * Since the UserLoginForm hook was changed to pass a SpecialPage
556 * instead of a QuickTemplate derivative, old extensions might
557 * easily try calling this method expecing it to exist. Tempting
558 * though it is to let them have the fatal error, let's at least
562 public function set(){
563 wfDeprecated( __METHOD__
);