MediaWiki  1.28.1
LoginSignupSpecialPage.php
Go to the documentation of this file.
1 <?php
30 
37  protected $mReturnTo;
38  protected $mPosted;
39  protected $mAction;
40  protected $mLanguage;
41  protected $mReturnToQuery;
42  protected $mToken;
43  protected $mStickHTTPS;
44  protected $mFromHTTP;
45  protected $mEntryError = '';
46  protected $mEntryErrorType = 'error';
47 
48  protected $mLoaded = false;
49  protected $mLoadedRequest = false;
50  protected $mSecureLoginUrl;
51 
53  protected $securityLevel;
54 
59  protected $targetUser;
60 
62  protected $authForm;
63 
65  protected $fakeTemplate;
66 
67  abstract protected function isSignup();
68 
75  abstract protected function successfulAction( $direct = false, $extraMessages = null );
76 
82  abstract protected function logAuthResult( $success, $status = null );
83 
84  public function __construct( $name ) {
86  parent::__construct( $name );
87 
88  // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
89  $wgUseMediaWikiUIEverywhere = true;
90  }
91 
92  protected function setRequest( array $data, $wasPosted = null ) {
93  parent::setRequest( $data, $wasPosted );
94  $this->mLoadedRequest = false;
95  }
96 
101  private function loadRequestParameters( $subPage ) {
102  if ( $this->mLoadedRequest ) {
103  return;
104  }
105  $this->mLoadedRequest = true;
106  $request = $this->getRequest();
107 
108  $this->mPosted = $request->wasPosted();
109  $this->mIsReturn = $subPage === 'return';
110  $this->mAction = $request->getVal( 'action' );
111  $this->mFromHTTP = $request->getBool( 'fromhttp', false )
112  || $request->getBool( 'wpFromhttp', false );
113  $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
114  || $request->getBool( 'wpForceHttps', false );
115  $this->mLanguage = $request->getText( 'uselang' );
116  $this->mReturnTo = $request->getVal( 'returnto', '' );
117  $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
118  }
119 
125  protected function load( $subPage ) {
126  global $wgSecureLogin;
127 
129  if ( $this->mLoaded ) {
130  return;
131  }
132  $this->mLoaded = true;
133  $request = $this->getRequest();
134 
135  $securityLevel = $this->getRequest()->getText( 'force' );
136  if (
137  $securityLevel && AuthManager::singleton()->securitySensitiveOperationStatus(
138  $securityLevel ) === AuthManager::SEC_REAUTH
139  ) {
140  $this->securityLevel = $securityLevel;
141  }
142 
143  $this->loadAuth( $subPage );
144 
145  $this->mToken = $request->getVal( $this->getTokenName() );
146 
147  // Show an error or warning passed on from a previous page
148  $entryError = $this->msg( $request->getVal( 'error', '' ) );
149  $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
150  // bc: provide login link as a parameter for messages where the translation
151  // was not updated
152  $loginreqlink = Linker::linkKnown(
153  $this->getPageTitle(),
154  $this->msg( 'loginreqlink' )->escaped(),
155  [],
156  [
157  'returnto' => $this->mReturnTo,
158  'returntoquery' => $this->mReturnToQuery,
159  'uselang' => $this->mLanguage,
160  'fromhttp' => $wgSecureLogin && $this->mFromHTTP ? '1' : null,
161  ]
162  );
163 
164  // Only show valid error or warning messages.
165  if ( $entryError->exists()
166  && in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true )
167  ) {
168  $this->mEntryErrorType = 'error';
169  $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
170 
171  } elseif ( $entryWarning->exists()
172  && in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true )
173  ) {
174  $this->mEntryErrorType = 'warning';
175  $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
176  }
177 
178  # 1. When switching accounts, it sucks to get automatically logged out
179  # 2. Do not return to PasswordReset after a successful password change
180  # but goto Wiki start page (Main_Page) instead ( bug 33997 )
181  $returnToTitle = Title::newFromText( $this->mReturnTo );
182  if ( is_object( $returnToTitle )
183  && ( $returnToTitle->isSpecial( 'Userlogout' )
184  || $returnToTitle->isSpecial( 'PasswordReset' ) )
185  ) {
186  $this->mReturnTo = '';
187  $this->mReturnToQuery = '';
188  }
189  }
190 
191  protected function getPreservedParams( $withToken = false ) {
192  global $wgSecureLogin;
193 
194  $params = parent::getPreservedParams( $withToken );
195  $params += [
196  'returnto' => $this->mReturnTo ?: null,
197  'returntoquery' => $this->mReturnToQuery ?: null,
198  ];
199  if ( $wgSecureLogin && !$this->isSignup() ) {
200  $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
201  }
202  return $params;
203  }
204 
205  protected function beforeExecute( $subPage ) {
206  // finish initializing the class before processing the request - T135924
208  return parent::beforeExecute( $subPage );
209  }
210 
214  public function execute( $subPage ) {
215  $authManager = AuthManager::singleton();
216  $session = SessionManager::getGlobalSession();
217 
218  // Session data is used for various things in the authentication process, so we must make
219  // sure a session cookie or some equivalent mechanism is set.
220  $session->persist();
221 
222  $this->load( $subPage );
223  $this->setHeaders();
224  $this->checkPermissions();
225 
226  // Make sure the system configuration allows log in / sign up
227  if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
228  if ( !$session->canSetUser() ) {
229  throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
230  $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
231  ] );
232  }
233  throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
234  } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
235  throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
236  }
237 
238  /*
239  * In the case where the user is already logged in, and was redirected to
240  * the login form from a page that requires login, do not show the login
241  * page. The use case scenario for this is when a user opens a large number
242  * of tabs, is redirected to the login page on all of them, and then logs
243  * in on one, expecting all the others to work properly.
244  *
245  * However, do show the form if it was visited intentionally (no 'returnto'
246  * is present). People who often switch between several accounts have grown
247  * accustomed to this behavior.
248  *
249  * Also make an exception when force=<level> is set in the URL, which means the user must
250  * reauthenticate for security reasons.
251  */
252  if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
253  ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
254  $this->getUser()->isLoggedIn()
255  ) {
256  $this->successfulAction();
257  }
258 
259  // If logging in and not on HTTPS, either redirect to it or offer a link.
260  global $wgSecureLogin;
261  if ( $this->getRequest()->getProtocol() !== 'https' ) {
262  $title = $this->getFullTitle();
263  $query = $this->getPreservedParams( false ) + [
264  'title' => null,
265  ( $this->mEntryErrorType === 'error' ? 'error'
266  : 'warning' ) => $this->mEntryError,
267  ] + $this->getRequest()->getQueryValues();
268  $url = $title->getFullURL( $query, false, PROTO_HTTPS );
269  if ( $wgSecureLogin && !$this->mFromHTTP &&
270  wfCanIPUseHTTPS( $this->getRequest()->getIP() )
271  ) {
272  // Avoid infinite redirect
273  $url = wfAppendQuery( $url, 'fromhttp=1' );
274  $this->getOutput()->redirect( $url );
275  // Since we only do this redir to change proto, always vary
276  $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
277 
278  return;
279  } else {
280  // A wiki without HTTPS login support should set $wgServer to
281  // http://somehost, in which case the secure URL generated
282  // above won't actually start with https://
283  if ( substr( $url, 0, 8 ) === 'https://' ) {
284  $this->mSecureLoginUrl = $url;
285  }
286  }
287  }
288 
289  if ( !$this->isActionAllowed( $this->authAction ) ) {
290  // FIXME how do we explain this to the user? can we handle session loss better?
291  // messages used: authpage-cannot-login, authpage-cannot-login-continue,
292  // authpage-cannot-create, authpage-cannot-create-continue
293  $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
294  return;
295  }
296 
297  if ( $this->canBypassForm( $button_name ) ) {
298  $this->setRequest( [], true );
299  $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
300  if ( $button_name ) {
301  $this->getRequest()->setVal( $button_name, true );
302  }
303  }
304 
305  $status = $this->trySubmit();
306 
307  if ( !$status || !$status->isGood() ) {
308  $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
309  return;
310  }
311 
313  $response = $status->getValue();
314 
315  $returnToUrl = $this->getPageTitle( 'return' )
316  ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
317  switch ( $response->status ) {
318  case AuthenticationResponse::PASS:
319  $this->logAuthResult( true );
320  $this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
321  $this->targetUser = User::newFromName( $response->username );
322 
323  if (
324  !$this->proxyAccountCreation
325  && $response->loginRequest
326  && $authManager->canAuthenticateNow()
327  ) {
328  // successful registration; log the user in instantly
329  $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
330  $returnToUrl );
331  if ( $response2->status !== AuthenticationResponse::PASS ) {
332  LoggerFactory::getInstance( 'login' )
333  ->error( 'Could not log in after account creation' );
334  $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
335  break;
336  }
337  }
338 
339  if ( !$this->proxyAccountCreation ) {
340  // Ensure that the context user is the same as the session user.
342  }
343 
344  $this->successfulAction( true );
345  break;
346  case AuthenticationResponse::FAIL:
347  // fall through
348  case AuthenticationResponse::RESTART:
349  unset( $this->authForm );
350  if ( $response->status === AuthenticationResponse::FAIL ) {
351  $action = $this->getDefaultAction( $subPage );
352  $messageType = 'error';
353  } else {
354  $action = $this->getContinueAction( $this->authAction );
355  $messageType = 'warning';
356  }
357  $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
358  $this->loadAuth( $subPage, $action, true );
359  $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
360  break;
361  case AuthenticationResponse::REDIRECT:
362  unset( $this->authForm );
363  $this->getOutput()->redirect( $response->redirectTarget );
364  break;
365  case AuthenticationResponse::UI:
366  unset( $this->authForm );
367  $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
368  : AuthManager::ACTION_LOGIN_CONTINUE;
369  $this->authRequests = $response->neededRequests;
370  $this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
371  break;
372  default:
373  throw new LogicException( 'invalid AuthenticationResponse' );
374  }
375  }
376 
390  private function canBypassForm( &$button_name ) {
391  $button_name = null;
392  if ( $this->isContinued() ) {
393  return false;
394  }
395  $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
396  foreach ( $fields as $fieldname => $field ) {
397  if ( !isset( $field['type'] ) ) {
398  return false;
399  }
400  if ( !empty( $field['skippable'] ) ) {
401  continue;
402  }
403  if ( $field['type'] === 'button' ) {
404  if ( $button_name !== null ) {
405  $button_name = null;
406  return false;
407  } else {
408  $button_name = $fieldname;
409  }
410  } elseif ( $field['type'] !== 'null' ) {
411  return false;
412  }
413  }
414  return true;
415  }
416 
426  protected function showSuccessPage(
427  $type, $title, $msgname, $injected_html, $extraMessages
428  ) {
429  $out = $this->getOutput();
430  $out->setPageTitle( $title );
431  if ( $msgname ) {
432  $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
433  }
434  if ( $extraMessages ) {
435  $extraMessages = Status::wrap( $extraMessages );
436  $out->addWikiText( $extraMessages->getWikiText() );
437  }
438 
439  $out->addHTML( $injected_html );
440 
441  $helper = new LoginHelper( $this->getContext() );
442  $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
443  }
444 
460  public function showReturnToPage(
461  $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
462  ) {
463  $helper = new LoginHelper( $this->getContext() );
464  $helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
465  }
466 
472  protected function setSessionUserForCurrentRequest() {
474 
476  $localContext = $this->getContext();
477  if ( $context !== $localContext ) {
478  // remove AuthManagerSpecialPage context hack
479  $this->setContext( $context );
480  }
481 
482  $user = $context->getRequest()->getSession()->getUser();
483 
484  $wgUser = $user;
485  $context->setUser( $user );
486 
487  $code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) );
488  $userLang = Language::factory( $code );
489  $wgLang = $userLang;
490  $context->setLanguage( $userLang );
491  }
492 
507  protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
508  $titleObj = $this->getPageTitle();
509  $user = $this->getUser();
510  $out = $this->getOutput();
511 
512  // FIXME how to handle empty $requests - restart, or no form, just an error message?
513  // no form would be better for no session type errors, restart is better when can* fails.
514  if ( !$requests ) {
515  $this->authAction = $this->getDefaultAction( $this->subPage );
516  $this->authForm = null;
517  $requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user );
518  }
519 
520  // Generic styles and scripts for both login and signup form
521  $out->addModuleStyles( [
522  'mediawiki.ui',
523  'mediawiki.ui.button',
524  'mediawiki.ui.checkbox',
525  'mediawiki.ui.input',
526  'mediawiki.special.userlogin.common.styles'
527  ] );
528  if ( $this->isSignup() ) {
529  // XXX hack pending RL or JS parse() support for complex content messages T27349
530  $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
531  $this->msg( 'createacct-imgcaptcha-help' )->parse() );
532 
533  // Additional styles and scripts for signup form
534  $out->addModules( [
535  'mediawiki.special.userlogin.signup.js'
536  ] );
537  $out->addModuleStyles( [
538  'mediawiki.special.userlogin.signup.styles'
539  ] );
540  } else {
541  // Additional styles for login form
542  $out->addModuleStyles( [
543  'mediawiki.special.userlogin.login.styles'
544  ] );
545  }
546  $out->disallowUserJs(); // just in case...
547 
548  $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
549  $form->prepareForm();
550 
551  $submitStatus = Status::newGood();
552  if ( $msg && $msgtype === 'warning' ) {
553  $submitStatus->warning( $msg );
554  } elseif ( $msg && $msgtype === 'error' ) {
555  $submitStatus->fatal( $msg );
556  }
557 
558  // warning header for non-standard workflows (e.g. security reauthentication)
559  if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
560  $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
561  $submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
562  }
563 
564  $formHtml = $form->getHTML( $submitStatus );
565 
566  $out->addHTML( $this->getPageHtml( $formHtml ) );
567  }
568 
575  protected function getPageHtml( $formHtml ) {
577 
578  $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
579  [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
580  $languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : '';
581  $signupStartMsg = $this->msg( 'signupstart' );
582  $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
583  ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
584  if ( $languageLinks ) {
585  $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
586  Html::rawElement( 'p', [], $languageLinks )
587  );
588  }
589 
590  $benefitsContainer = '';
591  if ( $this->isSignup() && $this->showExtraInformation() ) {
592  // messages used:
593  // createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
594  // createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
595  // createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
596  $benefitCount = 3;
597  $benefitList = '';
598  for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
599  $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
600  $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped();
601  $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
602  Html::rawElement( 'h3', [],
603  $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
604  )
605  . Html::rawElement( 'p', [],
606  $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
607  )
608  );
609  }
610  $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
611  Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
612  . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ],
613  $benefitList
614  )
615  );
616  }
617 
618  $html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
619  $loginPrompt
620  . $languageLinks
621  . $signupStart
622  . Html::rawElement( 'div', [ 'id' => 'userloginForm' ],
623  $formHtml
624  )
625  . $benefitsContainer
626  );
627 
628  return $html;
629  }
630 
639  protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
640  global $wgSecureLogin, $wgLoginLanguageSelector;
641  // FIXME merge this with parent
642 
643  if ( isset( $this->authForm ) ) {
644  return $this->authForm;
645  }
646 
647  $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
648 
649  // get basic form description from the auth logic
650  $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
651  $fakeTemplate = $this->getFakeTemplate( $msg, $msgType );
652  $this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
653  // this will call onAuthChangeFormFields()
654  $formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
655  $this->postProcessFormDescriptor( $formDescriptor, $requests );
656 
657  $context = $this->getContext();
658  if ( $context->getRequest() !== $this->getRequest() ) {
659  // We have overridden the request, need to make sure the form uses that too.
660  $context = new DerivativeContext( $this->getContext() );
661  $context->setRequest( $this->getRequest() );
662  }
663  $form = HTMLForm::factory( 'vform', $formDescriptor, $context );
664 
665  $form->addHiddenField( 'authAction', $this->authAction );
666  if ( $wgLoginLanguageSelector ) {
667  $form->addHiddenField( 'uselang', $this->mLanguage );
668  }
669  $form->addHiddenField( 'force', $this->securityLevel );
670  $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
671  if ( $wgSecureLogin ) {
672  // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
673  if ( !$this->isSignup() ) {
674  $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
675  $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
676  }
677  }
678 
679  // set properties of the form itself
680  $form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
681  $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
682  if ( $this->isSignup() ) {
683  $form->setId( 'userlogin2' );
684  }
685 
686  $form->suppressDefaultSubmit();
687 
688  $this->authForm = $form;
689 
690  return $form;
691  }
692 
699  protected function getFakeTemplate( $msg, $msgType ) {
700  global $wgAuth, $wgEnableEmail, $wgHiddenPrefs, $wgEmailConfirmToEdit, $wgEnableUserEmail,
701  $wgSecureLogin, $wgLoginLanguageSelector, $wgPasswordResetRoutes;
702 
703  // make a best effort to get the value of fields which used to be fixed in the old login
704  // template but now might or might not exist depending on what providers are used
705  $request = $this->getRequest();
706  $data = (object) [
707  'mUsername' => $request->getText( 'wpName' ),
708  'mPassword' => $request->getText( 'wpPassword' ),
709  'mRetype' => $request->getText( 'wpRetype' ),
710  'mEmail' => $request->getText( 'wpEmail' ),
711  'mRealName' => $request->getText( 'wpRealName' ),
712  'mDomain' => $request->getText( 'wpDomain' ),
713  'mReason' => $request->getText( 'wpReason' ),
714  'mRemember' => $request->getCheck( 'wpRemember' ),
715  ];
716 
717  // Preserves a bunch of logic from the old code that was rewritten in getAuthForm().
718  // There is no code reuse to make this easier to remove .
719  // If an extension tries to change any of these values, they are out of luck - we only
720  // actually use the domain/usedomain/domainnames, extraInput and extrafields keys.
721 
722  $titleObj = $this->getPageTitle();
723  $user = $this->getUser();
724  $template = new FakeAuthTemplate();
725 
726  // Pre-fill username (if not creating an account, bug 44775).
727  if ( $data->mUsername == '' && $this->isSignup() ) {
728  if ( $user->isLoggedIn() ) {
729  $data->mUsername = $user->getName();
730  } else {
731  $data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
732  }
733  }
734 
735  if ( $this->isSignup() ) {
736  // Must match number of benefits defined in messages
737  $template->set( 'benefitCount', 3 );
738 
739  $q = 'action=submitlogin&type=signup';
740  $linkq = 'type=login';
741  } else {
742  $q = 'action=submitlogin&type=login';
743  $linkq = 'type=signup';
744  }
745 
746  if ( $this->mReturnTo !== '' ) {
747  $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
748  if ( $this->mReturnToQuery !== '' ) {
749  $returnto .= '&returntoquery=' .
750  wfUrlencode( $this->mReturnToQuery );
751  }
752  $q .= $returnto;
753  $linkq .= $returnto;
754  }
755 
756  # Don't show a "create account" link if the user can't.
757  if ( $this->showCreateAccountLink() ) {
758  # Pass any language selection on to the mode switch link
759  if ( $wgLoginLanguageSelector && $this->mLanguage ) {
760  $linkq .= '&uselang=' . $this->mLanguage;
761  }
762  // Supply URL, login template creates the button.
763  $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
764  } else {
765  $template->set( 'link', '' );
766  }
767 
768  $resetLink = $this->isSignup()
769  ? null
770  : is_array( $wgPasswordResetRoutes )
771  && in_array( true, array_values( $wgPasswordResetRoutes ), true );
772 
773  $template->set( 'header', '' );
774  $template->set( 'formheader', '' );
775  $template->set( 'skin', $this->getSkin() );
776 
777  $template->set( 'name', $data->mUsername );
778  $template->set( 'password', $data->mPassword );
779  $template->set( 'retype', $data->mRetype );
780  $template->set( 'createemailset', false ); // no easy way to get that from AuthManager
781  $template->set( 'email', $data->mEmail );
782  $template->set( 'realname', $data->mRealName );
783  $template->set( 'domain', $data->mDomain );
784  $template->set( 'reason', $data->mReason );
785  $template->set( 'remember', $data->mRemember );
786 
787  $template->set( 'action', $titleObj->getLocalURL( $q ) );
788  $template->set( 'message', $msg );
789  $template->set( 'messagetype', $msgType );
790  $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
791  $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) );
792  $template->set( 'useemail', $wgEnableEmail );
793  $template->set( 'emailrequired', $wgEmailConfirmToEdit );
794  $template->set( 'emailothers', $wgEnableUserEmail );
795  $template->set( 'canreset', $wgAuth->allowPasswordChange() );
796  $template->set( 'resetlink', $resetLink );
797  $template->set( 'canremember', $request->getSession()->getProvider()
798  ->getRememberUserDuration() !== null );
799  $template->set( 'usereason', $user->isLoggedIn() );
800  $template->set( 'cansecurelogin', ( $wgSecureLogin ) );
801  $template->set( 'stickhttps', (int)$this->mStickHTTPS );
802  $template->set( 'loggedin', $user->isLoggedIn() );
803  $template->set( 'loggedinuser', $user->getName() );
804  $template->set( 'token', $this->getToken()->toString() );
805 
806  $action = $this->isSignup() ? 'signup' : 'login';
807  $wgAuth->modifyUITemplate( $template, $action );
808 
809  $oldTemplate = $template;
810  $hookName = $this->isSignup() ? 'UserCreateForm' : 'UserLoginForm';
811  Hooks::run( $hookName, [ &$template ] );
812  if ( $oldTemplate !== $template ) {
813  wfDeprecated( "reference in $hookName hook", '1.27' );
814  }
815 
816  return $template;
817 
818  }
819 
820  public function onAuthChangeFormFields(
821  array $requests, array $fieldInfo, array &$formDescriptor, $action
822  ) {
823  $coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate );
824  $specialFields = array_merge( [ 'extraInput' ],
825  array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) );
826 
827  // keep the ordering from getCoreFieldDescriptors() where there is no explicit weight
828  foreach ( $coreFieldDescriptors as $fieldName => $coreField ) {
829  $requestField = isset( $formDescriptor[$fieldName] ) ?
830  $formDescriptor[$fieldName] : [];
831 
832  // remove everything that is not in the fieldinfo, is not marked as a supplemental field
833  // to something in the fieldinfo, is not B/C for the pre-AuthManager templates,
834  // and is not an info field or a submit button
835  if (
836  !isset( $fieldInfo[$fieldName] )
837  && (
838  !isset( $coreField['baseField'] )
839  || !isset( $fieldInfo[$coreField['baseField']] )
840  )
841  && !in_array( $fieldName, $specialFields, true )
842  && (
843  !isset( $coreField['type'] )
844  || !in_array( $coreField['type'], [ 'submit', 'info' ], true )
845  )
846  ) {
847  $coreFieldDescriptors[$fieldName] = null;
848  continue;
849  }
850 
851  // core message labels should always take priority
852  if (
853  isset( $coreField['label'] )
854  || isset( $coreField['label-message'] )
855  || isset( $coreField['label-raw'] )
856  ) {
857  unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] );
858  }
859 
860  $coreFieldDescriptors[$fieldName] += $requestField;
861  }
862 
863  $formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor );
864  return true;
865  }
866 
873  protected function showExtraInformation() {
874  return $this->authAction !== $this->getContinueAction( $this->authAction )
876  }
877 
883  protected function getFieldDefinitions( $template ) {
884  global $wgEmailConfirmToEdit, $wgLoginLanguageSelector;
885 
886  $isLoggedIn = $this->getUser()->isLoggedIn();
887  $continuePart = $this->isContinued() ? 'continue-' : '';
888  $anotherPart = $isLoggedIn ? 'another-' : '';
889  $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
890  $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
891  $secureLoginLink = '';
892  if ( $this->mSecureLoginUrl ) {
893  $secureLoginLink = Html::element( 'a', [
894  'href' => $this->mSecureLoginUrl,
895  'class' => 'mw-ui-flush-right mw-secure',
896  ], $this->msg( 'userlogin-signwithsecure' )->text() );
897  }
898  $usernameHelpLink = '';
899  if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) {
900  $usernameHelpLink = Html::rawElement( 'span', [
901  'class' => 'mw-ui-flush-right',
902  ], $this->msg( 'createacct-helpusername' )->parse() );
903  }
904 
905  if ( $this->isSignup() ) {
906  $fieldDefinitions = [
907  'statusarea' => [
908  // used by the mediawiki.special.userlogin.signup.js module for error display
909  // FIXME merge this with HTMLForm's normal status (error) area
910  'type' => 'info',
911  'raw' => true,
912  'default' => Html::element( 'div', [ 'id' => 'mw-createacct-status-area' ] ),
913  'weight' => -105,
914  ],
915  'username' => [
916  'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
917  'id' => 'wpName2',
918  'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
919  : 'userlogin-yourname-ph',
920  ],
921  'mailpassword' => [
922  // create account without providing password, a temporary one will be mailed
923  'type' => 'check',
924  'label-message' => 'createaccountmail',
925  'name' => 'wpCreateaccountMail',
926  'id' => 'wpCreateaccountMail',
927  ],
928  'password' => [
929  'id' => 'wpPassword2',
930  'placeholder-message' => 'createacct-yourpassword-ph',
931  'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
932  ],
933  'domain' => [],
934  'retype' => [
935  'baseField' => 'password',
936  'type' => 'password',
937  'label-message' => 'createacct-yourpasswordagain',
938  'id' => 'wpRetype',
939  'cssclass' => 'loginPassword',
940  'size' => 20,
941  'validation-callback' => function ( $value, $alldata ) {
942  if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
943  if ( !$value ) {
944  return $this->msg( 'htmlform-required' );
945  } elseif ( $value !== $alldata['password'] ) {
946  return $this->msg( 'badretype' );
947  }
948  }
949  return true;
950  },
951  'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
952  'placeholder-message' => 'createacct-yourpasswordagain-ph',
953  ],
954  'email' => [
955  'type' => 'email',
956  'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired'
957  : 'createacct-emailoptional',
958  'id' => 'wpEmail',
959  'cssclass' => 'loginText',
960  'size' => '20',
961  // FIXME will break non-standard providers
962  'required' => $wgEmailConfirmToEdit,
963  'validation-callback' => function ( $value, $alldata ) {
964  global $wgEmailConfirmToEdit;
965 
966  // AuthManager will check most of these, but that will make the auth
967  // session fail and this won't, so nicer to do it this way
968  if ( !$value && $wgEmailConfirmToEdit ) {
969  // no point in allowing registration without email when email is
970  // required to edit
971  return $this->msg( 'noemailtitle' );
972  } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
973  // cannot send password via email when there is no email address
974  return $this->msg( 'noemailcreate' );
975  } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
976  return $this->msg( 'invalidemailaddress' );
977  }
978  return true;
979  },
980  'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
981  ],
982  'realname' => [
983  'type' => 'text',
984  'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
985  : 'prefs-help-realname',
986  'label-message' => 'createacct-realname',
987  'cssclass' => 'loginText',
988  'size' => 20,
989  'id' => 'wpRealName',
990  ],
991  'reason' => [
992  // comment for the user creation log
993  'type' => 'text',
994  'label-message' => 'createacct-reason',
995  'cssclass' => 'loginText',
996  'id' => 'wpReason',
997  'size' => '20',
998  'placeholder-message' => 'createacct-reason-ph',
999  ],
1000  'extrainput' => [], // placeholder for fields coming from the template
1001  'createaccount' => [
1002  // submit button
1003  'type' => 'submit',
1004  'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
1005  'submit' )->text(),
1006  'name' => 'wpCreateaccount',
1007  'id' => 'wpCreateaccount',
1008  'weight' => 100,
1009  ],
1010  ];
1011  } else {
1012  $fieldDefinitions = [
1013  'username' => [
1014  'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
1015  'id' => 'wpName1',
1016  'placeholder-message' => 'userlogin-yourname-ph',
1017  ],
1018  'password' => [
1019  'id' => 'wpPassword1',
1020  'placeholder-message' => 'userlogin-yourpassword-ph',
1021  ],
1022  'domain' => [],
1023  'extrainput' => [],
1024  'rememberMe' => [
1025  // option for saving the user token to a cookie
1026  'type' => 'check',
1027  'name' => 'wpRemember',
1028  'label-message' => $this->msg( 'userlogin-remembermypassword' )
1029  ->numParams( $expirationDays ),
1030  'id' => 'wpRemember',
1031  ],
1032  'loginattempt' => [
1033  // submit button
1034  'type' => 'submit',
1035  'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
1036  'id' => 'wpLoginAttempt',
1037  'weight' => 100,
1038  ],
1039  'linkcontainer' => [
1040  // help link
1041  'type' => 'info',
1042  'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1043  // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1044  'raw' => true,
1045  'default' => Html::element( 'a', [
1046  'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' )
1047  ->inContentLanguage()
1048  ->text() ),
1049  ], $this->msg( 'userlogin-helplink2' )->text() ),
1050  'weight' => 200,
1051  ],
1052  // button for ResetPasswordSecondaryAuthenticationProvider
1053  'skipReset' => [
1054  'weight' => 110,
1055  'flags' => [],
1056  ],
1057  ];
1058  }
1059 
1060  $fieldDefinitions['username'] += [
1061  'type' => 'text',
1062  'name' => 'wpName',
1063  'cssclass' => 'loginText',
1064  'size' => 20,
1065  // 'required' => true,
1066  ];
1067  $fieldDefinitions['password'] += [
1068  'type' => 'password',
1069  // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1070  'name' => 'wpPassword',
1071  'cssclass' => 'loginPassword',
1072  'size' => 20,
1073  // 'required' => true,
1074  ];
1075 
1076  if ( $template->get( 'header' ) || $template->get( 'formheader' ) ) {
1077  // B/C for old extensions that haven't been converted to AuthManager (or have been
1078  // but somebody is using the old version) and still use templates via the
1079  // UserCreateForm/UserLoginForm hook.
1080  // 'header' used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
1081  // 'formheader' used by MobileFrontend
1082  $fieldDefinitions['header'] = [
1083  'type' => 'info',
1084  'raw' => true,
1085  'default' => $template->get( 'header' ) ?: $template->get( 'formheader' ),
1086  'weight' => - 110,
1087  ];
1088  }
1089  if ( $this->mEntryError ) {
1090  $fieldDefinitions['entryError'] = [
1091  'type' => 'info',
1092  'default' => Html::rawElement( 'div', [ 'class' => $this->mEntryErrorType . 'box', ],
1093  $this->mEntryError ),
1094  'raw' => true,
1095  'rawrow' => true,
1096  'weight' => -100,
1097  ];
1098  }
1099  if ( !$this->showExtraInformation() ) {
1100  unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
1101  }
1102  if ( $this->isSignup() && $this->showExtraInformation() ) {
1103  // blank signup footer for site customization
1104  // uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
1105  $signupendMsg = $this->msg( 'signupend' );
1106  $signupendHttpsMsg = $this->msg( 'signupend-https' );
1107  if ( !$signupendMsg->isDisabled() ) {
1108  $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
1109  $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
1110  ? $signupendHttpsMsg ->parse() : $signupendMsg->parse();
1111  $fieldDefinitions['signupend'] = [
1112  'type' => 'info',
1113  'raw' => true,
1114  'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ),
1115  'weight' => 225,
1116  ];
1117  }
1118  }
1119  if ( !$this->isSignup() && $this->showExtraInformation() ) {
1120  $passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
1121  if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
1122  $fieldDefinitions['passwordReset'] = [
1123  'type' => 'info',
1124  'raw' => true,
1125  'cssclass' => 'mw-form-related-link-container',
1126  'default' => Linker::link(
1127  SpecialPage::getTitleFor( 'PasswordReset' ),
1128  $this->msg( 'userlogin-resetpassword-link' )->escaped()
1129  ),
1130  'weight' => 230,
1131  ];
1132  }
1133 
1134  // Don't show a "create account" link if the user can't.
1135  if ( $this->showCreateAccountLink() ) {
1136  // link to the other action
1137  $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' );
1138  $linkq = $this->getReturnToQueryStringFragment();
1139  // Pass any language selection on to the mode switch link
1140  if ( $wgLoginLanguageSelector && $this->mLanguage ) {
1141  $linkq .= '&uselang=' . $this->mLanguage;
1142  }
1143  $loggedIn = $this->getUser()->isLoggedIn();
1144 
1145  $fieldDefinitions['createOrLogin'] = [
1146  'type' => 'info',
1147  'raw' => true,
1148  'linkQuery' => $linkq,
1149  'default' => function ( $params ) use ( $loggedIn, $linkTitle ) {
1150  return Html::rawElement( 'div',
1151  [ 'id' => 'mw-createaccount' . ( !$loggedIn ? '-cta' : '' ),
1152  'class' => ( $loggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
1153  ( $loggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
1154  . Html::element( 'a',
1155  [
1156  'id' => 'mw-createaccount-join' . ( $loggedIn ? '-loggedin' : '' ),
1157  'href' => $linkTitle->getLocalURL( $params['linkQuery'] ),
1158  'class' => ( $loggedIn ? '' : 'mw-ui-button' ),
1159  'tabindex' => 100,
1160  ],
1161  $this->msg(
1162  $loggedIn ? 'userlogin-createanother' : 'userlogin-joinproject'
1163  )->escaped()
1164  )
1165  );
1166  },
1167  'weight' => 235,
1168  ];
1169  }
1170  }
1171 
1172  $fieldDefinitions = $this->getBCFieldDefinitions( $fieldDefinitions, $template );
1173  $fieldDefinitions = array_filter( $fieldDefinitions );
1174 
1175  return $fieldDefinitions;
1176  }
1177 
1184  protected function getBCFieldDefinitions( $fieldDefinitions, $template ) {
1185  if ( $template->get( 'usedomain', false ) ) {
1186  // TODO probably should be translated to the new domain notation in AuthManager
1187  $fieldDefinitions['domain'] = [
1188  'type' => 'select',
1189  'label-message' => 'yourdomainname',
1190  'options' => array_combine( $template->get( 'domainnames', [] ),
1191  $template->get( 'domainnames', [] ) ),
1192  'default' => $template->get( 'domain', '' ),
1193  'name' => 'wpDomain',
1194  // FIXME id => 'mw-user-domain-section' on the parent div
1195  ];
1196  }
1197 
1198  // poor man's associative array_splice
1199  $extraInputPos = array_search( 'extrainput', array_keys( $fieldDefinitions ), true );
1200  $fieldDefinitions = array_slice( $fieldDefinitions, 0, $extraInputPos, true )
1201  + $template->getExtraInputDefinitions()
1202  + array_slice( $fieldDefinitions, $extraInputPos + 1, null, true );
1203 
1204  return $fieldDefinitions;
1205  }
1206 
1216  protected function hasSessionCookie() {
1217  global $wgDisableCookieCheck, $wgInitialSessionId;
1218 
1219  return $wgDisableCookieCheck || (
1220  $wgInitialSessionId &&
1221  $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId
1222  );
1223  }
1224 
1229  protected function getReturnToQueryStringFragment() {
1230  $returnto = '';
1231  if ( $this->mReturnTo !== '' ) {
1232  $returnto = 'returnto=' . wfUrlencode( $this->mReturnTo );
1233  if ( $this->mReturnToQuery !== '' ) {
1234  $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery );
1235  }
1236  }
1237  return $returnto;
1238  }
1239 
1245  private function showCreateAccountLink() {
1246  if ( $this->isSignup() ) {
1247  return true;
1248  } elseif ( $this->getUser()->isAllowed( 'createaccount' ) ) {
1249  return true;
1250  } else {
1251  return false;
1252  }
1253  }
1254 
1255  protected function getTokenName() {
1256  return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1257  }
1258 
1265  protected function makeLanguageSelector() {
1266  $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1267  if ( $msg->isBlank() ) {
1268  return '';
1269  }
1270  $langs = explode( "\n", $msg->text() );
1271  $links = [];
1272  foreach ( $langs as $lang ) {
1273  $lang = trim( $lang, '* ' );
1274  $parts = explode( '|', $lang );
1275  if ( count( $parts ) >= 2 ) {
1276  $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1277  }
1278  }
1279 
1280  return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1281  $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1282  }
1283 
1292  protected function makeLanguageSelectorLink( $text, $lang ) {
1293  if ( $this->getLanguage()->getCode() == $lang ) {
1294  // no link for currently used language
1295  return htmlspecialchars( $text );
1296  }
1297  $query = [ 'uselang' => $lang ];
1298  if ( $this->mReturnTo !== '' ) {
1299  $query['returnto'] = $this->mReturnTo;
1300  $query['returntoquery'] = $this->mReturnToQuery;
1301  }
1302 
1303  $attr = [];
1304  $targetLanguage = Language::factory( $lang );
1305  $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1306 
1307  return Linker::linkKnown(
1308  $this->getPageTitle(),
1309  htmlspecialchars( $text ),
1310  $attr,
1311  $query
1312  );
1313  }
1314 
1315  protected function getGroupName() {
1316  return 'login';
1317  }
1318 
1322  protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1323  // Pre-fill username (if not creating an account, T46775).
1324  if (
1325  isset( $formDescriptor['username'] ) &&
1326  !isset( $formDescriptor['username']['default'] ) &&
1327  !$this->isSignup()
1328  ) {
1329  $user = $this->getUser();
1330  if ( $user->isLoggedIn() ) {
1331  $formDescriptor['username']['default'] = $user->getName();
1332  } else {
1333  $formDescriptor['username']['default'] =
1334  $this->getRequest()->getSession()->suggestLoginUsername();
1335  }
1336  }
1337 
1338  // don't show a submit button if there is nothing to submit (i.e. the only form content
1339  // is other submit buttons, for redirect flows)
1340  if ( !$this->needsSubmitButton( $requests ) ) {
1341  unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1342  }
1343 
1344  if ( !$this->isSignup() ) {
1345  // FIXME HACK don't focus on non-empty field
1346  // maybe there should be an autofocus-if similar to hide-if?
1347  if (
1348  isset( $formDescriptor['username'] )
1349  && empty( $formDescriptor['username']['default'] )
1350  && !$this->getRequest()->getCheck( 'wpName' )
1351  ) {
1352  $formDescriptor['username']['autofocus'] = true;
1353  } elseif ( isset( $formDescriptor['password'] ) ) {
1354  $formDescriptor['password']['autofocus'] = true;
1355  }
1356  }
1357 
1358  $this->addTabIndex( $formDescriptor );
1359  }
1360 }
1361 
1369  public function execute() {
1370  throw new LogicException( 'not used' );
1371  }
1372 
1377  public function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
1378  // use the same indexes as UserCreateForm just in case someone adds an item manually
1379  $this->data['extrainput'][] = [
1380  'name' => $name,
1381  'value' => $value,
1382  'type' => $type,
1383  'msg' => $msg,
1384  'helptext' => $helptext,
1385  ];
1386  }
1387 
1392  public function getExtraInputDefinitions() {
1393  $definitions = [];
1394 
1395  foreach ( $this->get( 'extrainput', [] ) as $field ) {
1396  $definition = [
1397  'type' => $field['type'] === 'checkbox' ? 'check' : $field['type'],
1398  'name' => $field['name'],
1399  'value' => $field['value'],
1400  'id' => $field['name'],
1401  ];
1402  if ( $field['msg'] ) {
1403  $definition['label-message'] = $this->getMsg( $field['msg'] );
1404  }
1405  if ( $field['helptext'] ) {
1406  $definition['help'] = $this->msgWiki( $field['helptext'] );
1407  }
1408 
1409  // the array key doesn't matter much when name is defined explicitly but
1410  // let's try and follow HTMLForm conventions
1411  $name = preg_replace( '/^wp(?=[A-Z])/', '', $field['name'] );
1412  $definitions[$name] = $definition;
1413  }
1414 
1415  if ( $this->haveData( 'extrafields' ) ) {
1416  $definitions['extrafields'] = [
1417  'type' => 'info',
1418  'raw' => true,
1419  'default' => $this->get( 'extrafields' ),
1420  ];
1421  }
1422 
1423  return $definitions;
1424  }
1425 }
1426 
1432 class LoginForm extends SpecialPage {
1433  const SUCCESS = 0;
1434  const NO_NAME = 1;
1435  const ILLEGAL = 2;
1437  const NOT_EXISTS = 4;
1438  const WRONG_PASS = 5;
1439  const EMPTY_PASS = 6;
1440  const RESET_PASS = 7;
1441  const ABORTED = 8;
1442  const CREATE_BLOCKED = 9;
1443  const THROTTLED = 10;
1444  const USER_BLOCKED = 11;
1445  const NEED_TOKEN = 12;
1446  const WRONG_TOKEN = 13;
1447  const USER_MIGRATED = 14;
1448 
1449  public static $statusCodes = [
1450  self::SUCCESS => 'success',
1451  self::NO_NAME => 'no_name',
1452  self::ILLEGAL => 'illegal',
1453  self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
1454  self::NOT_EXISTS => 'not_exists',
1455  self::WRONG_PASS => 'wrong_pass',
1456  self::EMPTY_PASS => 'empty_pass',
1457  self::RESET_PASS => 'reset_pass',
1458  self::ABORTED => 'aborted',
1459  self::CREATE_BLOCKED => 'create_blocked',
1460  self::THROTTLED => 'throttled',
1461  self::USER_BLOCKED => 'user_blocked',
1462  self::NEED_TOKEN => 'need_token',
1463  self::WRONG_TOKEN => 'wrong_token',
1464  self::USER_MIGRATED => 'user_migrated',
1465  ];
1466 
1470  public function __construct( $request = null ) {
1471  wfDeprecated( 'LoginForm', '1.27' );
1472  parent::__construct();
1473  }
1474 
1478  public static function getValidErrorMessages() {
1480  }
1481 
1485  public static function incrementLoginThrottle( $username ) {
1486  wfDeprecated( __METHOD__, "1.27" );
1489  $throttler = new Throttler();
1490  return $throttler->increase( $username, $wgRequest->getIP(), __METHOD__ );
1491  }
1492 
1496  public static function incLoginThrottle( $username ) {
1497  wfDeprecated( __METHOD__, "1.27" );
1498  $res = self::incrementLoginThrottle( $username );
1499  return is_array( $res ) ? true : 0;
1500  }
1501 
1505  public static function clearLoginThrottle( $username ) {
1506  wfDeprecated( __METHOD__, "1.27" );
1509  $throttler = new Throttler();
1510  return $throttler->clear( $username, $wgRequest->getIP() );
1511  }
1512 
1516  public static function getLoginToken() {
1517  wfDeprecated( __METHOD__, '1.27' );
1519  return $wgRequest->getSession()->getToken( '', 'login' )->toString();
1520  }
1521 
1525  public static function setLoginToken() {
1526  wfDeprecated( __METHOD__, '1.27' );
1527  }
1528 
1532  public static function clearLoginToken() {
1533  wfDeprecated( __METHOD__, '1.27' );
1535  $wgRequest->getSession()->resetToken( 'login' );
1536  }
1537 
1541  public static function getCreateaccountToken() {
1542  wfDeprecated( __METHOD__, '1.27' );
1544  return $wgRequest->getSession()->getToken( '', 'createaccount' )->toString();
1545  }
1546 
1550  public static function setCreateaccountToken() {
1551  wfDeprecated( __METHOD__, '1.27' );
1552  }
1553 
1557  public static function clearCreateaccountToken() {
1558  wfDeprecated( __METHOD__, '1.27' );
1560  $wgRequest->getSession()->resetToken( 'createaccount' );
1561  }
1562 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
$wgInitialSessionId
Definition: Setup.php:742
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:1936
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:802
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:1555
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:55
A special page subclass for authentication-related special pages.
$context
Definition: load.php:50
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:2491
$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 If you don't need a full Title object...
Definition: SpecialPage.php:82
static getCanonicalName($name, $validate= 'valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1046
if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:664
getFakeTemplate($msg, $msgType)
Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks.
static newFatal($message)
Factory function for fatal errors.
Definition: StatusValue.php:63
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:209
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:275
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.
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:262
this hook is for auditing only $response
Definition: hooks.txt:802
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.
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:802
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:1936
$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:224
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.
either a unescaped string or a HtmlArmor object 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
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.
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:302
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:953
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:255
static newGood($value=null)
Factory function for good results.
Definition: StatusValue.php:76
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:802
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:203
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:802
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2573
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.
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:1046
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:1098
getFullTitle()
Return the full title, including $par.
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1942
__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:2491
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:181
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:229
successfulAction($direct=false, $extraMessages=null)
setContext($context)
Sets the context this SpecialPage is executed in.
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:2491
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:806
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300
trySubmit()
Attempts to do an authentication step with the submitted data.