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