Followup to r55990 - rename method to avoid PHP Strict warnings
[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 $this->loadQuery();
55 $this->mLogin = new Login();
56
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();
61 $sp->execute( $par );
62 return;
63 }
64
65 if ( !is_null( $this->mCookieCheck ) ) {
66 $this->onCookieRedirectCheck();
67 return;
68 } else if( $this->mPosted ) {
69 if ( $this->mMailmypassword ) {
70 return $this->showMailPage();
71 } else {
72 return $this->processLogin();
73 }
74 } else {
75 $this->mainLoginForm( '' );
76 }
77 }
78
79 /**
80 * Load member variables from the HTTP request data
81 * @param $par String the fragment passed to execute()
82 */
83 protected function loadQuery(){
84 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
85
86 $this->mUsername = $wgRequest->getText( 'wpName' );
87 $this->mPassword = $wgRequest->getText( 'wpPassword' );
88 $this->mDomain = $wgRequest->getText( 'wpDomain' );
89 $this->mLanguage = $wgRequest->getText( 'uselang' );
90
91 $this->mReturnTo = $wgRequest->getVal( 'returnto' );
92 $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
93 $this->mCookieCheck = $wgRequest->getVal( 'wpCookieCheck' );
94
95 $this->mMailmypassword = $wgRequest->getCheck( 'wpMailmypassword' )
96 && $wgEnableEmail;
97 $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
98 $this->mSkipCookieCheck = $wgRequest->getCheck( 'wpSkipCookieCheck' );
99 $this->mPosted = $wgRequest->wasPosted();
100
101 if ( $wgRedirectOnLogin ) {
102 $this->mReturnTo = $wgRedirectOnLogin;
103 $this->mReturnToQuery = '';
104 }
105
106 if( !$wgAuth->validDomain( $this->mDomain ) ) {
107 $this->mDomain = 'invaliddomain';
108 }
109 $wgAuth->setDomain( $this->mDomain );
110
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 = '';
116 }
117 }
118
119 /**
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
123 */
124 protected function mainLoginForm( $msg, $msgtype = 'error' ) {
125 global $wgUser, $wgOut, $wgEnableEmail;
126 global $wgCookiePrefix, $wgLoginLanguageSelector;
127 global $wgAuth, $wgCookieExpiration;
128
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'];
135 }
136 }
137 if( $this->mUsername ){
138 $this->mFormFields['Name']['default'] = $this->mUsername;
139 $this->mFormFields['Password']['autofocus'] = '1';
140 } else {
141 $this->mFormFields['Name']['autofocus'] = '1';
142 }
143
144 # Parse the error message if we got one
145 if( $msg ){
146 if( $msgtype == 'error' ){
147 $msg = wfMsg( 'loginerror' ) . ' ' . $msg;
148 }
149 $msg = Html::rawElement(
150 'div',
151 array( 'class' => $msgtype . 'box' ),
152 $msg
153 );
154 } else {
155 $msg = '';
156 }
157
158 # Make sure the returnTo strings don't get lost if the
159 # user changes language, etc
160 $linkq = array();
161 if ( !empty( $this->mReturnTo ) ) {
162 $linkq['returnto'] = wfUrlencode( $this->mReturnTo );
163 if ( !empty( $this->mReturnToQuery ) )
164 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery );
165 }
166
167 # Pass any language selection on to the mode switch link
168 if( $wgLoginLanguageSelector && $this->mLanguage )
169 $linkq['uselang'] = $this->mLanguage;
170
171 $skin = $wgUser->getSkin();
172 $link = $skin->link(
173 SpecialPage::getTitleFor( 'CreateAccount' ),
174 wfMsgHtml( 'nologinlink' ),
175 array(),
176 $linkq );
177
178 # Don't show a "create account" link if the user can't
179 $link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
180 ? wfMsgWikiHtml( 'nologin', $link )
181 : '';
182
183 # Prepare language selection links as needed
184 $langSelector = $wgLoginLanguageSelector
185 ? Html::rawElement(
186 'div',
187 array( 'id' => 'languagelinks' ),
188 self::makeLanguageSelector( $this->getTitle(), $this->mReturnTo ) )
189 : '';
190
191 # Add a 'mail reset' button if available
192 $buttons = '';
193 if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
194 $buttons = Html::element(
195 'input',
196 array(
197 'type' => 'submit',
198 'name' => 'wpMailmypassword',
199 'value' => wfMsg( 'mailmypassword' ),
200 'id' => 'wpMailmypassword',
201 )
202 );
203 }
204
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 ) );
209
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;
215 } else {
216 unset( $this->mFormFields['Domain'] );
217 }
218
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';
227 }
228
229 $form = new HTMLForm( $this->mFormFields, '' );
230 $form->setTitle( $this->getTitle() );
231 $form->setSubmitText( wfMsg( 'login' ) );
232 $form->setSubmitId( 'wpLoginAttempt' );
233 $form->suppressReset();
234 $form->loadData();
235
236 $formContents = ''
237 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
238 $link )
239 . Html::rawElement( 'div', array( 'id' => 'userloginprompt' ),
240 wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
241 . $this->mFormHeader
242 . $langSelector
243 . $form->getBody()
244 . $form->getButtons()
245 . $buttons
246 . Xml::hidden( 'returnto', $this->mReturnTo )
247 . Xml::hidden( 'returntoquery', $this->mReturnToQuery )
248 ;
249
250 $wgOut->setPageTitle( wfMsg( 'login' ) );
251 $wgOut->setRobotPolicy( 'noindex,nofollow' );
252 $wgOut->setArticleRelated( false );
253 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
254
255 $wgOut->addHTML(
256 Html::rawElement(
257 'div',
258 array( 'id' => 'loginstart' ),
259 wfMsgExt( 'loginstart', array( 'parseinline' ) )
260 ) .
261 $msg .
262 Html::rawElement(
263 'div',
264 array( 'id' => 'userloginForm' ),
265 $form->wrapForm( $formContents )
266 ) .
267 Html::rawElement(
268 'div',
269 array( 'id' => 'loginend' ),
270 wfMsgExt( 'loginend', array( 'parseinline' ) )
271 )
272 );
273
274 }
275
276 /**
277 * Check if a session cookie is present.
278 *
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.
282 *
283 * @private
284 */
285 protected function hasSessionCookie() {
286 global $wgDisableCookieCheck, $wgRequest;
287 return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
288 }
289
290 /**
291 * Do a redirect back to the same page, so we can check any
292 * new session cookies.
293 */
294 protected function cookieRedirectCheck() {
295 global $wgOut;
296
297 $query = array( 'wpCookieCheck' => '1');
298 if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
299 $check = $this->getTitle()->getFullURL( $query );
300
301 return $wgOut->redirect( $check );
302 }
303
304 /**
305 * Check the cookies and show errors if they're not enabled.
306 * @param $type String action being performed
307 */
308 protected function onCookieRedirectCheck() {
309 if ( !$this->hasSessionCookie() ) {
310 return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
311 } else {
312 return self::successfulLogin( 'loginsuccess', $this->mReturnTo, $this->mReturnToQuery );
313 }
314 }
315
316 /**
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
322 */
323 public static function makeLanguageSelector( $title, $returnTo=false ) {
324 global $wgLang;
325
326 $msg = wfMsgForContent( 'loginlanguagelinks' );
327 if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
328 $langs = explode( "\n", $msg );
329 $links = array();
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 );
336 }
337 }
338 return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
339 } else {
340 return '';
341 }
342 }
343
344 /**
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
351 */
352 public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
353 global $wgUser;
354 $attr = array( 'uselang' => $lang );
355 if( $returnTo )
356 $attr['returnto'] = $returnTo;
357 $skin = $wgUser->getSkin();
358 return $skin->linkKnown(
359 $title,
360 htmlspecialchars( $text ),
361 array(),
362 $attr
363 );
364 }
365
366 /**
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
372 */
373 public static function displaySuccessfulLogin( $msgname, $injected_html='', $returnto=false, $returntoQuery=false ) {
374 global $wgOut, $wgUser;
375
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 );
381
382 if ( $returnto ) {
383 $wgOut->returnToMain( null, $returnto, $this->mReturnToQuery );
384 } else {
385 $wgOut->returnToMain( null );
386 }
387 }
388
389 /**
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.
396 */
397 public static function successfulLogin( $message, $returnTo='', $returnToQuery='' ) {
398 global $wgUser, $wgOut;
399
400 # Run any hooks; display injected HTML if any, else redirect
401 $injected_html = '';
402 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
403
404 if( $injected_html !== '' ) {
405 SpecialUserLogin::displaySuccessfulLogin( $message, $injected_html );
406 } else {
407 $titleObj = Title::newFromText( $returnTo );
408 if ( !$titleObj instanceof Title ) {
409 $titleObj = Title::newMainPage();
410 }
411 $wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
412 }
413 }
414
415
416 protected function processLogin(){
417 global $wgUser, $wgAuth;
418 $result = $this->mLogin->attemptLogin();
419 switch ( $result ) {
420 case Login::SUCCESS:
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 );
428 } else {
429 # Do a redirect check to ensure that the cookies are
430 # being retained by the user's browser.
431 return $this->cookieRedirectCheck();
432 }
433 break;
434
435 case Login::NO_NAME:
436 case Login::ILLEGAL:
437 $this->mainLoginForm( wfMsg( 'noname' ) );
438 break;
439 case Login::WRONG_PLUGIN_PASS:
440 $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
441 break;
442 case Login::NOT_EXISTS:
443 if( $wgUser->isAllowed( 'createaccount' ) ){
444 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) );
445 } else {
446 $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) );
447 }
448 break;
449 case Login::WRONG_PASS:
450 $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
451 break;
452 case Login::EMPTY_PASS:
453 $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) );
454 break;
455 case Login::RESET_PASS:
456 $this->resetLoginForm( wfMsg( 'resetpass_announce' ) );
457 break;
458 case Login::CREATE_BLOCKED:
459 $this->userBlockedMessage();
460 break;
461 case Login::THROTTLED:
462 $this->mainLoginForm( wfMsg( 'login-throttled' ) );
463 break;
464 default:
465 throw new MWException( "Unhandled case value" );
466 }
467 }
468
469 /**
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
473 */
474 function resetLoginForm( $error ) {
475 global $wgOut;
476 $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
477 $reset = new SpecialResetpass();
478 $reset->execute( null );
479 }
480
481 /**
482 * Attempt to send the user a password-reset mail, and display
483 * the results (good, bad or ugly).
484 * @return unknown_type
485 */
486 protected function showMailPage(){
487 global $wgOut;
488 $result = $this->mLogin->mailPassword();
489
490 switch( $result ){
491 case Login::MAIL_READ_ONLY :
492 $wgOut->readOnlyPage();
493 return;
494 case Login::MAIL_PASSCHANGE_FORBIDDEN:
495 $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) );
496 return;
497 case Login::MAIL_BLOCKED:
498 $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
499 return;
500 case Login::MAIL_PING_THROTTLED:
501 $wgOut->rateLimited();
502 return;
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',
509 array( 'parsemag' ),
510 round( $wgPasswordReminderResendTime, 3 )
511 ) );
512 return;
513 case Login::NO_NAME:
514 $this->mainLoginForm( wfMsg( 'noname' ) );
515 return;
516 case Login::NOT_EXISTS:
517 $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin->mUser->getName() ) ) );
518 return;
519 case Login::MAIL_EMPTY_EMAIL:
520 $this->mainLoginForm( wfMsg( 'noemail', $this->mLogin->mUser->getName() ) );
521 return;
522 case Login::MAIL_BAD_IP:
523 $this->mainLoginForm( wfMsg( 'badipaddress' ) );
524 return;
525 case Login::MAIL_ERROR:
526 $this->mainLoginForm( wfMsg( 'mailerror', $this->mLogin->mMailResult->getMessage() ) );
527 return;
528 case Login::SUCCESS:
529 $this->mainLoginForm( wfMsg( 'passwordsent', $this->mLogin->mUser->getName() ), 'success' );
530 return;
531 }
532 }
533
534 /**
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
539 * fail gracefully...
540 * @deprecated
541 */
542 public function set(){
543 wfDeprecated( __METHOD__ );
544 }
545 }