MediaWiki REL1_27
LoginSignupSpecialPage.php
Go to the documentation of this file.
1<?php
30use Psr\Log\LogLevel;
31
38 protected $mReturnTo;
39 protected $mPosted;
40 protected $mAction;
41 protected $mLanguage;
42 protected $mReturnToQuery;
43 protected $mToken;
44 protected $mStickHTTPS;
45 protected $mFromHTTP;
46 protected $mEntryError = '';
47 protected $mEntryErrorType = 'error';
48
49 protected $mLoaded = false;
50 protected $mLoadedRequest = false;
52
54 protected $securityLevel;
55
60 protected $targetUser;
61
63 protected $authForm;
64
66 protected $fakeTemplate;
67
68 abstract protected function isSignup();
69
76 abstract protected function successfulAction( $direct = false, $extraMessages = null );
77
83 abstract protected function logAuthResult( $success, $status = null );
84
85 public function __construct( $name ) {
87 parent::__construct( $name );
88
89 // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
91 }
92
93 protected function setRequest( array $data, $wasPosted = null ) {
94 parent::setRequest( $data, $wasPosted );
95 $this->mLoadedRequest = false;
96 }
97
102 private function loadRequestParameters( $subPage ) {
103 if ( $this->mLoadedRequest ) {
104 return;
105 }
106 $this->mLoadedRequest = true;
107 $request = $this->getRequest();
108
109 $this->mPosted = $request->wasPosted();
110 $this->mIsReturn = $subPage === 'return';
111 $this->mAction = $request->getVal( 'action' );
112 $this->mFromHTTP = $request->getBool( 'fromhttp', false )
113 || $request->getBool( 'wpFromhttp', false );
114 $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
115 || $request->getBool( 'wpForceHttps', false );
116 $this->mLanguage = $request->getText( 'uselang' );
117 $this->mReturnTo = $request->getVal( 'returnto', '' );
118 $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
119 }
120
126 protected function load( $subPage ) {
128
130 if ( $this->mLoaded ) {
131 return;
132 }
133 $this->mLoaded = true;
134 $request = $this->getRequest();
135
136 $securityLevel = $this->getRequest()->getText( 'force' );
137 if (
138 $securityLevel && AuthManager::singleton()->securitySensitiveOperationStatus(
139 $securityLevel ) === AuthManager::SEC_REAUTH
140 ) {
141 $this->securityLevel = $securityLevel;
142 }
143
144 $this->loadAuth( $subPage );
145
146 $this->mToken = $request->getVal( $this->getTokenName() );
147
148 // Show an error or warning passed on from a previous page
149 $entryError = $this->msg( $request->getVal( 'error', '' ) );
150 $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
151 // bc: provide login link as a parameter for messages where the translation
152 // was not updated
153 $loginreqlink = Linker::linkKnown(
154 $this->getPageTitle(),
155 $this->msg( 'loginreqlink' )->escaped(),
156 [],
157 [
158 'returnto' => $this->mReturnTo,
159 'returntoquery' => $this->mReturnToQuery,
160 'uselang' => $this->mLanguage,
161 'fromhttp' => $wgSecureLogin && $this->mFromHTTP ? '1' : null,
162 ]
163 );
164
165 // Only show valid error or warning messages.
166 if ( $entryError->exists()
167 && in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true )
168 ) {
169 $this->mEntryErrorType = 'error';
170 $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
171
172 } elseif ( $entryWarning->exists()
173 && in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true )
174 ) {
175 $this->mEntryErrorType = 'warning';
176 $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
177 }
178
179 # 1. When switching accounts, it sucks to get automatically logged out
180 # 2. Do not return to PasswordReset after a successful password change
181 # but goto Wiki start page (Main_Page) instead ( bug 33997 )
182 $returnToTitle = Title::newFromText( $this->mReturnTo );
183 if ( is_object( $returnToTitle )
184 && ( $returnToTitle->isSpecial( 'Userlogout' )
185 || $returnToTitle->isSpecial( 'PasswordReset' ) )
186 ) {
187 $this->mReturnTo = '';
188 $this->mReturnToQuery = '';
189 }
190 }
191
192 protected function getPreservedParams( $withToken = false ) {
194
195 $params = parent::getPreservedParams( $withToken );
196 $params += [
197 'returnto' => $this->mReturnTo ?: null,
198 'returntoquery' => $this->mReturnToQuery ?: null,
199 ];
200 if ( $wgSecureLogin && !$this->isSignup() ) {
201 $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
202 }
203 return $params;
204 }
205
206 protected function beforeExecute( $subPage ) {
207 // finish initializing the class before processing the request - T135924
209 return parent::beforeExecute( $subPage );
210 }
211
215 public function execute( $subPage ) {
216 $authManager = AuthManager::singleton();
217 $session = SessionManager::getGlobalSession();
218
219 // Session data is used for various things in the authentication process, so we must make
220 // sure a session cookie or some equivalent mechanism is set.
221 $session->persist();
222
223 $this->load( $subPage );
224 $this->setHeaders();
225 $this->checkPermissions();
226
227 // Make sure it's possible to log in
228 if ( !$this->isSignup() && !$session->canSetUser() ) {
229 throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
230 $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
231 ] );
232 }
233
234 /*
235 * In the case where the user is already logged in, and was redirected to
236 * the login form from a page that requires login, do not show the login
237 * page. The use case scenario for this is when a user opens a large number
238 * of tabs, is redirected to the login page on all of them, and then logs
239 * in on one, expecting all the others to work properly.
240 *
241 * However, do show the form if it was visited intentionally (no 'returnto'
242 * is present). People who often switch between several accounts have grown
243 * accustomed to this behavior.
244 *
245 * Also make an exception when force=<level> is set in the URL, which means the user must
246 * reauthenticate for security reasons.
247 */
248 if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
249 ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
250 $this->getUser()->isLoggedIn()
251 ) {
252 $this->successfulAction();
253 }
254
255 // If logging in and not on HTTPS, either redirect to it or offer a link.
257 if ( $this->getRequest()->getProtocol() !== 'https' ) {
258 $title = $this->getFullTitle();
259 $query = $this->getPreservedParams( false ) + [
260 'title' => null,
261 ( $this->mEntryErrorType === 'error' ? 'error'
262 : 'warning' ) => $this->mEntryError,
263 ] + $this->getRequest()->getQueryValues();
264 $url = $title->getFullURL( $query, false, PROTO_HTTPS );
265 if ( $wgSecureLogin && !$this->mFromHTTP &&
266 wfCanIPUseHTTPS( $this->getRequest()->getIP() )
267 ) {
268 // Avoid infinite redirect
269 $url = wfAppendQuery( $url, 'fromhttp=1' );
270 $this->getOutput()->redirect( $url );
271 // Since we only do this redir to change proto, always vary
272 $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
273
274 return;
275 } else {
276 // A wiki without HTTPS login support should set $wgServer to
277 // http://somehost, in which case the secure URL generated
278 // above won't actually start with https://
279 if ( substr( $url, 0, 8 ) === 'https://' ) {
280 $this->mSecureLoginUrl = $url;
281 }
282 }
283 }
284
285 if ( !$this->isActionAllowed( $this->authAction ) ) {
286 // FIXME how do we explain this to the user? can we handle session loss better?
287 // messages used: authpage-cannot-login, authpage-cannot-login-continue,
288 // authpage-cannot-create, authpage-cannot-create-continue
289 $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
290 return;
291 }
292
293 if ( $this->canBypassForm( $button_name ) ) {
294 $this->setRequest( [], true );
295 $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
296 if ( $button_name ) {
297 $this->getRequest()->setVal( $button_name, true );
298 }
299 }
300
301 $status = $this->trySubmit();
302
303 if ( !$status || !$status->isGood() ) {
304 $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
305 return;
306 }
307
309 $response = $status->getValue();
310
311 $returnToUrl = $this->getPageTitle( 'return' )
312 ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
313 switch ( $response->status ) {
314 case AuthenticationResponse::PASS:
315 $this->logAuthResult( true );
316 $this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
317 $this->targetUser = User::newFromName( $response->username );
318
319 if (
320 !$this->proxyAccountCreation
321 && $response->loginRequest
322 && $authManager->canAuthenticateNow()
323 ) {
324 // successful registration; log the user in instantly
325 $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
326 $returnToUrl );
327 if ( $response2->status !== AuthenticationResponse::PASS ) {
328 LoggerFactory::getInstance( 'login' )
329 ->error( 'Could not log in after account creation' );
330 $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
331 break;
332 }
333 }
334
335 if ( !$this->proxyAccountCreation ) {
336 // Ensure that the context user is the same as the session user.
338 }
339
340 $this->successfulAction( true );
341 break;
342 case AuthenticationResponse::FAIL:
343 // fall through
344 case AuthenticationResponse::RESTART:
345 unset( $this->authForm );
346 if ( $response->status === AuthenticationResponse::FAIL ) {
347 $action = $this->getDefaultAction( $subPage );
348 $messageType = 'error';
349 } else {
350 $action = $this->getContinueAction( $this->authAction );
351 $messageType = 'warning';
352 }
353 $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
354 $this->loadAuth( $subPage, $action, true );
355 $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
356 break;
357 case AuthenticationResponse::REDIRECT:
358 unset( $this->authForm );
359 $this->getOutput()->redirect( $response->redirectTarget );
360 break;
361 case AuthenticationResponse::UI:
362 unset( $this->authForm );
363 $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
364 : AuthManager::ACTION_LOGIN_CONTINUE;
365 $this->authRequests = $response->neededRequests;
366 $this->mainLoginForm( $response->neededRequests, $response->message, 'warning' );
367 break;
368 default:
369 throw new LogicException( 'invalid AuthenticationResponse' );
370 }
371 }
372
386 private function canBypassForm( &$button_name ) {
387 $button_name = null;
388 if ( $this->isContinued() ) {
389 return false;
390 }
391 $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
392 foreach ( $fields as $fieldname => $field ) {
393 if ( !isset( $field['type'] ) ) {
394 return false;
395 }
396 if ( !empty( $field['skippable'] ) ) {
397 continue;
398 }
399 if ( $field['type'] === 'button' ) {
400 if ( $button_name !== null ) {
401 $button_name = null;
402 return false;
403 } else {
404 $button_name = $fieldname;
405 }
406 } elseif ( $field['type'] !== 'null' ) {
407 return false;
408 }
409 }
410 return true;
411 }
412
422 protected function showSuccessPage(
423 $type, $title, $msgname, $injected_html, $extraMessages
424 ) {
425 $out = $this->getOutput();
426 $out->setPageTitle( $title );
427 if ( $msgname ) {
428 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
429 }
430 if ( $extraMessages ) {
431 $extraMessages = Status::wrap( $extraMessages );
432 $out->addWikiText( $extraMessages->getWikiText() );
433 }
434
435 $out->addHTML( $injected_html );
436
437 $helper = new LoginHelper( $this->getContext() );
438 $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
439 }
440
456 public function showReturnToPage(
457 $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
458 ) {
459 $helper = new LoginHelper( $this->getContext() );
460 $helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
461 }
462
468 protected function setSessionUserForCurrentRequest() {
470
472 $localContext = $this->getContext();
473 if ( $context !== $localContext ) {
474 // remove AuthManagerSpecialPage context hack
475 $this->setContext( $context );
476 }
477
478 $user = $context->getRequest()->getSession()->getUser();
479
480 $wgUser = $user;
481 $context->setUser( $user );
482
483 $code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) );
484 $userLang = Language::factory( $code );
485 $wgLang = $userLang;
486 $context->setLanguage( $userLang );
487 }
488
503 protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
504 $titleObj = $this->getPageTitle();
505 $user = $this->getUser();
506 $out = $this->getOutput();
507
508 // FIXME how to handle empty $requests - restart, or no form, just an error message?
509 // no form would be better for no session type errors, restart is better when can* fails.
510 if ( !$requests ) {
511 $this->authAction = $this->getDefaultAction( $this->subPage );
512 $this->authForm = null;
513 $requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user );
514 }
515
516 // Generic styles and scripts for both login and signup form
517 $out->addModuleStyles( [
518 'mediawiki.ui',
519 'mediawiki.ui.button',
520 'mediawiki.ui.checkbox',
521 'mediawiki.ui.input',
522 'mediawiki.special.userlogin.common.styles'
523 ] );
524 if ( $this->isSignup() ) {
525 // XXX hack pending RL or JS parse() support for complex content messages T27349
526 $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
527 $this->msg( 'createacct-imgcaptcha-help' )->parse() );
528
529 // Additional styles and scripts for signup form
530 $out->addModules( [
531 'mediawiki.special.userlogin.signup.js'
532 ] );
533 $out->addModuleStyles( [
534 'mediawiki.special.userlogin.signup.styles'
535 ] );
536 } else {
537 // Additional styles for login form
538 $out->addModuleStyles( [
539 'mediawiki.special.userlogin.login.styles'
540 ] );
541 }
542 $out->disallowUserJs(); // just in case...
543
544 $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
545 $form->prepareForm();
546 $formHtml = $form->getHTML( $msg ? Status::newFatal( $msg ) : false );
547
548 $out->addHTML( $this->getPageHtml( $formHtml ) );
549 }
550
557 protected function getPageHtml( $formHtml ) {
559
560 $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
561 [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
562 $languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : '';
563 $signupStartMsg = $this->msg( 'signupstart' );
564 $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
565 ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
566 if ( $languageLinks ) {
567 $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
568 Html::rawElement( 'p', [], $languageLinks )
569 );
570 }
571
572 $benefitsContainer = '';
573 if ( $this->isSignup() && $this->showExtraInformation() ) {
574 // messages used:
575 // createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
576 // createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
577 // createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
578 $benefitCount = 3;
579 $benefitList = '';
580 for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
581 $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
582 $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped();
583 $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
584 Html::rawElement( 'h3', [],
585 $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
586 )
587 . Html::rawElement( 'p', [],
588 $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
589 )
590 );
591 }
592 $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
593 Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
594 . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ],
595 $benefitList
596 )
597 );
598 }
599
600 $html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
601 $loginPrompt
602 . $languageLinks
603 . $signupStart
604 . Html::rawElement( 'div', [ 'id' => 'userloginForm' ],
605 $formHtml
606 )
607 . $benefitsContainer
608 );
609
610 return $html;
611 }
612
621 protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
623 // FIXME merge this with parent
624
625 if ( isset( $this->authForm ) ) {
626 return $this->authForm;
627 }
628
629 $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
630
631 // get basic form description from the auth logic
632 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
633 $fakeTemplate = $this->getFakeTemplate( $msg, $msgType );
634 $this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
635 // this will call onAuthChangeFormFields()
636 $formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
637 $this->postProcessFormDescriptor( $formDescriptor, $requests );
638
639 $context = $this->getContext();
640 if ( $context->getRequest() !== $this->getRequest() ) {
641 // We have overridden the request, need to make sure the form uses that too.
642 $context = new DerivativeContext( $this->getContext() );
643 $context->setRequest( $this->getRequest() );
644 }
645 $form = HTMLForm::factory( 'vform', $formDescriptor, $context );
646
647 $form->addHiddenField( 'authAction', $this->authAction );
649 $form->addHiddenField( 'uselang', $this->mLanguage );
650 }
651 $form->addHiddenField( 'force', $this->securityLevel );
652 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
653 if ( $wgSecureLogin ) {
654 // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
655 if ( !$this->isSignup() ) {
656 $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
657 $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
658 }
659 }
660
661 // set properties of the form itself
662 $form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
663 $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
664 if ( $this->isSignup() ) {
665 $form->setId( 'userlogin2' );
666 }
667
668 // add pre/post text
669 // header used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
670 // should be above the error message but HTMLForm doesn't support that
671 $form->addHeaderText( $fakeTemplate->get( 'header' ) );
672
673 // FIXME the old form used this for error/warning messages which does not play well with
674 // HTMLForm (maybe it could with a subclass?); for now only display it for signups
675 // (where the JS username validation needs it) and alway empty
676 if ( $this->isSignup() ) {
677 // used by the mediawiki.special.userlogin.signup.js module
678 $statusAreaAttribs = [ 'id' => 'mw-createacct-status-area' ];
679 // $statusAreaAttribs += $msg ? [ 'class' => "{$msgType}box" ] : [ 'style' => 'display: none;' ];
680 $form->addHeaderText( Html::element( 'div', $statusAreaAttribs ) );
681 }
682
683 // header used by MobileFrontend
684 $form->addHeaderText( $fakeTemplate->get( 'formheader' ) );
685
686 // blank signup footer for site customization
687 if ( $this->isSignup() && $this->showExtraInformation() ) {
688 // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
689 $signupendMsg = $this->msg( 'signupend' );
690 $signupendHttpsMsg = $this->msg( 'signupend-https' );
691 if ( !$signupendMsg->isDisabled() ) {
692 $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
693 ? $signupendHttpsMsg ->parse() : $signupendMsg->parse();
694 $form->addPostText( Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ) );
695 }
696 }
697
698 // warning header for non-standard workflows (e.g. security reauthentication)
699 if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
700 $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
701 $form->addHeaderText( Html::rawElement( 'div', [ 'class' => 'warningbox' ],
702 $this->msg( $reauthMessage )->params( $this->getUser()->getName() )->parse() ) );
703 }
704
705 if ( !$this->isSignup() && $this->showExtraInformation() ) {
706 $passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
707 if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
708 $form->addFooterText( Html::rawElement(
709 'div',
710 [ 'class' => 'mw-ui-vform-field mw-form-related-link-container' ],
712 SpecialPage::getTitleFor( 'PasswordReset' ),
713 $this->msg( 'userlogin-resetpassword-link' )->escaped()
714 )
715 ) );
716 }
717
718 // Don't show a "create account" link if the user can't.
719 if ( $this->showCreateAccountLink() ) {
720 // link to the other action
721 $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' );
722 $linkq = $this->getReturnToQueryStringFragment();
723 // Pass any language selection on to the mode switch link
724 if ( $wgLoginLanguageSelector && $this->mLanguage ) {
725 $linkq .= '&uselang=' . $this->mLanguage;
726 }
727 $createOrLoginHref = $linkTitle->getLocalURL( $linkq );
728
729 if ( $this->getUser()->isLoggedIn() ) {
730 $createOrLoginHtml = Html::rawElement( 'div',
731 [ 'class' => 'mw-ui-vform-field' ],
732 Html::element( 'a',
733 [
734 'id' => 'mw-createaccount-join',
735 'href' => $createOrLoginHref,
736 // put right after all auth inputs in the tab order
737 'tabindex' => 100,
738 ],
739 $this->msg( 'userlogin-createanother' )->escaped()
740 )
741 );
742 } else {
743 $createOrLoginHtml = Html::rawElement( 'div',
744 [ 'id' => 'mw-createaccount-cta',
745 'class' => 'mw-ui-vform-field' ],
746 $this->msg( 'userlogin-noaccount' )->escaped()
747 . Html::element( 'a',
748 [
749 'id' => 'mw-createaccount-join',
750 'href' => $createOrLoginHref,
751 'class' => 'mw-ui-button',
752 'tabindex' => 100,
753 ],
754 $this->msg( 'userlogin-joinproject' )->escaped()
755 )
756 );
757 }
758 $form->addFooterText( $createOrLoginHtml );
759 }
760 }
761
762 $form->suppressDefaultSubmit();
763
764 $this->authForm = $form;
765
766 return $form;
767 }
768
775 protected function getFakeTemplate( $msg, $msgType ) {
778
779 // make a best effort to get the value of fields which used to be fixed in the old login
780 // template but now might or might not exist depending on what providers are used
781 $request = $this->getRequest();
782 $data = (object) [
783 'mUsername' => $request->getText( 'wpName' ),
784 'mPassword' => $request->getText( 'wpPassword' ),
785 'mRetype' => $request->getText( 'wpRetype' ),
786 'mEmail' => $request->getText( 'wpEmail' ),
787 'mRealName' => $request->getText( 'wpRealName' ),
788 'mDomain' => $request->getText( 'wpDomain' ),
789 'mReason' => $request->getText( 'wpReason' ),
790 'mRemember' => $request->getCheck( 'wpRemember' ),
791 ];
792
793 // Preserves a bunch of logic from the old code that was rewritten in getAuthForm().
794 // There is no code reuse to make this easier to remove .
795 // If an extension tries to change any of these values, they are out of luck - we only
796 // actually use the domain/usedomain/domainnames, extraInput and extrafields keys.
797
798 $titleObj = $this->getPageTitle();
799 $user = $this->getUser();
801
802 // Pre-fill username (if not creating an account, bug 44775).
803 if ( $data->mUsername == '' && $this->isSignup() ) {
804 if ( $user->isLoggedIn() ) {
805 $data->mUsername = $user->getName();
806 } else {
807 $data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
808 }
809 }
810
811 if ( $this->isSignup() ) {
812 // Must match number of benefits defined in messages
813 $template->set( 'benefitCount', 3 );
814
815 $q = 'action=submitlogin&type=signup';
816 $linkq = 'type=login';
817 } else {
818 $q = 'action=submitlogin&type=login';
819 $linkq = 'type=signup';
820 }
821
822 if ( $this->mReturnTo !== '' ) {
823 $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
824 if ( $this->mReturnToQuery !== '' ) {
825 $returnto .= '&returntoquery=' .
826 wfUrlencode( $this->mReturnToQuery );
827 }
828 $q .= $returnto;
829 $linkq .= $returnto;
830 }
831
832 # Don't show a "create account" link if the user can't.
833 if ( $this->showCreateAccountLink() ) {
834 # Pass any language selection on to the mode switch link
835 if ( $wgLoginLanguageSelector && $this->mLanguage ) {
836 $linkq .= '&uselang=' . $this->mLanguage;
837 }
838 // Supply URL, login template creates the button.
839 $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
840 } else {
841 $template->set( 'link', '' );
842 }
843
844 $resetLink = $this->isSignup()
845 ? null
846 : is_array( $wgPasswordResetRoutes )
847 && in_array( true, array_values( $wgPasswordResetRoutes ), true );
848
849 $template->set( 'header', '' );
850 $template->set( 'formheader', '' );
851 $template->set( 'skin', $this->getSkin() );
852
853 $template->set( 'name', $data->mUsername );
854 $template->set( 'password', $data->mPassword );
855 $template->set( 'retype', $data->mRetype );
856 $template->set( 'createemailset', false ); // no easy way to get that from AuthManager
857 $template->set( 'email', $data->mEmail );
858 $template->set( 'realname', $data->mRealName );
859 $template->set( 'domain', $data->mDomain );
860 $template->set( 'reason', $data->mReason );
861 $template->set( 'remember', $data->mRemember );
862
863 $template->set( 'action', $titleObj->getLocalURL( $q ) );
864 $template->set( 'message', $msg );
865 $template->set( 'messagetype', $msgType );
866 $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
867 $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) );
868 $template->set( 'useemail', $wgEnableEmail );
869 $template->set( 'emailrequired', $wgEmailConfirmToEdit );
870 $template->set( 'emailothers', $wgEnableUserEmail );
871 $template->set( 'canreset', $wgAuth->allowPasswordChange() );
872 $template->set( 'resetlink', $resetLink );
873 $template->set( 'canremember', $request->getSession()->getProvider()
874 ->getRememberUserDuration() !== null );
875 $template->set( 'usereason', $user->isLoggedIn() );
876 $template->set( 'cansecurelogin', ( $wgSecureLogin ) );
877 $template->set( 'stickhttps', (int)$this->mStickHTTPS );
878 $template->set( 'loggedin', $user->isLoggedIn() );
879 $template->set( 'loggedinuser', $user->getName() );
880 $template->set( 'token', $this->getToken()->toString() );
881
882 $action = $this->isSignup() ? 'signup' : 'login';
883 $wgAuth->modifyUITemplate( $template, $action );
884
885 $oldTemplate = $template;
886 $hookName = $this->isSignup() ? 'UserCreateForm' : 'UserLoginForm';
887 Hooks::run( $hookName, [ &$template ] );
888 if ( $oldTemplate !== $template ) {
889 wfDeprecated( "reference in $hookName hook", '1.27' );
890 }
891
892 return $template;
893
894 }
895
896 public function onAuthChangeFormFields(
897 array $requests, array $fieldInfo, array &$formDescriptor, $action
898 ) {
899 $coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate );
900 $specialFields = array_merge( [ 'extraInput', 'linkcontainer', 'entryError' ],
901 array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) );
902
903 // keep the ordering from getCoreFieldDescriptors() where there is no explicit weight
904 foreach ( $coreFieldDescriptors as $fieldName => $coreField ) {
905 $requestField = isset( $formDescriptor[$fieldName] ) ?
906 $formDescriptor[$fieldName] : [];
907
908 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
909 // to something in the fieldinfo, and is not a generic or B/C field or a submit button
910 if (
911 !isset( $fieldInfo[$fieldName] )
912 && (
913 !isset( $coreField['baseField'] )
914 || !isset( $fieldInfo[$coreField['baseField']] )
915 ) && !in_array( $fieldName, $specialFields, true )
916 && ( !isset( $coreField['type'] ) || $coreField['type'] !== 'submit' )
917 ) {
918 $coreFieldDescriptors[$fieldName] = null;
919 continue;
920 }
921
922 // core message labels should always take priority
923 if (
924 isset( $coreField['label'] )
925 || isset( $coreField['label-message'] )
926 || isset( $coreField['label-raw'] )
927 ) {
928 unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] );
929 }
930
931 $coreFieldDescriptors[$fieldName] += $requestField;
932 }
933
934 $formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor );
935 return true;
936 }
937
944 protected function showExtraInformation() {
945 return $this->authAction !== $this->getContinueAction( $this->authAction )
947 }
948
954 protected function getFieldDefinitions( $template ) {
956
957 $isLoggedIn = $this->getUser()->isLoggedIn();
958 $continuePart = $this->isContinued() ? 'continue-' : '';
959 $anotherPart = $isLoggedIn ? 'another-' : '';
960 $expiration = $this->getRequest()->getSession()->getProvider()
961 ->getRememberUserDuration();
962 $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
963 $secureLoginLink = '';
964 if ( $this->mSecureLoginUrl ) {
965 $secureLoginLink = Html::element( 'a', [
966 'href' => $this->mSecureLoginUrl,
967 'class' => 'mw-ui-flush-right mw-secure',
968 ], $this->msg( 'userlogin-signwithsecure' )->text() );
969 }
970
971 if ( $this->isSignup() ) {
972 $fieldDefinitions = [
973 'username' => [
974 'label-message' => 'userlogin-yourname',
975 // FIXME help-message does not match old formatting
976 'help-message' => 'createacct-helpusername',
977 'id' => 'wpName2',
978 'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
979 : 'userlogin-yourname-ph',
980 ],
981 'mailpassword' => [
982 // create account without providing password, a temporary one will be mailed
983 'type' => 'check',
984 'label-message' => 'createaccountmail',
985 'name' => 'wpCreateaccountMail',
986 'id' => 'wpCreateaccountMail',
987 ],
988 'password' => [
989 'id' => 'wpPassword2',
990 'placeholder-message' => 'createacct-yourpassword-ph',
991 'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
992 ],
993 'domain' => [],
994 'retype' => [
995 'baseField' => 'password',
996 'type' => 'password',
997 'label-message' => 'createacct-yourpasswordagain',
998 'id' => 'wpRetype',
999 'cssclass' => 'loginPassword',
1000 'size' => 20,
1001 'validation-callback' => function ( $value, $alldata ) {
1002 if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
1003 if ( !$value ) {
1004 return $this->msg( 'htmlform-required' );
1005 } elseif ( $value !== $alldata['password'] ) {
1006 return $this->msg( 'badretype' );
1007 }
1008 }
1009 return true;
1010 },
1011 'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
1012 'placeholder-message' => 'createacct-yourpasswordagain-ph',
1013 ],
1014 'email' => [
1015 'type' => 'email',
1016 'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired'
1017 : 'createacct-emailoptional',
1018 'id' => 'wpEmail',
1019 'cssclass' => 'loginText',
1020 'size' => '20',
1021 // FIXME will break non-standard providers
1022 'required' => $wgEmailConfirmToEdit,
1023 'validation-callback' => function ( $value, $alldata ) {
1025
1026 // AuthManager will check most of these, but that will make the auth
1027 // session fail and this won't, so nicer to do it this way
1028 if ( !$value && $wgEmailConfirmToEdit ) {
1029 // no point in allowing registration without email when email is
1030 // required to edit
1031 return $this->msg( 'noemailtitle' );
1032 } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
1033 // cannot send password via email when there is no email address
1034 return $this->msg( 'noemailcreate' );
1035 } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
1036 return $this->msg( 'invalidemailaddress' );
1037 }
1038 return true;
1039 },
1040 'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
1041 ],
1042 'realname' => [
1043 'type' => 'text',
1044 'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
1045 : 'prefs-help-realname',
1046 'label-message' => 'createacct-realname',
1047 'cssclass' => 'loginText',
1048 'size' => 20,
1049 'id' => 'wpRealName',
1050 ],
1051 'reason' => [
1052 // comment for the user creation log
1053 'type' => 'text',
1054 'label-message' => 'createacct-reason',
1055 'cssclass' => 'loginText',
1056 'id' => 'wpReason',
1057 'size' => '20',
1058 'placeholder-message' => 'createacct-reason-ph',
1059 ],
1060 'extrainput' => [], // placeholder for fields coming from the template
1061 'createaccount' => [
1062 // submit button
1063 'type' => 'submit',
1064 'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
1065 'submit' )->text(),
1066 'name' => 'wpCreateaccount',
1067 'id' => 'wpCreateaccount',
1068 'weight' => 100,
1069 ],
1070 ];
1071 } else {
1072 $fieldDefinitions = [
1073 'username' => [
1074 'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
1075 'id' => 'wpName1',
1076 'placeholder-message' => 'userlogin-yourname-ph',
1077 ],
1078 'password' => [
1079 'id' => 'wpPassword1',
1080 'placeholder-message' => 'userlogin-yourpassword-ph',
1081 ],
1082 'domain' => [],
1083 'extrainput' => [],
1084 'rememberMe' => [
1085 // option for saving the user token to a cookie
1086 'type' => 'check',
1087 'name' => 'wpRemember',
1088 'label-message' => $this->msg( 'userlogin-remembermypassword' )
1089 ->numParams( $expirationDays ),
1090 'id' => 'wpRemember',
1091 ],
1092 'loginattempt' => [
1093 // submit button
1094 'type' => 'submit',
1095 'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
1096 'id' => 'wpLoginAttempt',
1097 'weight' => 100,
1098 ],
1099 'linkcontainer' => [
1100 // help link
1101 'type' => 'info',
1102 'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1103 // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1104 'raw' => true,
1105 'default' => Html::element( 'a', [
1106 'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' )
1107 ->inContentLanguage()
1108 ->text() ),
1109 ], $this->msg( 'userlogin-helplink2' )->text() ),
1110 'weight' => 200,
1111 ],
1112 // button for ResetPasswordSecondaryAuthenticationProvider
1113 'skipReset' => [
1114 'weight' => 110,
1115 'flags' => [],
1116 ],
1117 ];
1118 }
1119 $fieldDefinitions['username'] += [
1120 'type' => 'text',
1121 'name' => 'wpName',
1122 'cssclass' => 'loginText',
1123 'size' => 20,
1124 // 'required' => true,
1125 ];
1126 $fieldDefinitions['password'] += [
1127 'type' => 'password',
1128 // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1129 'name' => 'wpPassword',
1130 'cssclass' => 'loginPassword',
1131 'size' => 20,
1132 // 'required' => true,
1133 ];
1134
1135 if ( $this->mEntryError ) {
1136 $fieldDefinitions['entryError'] = [
1137 'type' => 'info',
1138 'default' => Html::rawElement( 'div', [ 'class' => $this->mEntryErrorType . 'box', ],
1139 $this->mEntryError ),
1140 'raw' => true,
1141 'rawrow' => true,
1142 'weight' => -100,
1143 ];
1144 }
1145
1146 if ( !$this->showExtraInformation() ) {
1147 unset( $fieldDefinitions['linkcontainer'] );
1148 }
1149
1150 $fieldDefinitions = $this->getBCFieldDefinitions( $fieldDefinitions, $template );
1151 $fieldDefinitions = array_filter( $fieldDefinitions );
1152
1153 return $fieldDefinitions;
1154 }
1155
1162 protected function getBCFieldDefinitions( $fieldDefinitions, $template ) {
1163 if ( $template->get( 'usedomain', false ) ) {
1164 // TODO probably should be translated to the new domain notation in AuthManager
1165 $fieldDefinitions['domain'] = [
1166 'type' => 'select',
1167 'label-message' => 'yourdomainname',
1168 'options' => array_combine( $template->get( 'domainnames', [] ),
1169 $template->get( 'domainnames', [] ) ),
1170 'default' => $template->get( 'domain', '' ),
1171 'name' => 'wpDomain',
1172 // FIXME id => 'mw-user-domain-section' on the parent div
1173 ];
1174 }
1175
1176 // poor man's associative array_splice
1177 $extraInputPos = array_search( 'extrainput', array_keys( $fieldDefinitions ), true );
1178 $fieldDefinitions = array_slice( $fieldDefinitions, 0, $extraInputPos, true )
1179 + $template->getExtraInputDefinitions()
1180 + array_slice( $fieldDefinitions, $extraInputPos + 1, null, true );
1181
1182 return $fieldDefinitions;
1183 }
1184
1194 protected function hasSessionCookie() {
1196
1197 return $wgDisableCookieCheck || (
1199 $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
1200 );
1201 }
1202
1207 protected function getReturnToQueryStringFragment() {
1208 $returnto = '';
1209 if ( $this->mReturnTo !== '' ) {
1210 $returnto = 'returnto=' . wfUrlencode( $this->mReturnTo );
1211 if ( $this->mReturnToQuery !== '' ) {
1212 $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery );
1213 }
1214 }
1215 return $returnto;
1216 }
1217
1223 private function showCreateAccountLink() {
1224 if ( $this->isSignup() ) {
1225 return true;
1226 } elseif ( $this->getUser()->isAllowed( 'createaccount' ) ) {
1227 return true;
1228 } else {
1229 return false;
1230 }
1231 }
1232
1233 protected function getTokenName() {
1234 return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1235 }
1236
1243 protected function makeLanguageSelector() {
1244 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1245 if ( $msg->isBlank() ) {
1246 return '';
1247 }
1248 $langs = explode( "\n", $msg->text() );
1249 $links = [];
1250 foreach ( $langs as $lang ) {
1251 $lang = trim( $lang, '* ' );
1252 $parts = explode( '|', $lang );
1253 if ( count( $parts ) >= 2 ) {
1254 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1255 }
1256 }
1257
1258 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1259 $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1260 }
1261
1270 protected function makeLanguageSelectorLink( $text, $lang ) {
1271 if ( $this->getLanguage()->getCode() == $lang ) {
1272 // no link for currently used language
1273 return htmlspecialchars( $text );
1274 }
1275 $query = [ 'uselang' => $lang ];
1276 if ( $this->mReturnTo !== '' ) {
1277 $query['returnto'] = $this->mReturnTo;
1278 $query['returntoquery'] = $this->mReturnToQuery;
1279 }
1280
1281 $attr = [];
1282 $targetLanguage = Language::factory( $lang );
1283 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1284
1285 return Linker::linkKnown(
1286 $this->getPageTitle(),
1287 htmlspecialchars( $text ),
1288 $attr,
1289 $query
1290 );
1291 }
1292
1293 protected function getGroupName() {
1294 return 'login';
1295 }
1296
1300 protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1301 // Pre-fill username (if not creating an account, T46775).
1302 if (
1303 isset( $formDescriptor['username'] ) &&
1304 !isset( $formDescriptor['username']['default'] ) &&
1305 !$this->isSignup()
1306 ) {
1307 $user = $this->getUser();
1308 if ( $user->isLoggedIn() ) {
1309 $formDescriptor['username']['default'] = $user->getName();
1310 } else {
1311 $formDescriptor['username']['default'] =
1312 $this->getRequest()->getSession()->suggestLoginUsername();
1313 }
1314 }
1315
1316 // don't show a submit button if there is nothing to submit (i.e. the only form content
1317 // is other submit buttons, for redirect flows)
1318 if ( !$this->needsSubmitButton( $requests ) ) {
1319 unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1320 }
1321
1322 if ( !$this->isSignup() ) {
1323 // FIXME HACK don't focus on non-empty field
1324 // maybe there should be an autofocus-if similar to hide-if?
1325 if (
1326 isset( $formDescriptor['username'] )
1327 && empty( $formDescriptor['username']['default'] )
1328 && !$this->getRequest()->getCheck( 'wpName' )
1329 ) {
1330 $formDescriptor['username']['autofocus'] = true;
1331 } elseif ( isset( $formDescriptor['password'] ) ) {
1332 $formDescriptor['password']['autofocus'] = true;
1333 }
1334 }
1335
1336 $this->addTabIndex( $formDescriptor );
1337 }
1338}
1339
1347 public function execute() {
1348 throw new LogicException( 'not used' );
1349 }
1350
1355 public function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
1356 // use the same indexes as UserCreateForm just in case someone adds an item manually
1357 $this->data['extrainput'][] = [
1358 'name' => $name,
1359 'value' => $value,
1360 'type' => $type,
1361 'msg' => $msg,
1362 'helptext' => $helptext,
1363 ];
1364 }
1365
1370 public function getExtraInputDefinitions() {
1371 $definitions = [];
1372
1373 foreach ( $this->get( 'extrainput', [] ) as $field ) {
1374 $definition = [
1375 'type' => $field['type'] === 'checkbox' ? 'check' : $field['type'],
1376 'name' => $field['name'],
1377 'value' => $field['value'],
1378 'id' => $field['name'],
1379 ];
1380 if ( $field['msg'] ) {
1381 $definition['label-message'] = $this->getMsg( $field['msg'] );
1382 }
1383 if ( $field['helptext'] ) {
1384 $definition['help'] = $this->msgWiki( $field['helptext'] );
1385 }
1386
1387 // the array key doesn't matter much when name is defined explicitly but
1388 // let's try and follow HTMLForm conventions
1389 $name = preg_replace( '/^wp(?=[A-Z])/', '', $field['name'] );
1390 $definitions[$name] = $definition;
1391 }
1392
1393 if ( $this->haveData( 'extrafields' ) ) {
1394 $definitions['extrafields'] = [
1395 'type' => 'info',
1396 'raw' => true,
1397 'default' => $this->get( 'extrafields' ),
1398 ];
1399 }
1400
1401 return $definitions;
1402 }
1403}
1404
1410class LoginForm extends SpecialPage {
1411 const SUCCESS = 0;
1412 const NO_NAME = 1;
1413 const ILLEGAL = 2;
1415 const NOT_EXISTS = 4;
1416 const WRONG_PASS = 5;
1417 const EMPTY_PASS = 6;
1418 const RESET_PASS = 7;
1419 const ABORTED = 8;
1421 const THROTTLED = 10;
1422 const USER_BLOCKED = 11;
1423 const NEED_TOKEN = 12;
1424 const WRONG_TOKEN = 13;
1425 const USER_MIGRATED = 14;
1426
1427 public static $statusCodes = [
1428 self::SUCCESS => 'success',
1429 self::NO_NAME => 'no_name',
1430 self::ILLEGAL => 'illegal',
1431 self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
1432 self::NOT_EXISTS => 'not_exists',
1433 self::WRONG_PASS => 'wrong_pass',
1434 self::EMPTY_PASS => 'empty_pass',
1435 self::RESET_PASS => 'reset_pass',
1436 self::ABORTED => 'aborted',
1437 self::CREATE_BLOCKED => 'create_blocked',
1438 self::THROTTLED => 'throttled',
1439 self::USER_BLOCKED => 'user_blocked',
1440 self::NEED_TOKEN => 'need_token',
1441 self::WRONG_TOKEN => 'wrong_token',
1442 self::USER_MIGRATED => 'user_migrated',
1443 ];
1444
1448 public function __construct( $request = null ) {
1449 wfDeprecated( 'LoginForm', '1.27' );
1450 parent::__construct();
1451 }
1452
1456 public static function getValidErrorMessages() {
1458 }
1459
1463 public static function incrementLoginThrottle( $username ) {
1464 wfDeprecated( __METHOD__, "1.27" );
1466 $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
1467 $throttler = new Throttler();
1468 return $throttler->increase( $username, $wgRequest->getIP(), __METHOD__ );
1469 }
1470
1474 public static function incLoginThrottle( $username ) {
1475 wfDeprecated( __METHOD__, "1.27" );
1476 $res = self::incrementLoginThrottle( $username );
1477 return is_array( $res ) ? true : 0;
1478 }
1479
1483 public static function clearLoginThrottle( $username ) {
1484 wfDeprecated( __METHOD__, "1.27" );
1486 $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
1487 $throttler = new Throttler();
1488 return $throttler->clear( $username, $wgRequest->getIP() );
1489 }
1490
1494 public static function getLoginToken() {
1495 wfDeprecated( __METHOD__, '1.27' );
1497 return $wgRequest->getSession()->getToken( '', 'login' )->toString();
1498 }
1499
1503 public static function setLoginToken() {
1504 wfDeprecated( __METHOD__, '1.27' );
1505 }
1506
1510 public static function clearLoginToken() {
1511 wfDeprecated( __METHOD__, '1.27' );
1513 $wgRequest->getSession()->resetToken( 'login' );
1514 }
1515
1519 public static function getCreateaccountToken() {
1520 wfDeprecated( __METHOD__, '1.27' );
1522 return $wgRequest->getSession()->getToken( '', 'createaccount' )->toString();
1523 }
1524
1528 public static function setCreateaccountToken() {
1529 wfDeprecated( __METHOD__, '1.27' );
1530 }
1531
1535 public static function clearCreateaccountToken() {
1536 wfDeprecated( __METHOD__, '1.27' );
1538 $wgRequest->getSession()->resetToken( 'createaccount' );
1539 }
1540}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgDisableCookieCheck
By default, MediaWiki checks if the client supports cookies during the login process,...
$wgHiddenPrefs
An array of preferences to not show for the user.
$wgLoginLanguageSelector
Show a bar of language selection links in the user login and user registration forms; edit the "login...
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
$wgPasswordResetRoutes
Whether to allow password resets ("enter some identifying data, and we'll send an email with a tempor...
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
$wgSecureLogin
This is to let user authenticate using https when they come from http.
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit?
$wgAuth $wgAuth
Authentication plugin.
$wgUseMediaWikiUIEverywhere
Temporary variable that applies MediaWiki UI wherever it can be supported.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$wgUser
Definition Setup.php:794
$wgInitialSessionId
Definition Setup.php:730
if(is_null($wgLocalTZoffset)) if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:657
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
getDefaultAction( $subPage)
Get the default action for this special page, if none is given via URL/POST data.
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
string $subPage
Subpage of the special page.
isContinued()
Returns true if this is not the first step of the authentication.
getRequest()
Get the WebRequest being used for this instance.
trySubmit()
Attempts to do an authentication step with the submitted data.
getToken()
Returns the CSRF token.
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
New base template for a skin's template extended from QuickTemplate this class features helper method...
msgWiki( $str)
An ugly, ugly hack.
getMsg( $name)
Get a Message object with its context set.
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
B/C class to try handling login/signup template modifications even though login/signup does not actua...
addInputItem( $name, $value, $type, $msg, $helptext=false)
Extensions (AntiSpoof and TitleBlacklist) call this in response to UserCreateForm hook to add checkbo...
getExtraInputDefinitions()
Turns addInputItem-style field definitions into HTMLForm field definitions.
execute()
Main function, used by classes that subclass QuickTemplate to show the actual HTML output.
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition HTMLForm.php:123
static factory( $displayFormat)
Construct a HTMLForm object for given display type.
Definition HTMLForm.php:264
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition Html.php:230
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition Html.php:210
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:195
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known', 'noclasses'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:264
LoginForm as a special page has been replaced by SpecialUserLogin and SpecialCreateAccount,...
static incrementLoginThrottle( $username)
static incLoginThrottle( $username)
__construct( $request=null)
static clearLoginThrottle( $username)
static clearCreateaccountToken()
Helper functions for the login form that need to be shared with other special pages (such as CentralA...
static getValidErrorMessages()
Returns an array of all valid error messages.
Holds shared logic for login and account creation pages.
mainLoginForm(array $requests, $msg='', $msgtype='error')
canBypassForm(&$button_name)
Determine if the login form can be bypassed.
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
logAuthResult( $success, $status=null)
Logs to the authmanager-stats channel.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form.
loadRequestParameters( $subPage)
Load basic request parameters for this Special page.
setSessionUserForCurrentRequest()
Replace some globals to make sure the fact that the user has just been logged in is reflected in the ...
getFakeTemplate( $msg, $msgType)
Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks.
showSuccessPage( $type, $title, $msgname, $injected_html, $extraMessages)
Show the success page.
getReturnToQueryStringFragment()
Returns a string that can be appended to the URL (without encoding) to preserve the return target.
User $targetUser
FIXME another flag for passing data.
successfulAction( $direct=false, $extraMessages=null)
getBCFieldDefinitions( $fieldDefinitions, $template)
Adds fields provided via the deprecated UserLoginForm / UserCreateForm hooks.
showExtraInformation()
Show extra information such as password recovery information, link from login to signup,...
getPageHtml( $formHtml)
Add page elements which are outside the form.
hasSessionCookie()
Check if a session cookie is present.
getAuthForm(array $requests, $action, $msg='', $msgType='error')
Generates a form from the given request.
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
makeLanguageSelectorLink( $text, $lang)
Create a language selector link for a particular language Links back to this page preserving type and...
bool $proxyAccountCreation
True if the user if creating an account for someone else.
showCreateAccountLink()
Whether the login/create account form should display a link to the other form (in addition to whateve...
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
beforeExecute( $subPage)
Gets called before.
getFieldDefinitions( $template)
Create a HTMLForm descriptor for the core login fields.
postProcessFormDescriptor(&$formDescriptor, $requests)
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
showReturnToPage( $type, $returnTo='', $returnToQuery='', $stickHTTPS=false)
Add a "return to" link or redirect to it.
makeLanguageSelector()
Produce a bar of links which allow the user to select another language during login/registration but ...
load( $subPage)
Load data from request.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
This is a value object to hold authentication response data.
PSR-3 logger instance factory.
This serves as the entry point to the MediaWiki session handling system.
Helper class for the password reset functionality shared by the web UI and the API.
get( $name, $default=null)
Gets the template data requested.
static getMain()
Static methods.
static validateEmail( $addr)
Does a string look like an e-mail address?
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition Skin.php:1097
Parent class for all special pages.
setContext( $context)
Sets the context this SpecialPage is executed in.
getName()
Get the name of this Special Page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name.
getContext()
Gets the context this SpecialPage is executed in.
getConfig()
Shortcut to get main config object.
getPageTitle( $subpage=false)
Get a self-referential title object.
getLanguage()
Shortcut to get user's language.
msg()
Wrapper around wfMessage that sets the current context.
getFullTitle()
Return the full title, including $par.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:277
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
$res
Definition database.txt:21
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition design.txt:56
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition globals.txt:64
const PROTO_HTTPS
Definition Defines.php:263
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1007
the array() calling protocol came about after MediaWiki 1.4rc1.
this hook is for auditing only WRONG_PASS
Definition hooks.txt:1949
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect & $returnTo
Definition hooks.txt:2411
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:183
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition hooks.txt:813
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2413
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:944
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:1811
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2530
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:846
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:1818
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead. & $feedLinks hooks can tweak the array to change how login etc forms should look $requests
Definition hooks.txt:316
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:767
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string & $returnToQuery
Definition hooks.txt:2412
this hook is for auditing only $response
Definition hooks.txt:765
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:314
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1458
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:847
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$context
Definition load.php:44
MediaWiki has optional support for a high distributed memory object caching system For general information on but for a larger site with heavy load
Definition memcached.txt:6
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN boolean columns are always mapped to as the code does not always treat the column as a and VARBINARY columns should simply be TEXT The only exception is when VARBINARY is used to store true binary data
Definition postgres.txt:43
$params
if(!isset( $args[0])) $lang