MediaWiki  master
LoginSignupSpecialPage.php
Go to the documentation of this file.
1 <?php
35 use Wikimedia\ScopedCallback;
36 
43  protected $mReturnTo;
44  protected $mPosted;
45  protected $mAction;
46  protected $mLanguage;
47  protected $mVariant;
48  protected $mReturnToQuery;
49  protected $mToken;
50  protected $mStickHTTPS;
51  protected $mFromHTTP;
52  protected $mEntryError = '';
53  protected $mEntryErrorType = 'error';
54 
55  protected $mLoaded = false;
56  protected $mLoadedRequest = false;
57  protected $mSecureLoginUrl;
58  private $reasonValidatorResult = null;
59 
61  protected $securityLevel;
62 
68  protected $targetUser;
69 
71  protected $authForm;
72 
73  abstract protected function isSignup();
74 
81  abstract protected function successfulAction( $direct = false, $extraMessages = null );
82 
88  abstract protected function logAuthResult( $success, $status = null );
89 
90  public function __construct( $name, $restriction = '' ) {
91  // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionConfigUsage
93  parent::__construct( $name, $restriction );
94 
95  // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
97  }
98 
99  protected function setRequest( array $data, $wasPosted = null ) {
100  parent::setRequest( $data, $wasPosted );
101  $this->mLoadedRequest = false;
102  }
103 
107  private function loadRequestParameters() {
108  if ( $this->mLoadedRequest ) {
109  return;
110  }
111  $this->mLoadedRequest = true;
112  $request = $this->getRequest();
113 
114  $this->mPosted = $request->wasPosted();
115  $this->mAction = $request->getRawVal( 'action' );
116  $this->mFromHTTP = $request->getBool( 'fromhttp', false )
117  || $request->getBool( 'wpFromhttp', false );
118  $this->mStickHTTPS = $this->getConfig()->get( MainConfigNames::ForceHTTPS )
119  || ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
120  || $request->getBool( 'wpForceHttps', false );
121  $this->mLanguage = $request->getText( 'uselang' );
122  $this->mVariant = $request->getText( 'variant' );
123  $this->mReturnTo = $request->getVal( 'returnto', '' );
124  $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
125  }
126 
132  protected function load( $subPage ) {
133  $this->loadRequestParameters();
134  if ( $this->mLoaded ) {
135  return;
136  }
137  $this->mLoaded = true;
138  $request = $this->getRequest();
139 
140  $securityLevel = $this->getRequest()->getText( 'force' );
141  if (
142  $securityLevel &&
143  MediaWikiServices::getInstance()->getAuthManager()->securitySensitiveOperationStatus(
144  $securityLevel ) === AuthManager::SEC_REAUTH
145  ) {
146  $this->securityLevel = $securityLevel;
147  }
148 
149  $this->loadAuth( $subPage );
150 
151  $this->mToken = $request->getVal( $this->getTokenName() );
152 
153  // Show an error or warning passed on from a previous page
154  $entryError = $this->msg( $request->getVal( 'error', '' ) );
155  $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
156  // bc: provide login link as a parameter for messages where the translation
157  // was not updated
158  $loginreqlink = $this->getLinkRenderer()->makeKnownLink(
159  $this->getPageTitle(),
160  $this->msg( 'loginreqlink' )->text(),
161  [],
162  [
163  'returnto' => $this->mReturnTo,
164  'returntoquery' => $this->mReturnToQuery,
165  'uselang' => $this->mLanguage ?: null,
166  'variant' => $this->mVariant ?: null,
167  'fromhttp' => $this->getConfig()->get( MainConfigNames::SecureLogin ) &&
168  $this->mFromHTTP ? '1' : null,
169  ]
170  );
171 
172  // Only show valid error or warning messages.
173  if ( $entryError->exists()
174  && in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true )
175  ) {
176  $this->mEntryErrorType = 'error';
177  $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
178 
179  } elseif ( $entryWarning->exists()
180  && in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true )
181  ) {
182  $this->mEntryErrorType = 'warning';
183  $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
184  }
185 
186  # 1. When switching accounts, it sucks to get automatically logged out
187  # 2. Do not return to PasswordReset after a successful password change
188  # but goto Wiki start page (Main_Page) instead ( T35997 )
189  $returnToTitle = Title::newFromText( $this->mReturnTo );
190  if ( is_object( $returnToTitle )
191  && ( $returnToTitle->isSpecial( 'Userlogout' )
192  || $returnToTitle->isSpecial( 'PasswordReset' ) )
193  ) {
194  $this->mReturnTo = '';
195  $this->mReturnToQuery = '';
196  }
197  }
198 
199  protected function getPreservedParams( $withToken = false ) {
200  $params = parent::getPreservedParams( $withToken );
201  $params += [
202  'returnto' => $this->mReturnTo ?: null,
203  'returntoquery' => $this->mReturnToQuery ?: null,
204  ];
205  if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->isSignup() ) {
206  $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
207  }
208  return $params;
209  }
210 
211  protected function beforeExecute( $subPage ) {
212  // finish initializing the class before processing the request - T135924
213  $this->loadRequestParameters();
214  return parent::beforeExecute( $subPage );
215  }
216 
221  public function execute( $subPage ) {
222  if ( $this->mPosted ) {
223  $time = microtime( true );
224  $profilingScope = new ScopedCallback( function () use ( $time ) {
225  $time = microtime( true ) - $time;
226  $statsd = MediaWikiServices::getInstance()->getStatsdDataFactory();
227  $statsd->timing( "timing.login.ui.{$this->authAction}", $time * 1000 );
228  } );
229  }
230 
231  $authManager = MediaWikiServices::getInstance()->getAuthManager();
232  $session = SessionManager::getGlobalSession();
233 
234  // Session data is used for various things in the authentication process, so we must make
235  // sure a session cookie or some equivalent mechanism is set.
236  $session->persist();
237  // Explicitly disable cache to ensure cookie blocks may be set (T152462).
238  // (Technically redundant with sessions persisting from this page.)
239  $this->getOutput()->disableClientCache();
240 
241  $this->load( $subPage );
242  $this->setHeaders();
243  $this->checkPermissions();
244 
245  // Make sure the system configuration allows log in / sign up
246  if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
247  if ( !$session->canSetUser() ) {
248  throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
249  $session->getProvider()->describe( $this->getLanguage() )
250  ] );
251  }
252  throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
253  } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
254  throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
255  }
256 
257  /*
258  * In the case where the user is already logged in, and was redirected to
259  * the login form from a page that requires login, do not show the login
260  * page. The use case scenario for this is when a user opens a large number
261  * of tabs, is redirected to the login page on all of them, and then logs
262  * in on one, expecting all the others to work properly.
263  *
264  * However, do show the form if it was visited intentionally (no 'returnto'
265  * is present). People who often switch between several accounts have grown
266  * accustomed to this behavior.
267  *
268  * For temporary users, the form is always shown, since the UI presents
269  * temporary users as not logged in and offers to discard their temporary
270  * account by logging in.
271  *
272  * Also make an exception when force=<level> is set in the URL, which means the user must
273  * reauthenticate for security reasons.
274  */
275  if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
276  ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
277  !$this->getUser()->isTemp() && $this->getUser()->isRegistered()
278  ) {
279  $this->successfulAction();
280  return;
281  }
282 
283  // If logging in and not on HTTPS, either redirect to it or offer a link.
284  if ( $this->getRequest()->getProtocol() !== 'https' ) {
285  $title = $this->getFullTitle();
286  $query = $this->getPreservedParams( false ) + [
287  'title' => null,
288  ( $this->mEntryErrorType === 'error' ? 'error'
289  : 'warning' ) => $this->mEntryError,
290  ] + $this->getRequest()->getQueryValues();
291  $url = $title->getFullURL( $query, false, PROTO_HTTPS );
292  if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->mFromHTTP ) {
293  // Avoid infinite redirect
294  $url = wfAppendQuery( $url, 'fromhttp=1' );
295  $this->getOutput()->redirect( $url );
296  // Since we only do this redir to change proto, always vary
297  $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
298 
299  return;
300  } else {
301  // A wiki without HTTPS login support should set $wgServer to
302  // http://somehost, in which case the secure URL generated
303  // above won't actually start with https://
304  if ( str_starts_with( $url, 'https://' ) ) {
305  $this->mSecureLoginUrl = $url;
306  }
307  }
308  }
309 
310  if ( !$this->isActionAllowed( $this->authAction ) ) {
311  // FIXME how do we explain this to the user? can we handle session loss better?
312  // messages used: authpage-cannot-login, authpage-cannot-login-continue,
313  // authpage-cannot-create, authpage-cannot-create-continue
314  $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
315  return;
316  }
317 
318  if ( $this->canBypassForm( $button_name ) ) {
319  $this->setRequest( [], true );
320  $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
321  if ( $button_name ) {
322  $this->getRequest()->setVal( $button_name, true );
323  }
324  }
325 
326  $status = $this->trySubmit();
327 
328  if ( !$status || !$status->isGood() ) {
329  $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
330  return;
331  }
332 
334  $response = $status->getValue();
335 
336  $returnToUrl = $this->getPageTitle( 'return' )
337  ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
338  switch ( $response->status ) {
339  case AuthenticationResponse::PASS:
340  $this->logAuthResult( true );
341  $this->proxyAccountCreation = $this->isSignup() && $this->getUser()->isNamed();
342  $this->targetUser = User::newFromName( $response->username );
343 
344  if (
345  !$this->proxyAccountCreation
346  && $response->loginRequest
347  && $authManager->canAuthenticateNow()
348  ) {
349  // successful registration; log the user in instantly
350  $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
351  $returnToUrl );
352  if ( $response2->status !== AuthenticationResponse::PASS ) {
353  LoggerFactory::getInstance( 'login' )
354  ->error( 'Could not log in after account creation' );
355  $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
356  break;
357  }
358  }
359 
360  if ( !$this->proxyAccountCreation ) {
361  // Ensure that the context user is the same as the session user.
363  }
364 
365  $this->successfulAction( true );
366  break;
367  case AuthenticationResponse::FAIL:
368  // fall through
369  case AuthenticationResponse::RESTART:
370  unset( $this->authForm );
371  if ( $response->status === AuthenticationResponse::FAIL ) {
372  $action = $this->getDefaultAction( $subPage );
373  $messageType = 'error';
374  } else {
375  $action = $this->getContinueAction( $this->authAction );
376  $messageType = 'warning';
377  }
378  $this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
379  $this->loadAuth( $subPage, $action, true );
380  $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
381  break;
382  case AuthenticationResponse::REDIRECT:
383  unset( $this->authForm );
384  $this->getOutput()->redirect( $response->redirectTarget );
385  break;
386  case AuthenticationResponse::UI:
387  unset( $this->authForm );
388  $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
389  : AuthManager::ACTION_LOGIN_CONTINUE;
390  $this->authRequests = $response->neededRequests;
391  $this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
392  break;
393  default:
394  throw new LogicException( 'invalid AuthenticationResponse' );
395  }
396  }
397 
411  private function canBypassForm( &$button_name ) {
412  $button_name = null;
413  if ( $this->isContinued() ) {
414  return false;
415  }
416  $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
417  foreach ( $fields as $fieldname => $field ) {
418  if ( !isset( $field['type'] ) ) {
419  return false;
420  }
421  if ( !empty( $field['skippable'] ) ) {
422  continue;
423  }
424  if ( $field['type'] === 'button' ) {
425  if ( $button_name !== null ) {
426  $button_name = null;
427  return false;
428  } else {
429  $button_name = $fieldname;
430  }
431  } elseif ( $field['type'] !== 'null' ) {
432  return false;
433  }
434  }
435  return true;
436  }
437 
447  protected function showSuccessPage(
448  $type, $title, $msgname, $injected_html, $extraMessages
449  ) {
450  $out = $this->getOutput();
451  $out->setPageTitle( $title );
452  if ( $msgname ) {
453  $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
454  }
455  if ( $extraMessages ) {
456  $extraMessages = Status::wrap( $extraMessages );
457  $out->addWikiTextAsInterface(
458  $extraMessages->getWikiText( false, false, $this->getLanguage() )
459  );
460  }
461 
462  $out->addHTML( $injected_html );
463 
464  $helper = new LoginHelper( $this->getContext() );
465  $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
466  }
467 
483  public function showReturnToPage(
484  $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
485  ) {
486  $helper = new LoginHelper( $this->getContext() );
487  $helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
488  }
489 
494  protected function setSessionUserForCurrentRequest() {
495  global $wgLang;
496 
497  $context = RequestContext::getMain();
498  $localContext = $this->getContext();
499  if ( $context !== $localContext ) {
500  // remove AuthManagerSpecialPage context hack
501  $this->setContext( $context );
502  }
503 
504  $user = $context->getRequest()->getSession()->getUser();
505 
506  StubGlobalUser::setUser( $user );
507  $context->setUser( $user );
508 
509  $wgLang = $context->getLanguage();
510  }
511 
526  protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
527  $user = $this->getUser();
528  $out = $this->getOutput();
529 
530  // FIXME how to handle empty $requests - restart, or no form, just an error message?
531  // no form would be better for no session type errors, restart is better when can* fails.
532  if ( !$requests ) {
533  $this->authAction = $this->getDefaultAction( $this->subPage );
534  $this->authForm = null;
535  $requests = MediaWikiServices::getInstance()->getAuthManager()
536  ->getAuthenticationRequests( $this->authAction, $user );
537  }
538 
539  // Generic styles and scripts for both login and signup form
540  $out->addModuleStyles( [
541  'mediawiki.ui',
542  'mediawiki.ui.button',
543  'mediawiki.ui.checkbox',
544  'mediawiki.ui.input',
545  'mediawiki.special.userlogin.common.styles'
546  ] );
547  if ( $this->isSignup() ) {
548  // XXX hack pending RL or JS parse() support for complex content messages T27349
549  $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
550  $this->msg( 'createacct-imgcaptcha-help' )->parse() );
551 
552  // Additional styles and scripts for signup form
553  $out->addModules( 'mediawiki.special.createaccount' );
554  $out->addModuleStyles( [
555  'mediawiki.special.userlogin.signup.styles'
556  ] );
557  } else {
558  // Additional styles for login form
559  $out->addModuleStyles( [
560  'mediawiki.special.userlogin.login.styles'
561  ] );
562  }
563  $out->disallowUserJs(); // just in case...
564 
565  $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
566  $form->prepareForm();
567 
568  $submitStatus = Status::newGood();
569  if ( $msg && $msgtype === 'warning' ) {
570  $submitStatus->warning( $msg );
571  } elseif ( $msg && $msgtype === 'error' ) {
572  $submitStatus->fatal( $msg );
573  }
574 
575  // warning header for non-standard workflows (e.g. security reauthentication)
576  if (
577  !$this->isSignup() &&
578  $this->getUser()->isRegistered() &&
579  !$this->getUser()->isTemp() &&
580  $this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE
581  ) {
582  $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
583  $submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
584  }
585 
586  $formHtml = $form->getHTML( $submitStatus );
587 
588  $out->addHTML( $this->getPageHtml( $formHtml ) );
589  }
590 
597  protected function getPageHtml( $formHtml ) {
598  $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
599  [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
600  $languageLinks = $this->getConfig()->get( MainConfigNames::LoginLanguageSelector )
601  ? $this->makeLanguageSelector() : '';
602  $signupStartMsg = $this->msg( 'signupstart' );
603  $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
604  ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
605  if ( $languageLinks ) {
606  $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
607  Html::rawElement( 'p', [], $languageLinks )
608  );
609  }
610 
611  $formBlock = Html::rawElement( 'div', [ 'id' => 'userloginForm' ], $formHtml );
612  $formAndBenefits = $formBlock;
613  if ( $this->isSignup() && $this->showExtraInformation() ) {
614  $benefitsContainerHtml = null;
615  $info = [
616  'context' => $this->getContext(),
617  'form' => $this->authForm,
618  ];
619  $options = [
620  'beforeForm' => false,
621  ];
622  $this->getHookRunner()->onSpecialCreateAccountBenefits(
623  $benefitsContainerHtml, $info, $options
624  );
625  if ( $benefitsContainerHtml === null ) {
626  $benefitsContainerHtml = $this->getBenefitsContainerHtml();
627  }
628  $formAndBenefits = $options['beforeForm']
629  ? ( $benefitsContainerHtml . $formBlock )
630  : ( $formBlock . $benefitsContainerHtml );
631  }
632 
633  return Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
634  $loginPrompt
635  . $languageLinks
636  . $signupStart
637  . $formAndBenefits
638  );
639  }
640 
648  protected function getBenefitsContainerHtml(): string {
649  $benefitsContainer = '';
650  if ( $this->isSignup() && $this->showExtraInformation() ) {
651  // The following messages are used here:
652  // * createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
653  // * createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
654  // * createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
655  $benefitCount = 3;
656  $benefitList = '';
657  for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
658  $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
659  $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->text();
660  $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
661  Html::rawElement( 'h3', [],
662  $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
663  )
664  . Html::rawElement( 'p', [],
665  $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
666  )
667  );
668  }
669  $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
670  Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
671  . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ], $benefitList )
672  );
673  }
674  return $benefitsContainer;
675  }
676 
685  protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
686  // FIXME merge this with parent
687 
688  if ( isset( $this->authForm ) ) {
689  return $this->authForm;
690  }
691 
692  $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
693 
694  // get basic form description from the auth logic
695  $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
696  // this will call onAuthChangeFormFields()
697  $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
698  $this->postProcessFormDescriptor( $formDescriptor, $requests );
699 
700  $context = $this->getContext();
701  if ( $context->getRequest() !== $this->getRequest() ) {
702  // We have overridden the request, need to make sure the form uses that too.
703  $context = new DerivativeContext( $this->getContext() );
704  $context->setRequest( $this->getRequest() );
705  }
706  $form = HTMLForm::factory( 'vform', $formDescriptor, $context );
707 
708  $form->addHiddenField( 'authAction', $this->authAction );
709  if ( $this->mLanguage ) {
710  $form->addHiddenField( 'uselang', $this->mLanguage );
711  }
712  if ( $this->mVariant ) {
713  $form->addHiddenField( 'variant', $this->mVariant );
714  }
715  $form->addHiddenField( 'force', $this->securityLevel );
716  $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
717  $config = $this->getConfig();
718  if ( $config->get( MainConfigNames::SecureLogin ) &&
719  !$config->get( MainConfigNames::ForceHTTPS ) ) {
720  // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
721  if ( !$this->isSignup() ) {
722  $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
723  $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
724  }
725  }
726 
727  // set properties of the form itself
728  $form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
729  $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
730  if ( $this->isSignup() ) {
731  $form->setId( 'userlogin2' );
732  }
733 
734  $form->suppressDefaultSubmit();
735 
736  $this->authForm = $form;
737 
738  return $form;
739  }
740 
742  public function onAuthChangeFormFields(
743  array $requests, array $fieldInfo, array &$formDescriptor, $action
744  ) {
745  $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor,
746  $this->getFieldDefinitions( $fieldInfo ) );
747  }
748 
755  protected function showExtraInformation() {
756  return $this->authAction !== $this->getContinueAction( $this->authAction )
757  && !$this->securityLevel;
758  }
759 
765  protected function getFieldDefinitions( array $fieldInfo ) {
766  $isLoggedIn = $this->getUser()->isRegistered();
767  $continuePart = $this->isContinued() ? 'continue-' : '';
768  $anotherPart = $isLoggedIn ? 'another-' : '';
769  // @phan-suppress-next-line PhanUndeclaredMethod
770  $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
771  $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
772  $secureLoginLink = '';
773  if ( $this->mSecureLoginUrl ) {
774  $secureLoginLink = Html::element( 'a', [
775  'href' => $this->mSecureLoginUrl,
776  'class' => 'mw-ui-flush-right mw-secure',
777  ], $this->msg( 'userlogin-signwithsecure' )->text() );
778  }
779  $usernameHelpLink = '';
780  if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) {
781  $usernameHelpLink = Html::rawElement( 'span', [
782  'class' => 'mw-ui-flush-right',
783  ], $this->msg( 'createacct-helpusername' )->parse() );
784  }
785 
786  if ( $this->isSignup() ) {
787  $config = $this->getConfig();
788  $hideIf = isset( $fieldInfo['mailpassword'] ) ? [ 'hide-if' => [ '===', 'mailpassword', '1' ] ] : [];
789  $fieldDefinitions = [
790  'statusarea' => [
791  // Used by the mediawiki.special.createaccount module for error display.
792  // FIXME: Merge this with HTMLForm's normal status (error) area
793  'type' => 'info',
794  'raw' => true,
795  'default' => Html::element( 'div', [ 'id' => 'mw-createacct-status-area' ] ),
796  'weight' => -105,
797  ],
798  'username' => [
799  'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
800  'id' => 'wpName2',
801  'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
802  : 'userlogin-yourname-ph',
803  ],
804  'mailpassword' => [
805  // create account without providing password, a temporary one will be mailed
806  'type' => 'check',
807  'label-message' => 'createaccountmail',
808  'name' => 'wpCreateaccountMail',
809  'id' => 'wpCreateaccountMail',
810  ],
811  'password' => [
812  'id' => 'wpPassword2',
813  'autocomplete' => 'new-password',
814  'placeholder-message' => 'createacct-yourpassword-ph',
815  'help-message' => 'createacct-useuniquepass',
816  ] + $hideIf,
817  'domain' => [],
818  'retype' => [
819  'type' => 'password',
820  'label-message' => 'createacct-yourpasswordagain',
821  'id' => 'wpRetype',
822  'cssclass' => 'loginPassword',
823  'size' => 20,
824  'autocomplete' => 'new-password',
825  'validation-callback' => function ( $value, $alldata ) {
826  if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
827  if ( !$value ) {
828  return $this->msg( 'htmlform-required' );
829  } elseif ( $value !== $alldata['password'] ) {
830  return $this->msg( 'badretype' );
831  }
832  }
833  return true;
834  },
835  'placeholder-message' => 'createacct-yourpasswordagain-ph',
836  ] + $hideIf,
837  'email' => [
838  'type' => 'email',
839  'label-message' => $config->get( MainConfigNames::EmailConfirmToEdit )
840  ? 'createacct-emailrequired' : 'createacct-emailoptional',
841  'id' => 'wpEmail',
842  'cssclass' => 'loginText',
843  'size' => '20',
844  'maxlength' => 255,
845  'autocomplete' => 'email',
846  // FIXME will break non-standard providers
847  'required' => $config->get( MainConfigNames::EmailConfirmToEdit ),
848  'validation-callback' => function ( $value, $alldata ) {
849  // AuthManager will check most of these, but that will make the auth
850  // session fail and this won't, so nicer to do it this way
851  if ( !$value &&
852  $this->getConfig()->get( MainConfigNames::EmailConfirmToEdit )
853  ) {
854  // no point in allowing registration without email when email is
855  // required to edit
856  return $this->msg( 'noemailtitle' );
857  } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
858  // cannot send password via email when there is no email address
859  return $this->msg( 'noemailcreate' );
860  } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
861  return $this->msg( 'invalidemailaddress' );
862  } elseif ( is_string( $value ) && strlen( $value ) > 255 ) {
863  return $this->msg( 'changeemail-maxlength' );
864  }
865  return true;
866  },
867  // The following messages are used here:
868  // * createacct-email-ph
869  // * createacct-another-email-ph
870  'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
871  ],
872  'realname' => [
873  'type' => 'text',
874  'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
875  : 'prefs-help-realname',
876  'label-message' => 'createacct-realname',
877  'cssclass' => 'loginText',
878  'size' => 20,
879  'id' => 'wpRealName',
880  'autocomplete' => 'name',
881  ],
882  'reason' => [
883  // comment for the user creation log
884  'type' => 'text',
885  'label-message' => 'createacct-reason',
886  'cssclass' => 'loginText',
887  'id' => 'wpReason',
888  'size' => '20',
889  'validation-callback' => function ( $value, $alldata ) {
890  // if the user sets an email address as the user creation reason, confirm that
891  // that was their intent
892  if ( $value && Sanitizer::validateEmail( $value ) ) {
893  if ( $this->reasonValidatorResult !== null ) {
894  return $this->reasonValidatorResult;
895  }
896  $this->reasonValidatorResult = true;
897  $authManager = MediaWikiServices::getInstance()->getAuthManager();
898  if ( !$authManager->getAuthenticationSessionData( 'reason-retry', false ) ) {
899  $authManager->setAuthenticationSessionData( 'reason-retry', true );
900  $this->reasonValidatorResult = $this->msg( 'createacct-reason-confirm' );
901  }
902  return $this->reasonValidatorResult;
903  }
904  return true;
905  },
906  'placeholder-message' => 'createacct-reason-ph',
907  ],
908  'createaccount' => [
909  // submit button
910  'type' => 'submit',
911  // The following messages are used here:
912  // * createacct-submit
913  // * createacct-another-submit
914  // * createacct-continue-submit
915  // * createacct-another-continue-submit
916  'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
917  'submit' )->text(),
918  'name' => 'wpCreateaccount',
919  'id' => 'wpCreateaccount',
920  'weight' => 100,
921  ],
922  ];
923  if ( !$this->msg( 'createacct-username-help' )->isDisabled() ) {
924  $fieldDefinitions['username']['help-message'] = 'createacct-username-help';
925  }
926  } else {
927  // When the user's password is too weak, they might be asked to provide a stronger one
928  // as a followup step. That is a form with only two fields, 'password' and 'retype',
929  // and they should behave more like account creation.
930  $passwordRequest = AuthenticationRequest::getRequestByClass( $this->authRequests,
931  PasswordAuthenticationRequest::class );
932  $changePassword = $passwordRequest && $passwordRequest->action == AuthManager::ACTION_CHANGE;
933  $fieldDefinitions = [
934  'username' => (
935  [
936  'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
937  'id' => 'wpName1',
938  'placeholder-message' => 'userlogin-yourname-ph',
939  ] + ( $changePassword ? [
940  // There is no username field on the AuthManager level when changing
941  // passwords. Fake one because password
942  'baseField' => 'password',
943  'nodata' => true,
944  'readonly' => true,
945  'cssclass' => 'mw-htmlform-hidden-field',
946  ] : [] )
947  ),
948  'password' => (
949  $changePassword ? [
950  'autocomplete' => 'new-password',
951  'placeholder-message' => 'createacct-yourpassword-ph',
952  'help-message' => 'createacct-useuniquepass',
953  ] : [
954  'id' => 'wpPassword1',
955  'autocomplete' => 'current-password',
956  'placeholder-message' => 'userlogin-yourpassword-ph',
957  ]
958  ),
959  'retype' => [
960  'type' => 'password',
961  'autocomplete' => 'new-password',
962  'placeholder-message' => 'createacct-yourpasswordagain-ph',
963  ],
964  'domain' => [],
965  'rememberMe' => [
966  // option for saving the user token to a cookie
967  'type' => 'check',
968  'cssclass' => 'mw-userlogin-rememberme',
969  'name' => 'wpRemember',
970  'label-message' => $this->msg( 'userlogin-remembermypassword' )
971  ->numParams( $expirationDays ),
972  'id' => 'wpRemember',
973  ],
974  'loginattempt' => [
975  // submit button
976  'type' => 'submit',
977  // The following messages are used here:
978  // * pt-login-button
979  // * pt-login-continue-button
980  'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
981  'id' => 'wpLoginAttempt',
982  'weight' => 100,
983  ],
984  'linkcontainer' => [
985  // help link
986  'type' => 'info',
987  'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
988  // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
989  'raw' => true,
990  'default' => Html::element( 'a', [
991  'href' => Skin::makeInternalOrExternalUrl( $this->msg( 'helplogin-url' )
992  ->inContentLanguage()
993  ->text() ),
994  ], $this->msg( 'userlogin-helplink2' )->text() ),
995  'weight' => 200,
996  ],
997  // button for ResetPasswordSecondaryAuthenticationProvider
998  'skipReset' => [
999  'weight' => 110,
1000  'flags' => [],
1001  ],
1002  ];
1003  }
1004 
1005  $fieldDefinitions['username'] += [
1006  'type' => 'text',
1007  'name' => 'wpName',
1008  'cssclass' => 'loginText',
1009  'size' => 20,
1010  'autocomplete' => 'username',
1011  // 'required' => true,
1012  ];
1013  $fieldDefinitions['password'] += [
1014  'type' => 'password',
1015  // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1016  'name' => 'wpPassword',
1017  'cssclass' => 'loginPassword',
1018  'size' => 20,
1019  // 'required' => true,
1020  ];
1021 
1022  if ( $this->mEntryError ) {
1023  $defaultHtml = '';
1024  if ( $this->mEntryErrorType === 'error' ) {
1025  $defaultHtml = Html::errorBox( $this->mEntryError );
1026  } elseif ( $this->mEntryErrorType === 'warning' ) {
1027  $defaultHtml = Html::warningBox( $this->mEntryError );
1028  }
1029  $fieldDefinitions['entryError'] = [
1030  'type' => 'info',
1031  'default' => $defaultHtml,
1032  'raw' => true,
1033  'rawrow' => true,
1034  'weight' => -100,
1035  ];
1036  }
1037  if ( $this->isSignup() && $this->getUser()->isTemp() ) {
1038  $fieldDefinitions['tempWarning'] = [
1039  'type' => 'info',
1040  'default' => Html::warningBox(
1041  $this->msg( 'createacct-temp-warning' )->parse()
1042  ),
1043  'raw' => true,
1044  'rawrow' => true,
1045  'weight' => -90,
1046  ];
1047  }
1048  if ( !$this->showExtraInformation() ) {
1049  unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
1050  }
1051  if ( $this->isSignup() && $this->showExtraInformation() ) {
1052  // blank signup footer for site customization
1053  // uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
1054  $signupendMsg = $this->msg( 'signupend' );
1055  $signupendHttpsMsg = $this->msg( 'signupend-https' );
1056  if ( !$signupendMsg->isDisabled() ) {
1057  $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
1058  $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
1059  ? $signupendHttpsMsg->parse() : $signupendMsg->parse();
1060  $fieldDefinitions['signupend'] = [
1061  'type' => 'info',
1062  'raw' => true,
1063  'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ),
1064  'weight' => 225,
1065  ];
1066  }
1067  }
1068  if ( !$this->isSignup() && $this->showExtraInformation() ) {
1069  $passwordReset = MediaWikiServices::getInstance()->getPasswordReset();
1070  if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
1071  $fieldDefinitions['passwordReset'] = [
1072  'type' => 'info',
1073  'raw' => true,
1074  'cssclass' => 'mw-form-related-link-container',
1075  'default' => $this->getLinkRenderer()->makeLink(
1076  SpecialPage::getTitleFor( 'PasswordReset' ),
1077  $this->msg( 'userlogin-resetpassword-link' )->text()
1078  ),
1079  'weight' => 230,
1080  ];
1081  }
1082 
1083  // Don't show a "create account" link if the user can't.
1084  if ( $this->showCreateAccountLink() ) {
1085  // link to the other action
1086  $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' : 'CreateAccount' );
1087  $linkq = $this->getReturnToQueryStringFragment();
1088  // Pass any language selection on to the mode switch link
1089  if ( $this->mLanguage ) {
1090  $linkq .= '&uselang=' . urlencode( $this->mLanguage );
1091  }
1092  if ( $this->mVariant ) {
1093  $linkq .= '&variant=' . urlencode( $this->mVariant );
1094  }
1095  $isLoggedIn = $this->getUser()->isRegistered()
1096  && !$this->getUser()->isTemp();
1097 
1098  $fieldDefinitions['createOrLogin'] = [
1099  'type' => 'info',
1100  'raw' => true,
1101  'linkQuery' => $linkq,
1102  'default' => function ( $params ) use ( $isLoggedIn, $linkTitle ) {
1103  return Html::rawElement( 'div',
1104  [ 'id' => 'mw-createaccount' . ( !$isLoggedIn ? '-cta' : '' ),
1105  'class' => ( $isLoggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
1106  ( $isLoggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
1107  . Html::element( 'a',
1108  [
1109  'id' => 'mw-createaccount-join' . ( $isLoggedIn ? '-loggedin' : '' ),
1110  'href' => $linkTitle->getLocalURL( $params['linkQuery'] ),
1111  'class' => ( $isLoggedIn ? '' : 'mw-ui-button' ),
1112  'tabindex' => 100,
1113  ],
1114  $this->msg(
1115  $isLoggedIn ? 'userlogin-createanother' : 'userlogin-joinproject'
1116  )->text()
1117  )
1118  );
1119  },
1120  'weight' => 235,
1121  ];
1122  }
1123  }
1124 
1125  return $fieldDefinitions;
1126  }
1127 
1137  protected function hasSessionCookie() {
1138  $config = $this->getConfig();
1139  return $config->get( MainConfigNames::DisableCookieCheck ) || (
1140  $config->get( 'InitialSessionId' ) &&
1141  $this->getRequest()->getSession()->getId() === (string)$config->get( 'InitialSessionId' )
1142  );
1143  }
1144 
1150  protected function getReturnToQueryStringFragment() {
1151  $returnto = '';
1152  if ( $this->mReturnTo !== '' ) {
1153  $returnto = 'returnto=' . wfUrlencode( $this->mReturnTo );
1154  if ( $this->mReturnToQuery !== '' ) {
1155  $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery );
1156  }
1157  }
1158  return $returnto;
1159  }
1160 
1166  private function showCreateAccountLink() {
1167  return $this->isSignup() ||
1168  $this->getContext()->getAuthority()->isAllowed( 'createaccount' );
1169  }
1170 
1171  protected function getTokenName() {
1172  return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1173  }
1174 
1181  protected function makeLanguageSelector() {
1182  $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1183  if ( $msg->isBlank() ) {
1184  return '';
1185  }
1186  $langs = explode( "\n", $msg->text() );
1187  $links = [];
1188  foreach ( $langs as $lang ) {
1189  $lang = trim( $lang, '* ' );
1190  $parts = explode( '|', $lang );
1191  if ( count( $parts ) >= 2 ) {
1192  $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1193  }
1194  }
1195 
1196  return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1197  $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1198  }
1199 
1208  protected function makeLanguageSelectorLink( $text, $lang ) {
1209  if ( $this->getLanguage()->getCode() == $lang ) {
1210  // no link for currently used language
1211  return htmlspecialchars( $text );
1212  }
1213  $query = [ 'uselang' => $lang ];
1214  if ( $this->mVariant ) {
1215  $query['variant'] = $this->mVariant;
1216  }
1217  if ( $this->mReturnTo !== '' ) {
1218  $query['returnto'] = $this->mReturnTo;
1219  $query['returntoquery'] = $this->mReturnToQuery;
1220  }
1221 
1222  $attr = [];
1223  $targetLanguage = MediaWikiServices::getInstance()->getLanguageFactory()
1224  ->getLanguage( $lang );
1225  $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1226 
1227  return $this->getLinkRenderer()->makeKnownLink(
1228  $this->getPageTitle(),
1229  $text,
1230  $attr,
1231  $query
1232  );
1233  }
1234 
1235  protected function getGroupName() {
1236  return 'login';
1237  }
1238 
1243  protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1244  // Pre-fill username (if not creating an account, T46775).
1245  if (
1246  isset( $formDescriptor['username'] ) &&
1247  !isset( $formDescriptor['username']['default'] ) &&
1248  !$this->isSignup()
1249  ) {
1250  $user = $this->getUser();
1251  if ( $user->isRegistered() && !$user->isTemp() ) {
1252  $formDescriptor['username']['default'] = $user->getName();
1253  } else {
1254  $formDescriptor['username']['default'] =
1255  $this->getRequest()->getSession()->suggestLoginUsername();
1256  }
1257  }
1258 
1259  // don't show a submit button if there is nothing to submit (i.e. the only form content
1260  // is other submit buttons, for redirect flows)
1261  if ( !$this->needsSubmitButton( $requests ) ) {
1262  unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1263  }
1264 
1265  if ( !$this->isSignup() ) {
1266  // FIXME HACK don't focus on non-empty field
1267  // maybe there should be an autofocus-if similar to hide-if?
1268  if (
1269  isset( $formDescriptor['username'] )
1270  && empty( $formDescriptor['username']['default'] )
1271  && !$this->getRequest()->getCheck( 'wpName' )
1272  ) {
1273  $formDescriptor['username']['autofocus'] = true;
1274  } elseif ( isset( $formDescriptor['password'] ) ) {
1275  $formDescriptor['password']['autofocus'] = true;
1276  }
1277  }
1278 
1279  $this->addTabIndex( $formDescriptor );
1280  }
1281 }
getUser()
const PROTO_HTTPS
Definition: Defines.php:194
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
$success
getContext()
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
Definition: Setup.php:528
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
getDefaultAction( $subPage)
Get the default action for this special page, if none is given via URL/POST data.
string $subPage
Subpage of the special page.
isContinued()
Returns true if this is not the first step of the authentication.
getRequest()
Get the WebRequest being used for this instance.
trySubmit()
Attempts to do an authentication step with the submitted data.
getToken()
Returns the CSRF token.
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:352
Helper functions for the login form that need to be shared with other special pages (such as CentralA...
Definition: LoginHelper.php:14
static getValidErrorMessages()
Returns an array of all valid error messages.
Definition: LoginHelper.php:45
Holds shared logic for login and account creation pages.
mainLoginForm(array $requests, $msg='', $msgtype='error')
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
logAuthResult( $success, $status=null)
Logs to the authmanager-stats channel.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form....
setSessionUserForCurrentRequest()
Replace some globals to make sure the fact that the user has just been logged in is reflected in the ...
getBenefitsContainerHtml()
The HTML to be shown in the "benefits to signing in / creating an account" section of the signup/logi...
showSuccessPage( $type, $title, $msgname, $injected_html, $extraMessages)
Show the success page.
getFieldDefinitions(array $fieldInfo)
Create a HTMLForm descriptor for the core login fields.
getReturnToQueryStringFragment()
Returns a string that can be appended to the URL (without encoding) to preserve the return target.
User $targetUser
FIXME another flag for passing data.
successfulAction( $direct=false, $extraMessages=null)
showExtraInformation()
Show extra information such as password recovery information, link from login to signup,...
getPageHtml( $formHtml)
Add page elements which are outside the form.
hasSessionCookie()
Check if a session cookie is present.
__construct( $name, $restriction='')
getAuthForm(array $requests, $action, $msg='', $msgType='error')
Generates a form from the given request.
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
makeLanguageSelectorLink( $text, $lang)
Create a language selector link for a particular language Links back to this page preserving type and...
bool $proxyAccountCreation
True if the user if creating an account for someone else.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
postProcessFormDescriptor(&$formDescriptor, $requests)
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
showReturnToPage( $type, $returnTo='', $returnToQuery='', $stickHTTPS=false)
Add a "return to" link or redirect to it.
makeLanguageSelector()
Produce a bar of links which allow the user to select another language during login/registration but ...
load( $subPage)
Load data from request.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
This is a value object to hold authentication response data.
This is a value object for authentication requests with a username and password.
This class is a collection of static functions that serve two purposes:
Definition: Html.php:55
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
This serves as the entry point to the MediaWiki session handling system.
Stub object for the global user ($wgUser) that makes it possible to change the relevant underlying ob...
Represents a title within MediaWiki.
Definition: Title.php:82
static getMain()
Get the RequestContext object associated with the main request.
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1895
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1116
setContext( $context)
Sets the context this SpecialPage is executed in.
getName()
Get the name of this Special Page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
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,...
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getPageTitle( $subpage=false)
Get a self-referential title object.
getFullTitle()
Return the full title, including $par.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:64
static newFromName( $name, $validate='valid')
Definition: User.php:592
$wgUseMediaWikiUIEverywhere
Config variable stub for the UseMediaWikiUIEverywhere setting, for use by phpdoc and IDEs.
if(!isset( $args[0])) $lang