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