Special:IPBlockList's internal name is 'Ipblocklist', not 'IPBlockList' (throwing...
[lhc/web/wiklou.git] / includes / specials / SpecialUserlogin.php
1 <?php
2 /**
3 * SpecialPage for logging users into the wiki
4 * @ingroup SpecialPage
5 */
6
7 class SpecialUserLogin extends SpecialPage {
8
9 var $mUsername, $mPassword, $mReturnTo, $mCookieCheck, $mPosted;
10 var $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
11 var $mRemember, $mDomain, $mLanguage;
12 var $mSkipCookieCheck, $mReturnToQuery;
13
14 public $mDomains = array();
15
16 public $mFormHeader = ''; # Can be filled by hooks etc
17 public $mFormFields = array(
18 'Name' => array(
19 'type' => 'text',
20 'label-message' => 'yourname',
21 'id' => 'wpName1',
22 'tabindex' => '1',
23 'size' => '20',
24 'required' => '1',
25 ),
26 'Password' => array(
27 'type' => 'password',
28 'label-message' => 'yourpassword',
29 'size' => '20',
30 'id' => 'wpPassword1',
31 ),
32 'Domain' => array(
33 'type' => 'select',
34 'id' => 'wpDomain',
35 'label-message' => 'yourdomainname',
36 'options' => null,
37 'default' => null,
38 ),
39 'Remember' => array(
40 'type' => 'check',
41 'label-message' => 'remembermypassword',
42 'id' => 'wpRemember',
43 )
44 );
45
46 protected $mLogin; # Login object
47
48 public function __construct(){
49 parent::__construct( 'Userlogin' );
50 }
51
52 function execute( $par ) {
53 global $wgRequest;
54
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();
59 $sp->execute( $par );
60 return;
61 }
62
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() == '' ) {
67 wfSetupSession();
68 }
69
70 $this->loadQuery();
71 $this->mLogin = new Login();
72
73 if ( $wgRequest->getCheck( 'wpCookieCheck' ) ) {
74 $this->onCookieRedirectCheck();
75 return;
76 } else if( $wgRequest->wasPosted() ) {
77 if ( $this->mMailmypassword ) {
78 return $this->showMailPage();
79 } else {
80 return $this->processLogin();
81 }
82 } else {
83 $this->mainLoginForm( '' );
84 }
85 }
86
87 /**
88 * Load member variables from the HTTP request data
89 * @param $par String the fragment passed to execute()
90 */
91 protected function loadQuery(){
92 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
93
94 $this->mUsername = $wgRequest->getText( 'wpName' );
95 $this->mPassword = $wgRequest->getText( 'wpPassword' );
96 $this->mDomain = $wgRequest->getText( 'wpDomain' );
97 $this->mLanguage = $wgRequest->getText( 'uselang' );
98
99 $this->mReturnTo = $wgRequest->getVal( 'returnto' );
100 $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
101
102 $this->mMailmypassword = $wgRequest->getCheck( 'wpMailmypassword' )
103 && $wgEnableEmail;
104 $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
105 $this->mSkipCookieCheck = $wgRequest->getCheck( 'wpSkipCookieCheck' );
106
107 if( !$wgAuth->validDomain( $this->mDomain ) ) {
108 $this->mDomain = 'invaliddomain';
109 }
110 $wgAuth->setDomain( $this->mDomain );
111
112 if ( $wgRedirectOnLogin ) {
113 $this->mReturnTo = $wgRedirectOnLogin;
114 $this->mReturnToQuery = '';
115 }
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 = '';
121 }
122 }
123
124 /**
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
128 */
129 protected function mainLoginForm( $msg, $msgtype = 'error' ) {
130 global $wgUser, $wgOut, $wgEnableEmail;
131 global $wgCookiePrefix, $wgLoginLanguageSelector;
132 global $wgAuth, $wgCookieExpiration;
133
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'];
140 }
141 }
142 if( $this->mUsername ){
143 $this->mFormFields['Name']['default'] = $this->mUsername;
144 $this->mFormFields['Password']['autofocus'] = '1';
145 } else {
146 $this->mFormFields['Name']['autofocus'] = '1';
147 }
148
149 # Parse the error message if we got one
150 if( $msg ){
151 if( $msgtype == 'error' ){
152 $msg = wfMsgExt( 'loginerror', 'parseinline' ) . ' ' . $msg;
153 }
154 $msg = Html::rawElement(
155 'div',
156 array( 'class' => $msgtype . 'box' ),
157 $msg
158 );
159 } else {
160 $msg = '';
161 }
162
163 # Make sure the returnTo strings don't get lost if the
164 # user changes language, etc
165 $linkq = array();
166 if ( !empty( $this->mReturnTo ) ) {
167 $linkq['returnto'] = wfUrlencode( $this->mReturnTo );
168 if ( !empty( $this->mReturnToQuery ) )
169 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery );
170 }
171
172 # Pass any language selection on to the mode switch link
173 if( $wgLoginLanguageSelector && $this->mLanguage )
174 $linkq['uselang'] = $this->mLanguage;
175
176 $skin = $wgUser->getSkin();
177 $link = $skin->link(
178 SpecialPage::getTitleFor( 'CreateAccount' ),
179 wfMsgHtml( 'nologinlink' ),
180 array(),
181 $linkq );
182
183 # Don't show a "create account" link if the user can't
184 $link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
185 ? wfMsgWikiHtml( 'nologin', $link )
186 : '';
187
188 # Prepare language selection links as needed
189 $langSelector = $wgLoginLanguageSelector
190 ? Html::rawElement(
191 'div',
192 array( 'id' => 'languagelinks' ),
193 self::makeLanguageSelector( $this->getTitle(), $this->mReturnTo ) )
194 : '';
195
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 ) );
200
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;
206 } else {
207 unset( $this->mFormFields['Domain'] );
208 }
209
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';
218 }
219
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' ) );
226
227 $form->addHiddenField( 'returnto', $this->mReturnTo );
228 $form->addHiddenField( 'returntoquery', $this->mReturnToQuery );
229
230 $form->addHeaderText( ''
231 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
232 $link )
233 . Html::rawElement( 'div', array( 'id' => 'userloginprompt' ),
234 wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
235 . $this->mFormHeader
236 . $langSelector
237 );
238 $form->addPreText( ''
239 . $msg
240 . Html::rawElement(
241 'div',
242 array( 'id' => 'loginstart' ),
243 wfMsgExt( 'loginstart', array( 'parseinline' ) )
244 )
245 );
246 $form->addPostText(
247 Html::rawElement(
248 'div',
249 array( 'id' => 'loginend' ),
250 wfMsgExt( 'loginend', array( 'parseinline' ) )
251 )
252 );
253
254 # Add a 'mail reset' button if available
255 $buttons = '';
256 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
257 $form->addButton(
258 'wpMailmypassword',
259 wfMsg( 'mailmypassword' ),
260 'wpMailmypassword'
261 );
262 }
263
264 $form->loadData();
265
266 $wgOut->setPageTitle( wfMsg( 'login' ) );
267 $wgOut->setRobotPolicy( 'noindex,nofollow' );
268 $wgOut->setArticleRelated( false );
269 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
270
271 $form->displayForm( '' );
272 }
273
274 /**
275 * Check if a session cookie is present.
276 *
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.
280 *
281 * @private
282 */
283 protected function hasSessionCookie() {
284 global $wgDisableCookieCheck, $wgRequest;
285 return $wgDisableCookieCheck || $wgRequest->checkSessionCookie();
286 }
287
288 /**
289 * Do a redirect back to the same page, so we can check any
290 * new session cookies.
291 */
292 protected function cookieRedirectCheck() {
293 global $wgOut;
294
295 $query = array( 'wpCookieCheck' => '1');
296 if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
297 $check = $this->getTitle()->getFullURL( $query );
298
299 return $wgOut->redirect( $check );
300 }
301
302 /**
303 * Check the cookies and show errors if they're not enabled.
304 * @param $type String action being performed
305 */
306 protected function onCookieRedirectCheck() {
307 if ( $this->hasSessionCookie() ) {
308 return self::successfulLogin(
309 'loginsuccess',
310 $this->mReturnTo,
311 $this->mReturnToQuery
312 );
313 } else {
314 return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
315 }
316 }
317
318 /**
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
324 */
325 public static function makeLanguageSelector( $title, $returnTo=false ) {
326 global $wgLang;
327
328 $msg = wfMsgForContent( 'loginlanguagelinks' );
329 if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
330 $langs = explode( "\n", $msg );
331 $links = array();
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 );
338 }
339 }
340 return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
341 } else {
342 return '';
343 }
344 }
345
346 /**
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
353 */
354 public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
355 global $wgUser;
356 $attr = array( 'uselang' => $lang );
357 if( $returnTo )
358 $attr['returnto'] = $returnTo;
359 $skin = $wgUser->getSkin();
360 return $skin->linkKnown(
361 $title,
362 htmlspecialchars( $text ),
363 array(),
364 $attr
365 );
366 }
367
368 /**
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
374 */
375 public static function displaySuccessfulLogin( $message, $html='', $returnTo=false, $returnToQuery=false ) {
376 global $wgOut, $wgUser;
377
378 $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
379 $wgOut->setRobotPolicy( 'noindex,nofollow' );
380 $wgOut->setArticleRelated( false );
381 $wgOut->addWikiMsg( $message, $wgUser->getName() );
382 $wgOut->addHTML( $html );
383
384 if ( $returnTo ) {
385 $wgOut->returnToMain( null, $returnTo, $returnToQuery );
386 } else {
387 $wgOut->returnToMain( null );
388 }
389 }
390
391 /**
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.
398 *
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
402 * we don't redirect
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.
407 */
408 public static function successfulLogin( $message, $returnTo='', $returnToQuery='', $html='' ) {
409 global $wgUser, $wgOut;
410
411 if( $html === '' ) {
412 $titleObj = Title::newFromText( $returnTo );
413 if ( !$titleObj instanceof Title ) {
414 $titleObj = Title::newMainPage();
415 }
416 $wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
417 } else {
418 SpecialUserLogin::displaySuccessfulLogin( $message, $html, $returnTo, $returnToQuery );
419 }
420 }
421
422
423 protected function processLogin(){
424 global $wgUser, $wgAuth;
425 $result = $this->mLogin->attemptLogin();
426 switch ( $result ) {
427 case Login::SUCCESS:
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(
435 'loginsuccess',
436 $this->mReturnTo,
437 $this->mReturnToQuery,
438 $this->mLogin->mLoginResult );
439 } else {
440 # Do a redirect check to ensure that the cookies are
441 # being retained by the user's browser.
442 return $this->cookieRedirectCheck();
443 }
444 break;
445
446 case Login::NO_NAME:
447 case Login::ILLEGAL:
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' ) );
453 break;
454
455 case Login::NOT_EXISTS:
456 if( $wgUser->isAllowed( 'createaccount' ) ){
457 $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $this->mUsername ) ) );
458 } else {
459 $this->mainLoginForm( wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $this->mUsername ) ) );
460 }
461 break;
462
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 );
470 break;
471
472 case Login::CREATE_BLOCKED:
473 $this->userBlockedMessage();
474 break;
475
476 case Login::ABORTED:
477 $msg = $this->mLogin->mLoginResult ? $this->mLogin->mLoginResult : $this->mLogin->mCreateResult;
478 $this->mainLoginForm( wfMsgExt( $msg, 'parseinline' ) );
479 break;
480
481 default:
482 throw new MWException( "Unhandled case value: $result" );
483 }
484 }
485
486 /**
487 * Attempt to send the user a password-reset mail, and display
488 * the results (good, bad or ugly).
489 */
490 protected function showMailPage(){
491 global $wgOut;
492 $result = $this->mLogin->mailPassword();
493
494 switch( $result ){
495 case Login::READ_ONLY :
496 $wgOut->readOnlyPage();
497 return;
498 case Login::MAIL_PASSCHANGE_FORBIDDEN:
499 $this->mainLoginForm( wfMsgExt( 'resetpass_forbidden', 'parseinline' ) );
500 return;
501 case Login::MAIL_BLOCKED:
502 $this->mainLoginForm( wfMsgExt( 'blocked-mailpassword', 'parseinline' ) );
503 return;
504 case Login::MAIL_PING_THROTTLED:
505 $wgOut->rateLimited();
506 return;
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',
513 array( 'parsemag' ),
514 round( $wgPasswordReminderResendTime, 3 )
515 ) );
516 return;
517 case Login::NO_NAME:
518 $this->mainLoginForm( wfMsgExt( 'noname', 'parseinline' ) );
519 return;
520 case Login::NOT_EXISTS:
521 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin->mUser->getName() ) ) );
522 return;
523 case Login::MAIL_EMPTY_EMAIL:
524 $this->mainLoginForm( wfMsgExt( 'noemail', 'parseinline', $this->mLogin->mUser->getName() ) );
525 return;
526 case Login::MAIL_BAD_IP:
527 $this->mainLoginForm( wfMsgExt( 'badipaddress', 'parseinline' ) );
528 return;
529 case Login::MAIL_ERROR:
530 $this->mainLoginForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin->mMailResult->getMessage() ) );
531 return;
532 case Login::SUCCESS:
533 $this->mainLoginForm( wfMsgExt( 'passwordsent', 'parseinline', $this->mLogin->mUser->getName() ), 'success' );
534 return;
535 }
536 }
537
538 /**
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
543 */
544 public function addFormHeader( $text ){
545 $this->mFormHeader .= $text;
546 }
547
548 /**
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
553 * fail gracefully...
554 * @deprecated
555 */
556 public function set(){
557 wfDeprecated( __METHOD__ );
558 }
559 }