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