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