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 # Give authentication and captcha plugins a chance to
197 # modify the form, by hook or by using $wgAuth
198 $wgAuth->modifyUITemplate( $this, 'login' );
199 wfRunHooks( 'UserLoginForm', array( &$this ) );
201 # The most likely use of the hook is to enable domains;
202 # check that now, and add fields if necessary
203 if( $this->mDomains
){
204 $this->mFormFields
['Domain']['options'] = $this->mDomains
;
205 $this->mFormFields
['Domain']['default'] = $this->mDomain
;
207 unset( $this->mFormFields
['Domain'] );
210 # Or to tweak the 'remember my password' checkbox
211 if( !($wgCookieExpiration > 0) ){
212 # Remove it altogether
213 unset( $this->mFormFields
['Remember'] );
214 } elseif( $wgUser->getOption( 'rememberpassword' ) ||
$this->mRemember
){
215 # Or check it by default
216 # FIXME: this doesn't always work?
217 $this->mFormFields
['Remember']['checked'] = '1';
220 $form = new HTMLForm( $this->mFormFields
, '' );
221 $form->setTitle( $this->getTitle() );
222 $form->setSubmitText( wfMsg( 'login' ) );
223 $form->setSubmitId( 'wpLoginAttempt' );
224 $form->suppressReset();
225 $form->setWrapperLegend( wfMsg( 'userlogin' ) );
227 $form->addHiddenField( 'returnto', $this->mReturnTo
);
228 $form->addHiddenField( 'returntoquery', $this->mReturnToQuery
);
230 $form->addHeaderText( ''
231 . Html
::rawElement( 'p', array( 'id' => 'userloginlink' ),
233 . Html
::rawElement( 'div', array( 'id' => 'userloginprompt' ),
234 wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
238 $form->addPreText( ''
242 array( 'id' => 'loginstart' ),
243 wfMsgExt( 'loginstart', array( 'parseinline' ) )
249 array( 'id' => 'loginend' ),
250 wfMsgExt( 'loginend', array( 'parseinline' ) )
254 # Add a 'mail reset' button if available
256 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
259 wfMsg( 'mailmypassword' ),
266 $wgOut->setPageTitle( wfMsg( 'login' ) );
267 $wgOut->setRobotPolicy( 'noindex,nofollow' );
268 $wgOut->setArticleRelated( false );
269 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
271 $form->displayForm( '' );
275 * Check if a session cookie is present.
277 * This will not pick up a cookie set during _this_ request, but is meant
278 * to ensure that the client is returning the cookie which was set on a
279 * previous pass through the system.
283 protected function hasSessionCookie() {
284 global $wgDisableCookieCheck, $wgRequest;
285 return $wgDisableCookieCheck ||
$wgRequest->checkSessionCookie();
289 * Do a redirect back to the same page, so we can check any
290 * new session cookies.
292 protected function cookieRedirectCheck() {
295 $query = array( 'wpCookieCheck' => '1');
296 if ( $this->mReturnTo
) $query['returnto'] = $this->mReturnTo
;
297 $check = $this->getTitle()->getFullURL( $query );
299 return $wgOut->redirect( $check );
303 * Check the cookies and show errors if they're not enabled.
304 * @param $type String action being performed
306 protected function onCookieRedirectCheck() {
307 if ( $this->hasSessionCookie() ) {
308 return self
::successfulLogin(
311 $this->mReturnToQuery
314 return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
319 * Produce a bar of links which allow the user to select another language
320 * during login/registration but retain "returnto"
321 * @param $title Title to use in the link
322 * @param $returnTo query string to append
323 * @return String HTML for bar
325 public static function makeLanguageSelector( $title, $returnTo=false ) {
328 $msg = wfMsgForContent( 'loginlanguagelinks' );
329 if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
330 $langs = explode( "\n", $msg );
332 foreach( $langs as $lang ) {
333 $lang = trim( $lang, '* ' );
334 $parts = explode( '|', $lang );
335 if (count($parts) >= 2) {
336 $links[] = SpecialUserLogin
::makeLanguageSelectorLink(
337 $parts[0], $parts[1], $title, $returnTo );
340 return count( $links ) > 0 ?
wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
347 * Create a language selector link for a particular language
348 * Links back to this page preserving type and returnto
349 * @param $text Link text
350 * @param $lang Language code
351 * @param $title Title to link to
352 * @param $returnTo String returnto query
354 public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
356 $attr = array( 'uselang' => $lang );
358 $attr['returnto'] = $returnTo;
359 $skin = $wgUser->getSkin();
360 return $skin->linkKnown(
362 htmlspecialchars( $text ),
369 * Display a "login successful" page.
370 * @param $message String message key of main message to display
371 * @param $html String HTML to optionally add
372 * @param $returnto Title to returnto
373 * @param $returntoQuery String query string for returnto link
375 public static function displaySuccessfulLogin( $message, $html='', $returnTo=false, $returnToQuery=false ) {
376 global $wgOut, $wgUser;
378 $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
379 $wgOut->setRobotPolicy( 'noindex,nofollow' );
380 $wgOut->setArticleRelated( false );
381 $wgOut->addWikiMsg( $message, $wgUser->getName() );
382 $wgOut->addHTML( $html );
385 $wgOut->returnToMain( null, $returnTo, $returnToQuery );
387 $wgOut->returnToMain( null );
392 * Display any messages generated by hooks, or HTTP redirect to
393 * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
394 * nice message here, but that's not as useful as just being sent to
395 * wherever you logged in from. It should be clear that the action was
396 * successful, given the lack of error messages plus the appearance of your
397 * name in the upper right.
399 * Remember that this function can be accessed from a variety of
400 * places, such as Special:ResetPass, or Special:CreateAccount.
401 * @param $message String message key of a message to display if
403 * @param $returnTo String title of page to redirect to
404 * @param $returnToQuery String query string to add to the redirect.
405 * @param $html String empty string to go straight
406 * to the redirect, or valid HTML to add underneath the text.
408 public static function successfulLogin( $message, $returnTo='', $returnToQuery='', $html='' ) {
409 global $wgUser, $wgOut;
412 $titleObj = Title
::newFromText( $returnTo );
413 if ( !$titleObj instanceof Title
) {
414 $titleObj = Title
::newMainPage();
416 $wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
418 SpecialUserLogin
::displaySuccessfulLogin( $message, $html, $returnTo, $returnToQuery );
423 protected function processLogin(){
424 global $wgUser, $wgAuth;
425 $result = $this->mLogin
->attemptLogin();
428 if( $this->hasSessionCookie() ||
$this->mSkipCookieCheck
) {
429 # Replace the language object to provide user interface in
430 # correct language immediately on this first page load.
431 global $wgLang, $wgRequest;
432 $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
433 $wgLang = Language
::factory( $code );
434 return self
::successfulLogin(
437 $this->mReturnToQuery
,
438 $this->mLogin
->mLoginResult
);
440 # Do a redirect check to ensure that the cookies are
441 # being retained by the user's browser.
442 return $this->cookieRedirectCheck();
448 case Login
::WRONG_PLUGIN_PASS
:
449 case Login
::WRONG_PASS
:
450 case Login
::EMPTY_PASS
:
451 case Login
::THROTTLED
:
452 $this->mainLoginForm( wfMsgExt( $this->mLogin
->mLoginResult
, 'parseinline' ) );
455 case Login
::NOT_EXISTS
:
456 if( $wgUser->isAllowed( 'createaccount' ) ){
457 $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $this->mUsername
) ) );
459 $this->mainLoginForm( wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $this->mUsername
) ) );
463 case Login
::RESET_PASS
:
464 # 'Shell out' to Special:ResetPass to get the user to
465 # set a new permanent password from a temporary one.
466 $reset = new SpecialResetpass();
467 $reset->mHeaderMsg
= 'resetpass_announce';
468 $reset->mHeaderMsgType
= 'success';
469 $reset->execute( null );
472 case Login
::CREATE_BLOCKED
:
473 $this->userBlockedMessage();
477 $msg = $this->mLogin
->mLoginResult ?
$this->mLogin
->mLoginResult
: $this->mLogin
->mCreateResult
;
478 $this->mainLoginForm( wfMsgExt( $msg, 'parseinline' ) );
482 throw new MWException( "Unhandled case value: $result" );
487 * Attempt to send the user a password-reset mail, and display
488 * the results (good, bad or ugly).
490 protected function showMailPage(){
492 $result = $this->mLogin
->mailPassword();
495 case Login
::READ_ONLY
:
496 $wgOut->readOnlyPage();
498 case Login
::MAIL_PASSCHANGE_FORBIDDEN
:
499 $this->mainLoginForm( wfMsgExt( 'resetpass_forbidden', 'parseinline' ) );
501 case Login
::MAIL_BLOCKED
:
502 $this->mainLoginForm( wfMsgExt( 'blocked-mailpassword', 'parseinline' ) );
504 case Login
::MAIL_PING_THROTTLED
:
505 $wgOut->rateLimited();
507 case Login
::MAIL_PASS_THROTTLED
:
508 global $wgPasswordReminderResendTime;
509 # Round the time in hours to 3 d.p., in case someone
510 # is specifying minutes or seconds.
511 $this->mainLoginForm( wfMsgExt(
512 'throttled-mailpassword',
514 round( $wgPasswordReminderResendTime, 3 )
518 $this->mainLoginForm( wfMsgExt( 'noname', 'parseinline' ) );
520 case Login
::NOT_EXISTS
:
521 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin
->mUser
->getName() ) ) );
523 case Login
::MAIL_EMPTY_EMAIL
:
524 $this->mainLoginForm( wfMsgExt( 'noemail', 'parseinline', $this->mLogin
->mUser
->getName() ) );
526 case Login
::MAIL_BAD_IP
:
527 $this->mainLoginForm( wfMsgExt( 'badipaddress', 'parseinline' ) );
529 case Login
::MAIL_ERROR
:
530 $this->mainLoginForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin
->mMailResult
->getMessage() ) );
533 $this->mainLoginForm( wfMsgExt( 'passwordsent', 'parseinline', $this->mLogin
->mUser
->getName() ), 'success' );
539 * Add text to the header. Only write to $mFormHeader directly
540 * if you're determined to overwrite anything that other
541 * extensions might have added.
542 * @param $text String HTML
544 public function addFormHeader( $text ){
545 $this->mFormHeader
.= $text;
549 * Since the UserLoginForm hook was changed to pass a SpecialPage
550 * instead of a QuickTemplate derivative, old extensions might
551 * easily try calling this method expecing it to exist. Tempting
552 * though it is to let them have the fatal error, let's at least
556 public function set(){
557 wfDeprecated( __METHOD__
);