MediaWiki  1.27.2
LoginSignupSpecialPage.php
Go to the documentation of this file.
1 <?php
31 
38  protected $mReturnTo;
39  protected $mPosted;
40  protected $mAction;
41  protected $mLanguage;
42  protected $mReturnToQuery;
43  protected $mToken;
44  protected $mStickHTTPS;
45  protected $mFromHTTP;
46  protected $mEntryError = '';
47  protected $mEntryErrorType = 'error';
48 
49  protected $mLoaded = false;
50  protected $mLoadedRequest = false;
51  protected $mSecureLoginUrl;
52 
54  protected $securityLevel;
55 
60  protected $targetUser;
61 
63  protected $authForm;
64 
66  protected $fakeTemplate;
67 
68  abstract protected function isSignup();
69 
76  abstract protected function successfulAction( $direct = false, $extraMessages = null );
77 
83  abstract protected function logAuthResult( $success, $status = null );
84 
85  public function __construct( $name ) {
87  parent::__construct( $name );
88 
89  // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
90  $wgUseMediaWikiUIEverywhere = true;
91  }
92 
93  protected function setRequest( array $data, $wasPosted = null ) {
94  parent::setRequest( $data, $wasPosted );
95  $this->mLoadedRequest = false;
96  }
97 
102  private function loadRequestParameters( $subPage ) {
103  if ( $this->mLoadedRequest ) {
104  return;
105  }
106  $this->mLoadedRequest = true;
107  $request = $this->getRequest();
108 
109  $this->mPosted = $request->wasPosted();
110  $this->mIsReturn = $subPage === 'return';
111  $this->mAction = $request->getVal( 'action' );
112  $this->mFromHTTP = $request->getBool( 'fromhttp', false )
113  || $request->getBool( 'wpFromhttp', false );
114  $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
115  || $request->getBool( 'wpForceHttps', false );
116  $this->mLanguage = $request->getText( 'uselang' );
117  $this->mReturnTo = $request->getVal( 'returnto', '' );
118  $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
119  }
120 
126  protected function load( $subPage ) {
127  global $wgSecureLogin;
128 
130  if ( $this->mLoaded ) {
131  return;
132  }
133  $this->mLoaded = true;
134  $request = $this->getRequest();
135 
136  $securityLevel = $this->getRequest()->getText( 'force' );
137  if (
138  $securityLevel && AuthManager::singleton()->securitySensitiveOperationStatus(
139  $securityLevel ) === AuthManager::SEC_REAUTH
140  ) {
141  $this->securityLevel = $securityLevel;
142  }
143 
144  $this->loadAuth( $subPage );
145 
146  $this->mToken = $request->getVal( $this->getTokenName() );
147 
148  // Show an error or warning passed on from a previous page
149  $entryError = $this->msg( $request->getVal( 'error', '' ) );
150  $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
151  // bc: provide login link as a parameter for messages where the translation
152  // was not updated
153  $loginreqlink = Linker::linkKnown(
154  $this->getPageTitle(),
155  $this->msg( 'loginreqlink' )->escaped(),
156  [],
157  [
158  'returnto' => $this->mReturnTo,
159  'returntoquery' => $this->mReturnToQuery,
160  'uselang' => $this->mLanguage,
161  'fromhttp' => $wgSecureLogin && $this->mFromHTTP ? '1' : null,
162  ]
163  );
164 
165  // Only show valid error or warning messages.
166  if ( $entryError->exists()
167  && in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true )
168  ) {
169  $this->mEntryErrorType = 'error';
170  $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
171 
172  } elseif ( $entryWarning->exists()
173  && in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true )
174  ) {
175  $this->mEntryErrorType = 'warning';
176  $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
177  }
178 
179  # 1. When switching accounts, it sucks to get automatically logged out
180  # 2. Do not return to PasswordReset after a successful password change
181  # but goto Wiki start page (Main_Page) instead ( bug 33997 )
182  $returnToTitle = Title::newFromText( $this->mReturnTo );
183  if ( is_object( $returnToTitle )
184  && ( $returnToTitle->isSpecial( 'Userlogout' )
185  || $returnToTitle->isSpecial( 'PasswordReset' ) )
186  ) {
187  $this->mReturnTo = '';
188  $this->mReturnToQuery = '';
189  }
190  }
191 
192  protected function getPreservedParams( $withToken = false ) {
193  global $wgSecureLogin;
194 
195  $params = parent::getPreservedParams( $withToken );
196  $params += [
197  'returnto' => $this->mReturnTo ?: null,
198  'returntoquery' => $this->mReturnToQuery ?: null,
199  ];
200  if ( $wgSecureLogin && !$this->isSignup() ) {
201  $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
202  }
203  return $params;
204  }
205 
206  protected function beforeExecute( $subPage ) {
207  // finish initializing the class before processing the request - T135924
209  return parent::beforeExecute( $subPage );
210  }
211 
215  public function execute( $subPage ) {
216  $authManager = AuthManager::singleton();
217  $session = SessionManager::getGlobalSession();
218 
219  // Session data is used for various things in the authentication process, so we must make
220  // sure a session cookie or some equivalent mechanism is set.
221  $session->persist();
222 
223  $this->load( $subPage );
224  $this->setHeaders();
225  $this->checkPermissions();
226 
227  // Make sure it's possible to log in
228  if ( !$this->isSignup() && !$session->canSetUser() ) {
229  throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
230  $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
231  ] );
232  }
233 
234  /*
235  * In the case where the user is already logged in, and was redirected to
236  * the login form from a page that requires login, do not show the login
237  * page. The use case scenario for this is when a user opens a large number
238  * of tabs, is redirected to the login page on all of them, and then logs
239  * in on one, expecting all the others to work properly.
240  *
241  * However, do show the form if it was visited intentionally (no 'returnto'
242  * is present). People who often switch between several accounts have grown
243  * accustomed to this behavior.
244  *
245  * Also make an exception when force=<level> is set in the URL, which means the user must
246  * reauthenticate for security reasons.
247  */
248  if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
249  ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
250  $this->getUser()->isLoggedIn()
251  ) {
252  $this->successfulAction();
253  }
254 
255  // If logging in and not on HTTPS, either redirect to it or offer a link.
256  global $wgSecureLogin;
257  if ( $this->getRequest()->getProtocol() !== 'https' ) {
258  $title = $this->getFullTitle();
259  $query = $this->getPreservedParams( false ) + [
260  'title' => null,
261  ( $this->mEntryErrorType === 'error' ? 'error'
262  : 'warning' ) => $this->mEntryError,
263  ] + $this->getRequest()->getQueryValues();
264  $url = $title->getFullURL( $query, false, PROTO_HTTPS );
265  if ( $wgSecureLogin && !$this->mFromHTTP &&
266  wfCanIPUseHTTPS( $this->getRequest()->getIP() )
267  ) {
268  // Avoid infinite redirect
269  $url = wfAppendQuery( $url, 'fromhttp=1' );
270  $this->getOutput()->redirect( $url );
271  // Since we only do this redir to change proto, always vary
272  $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
273 
274  return;
275  } else {
276  // A wiki without HTTPS login support should set $wgServer to
277  // http://somehost, in which case the secure URL generated
278  // above won't actually start with https://
279  if ( substr( $url, 0, 8 ) === 'https://' ) {
280  $this->mSecureLoginUrl = $url;
281  }
282  }
283  }
284 
285  if ( !$this->isActionAllowed( $this->authAction ) ) {
286  // FIXME how do we explain this to the user? can we handle session loss better?
287  // messages used: authpage-cannot-login, authpage-cannot-login-continue,
288  // authpage-cannot-create, authpage-cannot-create-continue
289  $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
290  return;
291  }
292 
293  if ( $this->canBypassForm( $button_name ) ) {
294  $this->setRequest( [], true );
295  $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
296  if ( $button_name ) {
297  $this->getRequest()->setVal( $button_name, true );
298  }
299  }
300 
301  $status = $this->trySubmit();
302 
303  if ( !$status || !$status->isGood() ) {
304  $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
305  return;
306  }
307 
309  $response = $status->getValue();
310 
311  $returnToUrl = $this->getPageTitle( 'return' )
312  ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
313  switch ( $response->status ) {
314  case AuthenticationResponse::PASS:
315  $this->logAuthResult( true );
316  $this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
317  $this->targetUser = User::newFromName( $response->username );
318 
319  if (
320  !$this->proxyAccountCreation
321  && $response->loginRequest
322  && $authManager->canAuthenticateNow()
323  ) {
324  // successful registration; log the user in instantly
325  $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
326  $returnToUrl );
327  if ( $response2->status !== AuthenticationResponse::PASS ) {
328  LoggerFactory::getInstance( 'login' )
329  ->error( 'Could not log in after account creation' );
330  $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
331  break;
332  }
333  }
334 
335  if ( !$this->proxyAccountCreation ) {
336  // Ensure that the context user is the same as the session user.
338  }
339 
340  $this->successfulAction( true );
341  break;
342  case AuthenticationResponse::FAIL:
343  // fall through
344  case AuthenticationResponse::RESTART:
345  unset( $this->authForm );
346  if ( $response->status === AuthenticationResponse::FAIL ) {
347  $action = $this->getDefaultAction( $subPage );
348  $messageType = 'error';
349  } else {
350  $action = $this->getContinueAction( $this->authAction );
351  $messageType = 'warning';
352  }
353  $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
354  $this->loadAuth( $subPage, $action, true );
355  $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
356  break;
357  case AuthenticationResponse::REDIRECT:
358  unset( $this->authForm );
359  $this->getOutput()->redirect( $response->redirectTarget );
360  break;
361  case AuthenticationResponse::UI:
362  unset( $this->authForm );
363  $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
364  : AuthManager::ACTION_LOGIN_CONTINUE;
365  $this->authRequests = $response->neededRequests;
366  $this->mainLoginForm( $response->neededRequests, $response->message, 'warning' );
367  break;
368  default:
369  throw new LogicException( 'invalid AuthenticationResponse' );
370  }
371  }
372 
386  private function canBypassForm( &$button_name ) {
387  $button_name = null;
388  if ( $this->isContinued() ) {
389  return false;
390  }
391  $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
392  foreach ( $fields as $fieldname => $field ) {
393  if ( !isset( $field['type'] ) ) {
394  return false;
395  }
396  if ( !empty( $field['skippable'] ) ) {
397  continue;
398  }
399  if ( $field['type'] === 'button' ) {
400  if ( $button_name !== null ) {
401  $button_name = null;
402  return false;
403  } else {
404  $button_name = $fieldname;
405  }
406  } elseif ( $field['type'] !== 'null' ) {
407  return false;
408  }
409  }
410  return true;
411  }
412 
422  protected function showSuccessPage(
423  $type, $title, $msgname, $injected_html, $extraMessages
424  ) {
425  $out = $this->getOutput();
426  $out->setPageTitle( $title );
427  if ( $msgname ) {
428  $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
429  }
430  if ( $extraMessages ) {
431  $extraMessages = Status::wrap( $extraMessages );
432  $out->addWikiText( $extraMessages->getWikiText() );
433  }
434 
435  $out->addHTML( $injected_html );
436 
437  $helper = new LoginHelper( $this->getContext() );
438  $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
439  }
440 
456  public function showReturnToPage(
457  $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
458  ) {
459  $helper = new LoginHelper( $this->getContext() );
460  $helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
461  }
462 
468  protected function setSessionUserForCurrentRequest() {
470 
472  $localContext = $this->getContext();
473  if ( $context !== $localContext ) {
474  // remove AuthManagerSpecialPage context hack
475  $this->setContext( $context );
476  }
477 
478  $user = $context->getRequest()->getSession()->getUser();
479 
480  $wgUser = $user;
481  $context->setUser( $user );
482 
483  $code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) );
484  $userLang = Language::factory( $code );
485  $wgLang = $userLang;
486  $context->setLanguage( $userLang );
487  }
488 
503  protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
504  $titleObj = $this->getPageTitle();
505  $user = $this->getUser();
506  $out = $this->getOutput();
507 
508  // FIXME how to handle empty $requests - restart, or no form, just an error message?
509  // no form would be better for no session type errors, restart is better when can* fails.
510  if ( !$requests ) {
511  $this->authAction = $this->getDefaultAction( $this->subPage );
512  $this->authForm = null;
513  $requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user );
514  }
515 
516  // Generic styles and scripts for both login and signup form
517  $out->addModuleStyles( [
518  'mediawiki.ui',
519  'mediawiki.ui.button',
520  'mediawiki.ui.checkbox',
521  'mediawiki.ui.input',
522  'mediawiki.special.userlogin.common.styles'
523  ] );
524  if ( $this->isSignup() ) {
525  // XXX hack pending RL or JS parse() support for complex content messages T27349
526  $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
527  $this->msg( 'createacct-imgcaptcha-help' )->parse() );
528 
529  // Additional styles and scripts for signup form
530  $out->addModules( [
531  'mediawiki.special.userlogin.signup.js'
532  ] );
533  $out->addModuleStyles( [
534  'mediawiki.special.userlogin.signup.styles'
535  ] );
536  } else {
537  // Additional styles for login form
538  $out->addModuleStyles( [
539  'mediawiki.special.userlogin.login.styles'
540  ] );
541  }
542  $out->disallowUserJs(); // just in case...
543 
544  $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
545  $form->prepareForm();
546  $formHtml = $form->getHTML( $msg ? Status::newFatal( $msg ) : false );
547 
548  $out->addHTML( $this->getPageHtml( $formHtml ) );
549  }
550 
557  protected function getPageHtml( $formHtml ) {
559 
560  $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
561  [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
562  $languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : '';
563  $signupStartMsg = $this->msg( 'signupstart' );
564  $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
565  ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
566  if ( $languageLinks ) {
567  $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
568  Html::rawElement( 'p', [], $languageLinks )
569  );
570  }
571 
572  $benefitsContainer = '';
573  if ( $this->isSignup() && $this->showExtraInformation() ) {
574  // messages used:
575  // createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
576  // createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
577  // createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
578  $benefitCount = 3;
579  $benefitList = '';
580  for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
581  $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
582  $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped();
583  $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
584  Html::rawElement( 'h3', [],
585  $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
586  )
587  . Html::rawElement( 'p', [],
588  $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
589  )
590  );
591  }
592  $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
593  Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
594  . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ],
595  $benefitList
596  )
597  );
598  }
599 
600  $html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
601  $loginPrompt
602  . $languageLinks
603  . $signupStart
604  . Html::rawElement( 'div', [ 'id' => 'userloginForm' ],
605  $formHtml
606  )
607  . $benefitsContainer
608  );
609 
610  return $html;
611  }
612 
621  protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
622  global $wgSecureLogin, $wgLoginLanguageSelector;
623  // FIXME merge this with parent
624 
625  if ( isset( $this->authForm ) ) {
626  return $this->authForm;
627  }
628 
629  $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
630 
631  // get basic form description from the auth logic
632  $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
633  $fakeTemplate = $this->getFakeTemplate( $msg, $msgType );
634  $this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
635  // this will call onAuthChangeFormFields()
636  $formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
637  $this->postProcessFormDescriptor( $formDescriptor, $requests );
638 
639  $context = $this->getContext();
640  if ( $context->getRequest() !== $this->getRequest() ) {
641  // We have overridden the request, need to make sure the form uses that too.
642  $context = new DerivativeContext( $this->getContext() );
643  $context->setRequest( $this->getRequest() );
644  }
645  $form = HTMLForm::factory( 'vform', $formDescriptor, $context );
646 
647  $form->addHiddenField( 'authAction', $this->authAction );
648  if ( $wgLoginLanguageSelector ) {
649  $form->addHiddenField( 'uselang', $this->mLanguage );
650  }
651  $form->addHiddenField( 'force', $this->securityLevel );
652  $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
653  if ( $wgSecureLogin ) {
654  // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
655  if ( !$this->isSignup() ) {
656  $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
657  $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
658  }
659  }
660 
661  // set properties of the form itself
662  $form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
663  $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
664  if ( $this->isSignup() ) {
665  $form->setId( 'userlogin2' );
666  }
667 
668  // add pre/post text
669  // header used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
670  // should be above the error message but HTMLForm doesn't support that
671  $form->addHeaderText( $fakeTemplate->get( 'header' ) );
672 
673  // FIXME the old form used this for error/warning messages which does not play well with
674  // HTMLForm (maybe it could with a subclass?); for now only display it for signups
675  // (where the JS username validation needs it) and alway empty
676  if ( $this->isSignup() ) {
677  // used by the mediawiki.special.userlogin.signup.js module
678  $statusAreaAttribs = [ 'id' => 'mw-createacct-status-area' ];
679  // $statusAreaAttribs += $msg ? [ 'class' => "{$msgType}box" ] : [ 'style' => 'display: none;' ];
680  $form->addHeaderText( Html::element( 'div', $statusAreaAttribs ) );
681  }
682 
683  // header used by MobileFrontend
684  $form->addHeaderText( $fakeTemplate->get( 'formheader' ) );
685 
686  // blank signup footer for site customization
687  if ( $this->isSignup() && $this->showExtraInformation() ) {
688  // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
689  $signupendMsg = $this->msg( 'signupend' );
690  $signupendHttpsMsg = $this->msg( 'signupend-https' );
691  if ( !$signupendMsg->isDisabled() ) {
692  $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
693  ? $signupendHttpsMsg ->parse() : $signupendMsg->parse();
694  $form->addPostText( Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ) );
695  }
696  }
697 
698  // warning header for non-standard workflows (e.g. security reauthentication)
699  if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
700  $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
701  $form->addHeaderText( Html::rawElement( 'div', [ 'class' => 'warningbox' ],
702  $this->msg( $reauthMessage )->params( $this->getUser()->getName() )->parse() ) );
703  }
704 
705  if ( !$this->isSignup() && $this->showExtraInformation() ) {
706  $passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
707  if ( $passwordReset->isAllowed( $this->getUser() ) ) {
708  $form->addFooterText( Html::rawElement(
709  'div',
710  [ 'class' => 'mw-ui-vform-field mw-form-related-link-container' ],
711  Linker::link(
712  SpecialPage::getTitleFor( 'PasswordReset' ),
713  $this->msg( 'userlogin-resetpassword-link' )->escaped()
714  )
715  ) );
716  }
717 
718  // Don't show a "create account" link if the user can't.
719  if ( $this->showCreateAccountLink() ) {
720  // link to the other action
721  $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' );
722  $linkq = $this->getReturnToQueryStringFragment();
723  // Pass any language selection on to the mode switch link
724  if ( $wgLoginLanguageSelector && $this->mLanguage ) {
725  $linkq .= '&uselang=' . $this->mLanguage;
726  }
727  $createOrLoginHref = $linkTitle->getLocalURL( $linkq );
728 
729  if ( $this->getUser()->isLoggedIn() ) {
730  $createOrLoginHtml = Html::rawElement( 'div',
731  [ 'class' => 'mw-ui-vform-field' ],
732  Html::element( 'a',
733  [
734  'id' => 'mw-createaccount-join',
735  'href' => $createOrLoginHref,
736  // put right after all auth inputs in the tab order
737  'tabindex' => 100,
738  ],
739  $this->msg( 'userlogin-createanother' )->escaped()
740  )
741  );
742  } else {
743  $createOrLoginHtml = Html::rawElement( 'div',
744  [ 'id' => 'mw-createaccount-cta',
745  'class' => 'mw-ui-vform-field' ],
746  $this->msg( 'userlogin-noaccount' )->escaped()
747  . Html::element( 'a',
748  [
749  'id' => 'mw-createaccount-join',
750  'href' => $createOrLoginHref,
751  'class' => 'mw-ui-button',
752  'tabindex' => 100,
753  ],
754  $this->msg( 'userlogin-joinproject' )->escaped()
755  )
756  );
757  }
758  $form->addFooterText( $createOrLoginHtml );
759  }
760  }
761 
762  $form->suppressDefaultSubmit();
763 
764  $this->authForm = $form;
765 
766  return $form;
767  }
768 
775  protected function getFakeTemplate( $msg, $msgType ) {
776  global $wgAuth, $wgEnableEmail, $wgHiddenPrefs, $wgEmailConfirmToEdit, $wgEnableUserEmail,
777  $wgSecureLogin, $wgLoginLanguageSelector, $wgPasswordResetRoutes;
778 
779  // make a best effort to get the value of fields which used to be fixed in the old login
780  // template but now might or might not exist depending on what providers are used
781  $request = $this->getRequest();
782  $data = (object) [
783  'mUsername' => $request->getText( 'wpName' ),
784  'mPassword' => $request->getText( 'wpPassword' ),
785  'mRetype' => $request->getText( 'wpRetype' ),
786  'mEmail' => $request->getText( 'wpEmail' ),
787  'mRealName' => $request->getText( 'wpRealName' ),
788  'mDomain' => $request->getText( 'wpDomain' ),
789  'mReason' => $request->getText( 'wpReason' ),
790  'mRemember' => $request->getCheck( 'wpRemember' ),
791  ];
792 
793  // Preserves a bunch of logic from the old code that was rewritten in getAuthForm().
794  // There is no code reuse to make this easier to remove .
795  // If an extension tries to change any of these values, they are out of luck - we only
796  // actually use the domain/usedomain/domainnames, extraInput and extrafields keys.
797 
798  $titleObj = $this->getPageTitle();
799  $user = $this->getUser();
800  $template = new FakeAuthTemplate();
801 
802  // Pre-fill username (if not creating an account, bug 44775).
803  if ( $data->mUsername == '' && $this->isSignup() ) {
804  if ( $user->isLoggedIn() ) {
805  $data->mUsername = $user->getName();
806  } else {
807  $data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
808  }
809  }
810 
811  if ( $this->isSignup() ) {
812  // Must match number of benefits defined in messages
813  $template->set( 'benefitCount', 3 );
814 
815  $q = 'action=submitlogin&type=signup';
816  $linkq = 'type=login';
817  } else {
818  $q = 'action=submitlogin&type=login';
819  $linkq = 'type=signup';
820  }
821 
822  if ( $this->mReturnTo !== '' ) {
823  $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
824  if ( $this->mReturnToQuery !== '' ) {
825  $returnto .= '&returntoquery=' .
826  wfUrlencode( $this->mReturnToQuery );
827  }
828  $q .= $returnto;
829  $linkq .= $returnto;
830  }
831 
832  # Don't show a "create account" link if the user can't.
833  if ( $this->showCreateAccountLink() ) {
834  # Pass any language selection on to the mode switch link
835  if ( $wgLoginLanguageSelector && $this->mLanguage ) {
836  $linkq .= '&uselang=' . $this->mLanguage;
837  }
838  // Supply URL, login template creates the button.
839  $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
840  } else {
841  $template->set( 'link', '' );
842  }
843 
844  $resetLink = $this->isSignup()
845  ? null
846  : is_array( $wgPasswordResetRoutes )
847  && in_array( true, array_values( $wgPasswordResetRoutes ), true );
848 
849  $template->set( 'header', '' );
850  $template->set( 'formheader', '' );
851  $template->set( 'skin', $this->getSkin() );
852 
853  $template->set( 'name', $data->mUsername );
854  $template->set( 'password', $data->mPassword );
855  $template->set( 'retype', $data->mRetype );
856  $template->set( 'createemailset', false ); // no easy way to get that from AuthManager
857  $template->set( 'email', $data->mEmail );
858  $template->set( 'realname', $data->mRealName );
859  $template->set( 'domain', $data->mDomain );
860  $template->set( 'reason', $data->mReason );
861  $template->set( 'remember', $data->mRemember );
862 
863  $template->set( 'action', $titleObj->getLocalURL( $q ) );
864  $template->set( 'message', $msg );
865  $template->set( 'messagetype', $msgType );
866  $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
867  $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) );
868  $template->set( 'useemail', $wgEnableEmail );
869  $template->set( 'emailrequired', $wgEmailConfirmToEdit );
870  $template->set( 'emailothers', $wgEnableUserEmail );
871  $template->set( 'canreset', $wgAuth->allowPasswordChange() );
872  $template->set( 'resetlink', $resetLink );
873  $template->set( 'canremember', $request->getSession()->getProvider()
874  ->getRememberUserDuration() !== null );
875  $template->set( 'usereason', $user->isLoggedIn() );
876  $template->set( 'cansecurelogin', ( $wgSecureLogin ) );
877  $template->set( 'stickhttps', (int)$this->mStickHTTPS );
878  $template->set( 'loggedin', $user->isLoggedIn() );
879  $template->set( 'loggedinuser', $user->getName() );
880  $template->set( 'token', $this->getToken()->toString() );
881 
882  $action = $this->isSignup() ? 'signup' : 'login';
883  $wgAuth->modifyUITemplate( $template, $action );
884 
885  $oldTemplate = $template;
886  $hookName = $this->isSignup() ? 'UserCreateForm' : 'UserLoginForm';
887  Hooks::run( $hookName, [ &$template ] );
888  if ( $oldTemplate !== $template ) {
889  wfDeprecated( "reference in $hookName hook", '1.27' );
890  }
891 
892  return $template;
893 
894  }
895 
896  public function onAuthChangeFormFields(
897  array $requests, array $fieldInfo, array &$formDescriptor, $action
898  ) {
899  $coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate );
900  $specialFields = array_merge( [ 'extraInput', 'linkcontainer', 'entryError' ],
901  array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) );
902 
903  // keep the ordering from getCoreFieldDescriptors() where there is no explicit weight
904  foreach ( $coreFieldDescriptors as $fieldName => $coreField ) {
905  $requestField = isset( $formDescriptor[$fieldName] ) ?
906  $formDescriptor[$fieldName] : [];
907 
908  // remove everything that is not in the fieldinfo, is not marked as a supplemental field
909  // to something in the fieldinfo, and is not a generic or B/C field or a submit button
910  if (
911  !isset( $fieldInfo[$fieldName] )
912  && (
913  !isset( $coreField['baseField'] )
914  || !isset( $fieldInfo[$coreField['baseField']] )
915  ) && !in_array( $fieldName, $specialFields, true )
916  && ( !isset( $coreField['type'] ) || $coreField['type'] !== 'submit' )
917  ) {
918  $coreFieldDescriptors[$fieldName] = null;
919  continue;
920  }
921 
922  // core message labels should always take priority
923  if (
924  isset( $coreField['label'] )
925  || isset( $coreField['label-message'] )
926  || isset( $coreField['label-raw'] )
927  ) {
928  unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] );
929  }
930 
931  $coreFieldDescriptors[$fieldName] += $requestField;
932  }
933 
934  $formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor );
935  return true;
936  }
937 
944  protected function showExtraInformation() {
945  return $this->authAction !== $this->getContinueAction( $this->authAction )
947  }
948 
954  protected function getFieldDefinitions( $template ) {
955  global $wgEmailConfirmToEdit;
956 
957  $isLoggedIn = $this->getUser()->isLoggedIn();
958  $continuePart = $this->isContinued() ? 'continue-' : '';
959  $anotherPart = $isLoggedIn ? 'another-' : '';
960  $expiration = $this->getRequest()->getSession()->getProvider()
961  ->getRememberUserDuration();
962  $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
963  $secureLoginLink = '';
964  if ( $this->mSecureLoginUrl ) {
965  $secureLoginLink = Html::element( 'a', [
966  'href' => $this->mSecureLoginUrl,
967  'class' => 'mw-ui-flush-right mw-secure',
968  ], $this->msg( 'userlogin-signwithsecure' )->text() );
969  }
970 
971  if ( $this->isSignup() ) {
972  $fieldDefinitions = [
973  'username' => [
974  'label-message' => 'userlogin-yourname',
975  // FIXME help-message does not match old formatting
976  'help-message' => 'createacct-helpusername',
977  'id' => 'wpName2',
978  'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
979  : 'userlogin-yourname-ph',
980  ],
981  'mailpassword' => [
982  // create account without providing password, a temporary one will be mailed
983  'type' => 'check',
984  'label-message' => 'createaccountmail',
985  'name' => 'wpCreateaccountMail',
986  'id' => 'wpCreateaccountMail',
987  ],
988  'password' => [
989  'id' => 'wpPassword2',
990  'placeholder-message' => 'createacct-yourpassword-ph',
991  'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
992  ],
993  'domain' => [],
994  'retype' => [
995  'baseField' => 'password',
996  'type' => 'password',
997  'label-message' => 'createacct-yourpasswordagain',
998  'id' => 'wpRetype',
999  'cssclass' => 'loginPassword',
1000  'size' => 20,
1001  'validation-callback' => function ( $value, $alldata ) {
1002  if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
1003  if ( !$value ) {
1004  return $this->msg( 'htmlform-required' );
1005  } elseif ( $value !== $alldata['password'] ) {
1006  return $this->msg( 'badretype' );
1007  }
1008  }
1009  return true;
1010  },
1011  'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
1012  'placeholder-message' => 'createacct-yourpasswordagain-ph',
1013  ],
1014  'email' => [
1015  'type' => 'email',
1016  'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired'
1017  : 'createacct-emailoptional',
1018  'id' => 'wpEmail',
1019  'cssclass' => 'loginText',
1020  'size' => '20',
1021  // FIXME will break non-standard providers
1022  'required' => $wgEmailConfirmToEdit,
1023  'validation-callback' => function ( $value, $alldata ) {
1024  global $wgEmailConfirmToEdit;
1025 
1026  // AuthManager will check most of these, but that will make the auth
1027  // session fail and this won't, so nicer to do it this way
1028  if ( !$value && $wgEmailConfirmToEdit ) {
1029  // no point in allowing registration without email when email is
1030  // required to edit
1031  return $this->msg( 'noemailtitle' );
1032  } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
1033  // cannot send password via email when there is no email address
1034  return $this->msg( 'noemailcreate' );
1035  } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
1036  return $this->msg( 'invalidemailaddress' );
1037  }
1038  return true;
1039  },
1040  'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
1041  ],
1042  'realname' => [
1043  'type' => 'text',
1044  'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
1045  : 'prefs-help-realname',
1046  'label-message' => 'createacct-realname',
1047  'cssclass' => 'loginText',
1048  'size' => 20,
1049  'id' => 'wpRealName',
1050  ],
1051  'reason' => [
1052  // comment for the user creation log
1053  'type' => 'text',
1054  'label-message' => 'createacct-reason',
1055  'cssclass' => 'loginText',
1056  'id' => 'wpReason',
1057  'size' => '20',
1058  'placeholder-message' => 'createacct-reason-ph',
1059  ],
1060  'extrainput' => [], // placeholder for fields coming from the template
1061  'createaccount' => [
1062  // submit button
1063  'type' => 'submit',
1064  'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
1065  'submit' )->text(),
1066  'name' => 'wpCreateaccount',
1067  'id' => 'wpCreateaccount',
1068  'weight' => 100,
1069  ],
1070  ];
1071  } else {
1072  $fieldDefinitions = [
1073  'username' => [
1074  'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
1075  'id' => 'wpName1',
1076  'placeholder-message' => 'userlogin-yourname-ph',
1077  ],
1078  'password' => [
1079  'id' => 'wpPassword1',
1080  'placeholder-message' => 'userlogin-yourpassword-ph',
1081  ],
1082  'domain' => [],
1083  'extrainput' => [],
1084  'rememberMe' => [
1085  // option for saving the user token to a cookie
1086  'type' => 'check',
1087  'name' => 'wpRemember',
1088  'label-message' => $this->msg( 'userlogin-remembermypassword' )
1089  ->numParams( $expirationDays ),
1090  'id' => 'wpRemember',
1091  ],
1092  'loginattempt' => [
1093  // submit button
1094  'type' => 'submit',
1095  'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
1096  'id' => 'wpLoginAttempt',
1097  'weight' => 100,
1098  ],
1099  'linkcontainer' => [
1100  // help link
1101  'type' => 'info',
1102  'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1103  // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1104  'raw' => true,
1105  'default' => Html::element( 'a', [
1106  'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' )
1107  ->inContentLanguage()
1108  ->text() ),
1109  ], $this->msg( 'userlogin-helplink2' )->text() ),
1110  'weight' => 200,
1111  ],
1112  // button for ResetPasswordSecondaryAuthenticationProvider
1113  'skipReset' => [
1114  'weight' => 110,
1115  'flags' => [],
1116  ],
1117  ];
1118  }
1119  $fieldDefinitions['username'] += [
1120  'type' => 'text',
1121  'name' => 'wpName',
1122  'cssclass' => 'loginText',
1123  'size' => 20,
1124  // 'required' => true,
1125  ];
1126  $fieldDefinitions['password'] += [
1127  'type' => 'password',
1128  // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1129  'name' => 'wpPassword',
1130  'cssclass' => 'loginPassword',
1131  'size' => 20,
1132  // 'required' => true,
1133  ];
1134 
1135  if ( $this->mEntryError ) {
1136  $fieldDefinitions['entryError'] = [
1137  'type' => 'info',
1138  'default' => Html::rawElement( 'div', [ 'class' => $this->mEntryErrorType . 'box', ],
1139  $this->mEntryError ),
1140  'raw' => true,
1141  'rawrow' => true,
1142  'weight' => -100,
1143  ];
1144  }
1145 
1146  if ( !$this->showExtraInformation() ) {
1147  unset( $fieldDefinitions['linkcontainer'] );
1148  }
1149 
1150  $fieldDefinitions = $this->getBCFieldDefinitions( $fieldDefinitions, $template );
1151  $fieldDefinitions = array_filter( $fieldDefinitions );
1152 
1153  return $fieldDefinitions;
1154  }
1155 
1162  protected function getBCFieldDefinitions( $fieldDefinitions, $template ) {
1163  if ( $template->get( 'usedomain', false ) ) {
1164  // TODO probably should be translated to the new domain notation in AuthManager
1165  $fieldDefinitions['domain'] = [
1166  'type' => 'select',
1167  'label-message' => 'yourdomainname',
1168  'options' => array_combine( $template->get( 'domainnames', [] ),
1169  $template->get( 'domainnames', [] ) ),
1170  'default' => $template->get( 'domain', '' ),
1171  'name' => 'wpDomain',
1172  // FIXME id => 'mw-user-domain-section' on the parent div
1173  ];
1174  }
1175 
1176  // poor man's associative array_splice
1177  $extraInputPos = array_search( 'extrainput', array_keys( $fieldDefinitions ), true );
1178  $fieldDefinitions = array_slice( $fieldDefinitions, 0, $extraInputPos, true )
1179  + $template->getExtraInputDefinitions()
1180  + array_slice( $fieldDefinitions, $extraInputPos + 1, null, true );
1181 
1182  return $fieldDefinitions;
1183  }
1184 
1194  protected function hasSessionCookie() {
1195  global $wgDisableCookieCheck, $wgInitialSessionId;
1196 
1197  return $wgDisableCookieCheck || (
1198  $wgInitialSessionId &&
1199  $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
1200  );
1201  }
1202 
1207  protected function getReturnToQueryStringFragment() {
1208  $returnto = '';
1209  if ( $this->mReturnTo !== '' ) {
1210  $returnto = 'returnto=' . wfUrlencode( $this->mReturnTo );
1211  if ( $this->mReturnToQuery !== '' ) {
1212  $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery );
1213  }
1214  }
1215  return $returnto;
1216  }
1217 
1223  private function showCreateAccountLink() {
1224  if ( $this->isSignup() ) {
1225  return true;
1226  } elseif ( $this->getUser()->isAllowed( 'createaccount' ) ) {
1227  return true;
1228  } else {
1229  return false;
1230  }
1231  }
1232 
1233  protected function getTokenName() {
1234  return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1235  }
1236 
1243  protected function makeLanguageSelector() {
1244  $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1245  if ( $msg->isBlank() ) {
1246  return '';
1247  }
1248  $langs = explode( "\n", $msg->text() );
1249  $links = [];
1250  foreach ( $langs as $lang ) {
1251  $lang = trim( $lang, '* ' );
1252  $parts = explode( '|', $lang );
1253  if ( count( $parts ) >= 2 ) {
1254  $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1255  }
1256  }
1257 
1258  return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1259  $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1260  }
1261 
1270  protected function makeLanguageSelectorLink( $text, $lang ) {
1271  if ( $this->getLanguage()->getCode() == $lang ) {
1272  // no link for currently used language
1273  return htmlspecialchars( $text );
1274  }
1275  $query = [ 'uselang' => $lang ];
1276  if ( $this->mReturnTo !== '' ) {
1277  $query['returnto'] = $this->mReturnTo;
1278  $query['returntoquery'] = $this->mReturnToQuery;
1279  }
1280 
1281  $attr = [];
1282  $targetLanguage = Language::factory( $lang );
1283  $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1284 
1285  return Linker::linkKnown(
1286  $this->getPageTitle(),
1287  htmlspecialchars( $text ),
1288  $attr,
1289  $query
1290  );
1291  }
1292 
1293  protected function getGroupName() {
1294  return 'login';
1295  }
1296 
1300  protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1301  // Pre-fill username (if not creating an account, T46775).
1302  if (
1303  isset( $formDescriptor['username'] ) &&
1304  !isset( $formDescriptor['username']['default'] ) &&
1305  !$this->isSignup()
1306  ) {
1307  $user = $this->getUser();
1308  if ( $user->isLoggedIn() ) {
1309  $formDescriptor['username']['default'] = $user->getName();
1310  } else {
1311  $formDescriptor['username']['default'] =
1312  $this->getRequest()->getSession()->suggestLoginUsername();
1313  }
1314  }
1315 
1316  // don't show a submit button if there is nothing to submit (i.e. the only form content
1317  // is other submit buttons, for redirect flows)
1318  if ( !$this->needsSubmitButton( $requests ) ) {
1319  unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1320  }
1321 
1322  if ( !$this->isSignup() ) {
1323  // FIXME HACK don't focus on non-empty field
1324  // maybe there should be an autofocus-if similar to hide-if?
1325  if (
1326  isset( $formDescriptor['username'] )
1327  && empty( $formDescriptor['username']['default'] )
1328  && !$this->getRequest()->getCheck( 'wpName' )
1329  ) {
1330  $formDescriptor['username']['autofocus'] = true;
1331  } elseif ( isset( $formDescriptor['password'] ) ) {
1332  $formDescriptor['password']['autofocus'] = true;
1333  }
1334  }
1335 
1336  $this->addTabIndex( $formDescriptor );
1337  }
1338 }
1339 
1347  public function execute() {
1348  throw new LogicException( 'not used' );
1349  }
1350 
1355  public function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
1356  // use the same indexes as UserCreateForm just in case someone adds an item manually
1357  $this->data['extrainput'][] = [
1358  'name' => $name,
1359  'value' => $value,
1360  'type' => $type,
1361  'msg' => $msg,
1362  'helptext' => $helptext,
1363  ];
1364  }
1365 
1370  public function getExtraInputDefinitions() {
1371  $definitions = [];
1372 
1373  foreach ( $this->get( 'extrainput', [] ) as $field ) {
1374  $definition = [
1375  'type' => $field['type'] === 'checkbox' ? 'check' : $field['type'],
1376  'name' => $field['name'],
1377  'value' => $field['value'],
1378  'id' => $field['name'],
1379  ];
1380  if ( $field['msg'] ) {
1381  $definition['label-message'] = $this->getMsg( $field['msg'] );
1382  }
1383  if ( $field['helptext'] ) {
1384  $definition['help'] = $this->msgWiki( $field['helptext'] );
1385  }
1386 
1387  // the array key doesn't matter much when name is defined explicitly but
1388  // let's try and follow HTMLForm conventions
1389  $name = preg_replace( '/^wp(?=[A-Z])/', '', $field['name'] );
1390  $definitions[$name] = $definition;
1391  }
1392 
1393  if ( $this->haveData( 'extrafields' ) ) {
1394  $definitions['extrafields'] = [
1395  'type' => 'info',
1396  'raw' => true,
1397  'default' => $this->get( 'extrafields' ),
1398  ];
1399  }
1400 
1401  return $definitions;
1402  }
1403 }
1404 
1410 class LoginForm extends SpecialPage {
1411  const SUCCESS = 0;
1412  const NO_NAME = 1;
1413  const ILLEGAL = 2;
1415  const NOT_EXISTS = 4;
1416  const WRONG_PASS = 5;
1417  const EMPTY_PASS = 6;
1418  const RESET_PASS = 7;
1419  const ABORTED = 8;
1420  const CREATE_BLOCKED = 9;
1421  const THROTTLED = 10;
1422  const USER_BLOCKED = 11;
1423  const NEED_TOKEN = 12;
1424  const WRONG_TOKEN = 13;
1425  const USER_MIGRATED = 14;
1426 
1427  public static $statusCodes = [
1428  self::SUCCESS => 'success',
1429  self::NO_NAME => 'no_name',
1430  self::ILLEGAL => 'illegal',
1431  self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
1432  self::NOT_EXISTS => 'not_exists',
1433  self::WRONG_PASS => 'wrong_pass',
1434  self::EMPTY_PASS => 'empty_pass',
1435  self::RESET_PASS => 'reset_pass',
1436  self::ABORTED => 'aborted',
1437  self::CREATE_BLOCKED => 'create_blocked',
1438  self::THROTTLED => 'throttled',
1439  self::USER_BLOCKED => 'user_blocked',
1440  self::NEED_TOKEN => 'need_token',
1441  self::WRONG_TOKEN => 'wrong_token',
1442  self::USER_MIGRATED => 'user_migrated',
1443  ];
1444 
1448  public function __construct( $request = null ) {
1449  wfDeprecated( 'LoginForm', '1.27' );
1450  parent::__construct();
1451  }
1452 
1456  public static function getValidErrorMessages() {
1458  }
1459 
1463  public static function incrementLoginThrottle( $username ) {
1464  wfDeprecated( __METHOD__, "1.27" );
1467  $throttler = new Throttler();
1468  return $throttler->increase( $username, $wgRequest->getIP(), __METHOD__ );
1469  }
1470 
1474  public static function incLoginThrottle( $username ) {
1475  wfDeprecated( __METHOD__, "1.27" );
1476  $res = self::incrementLoginThrottle( $username );
1477  return is_array( $res ) ? true : 0;
1478  }
1479 
1483  public static function clearLoginThrottle( $username ) {
1484  wfDeprecated( __METHOD__, "1.27" );
1487  $throttler = new Throttler();
1488  return $throttler->clear( $username, $wgRequest->getIP() );
1489  }
1490 
1494  public static function getLoginToken() {
1495  wfDeprecated( __METHOD__, '1.27' );
1497  return $wgRequest->getSession()->getToken( '', 'login' )->toString();
1498  }
1499 
1503  public static function setLoginToken() {
1504  wfDeprecated( __METHOD__, '1.27' );
1505  }
1506 
1510  public static function clearLoginToken() {
1511  wfDeprecated( __METHOD__, '1.27' );
1513  $wgRequest->getSession()->resetToken( 'login' );
1514  }
1515 
1519  public static function getCreateaccountToken() {
1520  wfDeprecated( __METHOD__, '1.27' );
1522  return $wgRequest->getSession()->getToken( '', 'createaccount' )->toString();
1523  }
1524 
1528  public static function setCreateaccountToken() {
1529  wfDeprecated( __METHOD__, '1.27' );
1530  }
1531 
1535  public static function clearCreateaccountToken() {
1536  wfDeprecated( __METHOD__, '1.27' );
1538  $wgRequest->getSession()->resetToken( 'createaccount' );
1539  }
1540 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:568
$wgInitialSessionId
Definition: Setup.php:730
setSessionUserForCurrentRequest()
Replace some globals to make sure the fact that the user has just been logged in is reflected in the ...
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of data
Definition: hooks.txt:6
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1798
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:762
the array() calling protocol came about after MediaWiki 1.4rc1.
getAuthForm(array $requests, $action, $msg= '', $msgType= 'error')
Generates a form from the given request.
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1418
wfCanIPUseHTTPS($ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
static getCreateaccountToken()
static wrap($sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:79
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known', 'noclasses'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
A special page subclass for authentication-related special pages.
$context
Definition: load.php:44
getContext()
Gets the context this SpecialPage is executed in.
static setCreateaccountToken()
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string & $returnToQuery
Definition: hooks.txt:2338
$success
makeLanguageSelectorLink($text, $lang)
Create a language selector link for a particular language Links back to this page preserving type and...
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:75
static getCanonicalName($name, $validate= 'valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1050
getFakeTemplate($msg, $msgType)
Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks.
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button...
makeLanguageSelector()
Produce a bar of links which allow the user to select another language during login/registration but ...
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
if(!isset($args[0])) $lang
An IContextSource implementation which will inherit context from another source but allow individual ...
static factory($displayFormat)
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:264
hasSessionCookie()
Check if a session cookie is present.
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:177
New base template for a skin's template extended from QuickTemplate this class features helper method...
$value
getToken()
Returns the CSRF token.
mainLoginForm(array $requests, $msg= '', $msgtype= 'error')
msg()
Wrapper around wfMessage that sets the current context.
User $targetUser
FIXME another flag for passing data.
get($name, $default=null)
Gets the template data requested.
getOutput()
Get the OutputPage being used for this instance.
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:277
this hook is for auditing only $response
Definition: hooks.txt:762
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
Holds shared logic for login and account creation pages.
static newFatal($message)
Factory function for fatal errors.
Definition: Status.php:89
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
static getValidErrorMessages()
showExtraInformation()
Show extra information such as password recovery information, link from login to signup, CTA etc? Such information should only be shown on the "landing page", ie.
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
showSuccessPage($type, $title, $msgname, $injected_html, $extraMessages)
Show the success page.
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:762
static incLoginThrottle($username)
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1798
$wgLoginLanguageSelector
Show a bar of language selection links in the user login and user registration forms; edit the "login...
isActionAllowed($action)
Checks whether AuthManager is ready to perform the action.
Parent class for all special pages.
Definition: SpecialPage.php:36
const PROTO_HTTPS
Definition: Defines.php:262
isContinued()
Returns true if this is not the first step of the authentication.
wfEscapeWikiText($text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
getContinueAction($action)
Gets the _CONTINUE version of an action.
static getMain()
Static methods.
canBypassForm(&$button_name)
Determine if the login form can be bypassed.
getMsg($name)
Get a Message object with its context set.
logAuthResult($success, $status=null)
Logs to the authmanager-stats channel.
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
getExtraInputDefinitions()
Turns addInputItem-style field definitions into HTMLForm field definitions.
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
An error page which can definitely be safely rendered using the OutputPage.
loadRequestParameters($subPage)
Load basic request parameters for this Special page.
static clearCreateaccountToken()
$res
Definition: database.txt:21
getBCFieldDefinitions($fieldDefinitions, $template)
Adds fields provided via the deprecated UserLoginForm / UserCreateForm hooks.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
B/C class to try handling login/signup template modifications even though login/signup does not actua...
static incrementLoginThrottle($username)
$params
getSkin()
Shortcut to get the skin being used for this instance.
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
$wgUseMediaWikiUIEverywhere
Temporary variable that applies MediaWiki UI wherever it can be supported.
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks hooks can tweak the array to change how login etc forms should look $requests
Definition: hooks.txt:312
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Helper functions for the login form that need to be shared with other special pages (such as CentralA...
Definition: LoginHelper.php:8
getFieldDefinitions($template)
Create a HTMLForm descriptor for the core login fields.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:762
loadAuth($subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
Helper class for the password reset functionality shared by the web UI and the API.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:195
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
setRequest(array $data, $wasPosted=null)
showReturnToPage($type, $returnTo= '', $returnToQuery= '', $stickHTTPS=false)
Add a "return to" link or redirect to it.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:762
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2418
getName()
Get the name of this Special Page.
bool $proxyAccountCreation
True if the user if creating an account for someone else.
getPageHtml($formHtml)
Add page elements which are outside the form.
getDefaultAction($subPage)
Get the default action for this special page, if none is given via URL/POST data. ...
getUser()
Shortcut to get the User executing this instance.
getConfig()
Shortcut to get main config object.
this hook is for auditing only WRONG_PASS
Definition: hooks.txt:1946
static getValidErrorMessages()
Returns an array of all valid error messages.
Definition: LoginHelper.php:36
getLanguage()
Shortcut to get user's language.
load($subPage)
Load data from request.
postProcessFormDescriptor(&$formDescriptor, $requests)
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1004
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method.MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances.The"Spi"in MediaWiki\Logger\Spi stands for"service provider interface".An SPI is an API intended to be implemented or extended by a third party.This software design pattern is intended to enable framework extension and replaceable components.It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki.The service provider interface allows the backend logging library to be implemented in multiple ways.The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime.This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance.Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
static makeInternalOrExternalUrl($name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1097
getFullTitle()
Return the full title, including $par.
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1904
__construct($request=null)
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
static clearLoginThrottle($username)
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect & $returnTo
Definition: hooks.txt:2338
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:179
showCreateAccountLink()
Whether the login/create account form should display a link to the other form (in addition to whateve...
string $subPage
Subpage of the special page.
getPreservedParams($withToken=false)
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
successfulAction($direct=false, $extraMessages=null)
setContext($context)
Sets the context this SpecialPage is executed in.
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:657
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2338
getReturnToQueryStringFragment()
Returns a string that can be appended to the URL (without encoding) to preserve the return target...
addInputItem($name, $value, $type, $msg, $helptext=false)
Extensions (AntiSpoof and TitleBlacklist) call this in response to UserCreateForm hook to add checkbo...
getPageTitle($subpage=false)
Get a self-referential title object.
$wgUser
Definition: Setup.php:794
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310
trySubmit()
Attempts to do an authentication step with the submitted data.