MediaWiki 1.43.1
LoginSignupSpecialPage.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\SpecialPage;
25
27use Exception;
28use FatalError;
29use LogicException;
30use LoginHelper;
52use Skin;
53use StatusValue;
54use Wikimedia\ScopedCallback;
55
63
71 protected string $mReturnTo;
77 protected string $mReturnToQuery;
84 protected string $mReturnToAnchor;
85
87 protected $mPosted;
89 protected $mAction;
91 protected $mToken;
93 protected $mStickHTTPS;
95 protected $mFromHTTP;
97 protected $mEntryError = '';
99 protected $mEntryErrorType = 'error';
101 protected $mDisplay = 'page';
102
104 protected $mLoaded = false;
106 protected $mLoadedRequest = false;
110 private $reasonValidatorResult = null;
111
113 protected $securityLevel;
114
120 protected $targetUser;
121
123 protected $authForm;
124
125 abstract protected function isSignup();
126
133 abstract protected function successfulAction( $direct = false, $extraMessages = null );
134
140 abstract protected function logAuthResult( $success, $status = null );
141
142 protected function setRequest( array $data, $wasPosted = null ) {
143 parent::setRequest( $data, $wasPosted );
144 $this->mLoadedRequest = false;
145 }
146
150 private function loadRequestParameters() {
151 if ( $this->mLoadedRequest ) {
152 return;
153 }
154 $this->mLoadedRequest = true;
155 $request = $this->getRequest();
156
157 $this->mPosted = $request->wasPosted();
158 $this->mAction = $request->getRawVal( 'action' );
159 $this->mFromHTTP = $request->getBool( 'fromhttp', false )
160 || $request->getBool( 'wpFromhttp', false );
161 $this->mStickHTTPS = $this->getConfig()->get( MainConfigNames::ForceHTTPS )
162 || ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
163 || $request->getBool( 'wpForceHttps', false );
164 $this->mReturnTo = $request->getVal( 'returnto', '' );
165 $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
166 $this->mReturnToAnchor = $request->getVal( 'returntoanchor', '' );
167 if ( $request->getVal( 'display' ) === 'popup' ) {
168 $this->mDisplay = 'popup';
169 }
170 }
171
177 protected function load( $subPage ) {
178 $this->loadRequestParameters();
179 if ( $this->mLoaded ) {
180 return;
181 }
182 $this->mLoaded = true;
183 $request = $this->getRequest();
184
185 $securityLevel = $this->getRequest()->getText( 'force' );
186 if (
187 $securityLevel &&
188 MediaWikiServices::getInstance()->getAuthManager()->securitySensitiveOperationStatus(
189 $securityLevel ) === AuthManager::SEC_REAUTH
190 ) {
191 $this->securityLevel = $securityLevel;
192 }
193
194 $this->loadAuth( $subPage );
195
196 $this->mToken = $request->getVal( $this->getTokenName() );
197
198 // Show an error or warning or a notice passed on from a previous page
199 $entryError = $this->msg( $request->getVal( 'error', '' ) );
200 $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
201 $entryNotice = $this->msg( $request->getVal( 'notice', '' ) );
202 // bc: provide login link as a parameter for messages where the translation
203 // was not updated
204 $loginreqlink = $this->getLinkRenderer()->makeKnownLink(
205 $this->getPageTitle(),
206 $this->msg( 'loginreqlink' )->text(),
207 [],
208 $this->getPreservedParams( [ 'reset' => true ] )
209 );
210
211 // Only show valid error or warning messages.
212 $validErrorMessages = LoginHelper::getValidErrorMessages();
213 if ( $entryError->exists()
214 && in_array( $entryError->getKey(), $validErrorMessages, true )
215 ) {
216 $this->mEntryErrorType = 'error';
217 $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
218
219 } elseif ( $entryWarning->exists()
220 && in_array( $entryWarning->getKey(), $validErrorMessages, true )
221 ) {
222 $this->mEntryErrorType = 'warning';
223 $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
224 } elseif ( $entryNotice->exists()
225 && in_array( $entryNotice->getKey(), $validErrorMessages, true )
226 ) {
227 $this->mEntryErrorType = 'notice';
228 $this->mEntryError = $entryNotice->parse();
229 }
230
231 # 1. When switching accounts, it sucks to get automatically logged out
232 # 2. Do not return to PasswordReset after a successful password change
233 # but goto Wiki start page (Main_Page) instead ( T35997 )
234 $returnToTitle = Title::newFromText( $this->mReturnTo );
235 if ( is_object( $returnToTitle )
236 && ( $returnToTitle->isSpecial( 'Userlogout' )
237 || $returnToTitle->isSpecial( 'PasswordReset' ) )
238 ) {
239 $this->mReturnTo = '';
240 $this->mReturnToQuery = '';
241 }
242 }
243
245 protected function getPreservedParams( $options = [] ) {
246 $params = parent::getPreservedParams( $options );
247
248 // Override returnto* with their property-based values, to account for the
249 // special-casing in load().
250 $this->loadRequestParameters();
251 $properties = [
252 'returnto' => 'mReturnTo',
253 'returntoquery' => 'mReturnToQuery',
254 'returntoanchor' => 'mReturnToAnchor',
255 ];
256 foreach ( $properties as $key => $prop ) {
257 $value = $this->$prop;
258 if ( $value !== '' ) {
259 $params[$key] = $value;
260 } else {
261 unset( $params[$key] );
262 }
263 }
264
265 if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->isSignup() ) {
266 $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
267 }
268 if ( $this->mDisplay !== 'page' ) {
269 $params['display'] = $this->mDisplay;
270 }
271
272 return array_filter( $params, fn ( $val ) => $val !== null );
273 }
274
275 protected function beforeExecute( $subPage ) {
276 // finish initializing the class before processing the request - T135924
277 $this->loadRequestParameters();
278 return parent::beforeExecute( $subPage );
279 }
280
285 public function execute( $subPage ) {
286 if ( $this->mPosted ) {
287 $time = microtime( true );
288 $profilingScope = new ScopedCallback( function () use ( $time ) {
289 $time = microtime( true ) - $time;
290 $stats = MediaWikiServices::getInstance()->getStatsFactory();
291 $stats->getTiming( 'auth_specialpage_executeTiming_seconds' )
292 ->setLabel( 'action', $this->authAction )
293 ->copyToStatsdAt( "timing.login.ui.{$this->authAction}" )
294 ->observe( $time * 1000 );
295 } );
296 }
297
298 $authManager = MediaWikiServices::getInstance()->getAuthManager();
299 $session = SessionManager::getGlobalSession();
300
301 // Session data is used for various things in the authentication process, so we must make
302 // sure a session cookie or some equivalent mechanism is set.
303 $session->persist();
304 // Explicitly disable cache to ensure cookie blocks may be set (T152462).
305 // (Technically redundant with sessions persisting from this page.)
306 $this->getOutput()->disableClientCache();
307
308 $this->load( $subPage );
309
310 // Do this early, so that it affects how error pages are rendered too
311 if ( $this->mDisplay === 'popup' ) {
312 // Replace the default skin with a "micro-skin" that omits most of the interface. (T362706)
313 // In the future, we might allow normal skins to serve this mode too, if they advise that
314 // they support it by setting a skin option, so that colors and fonts could stay consistent.
315 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
316 $this->getContext()->setSkin( $skinFactory->makeSkin( 'authentication-popup' ) );
317 }
318
319 $this->setHeaders();
320 $this->checkPermissions();
321
322 // Make sure the system configuration allows log in / sign up
323 if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
324 if ( !$session->canSetUser() ) {
325 throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
326 $session->getProvider()->describe( $this->getLanguage() )
327 ] );
328 }
329 throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
330 } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
331 throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
332 }
333
334 /*
335 * In the case where the user is already logged in, and was redirected to
336 * the login form from a page that requires login, do not show the login
337 * page. The use case scenario for this is when a user opens a large number
338 * of tabs, is redirected to the login page on all of them, and then logs
339 * in on one, expecting all the others to work properly.
340 *
341 * However, do show the form if it was visited intentionally (no 'returnto'
342 * is present). People who often switch between several accounts have grown
343 * accustomed to this behavior.
344 *
345 * For temporary users, the form is always shown, since the UI presents
346 * temporary users as not logged in and offers to discard their temporary
347 * account by logging in.
348 *
349 * Also make an exception when force=<level> is set in the URL, which means the user must
350 * reauthenticate for security reasons.
351 */
352 if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
353 ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
354 !$this->getUser()->isTemp() && $this->getUser()->isRegistered()
355 ) {
356 $this->successfulAction();
357 return;
358 }
359
360 // If logging in and not on HTTPS, either redirect to it or offer a link.
361 if ( $this->getRequest()->getProtocol() !== 'https' ) {
362 $title = $this->getFullTitle();
363 $query = $this->getPreservedParams() + [
364 'title' => null,
365 ( $this->mEntryErrorType === 'error' ? 'error'
366 : 'warning' ) => $this->mEntryError,
367 ] + $this->getRequest()->getQueryValues();
368 $url = $title->getFullURL( $query, false, PROTO_HTTPS );
369 if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->mFromHTTP ) {
370 // Avoid infinite redirect
371 $url = wfAppendQuery( $url, 'fromhttp=1' );
372 $this->getOutput()->redirect( $url );
373 // Since we only do this redir to change proto, always vary
374 $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
375
376 return;
377 } else {
378 // A wiki without HTTPS login support should set $wgServer to
379 // http://somehost, in which case the secure URL generated
380 // above won't actually start with https://
381 if ( str_starts_with( $url, 'https://' ) ) {
382 $this->mSecureLoginUrl = $url;
383 }
384 }
385 }
386
387 if ( !$this->isActionAllowed( $this->authAction ) ) {
388 // FIXME how do we explain this to the user? can we handle session loss better?
389 // messages used: authpage-cannot-login, authpage-cannot-login-continue,
390 // authpage-cannot-create, authpage-cannot-create-continue
391 $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
392 return;
393 }
394
395 if ( $this->canBypassForm( $button_name ) ) {
396 $this->setRequest( [], true );
397 $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
398 if ( $button_name ) {
399 $this->getRequest()->setVal( $button_name, true );
400 }
401 }
402
403 $status = $this->trySubmit();
404
405 if ( !$status || !$status->isGood() ) {
406 $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
407 return;
408 }
409
411 $response = $status->getValue();
412
413 $returnToUrl = $this->getPageTitle( 'return' )
414 ->getFullURL( $this->getPreservedParams( [ 'withToken' => true ] ), false, PROTO_HTTPS );
415 switch ( $response->status ) {
416 case AuthenticationResponse::PASS:
417 $this->logAuthResult( true );
418 $this->proxyAccountCreation = $this->isSignup() && $this->getUser()->isNamed();
419 $this->targetUser = User::newFromName( $response->username );
420
421 if (
422 !$this->proxyAccountCreation
423 && $response->loginRequest
424 && $authManager->canAuthenticateNow()
425 ) {
426 // successful registration; log the user in instantly
427 $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
428 $returnToUrl );
429 if ( $response2->status !== AuthenticationResponse::PASS ) {
430 LoggerFactory::getInstance( 'login' )
431 ->error( 'Could not log in after account creation' );
432 $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
433 break;
434 }
435 }
436
437 if ( !$this->proxyAccountCreation ) {
438 $context = RequestContext::getMain();
439 $localContext = $this->getContext();
440 if ( $context !== $localContext ) {
441 // remove AuthManagerSpecialPage context hack
442 $this->setContext( $context );
443 }
444 // Ensure that the context user is the same as the session user.
445 $this->getAuthManager()->setRequestContextUserFromSessionUser();
446 }
447
448 $this->successfulAction( true );
449 break;
450 case AuthenticationResponse::FAIL:
451 // fall through
452 case AuthenticationResponse::RESTART:
453 unset( $this->authForm );
454 if ( $response->status === AuthenticationResponse::FAIL ) {
455 $action = $this->getDefaultAction( $subPage );
456 $messageType = 'error';
457 } else {
458 $action = $this->getContinueAction( $this->authAction );
459 $messageType = 'warning';
460 }
461 $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
462 $this->loadAuth( $subPage, $action, true );
463 $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
464 break;
465 case AuthenticationResponse::REDIRECT:
466 unset( $this->authForm );
467 $this->getOutput()->redirect( $response->redirectTarget );
468 break;
469 case AuthenticationResponse::UI:
470 unset( $this->authForm );
471 $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
472 : AuthManager::ACTION_LOGIN_CONTINUE;
473 $this->authRequests = $response->neededRequests;
474 $this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
475 break;
476 default:
477 throw new LogicException( 'invalid AuthenticationResponse' );
478 }
479 }
480
494 private function canBypassForm( &$button_name ) {
495 $button_name = null;
496 if ( $this->isContinued() ) {
497 return false;
498 }
499 $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
500 foreach ( $fields as $fieldname => $field ) {
501 if ( !isset( $field['type'] ) ) {
502 return false;
503 }
504 if ( !empty( $field['skippable'] ) ) {
505 continue;
506 }
507 if ( $field['type'] === 'button' ) {
508 if ( $button_name !== null ) {
509 $button_name = null;
510 return false;
511 } else {
512 $button_name = $fieldname;
513 }
514 } elseif ( $field['type'] !== 'null' ) {
515 return false;
516 }
517 }
518 return true;
519 }
520
530 protected function showSuccessPage(
531 $type, $title, $msgname, $injected_html, $extraMessages
532 ) {
533 $out = $this->getOutput();
534 if ( is_string( $title ) ) {
535 wfDeprecated( __METHOD__ . ' with string title', '1.41' ); // T343849
536 $title = ( new RawMessage( '$1' ) )->rawParams( $title );
537 }
538 $out->setPageTitleMsg( $title );
539 if ( $msgname ) {
540 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
541 }
542 if ( $extraMessages ) {
543 $extraMessages = Status::wrap( $extraMessages );
544 $out->addWikiTextAsInterface(
545 $extraMessages->getWikiText( false, false, $this->getLanguage() )
546 );
547 }
548
549 $out->addHTML( $injected_html );
550
551 $helper = new LoginHelper( $this->getContext() );
552 $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery,
553 $this->mStickHTTPS, $this->mReturnToAnchor );
554 }
555
569 protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
570 $user = $this->getUser();
571 $out = $this->getOutput();
572
573 // FIXME how to handle empty $requests - restart, or no form, just an error message?
574 // no form would be better for no session type errors, restart is better when can* fails.
575 if ( !$requests ) {
576 $this->authAction = $this->getDefaultAction( $this->subPage );
577 $this->authForm = null;
578 $requests = MediaWikiServices::getInstance()->getAuthManager()
579 ->getAuthenticationRequests( $this->authAction, $user );
580 }
581
582 // Generic styles and scripts for both login and signup form
583 $out->addModuleStyles( [
584 'mediawiki.special.userlogin.common.styles'
585 ] );
586 if ( $this->isSignup() ) {
587 // Additional styles and scripts for signup form
588 $out->addModules( 'mediawiki.special.createaccount' );
589 $out->addModuleStyles( [
590 'mediawiki.special.userlogin.signup.styles'
591 ] );
592 } else {
593 // Additional styles for login form
594 $out->addModuleStyles( [
595 'mediawiki.special.userlogin.login.styles'
596 ] );
597 }
598 $out->disallowUserJs(); // just in case...
599
600 $form = $this->getAuthForm( $requests, $this->authAction );
601 $form->prepareForm();
602
603 $submitStatus = Status::newGood();
604 if ( $msg && $msgtype === 'warning' ) {
605 $submitStatus->warning( $msg );
606 } elseif ( $msg && $msgtype === 'error' ) {
607 $submitStatus->fatal( $msg );
608 }
609
610 // warning header for non-standard workflows (e.g. security reauthentication)
611 if (
612 !$this->isSignup() &&
613 $this->getUser()->isRegistered() &&
614 !$this->getUser()->isTemp() &&
615 $this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE
616 ) {
617 $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
618 $submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
619 }
620
621 $formHtml = $form->getHTML( $submitStatus );
622
623 $out->addHTML( $this->getPageHtml( $formHtml ) );
624 }
625
632 protected function getPageHtml( $formHtml ) {
633 $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
634 [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
635 $languageLinks = $this->getConfig()->get( MainConfigNames::LoginLanguageSelector )
636 ? $this->makeLanguageSelector() : '';
637 $signupStartMsg = $this->msg( 'signupstart' );
638 $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
639 ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
640 if ( $languageLinks ) {
641 $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
642 Html::rawElement( 'p', [], $languageLinks )
643 );
644 }
645 if ( $this->getUser()->isTemp() ) {
646 $noticeHtml = $this->getNoticeHtml();
647 } else {
648 $noticeHtml = '';
649 }
650 $formBlock = Html::rawElement( 'div', [ 'id' => 'userloginForm' ], $formHtml );
651 $formAndBenefits = $formBlock;
652 if ( $this->isSignup() && $this->showExtraInformation() ) {
653 $benefitsContainerHtml = null;
654 $info = [
655 'context' => $this->getContext(),
656 'form' => $this->authForm,
657 ];
658 $options = [
659 'beforeForm' => false,
660 ];
661 $this->getHookRunner()->onSpecialCreateAccountBenefits(
662 $benefitsContainerHtml, $info, $options
663 );
664 $benefitsContainerHtml ??= $this->getBenefitsContainerHtml();
665 $formAndBenefits = $options['beforeForm']
666 ? ( $benefitsContainerHtml . $formBlock )
667 : ( $formBlock . $benefitsContainerHtml );
668 }
669
670 return $loginPrompt
671 . $languageLinks
672 . $signupStart
673 . $noticeHtml
674 . Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
675 $formAndBenefits
676 );
677 }
678
686 protected function getBenefitsContainerHtml(): string {
687 $benefitsContainer = '';
688 $this->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-user' ] );
689 if ( $this->isSignup() && $this->showExtraInformation() ) {
690 if ( !$this->getUser()->isTemp() ) {
691 // The following messages are used here:
692 // * createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
693 // * createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
694 // * createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
695 $benefitCount = 3;
696 $benefitList = '';
697 for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
698 $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
699 $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->text();
700 $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
701 Html::rawElement( 'span', [],
702 $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
703 )
704 . Html::rawElement( 'p', [],
705 $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
706 )
707 );
708 }
709 $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
710 Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-heading' ],
711 $this->msg( 'createacct-benefit-heading' )->escaped()
712 )
713 . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ], $benefitList )
714 );
715 } else {
716 $benefitList = '';
717 $this->getOutput()->addModuleStyles(
718 [
719 'oojs-ui.styles.icons-moderation',
720 'oojs-ui.styles.icons-interactions',
721 ]
722 );
723 $benefits = [
724 [
725 'icon' => 'oo-ui-icon-unStar',
726 'description' => $this->msg( "benefit-1-description" )->escaped()
727 ],
728 [
729 'icon' => 'oo-ui-icon-userContributions',
730 'description' => $this->msg( "benefit-2-description" )->escaped()
731 ],
732 [
733 'icon' => 'oo-ui-icon-settings',
734 'description' => $this->msg( "benefit-3-description" )->escaped()
735 ]
736 ];
737 foreach ( $benefits as $benefit ) {
738 $benefitContent = Html::rawElement( 'div', [ 'class' => 'mw-benefit-item' ],
739 Html::rawElement( 'span', [ 'class' => $benefit[ 'icon' ] ] )
740 . Html::rawElement( 'p', [], $benefit['description'] )
741 );
742
743 $benefitList .= Html::rawElement(
744 'div', [ 'class' => 'mw-benefit-item-wrapper' ], $benefitContent );
745 }
746
747 $benefitsListWrapper = Html::rawElement(
748 'div', [ 'class' => 'mw-benefit-list-wrapper' ], $benefitList );
749
750 $headingSubheadingWrapper = Html::rawElement( 'div', [ 'class' => 'mw-heading-subheading-wrapper' ],
751 Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading-temp-user' )->escaped() )
752 . Html::rawElement( 'p', [ 'class' => 'mw-benefit-subheading' ], $this->msg(
753 'createacct-benefit-subheading-temp-user' )->escaped() )
754 );
755
756 $benefitsContainer = Html::rawElement(
757 'div', [ 'class' => 'mw-createacct-benefits-container' ],
758 $headingSubheadingWrapper
759 . $benefitsListWrapper
760 );
761 }
762 }
763 return $benefitsContainer;
764 }
765
772 protected function getAuthForm( array $requests, $action ) {
773 // FIXME merge this with parent
774
775 if ( isset( $this->authForm ) ) {
776 return $this->authForm;
777 }
778
779 $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
780
781 // get basic form description from the auth logic
782 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
783 // this will call onAuthChangeFormFields()
784 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
785 $this->postProcessFormDescriptor( $formDescriptor, $requests );
786
787 $context = $this->getContext();
788 if ( $context->getRequest() !== $this->getRequest() ) {
789 // We have overridden the request, need to make sure the form uses that too.
790 $context = new DerivativeContext( $this->getContext() );
791 $context->setRequest( $this->getRequest() );
792 }
793 $form = HTMLForm::factory( 'codex', $formDescriptor, $context );
794
795 $form->addHiddenField( 'authAction', $this->authAction );
796 $form->addHiddenField( 'force', $this->securityLevel );
797 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
798 $config = $this->getConfig();
799 if ( $config->get( MainConfigNames::SecureLogin ) &&
800 !$config->get( MainConfigNames::ForceHTTPS ) ) {
801 // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
802 if ( !$this->isSignup() ) {
803 $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
804 $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
805 }
806 }
807
808 $form->setAction( $this->getPageTitle()->getLocalURL( $this->getPreservedParams(
809 // We have manually set authAction above, so we don't need it in the action URL.
810 [ 'reset' => true ]
811 ) ) );
812 $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
813 if ( $this->isSignup() ) {
814 $form->setId( 'userlogin2' );
815 }
816
817 $form->suppressDefaultSubmit();
818
819 $this->authForm = $form;
820
821 return $form;
822 }
823
825 public function onAuthChangeFormFields(
826 array $requests, array $fieldInfo, array &$formDescriptor, $action
827 ) {
828 $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor,
829 $this->getFieldDefinitions( $fieldInfo, $requests ) );
830 }
831
838 protected function showExtraInformation() {
839 return $this->authAction !== $this->getContinueAction( $this->authAction )
840 && !$this->securityLevel;
841 }
842
851 protected function getFieldDefinitions( array $fieldInfo, array $requests ) {
852 $isLoggedIn = $this->getUser()->isRegistered();
853 $continuePart = $this->isContinued() ? 'continue-' : '';
854 $anotherPart = $isLoggedIn ? 'another-' : '';
855 // @phan-suppress-next-line PhanUndeclaredMethod
856 $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
857 $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
858 $secureLoginLink = '';
859 if ( $this->mSecureLoginUrl ) {
860 $secureLoginLink = Html::rawElement( 'a', [
861 'href' => $this->mSecureLoginUrl,
862 'class' => 'mw-login-flush-right mw-secure',
863 ], Html::element( 'span', [ 'class' => 'mw-secure--icon' ] ) .
864 $this->msg( 'userlogin-signwithsecure' )->parse() );
865 }
866 $usernameHelpLink = '';
867 if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) {
868 $usernameHelpLink = Html::rawElement( 'span', [
869 'class' => 'mw-login-flush-right',
870 ], $this->msg( 'createacct-helpusername' )->parse() );
871 }
872
873 if ( $this->isSignup() ) {
874 $config = $this->getConfig();
875 $hideIf = isset( $fieldInfo['mailpassword'] ) ? [ 'hide-if' => [ '===', 'mailpassword', '1' ] ] : [];
876 $fieldDefinitions = [
877 'username' => [
878 'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
879 'id' => 'wpName2',
880 'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
881 : 'userlogin-yourname-ph',
882 ],
883 'mailpassword' => [
884 // create account without providing password, a temporary one will be mailed
885 'type' => 'check',
886 'label-message' => 'createaccountmail',
887 'name' => 'wpCreateaccountMail',
888 'id' => 'wpCreateaccountMail',
889 ],
890 'password' => [
891 'id' => 'wpPassword2',
892 'autocomplete' => 'new-password',
893 'placeholder-message' => 'createacct-yourpassword-ph',
894 'help-message' => 'createacct-useuniquepass',
895 ] + $hideIf,
896 'domain' => [],
897 'retype' => [
898 'type' => 'password',
899 'label-message' => 'createacct-yourpasswordagain',
900 'id' => 'wpRetype',
901 'cssclass' => 'loginPassword',
902 'size' => 20,
903 'autocomplete' => 'new-password',
904 'validation-callback' => function ( $value, $alldata ) {
905 if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
906 if ( !$value ) {
907 return $this->msg( 'htmlform-required' );
908 } elseif ( $value !== $alldata['password'] ) {
909 return $this->msg( 'badretype' );
910 }
911 }
912 return true;
913 },
914 'placeholder-message' => 'createacct-yourpasswordagain-ph',
915 ] + $hideIf,
916 'email' => [
917 'type' => 'email',
918 'label-message' => $config->get( MainConfigNames::EmailConfirmToEdit )
919 ? 'createacct-emailrequired' : 'createacct-emailoptional',
920 'id' => 'wpEmail',
921 'cssclass' => 'loginText',
922 'size' => '20',
923 'maxlength' => 255,
924 'autocomplete' => 'email',
925 // FIXME will break non-standard providers
926 'required' => $config->get( MainConfigNames::EmailConfirmToEdit ),
927 'validation-callback' => function ( $value, $alldata ) {
928 // AuthManager will check most of these, but that will make the auth
929 // session fail and this won't, so nicer to do it this way
930 if ( !$value &&
931 $this->getConfig()->get( MainConfigNames::EmailConfirmToEdit )
932 ) {
933 // no point in allowing registration without email when email is
934 // required to edit
935 return $this->msg( 'noemailtitle' );
936 } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
937 // cannot send password via email when there is no email address
938 return $this->msg( 'noemailcreate' );
939 } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
940 return $this->msg( 'invalidemailaddress' );
941 } elseif ( is_string( $value ) && strlen( $value ) > 255 ) {
942 return $this->msg( 'changeemail-maxlength' );
943 }
944 return true;
945 },
946 // The following messages are used here:
947 // * createacct-email-ph
948 // * createacct-another-email-ph
949 'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
950 ],
951 'realname' => [
952 'type' => 'text',
953 'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
954 : 'prefs-help-realname',
955 'label-message' => 'createacct-realname',
956 'cssclass' => 'loginText',
957 'size' => 20,
958 'placeholder-message' => 'createacct-realname',
959 'id' => 'wpRealName',
960 'autocomplete' => 'name',
961 ],
962 'reason' => [
963 // comment for the user creation log
964 'type' => 'text',
965 'label-message' => 'createacct-reason',
966 'cssclass' => 'loginText',
967 'id' => 'wpReason',
968 'size' => '20',
969 'validation-callback' => function ( $value, $alldata ) {
970 // if the user sets an email address as the user creation reason, confirm that
971 // that was their intent
972 if ( $value && Sanitizer::validateEmail( $value ) ) {
973 if ( $this->reasonValidatorResult !== null ) {
974 return $this->reasonValidatorResult;
975 }
976 $this->reasonValidatorResult = true;
977 $authManager = MediaWikiServices::getInstance()->getAuthManager();
978 if ( !$authManager->getAuthenticationSessionData( 'reason-retry', false ) ) {
979 $authManager->setAuthenticationSessionData( 'reason-retry', true );
980 $this->reasonValidatorResult = $this->msg( 'createacct-reason-confirm' );
981 }
982 return $this->reasonValidatorResult;
983 }
984 return true;
985 },
986 'placeholder-message' => 'createacct-reason-ph',
987 ],
988 'createaccount' => [
989 // submit button
990 'type' => 'submit',
991 // The following messages are used here:
992 // * createacct-submit
993 // * createacct-another-submit
994 // * createacct-continue-submit
995 // * createacct-another-continue-submit
996 'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
997 'submit' )->text(),
998 'name' => 'wpCreateaccount',
999 'id' => 'wpCreateaccount',
1000 'weight' => 100,
1001 ],
1002 ];
1003 if ( !$this->msg( 'createacct-username-help' )->isDisabled() ) {
1004 $fieldDefinitions['username']['help-message'] = 'createacct-username-help';
1005 }
1006 } else {
1007 // When the user's password is too weak, they might be asked to provide a stronger one
1008 // as a followup step. That is a form with only two fields, 'password' and 'retype',
1009 // and they should behave more like account creation.
1010 $passwordRequest = AuthenticationRequest::getRequestByClass( $this->authRequests,
1011 PasswordAuthenticationRequest::class );
1012 $changePassword = $passwordRequest && $passwordRequest->action == AuthManager::ACTION_CHANGE;
1013 $fieldDefinitions = [
1014 'username' => (
1015 [
1016 'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
1017 'id' => 'wpName1',
1018 'placeholder-message' => 'userlogin-yourname-ph',
1019 ] + ( $changePassword ? [
1020 // There is no username field on the AuthManager level when changing
1021 // passwords. Fake one because password
1022 'baseField' => 'password',
1023 'nodata' => true,
1024 'readonly' => true,
1025 'cssclass' => 'mw-htmlform-hidden-field',
1026 ] : [] )
1027 ),
1028 'password' => (
1029 $changePassword ? [
1030 'autocomplete' => 'new-password',
1031 'placeholder-message' => 'createacct-yourpassword-ph',
1032 'help-message' => 'createacct-useuniquepass',
1033 ] : [
1034 'id' => 'wpPassword1',
1035 'autocomplete' => 'current-password',
1036 'placeholder-message' => 'userlogin-yourpassword-ph',
1037 ]
1038 ),
1039 'retype' => [
1040 'type' => 'password',
1041 'autocomplete' => 'new-password',
1042 'placeholder-message' => 'createacct-yourpasswordagain-ph',
1043 ],
1044 'domain' => [],
1045 'rememberMe' => [
1046 // option for saving the user token to a cookie
1047 'type' => 'check',
1048 'cssclass' => 'mw-userlogin-rememberme',
1049 'name' => 'wpRemember',
1050 'label-message' => $this->msg( 'userlogin-remembermypassword' )
1051 ->numParams( $expirationDays ),
1052 'id' => 'wpRemember',
1053 ],
1054 'loginattempt' => [
1055 // submit button
1056 'type' => 'submit',
1057 // The following messages are used here:
1058 // * pt-login-button
1059 // * pt-login-continue-button
1060 'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
1061 'id' => 'wpLoginAttempt',
1062 'weight' => 100,
1063 ],
1064 'linkcontainer' => [
1065 // help link
1066 'type' => 'info',
1067 'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1068 // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1069 'raw' => true,
1070 'default' => Html::element( 'a', [
1071 'href' => Skin::makeInternalOrExternalUrl( $this->msg( 'helplogin-url' )
1072 ->inContentLanguage()
1073 ->text() ),
1074 ], $this->msg( 'userlogin-helplink2' )->text() ),
1075 'weight' => 200,
1076 ],
1077 // button for ResetPasswordSecondaryAuthenticationProvider
1078 'skipReset' => [
1079 'weight' => 110,
1080 'flags' => [],
1081 ],
1082 ];
1083 }
1084
1085 // T369641: We want to ensure that this transformation to the username and/or
1086 // password fields are applied only when we have matching requests within the
1087 // authentication manager.
1088 $isUsernameOrPasswordRequest =
1089 AuthenticationRequest::getRequestByClass( $requests, UsernameAuthenticationRequest::class ) ||
1090 AuthenticationRequest::getRequestByClass( $requests, PasswordAuthenticationRequest::class );
1091
1092 if ( $isUsernameOrPasswordRequest ) {
1093 $fieldDefinitions['username'] += [
1094 'type' => 'text',
1095 'name' => 'wpName',
1096 'cssclass' => 'loginText mw-userlogin-username',
1097 'size' => 20,
1098 'autocomplete' => 'username',
1099 // 'required' => true,
1100 ];
1101 $fieldDefinitions['password'] += [
1102 'type' => 'password',
1103 // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1104 'name' => 'wpPassword',
1105 'cssclass' => 'loginPassword mw-userlogin-password',
1106 'size' => 20,
1107 // 'required' => true,
1108 ];
1109 }
1110
1111 if ( $this->mEntryError ) {
1112 $defaultHtml = '';
1113 if ( $this->mEntryErrorType === 'error' ) {
1114 $defaultHtml = Html::errorBox( $this->mEntryError );
1115 } elseif ( $this->mEntryErrorType === 'warning' ) {
1116 $defaultHtml = Html::warningBox( $this->mEntryError );
1117 } elseif ( $this->mEntryErrorType === 'notice' ) {
1118 $defaultHtml = Html::noticeBox( $this->mEntryError, '' );
1119 }
1120 $fieldDefinitions['entryError'] = [
1121 'type' => 'info',
1122 'default' => $defaultHtml,
1123 'raw' => true,
1124 'rawrow' => true,
1125 'weight' => -100,
1126 ];
1127 }
1128 if ( !$this->showExtraInformation() ) {
1129 unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
1130 }
1131 if ( $this->isSignup() && $this->showExtraInformation() ) {
1132 // blank signup footer for site customization
1133 // uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
1134 $signupendMsg = $this->msg( 'signupend' );
1135 $signupendHttpsMsg = $this->msg( 'signupend-https' );
1136 if ( !$signupendMsg->isDisabled() ) {
1137 $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
1138 $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
1139 ? $signupendHttpsMsg->parse() : $signupendMsg->parse();
1140 $fieldDefinitions['signupend'] = [
1141 'type' => 'info',
1142 'raw' => true,
1143 'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ),
1144 'weight' => 225,
1145 ];
1146 }
1147 }
1148 if ( !$this->isSignup() && $this->showExtraInformation() ) {
1149 $passwordReset = MediaWikiServices::getInstance()->getPasswordReset();
1150 if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
1151 $fieldDefinitions['passwordReset'] = [
1152 'type' => 'info',
1153 'raw' => true,
1154 'cssclass' => 'mw-form-related-link-container',
1155 'default' => $this->getLinkRenderer()->makeLink(
1156 SpecialPage::getTitleFor( 'PasswordReset' ),
1157 $this->msg( 'userlogin-resetpassword-link' )->text()
1158 ),
1159 'weight' => 230,
1160 ];
1161 }
1162
1163 // Don't show a "create account" link if the user can't.
1164 if ( $this->showCreateAccountLink() ) {
1165 // link to the other action
1166 $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' : 'CreateAccount' );
1167 $linkq = wfArrayToCgi( $this->getPreservedParams( [ 'reset' => true ] ) );
1168 $isLoggedIn = $this->getUser()->isRegistered()
1169 && !$this->getUser()->isTemp();
1170
1171 $fieldDefinitions['createOrLogin'] = [
1172 'type' => 'info',
1173 'raw' => true,
1174 'linkQuery' => $linkq,
1175 'default' => function ( $params ) use ( $isLoggedIn, $linkTitle ) {
1176 $buttonClasses = 'cdx-button cdx-button--action-progressive '
1177 . 'cdx-button--fake-button cdx-button--fake-button--enabled';
1178
1179 return Html::rawElement( 'div',
1180 // The following element IDs are used here:
1181 // mw-createaccount, mw-createaccount-cta
1182 [ 'id' => 'mw-createaccount' . ( !$isLoggedIn ? '-cta' : '' ),
1183 'class' => ( $isLoggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
1184 ( $isLoggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
1185 . Html::element( 'a',
1186 [
1187 // The following element IDs are used here:
1188 // mw-createaccount-join, mw-createaccount-join-loggedin
1189 'id' => 'mw-createaccount-join' . ( $isLoggedIn ? '-loggedin' : '' ),
1190 'href' => $linkTitle->getLocalURL( $params['linkQuery'] ),
1191 'class' => [ 'mw-authentication-popup-link', $buttonClasses => !$isLoggedIn ],
1192 'target' => '_self',
1193 'tabindex' => 100,
1194 ],
1195 $this->msg(
1196 $isLoggedIn ? 'userlogin-createanother' : 'userlogin-joinproject'
1197 )->text()
1198 )
1199 );
1200 },
1201 'weight' => 235,
1202 ];
1203 }
1204 }
1205
1206 return $fieldDefinitions;
1207 }
1208
1218 protected function hasSessionCookie() {
1219 global $wgInitialSessionId;
1220
1221 return $wgInitialSessionId &&
1222 $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId;
1223 }
1224
1230 private function showCreateAccountLink() {
1231 return $this->isSignup() ||
1232 $this->getContext()->getAuthority()->isAllowed( 'createaccount' );
1233 }
1234
1235 protected function getTokenName() {
1236 return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1237 }
1238
1245 protected function makeLanguageSelector() {
1246 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1247 if ( $msg->isBlank() ) {
1248 return '';
1249 }
1250 $langs = explode( "\n", $msg->text() );
1251 $links = [];
1252 foreach ( $langs as $lang ) {
1253 $lang = trim( $lang, '* ' );
1254 $parts = explode( '|', $lang );
1255 if ( count( $parts ) >= 2 ) {
1256 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1257 }
1258 }
1259
1260 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1261 $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1262 }
1263
1272 protected function makeLanguageSelectorLink( $text, $lang ) {
1273 $services = MediaWikiServices::getInstance();
1274
1275 if ( $this->getLanguage()->getCode() == $lang
1276 || !$services->getLanguageNameUtils()->isValidCode( $lang )
1277 ) {
1278 // no link for currently used language
1279 // or invalid language code
1280 return htmlspecialchars( $text );
1281 }
1282
1283 $query = $this->getPreservedParams();
1284 $query['uselang'] = $lang;
1285
1286 $attr = [];
1287 $targetLanguage = $services->getLanguageFactory()->getLanguage( $lang );
1288 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1289 $attr['class'] = 'mw-authentication-popup-link';
1290
1291 return $this->getLinkRenderer()->makeKnownLink(
1292 $this->getPageTitle(),
1293 $text,
1294 $attr,
1295 $query
1296 );
1297 }
1298
1299 protected function getGroupName() {
1300 return 'login';
1301 }
1302
1307 protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1308 // Pre-fill username (if not creating an account, T46775).
1309 if (
1310 isset( $formDescriptor['username'] ) &&
1311 !isset( $formDescriptor['username']['default'] ) &&
1312 !$this->isSignup()
1313 ) {
1314 $user = $this->getUser();
1315 if ( $user->isRegistered() && !$user->isTemp() ) {
1316 $formDescriptor['username']['default'] = $user->getName();
1317 } else {
1318 $formDescriptor['username']['default'] =
1319 $this->getRequest()->getSession()->suggestLoginUsername();
1320 }
1321 }
1322
1323 // don't show a submit button if there is nothing to submit (i.e. the only form content
1324 // is other submit buttons, for redirect flows)
1325 if ( !$this->needsSubmitButton( $requests ) ) {
1326 unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1327 }
1328
1329 if ( !$this->isSignup() ) {
1330 // FIXME HACK don't focus on non-empty field
1331 // maybe there should be an autofocus-if similar to hide-if?
1332 if (
1333 isset( $formDescriptor['username'] )
1334 && empty( $formDescriptor['username']['default'] )
1335 && !$this->getRequest()->getCheck( 'wpName' )
1336 ) {
1337 $formDescriptor['username']['autofocus'] = true;
1338 } elseif ( isset( $formDescriptor['password'] ) ) {
1339 $formDescriptor['password']['autofocus'] = true;
1340 }
1341 }
1342
1343 $this->addTabIndex( $formDescriptor );
1344 }
1345
1351 protected function getNoticeHtml() {
1352 $noticeContent = $this->msg( 'createacct-temp-warning', $this->getUser()->getName() )->parse();
1353 return Html::noticeBox(
1354 $noticeContent,
1355 'mw-createaccount-temp-warning',
1356 '',
1357 'mw-userLogin-icon--user-temporary'
1358 );
1359 }
1360
1361}
1362
1364class_alias( LoginSignupSpecialPage::class, 'LoginSignupSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:205
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
global $wgInitialSessionId
Definition Setup.php:420
array $params
The job parameters.
An error page which can definitely be safely rendered using the OutputPage.
Abort the web request with a custom HTML string that will represent the entire response.
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.
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.
This is a value object for authentication requests with a username and password.
AuthenticationRequest to ensure something with a username is present.
An IContextSource implementation which will inherit context from another source but allow individual ...
Group all the pieces relevant to the context of a request into one instance.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Variant of the Message class.
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const LoginLanguageSelector
Name constant for the LoginLanguageSelector setting, for use with Config::get()
const ForceHTTPS
Name constant for the ForceHTTPS setting, for use with Config::get()
const EmailConfirmToEdit
Name constant for the EmailConfirmToEdit setting, for use with Config::get()
const SecureLogin
Name constant for the SecureLogin setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:156
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
This serves as the entry point to the MediaWiki session handling system.
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
isContinued()
Returns true if this is not the first step of the authentication.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
trySubmit()
Attempts to do an authentication step with the submitted data.
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.
getRequest()
Get the WebRequest being used for this instance.
Holds shared logic for login and account creation pages.
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
logAuthResult( $success, $status=null)
Logs to the authmanager-stats channel.
makeLanguageSelectorLink( $text, $lang)
Create a language selector link for a particular language Links back to this page preserving type and...
string string $mReturnToAnchor
The fragment part of the URL to return to after authentication finishes.
showSuccessPage( $type, $title, $msgname, $injected_html, $extraMessages)
Show the success page.
getAuthForm(array $requests, $action)
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).
getBenefitsContainerHtml()
The HTML to be shown in the "benefits to signing in / creating an account" section of the signup/logi...
mainLoginForm(array $requests, $msg='', $msgtype='error')
getPreservedParams( $options=[])
Returns URL query parameters which should be preserved between authentication requests....
bool $proxyAccountCreation
True if the user if creating an account for someone else.
showExtraInformation()
Show extra information such as password recovery information, link from login to signup,...
string string $mReturnToQuery
The query string part of the URL to return to after authentication finishes.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form....
makeLanguageSelector()
Produce a bar of links which allow the user to select another language during login/registration but ...
successfulAction( $direct=false, $extraMessages=null)
string string $mReturnTo
The title of the page to return to after authentication finishes, or the empty string when there is n...
getNoticeHtml()
Generates the HTML for a notice box to be displayed to a temporary user.
hasSessionCookie()
Check if a session cookie is present.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getFieldDefinitions(array $fieldInfo, array $requests)
Create a HTMLForm descriptor for the core login fields.
getPageHtml( $formHtml)
Add page elements which are outside the form.
User $targetUser
FIXME another flag for passing data.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getUser()
Shortcut to get the User executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
setContext( $context)
Sets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getName()
Get the canonical, unlocalized name of this special page without namespace.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
internal since 1.36
Definition User.php:94
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
The base class for all skins.
Definition Skin.php:64
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition Skin.php:1195
Generic operation result class Has warning/error list, boolean status and arbitrary value.
element(SerializerNode $parent, SerializerNode $node, $contents)