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