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