Completed
Branch master (2b1da7)
by
unknown
23:13
created

LoginSignupSpecialPage::getAuthForm()   C

Complexity

Conditions 9
Paths 25

Size

Total Lines 53
Code Lines 31

Duplication

Lines 5
Ratio 9.43 %

Importance

Changes 0
Metric Value
cc 9
eloc 31
nc 25
nop 4
dl 5
loc 53
rs 6.8963
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Holds shared logic for login and account creation pages.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup SpecialPage
22
 */
23
24
use MediaWiki\Auth\AuthenticationRequest;
25
use MediaWiki\Auth\AuthenticationResponse;
26
use MediaWiki\Auth\AuthManager;
27
use MediaWiki\Auth\Throttler;
28
use MediaWiki\Logger\LoggerFactory;
29
use MediaWiki\Session\SessionManager;
30
31
/**
32
 * Holds shared logic for login and account creation pages.
33
 *
34
 * @ingroup SpecialPage
35
 */
36
abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
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
52
	/** @var string */
53
	protected $securityLevel;
54
55
	/** @var bool True if the user if creating an account for someone else. Flag used for internal
56
	 * communication, only set at the very end. */
57
	protected $proxyAccountCreation;
58
	/** @var User FIXME another flag for passing data. */
59
	protected $targetUser;
60
61
	/** @var HTMLForm */
62
	protected $authForm;
63
64
	/** @var FakeAuthTemplate */
65
	protected $fakeTemplate;
66
67
	abstract protected function isSignup();
68
69
	/**
70
	 * @param bool $direct True if the action was successful just now; false if that happened
71
	 *    pre-redirection (so this handler was called already)
72
	 * @param StatusValue|null $extraMessages
73
	 * @return void
74
	 */
75
	abstract protected function successfulAction( $direct = false, $extraMessages = null );
76
77
	/**
78
	 * Logs to the authmanager-stats channel.
79
	 * @param bool $success
80
	 * @param string|null $status Error message key
81
	 */
82
	abstract protected function logAuthResult( $success, $status = null );
83
84
	public function __construct( $name ) {
85
		global $wgUseMediaWikiUIEverywhere;
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
97
	/**
98
	 * Load basic request parameters for this Special page.
99
	 * @param $subPage
100
	 */
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';
0 ignored issues
show
Bug introduced by Gergő Tisza
The property mIsReturn does not seem to exist. Did you mean isReturn?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
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
120
	/**
121
	 * Load data from request.
122
	 * @private
123
	 * @param string $subPage Subpage of Special:Userlogin
124
	 */
125
	protected function load( $subPage ) {
126
		global $wgSecureLogin;
127
128
		$this->loadRequestParameters( $subPage );
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 ?: null,
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
207
		$this->loadRequestParameters( $subPage );
208
		return parent::beforeExecute( $subPage );
209
	}
210
211
	/**
212
	 * @param string|null $subPage
213
	 */
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 View Code Duplication
			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
		$status = $this->trySubmit();
298
299
		if ( !$status || !$status->isGood() ) {
300
			$this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
301
			return;
302
		}
303
304
		/** @var AuthenticationResponse $response */
305
		$response = $status->getValue();
306
307
		$returnToUrl = $this->getPageTitle( 'return' )
308
			->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
309
		switch ( $response->status ) {
310
			case AuthenticationResponse::PASS:
311
				$this->logAuthResult( true );
312
				$this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon();
313
				$this->targetUser = User::newFromName( $response->username );
0 ignored issues
show
Documentation Bug introduced by Gergő Tisza
It seems like \User::newFromName($response->username) can also be of type false. However, the property $targetUser is declared as type object<User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
314
315
				if (
316
					!$this->proxyAccountCreation
317
					&& $response->loginRequest
318
					&& $authManager->canAuthenticateNow()
319
				) {
320
					// successful registration; log the user in instantly
321
					$response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
322
						$returnToUrl );
323
					if ( $response2->status !== AuthenticationResponse::PASS ) {
324
						LoggerFactory::getInstance( 'login' )
325
							->error( 'Could not log in after account creation' );
326
						$this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
327
						break;
328
					}
329
				}
330
331
				if ( !$this->proxyAccountCreation ) {
332
					// Ensure that the context user is the same as the session user.
333
					$this->setSessionUserForCurrentRequest();
334
				}
335
336
				$this->successfulAction( true );
337
				break;
338
			case AuthenticationResponse::FAIL:
339
				// fall through
340
			case AuthenticationResponse::RESTART:
341
				unset( $this->authForm );
342
				if ( $response->status === AuthenticationResponse::FAIL ) {
343
					$action = $this->getDefaultAction( $subPage );
344
					$messageType = 'error';
345
				} else {
346
					$action = $this->getContinueAction( $this->authAction );
347
					$messageType = 'warning';
348
				}
349
				$this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' );
350
				$this->loadAuth( $subPage, $action, true );
351
				$this->mainLoginForm( $this->authRequests, $response->message, $messageType );
0 ignored issues
show
Bug introduced by Gergő Tisza
It seems like $response->message can be null; however, mainLoginForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
352
				break;
353
			case AuthenticationResponse::REDIRECT:
354
				unset( $this->authForm );
355
				$this->getOutput()->redirect( $response->redirectTarget );
356
				break;
357
			case AuthenticationResponse::UI:
358
				unset( $this->authForm );
359
				$this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
360
					: AuthManager::ACTION_LOGIN_CONTINUE;
361
				$this->authRequests = $response->neededRequests;
362
				$this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
0 ignored issues
show
Bug introduced by Gergő Tisza
It seems like $response->message can be null; however, mainLoginForm() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
363
				break;
364
			default:
365
				throw new LogicException( 'invalid AuthenticationResponse' );
366
		}
367
	}
368
369
	/**
370
	 * Show the success page.
371
	 *
372
	 * @param string $type Condition of return to; see `executeReturnTo`
373
	 * @param string|Message $title Page's title
374
	 * @param string $msgname
375
	 * @param string $injected_html
376
	 * @param StatusValue|null $extraMessages
377
	 */
378
	protected function showSuccessPage(
379
		$type, $title, $msgname, $injected_html, $extraMessages
380
	) {
381
		$out = $this->getOutput();
382
		$out->setPageTitle( $title );
383
		if ( $msgname ) {
384
			$out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
385
		}
386
		if ( $extraMessages ) {
387
			$extraMessages = Status::wrap( $extraMessages );
388
			$out->addWikiText( $extraMessages->getWikiText() );
389
		}
390
391
		$out->addHTML( $injected_html );
392
393
		$helper = new LoginHelper( $this->getContext() );
394
		$helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS );
395
	}
396
397
	/**
398
	 * Add a "return to" link or redirect to it.
399
	 * Extensions can use this to reuse the "return to" logic after
400
	 * inject steps (such as redirection) into the login process.
401
	 *
402
	 * @param string $type One of the following:
403
	 *    - error: display a return to link ignoring $wgRedirectOnLogin
404
	 *    - signup: display a return to link using $wgRedirectOnLogin if needed
405
	 *    - success: display a return to link using $wgRedirectOnLogin if needed
406
	 *    - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
407
	 * @param string $returnTo
408
	 * @param array|string $returnToQuery
409
	 * @param bool $stickHTTPS Keep redirect link on HTTPS
410
	 * @since 1.22
411
	 */
412
	public function showReturnToPage(
413
		$type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false
414
	) {
415
		$helper = new LoginHelper( $this->getContext() );
416
		$helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS );
417
	}
418
419
	/**
420
	 * Replace some globals to make sure the fact that the user has just been logged in is
421
	 * reflected in the current request.
422
	 * @param User $user
0 ignored issues
show
Bug introduced by Gergő Tisza
There is no parameter named $user. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
423
	 */
424
	protected function setSessionUserForCurrentRequest() {
425
		global $wgUser, $wgLang;
426
427
		$context = RequestContext::getMain();
428
		$localContext = $this->getContext();
429
		if ( $context !== $localContext ) {
430
			// remove AuthManagerSpecialPage context hack
431
			$this->setContext( $context );
432
		}
433
434
		$user = $context->getRequest()->getSession()->getUser();
435
436
		$wgUser = $user;
437
		$context->setUser( $user );
438
439
		$code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) );
440
		$userLang = Language::factory( $code );
441
		$wgLang = $userLang;
442
		$context->setLanguage( $userLang );
443
	}
444
445
	/**
446
	 * @param AuthenticationRequest[] $requests A list of AuthorizationRequest objects,
447
	 *   used to generate the form fields. An empty array means a fatal error
448
	 *   (authentication cannot continue).
449
	 * @param string|Message $msg
450
	 * @param string $msgtype
451
	 * @throws ErrorPageError
452
	 * @throws Exception
453
	 * @throws FatalError
454
	 * @throws MWException
455
	 * @throws PermissionsError
456
	 * @throws ReadOnlyError
457
	 * @private
458
	 */
459
	protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
460
		$titleObj = $this->getPageTitle();
0 ignored issues
show
Unused Code introduced by Gergő Tisza
$titleObj is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
461
		$user = $this->getUser();
462
		$out = $this->getOutput();
463
464
		// FIXME how to handle empty $requests - restart, or no form, just an error message?
465
		// no form would be better for no session type errors, restart is better when can* fails.
466
		if ( !$requests ) {
467
			$this->authAction = $this->getDefaultAction( $this->subPage );
468
			$this->authForm = null;
469
			$requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user );
470
		}
471
472
		// Generic styles and scripts for both login and signup form
473
		$out->addModuleStyles( [
474
			'mediawiki.ui',
475
			'mediawiki.ui.button',
476
			'mediawiki.ui.checkbox',
477
			'mediawiki.ui.input',
478
			'mediawiki.special.userlogin.common.styles'
479
		] );
480
		if ( $this->isSignup() ) {
481
			// XXX hack pending RL or JS parse() support for complex content messages T27349
482
			$out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
483
				$this->msg( 'createacct-imgcaptcha-help' )->parse() );
484
485
			// Additional styles and scripts for signup form
486
			$out->addModules( [
487
				'mediawiki.special.userlogin.signup.js'
488
			] );
489
			$out->addModuleStyles( [
490
				'mediawiki.special.userlogin.signup.styles'
491
			] );
492
		} else {
493
			// Additional styles for login form
494
			$out->addModuleStyles( [
495
				'mediawiki.special.userlogin.login.styles'
496
			] );
497
		}
498
		$out->disallowUserJs(); // just in case...
499
500
		$form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
501
		$form->prepareForm();
502
503
		$submitStatus = Status::newGood();
504
		if ( $msg && $msgtype === 'warning' ) {
505
			$submitStatus->warning( $msg );
506
		} elseif ( $msg && $msgtype === 'error' ) {
507
			$submitStatus->fatal( $msg );
508
		}
509
510
		// warning header for non-standard workflows (e.g. security reauthentication)
511
		if (
512
			!$this->isSignup() &&
513
			$this->getUser()->isLoggedIn() &&
514
			$this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE
515
		) {
516
			$reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
517
			$submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
518
		}
519
520
		$formHtml = $form->getHTML( $submitStatus );
521
522
		$out->addHTML( $this->getPageHtml( $formHtml ) );
523
	}
524
525
	/**
526
	 * Add page elements which are outside the form.
527
	 * FIXME this should probably be a template, but use a sane language (handlebars?)
528
	 * @param string $formHtml
529
	 * @return string
530
	 */
531
	protected function getPageHtml( $formHtml ) {
532
		global $wgLoginLanguageSelector;
533
534
		$loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
535
			[ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
536
		$languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : '';
537
		$signupStartMsg = $this->msg( 'signupstart' );
538
		$signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
539
			? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
540
		if ( $languageLinks ) {
541
			$languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
542
				Html::rawElement( 'p', [], $languageLinks )
543
			);
544
		}
545
546
		$benefitsContainer = '';
547
		if ( $this->isSignup() && $this->showExtraInformation() ) {
548
			// messages used:
549
			// createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
550
			// createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
551
			// createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
552
			$benefitCount = 3;
553
			$benefitList = '';
554
			for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
555
				$headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
556
				$iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped();
557
				$benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
558
					Html::rawElement( 'h3', [],
559
						$this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
560
					)
561
					. Html::rawElement( 'p', [],
562
						$this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
563
					)
564
				);
565
			}
566
			$benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
567
				Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() )
568
				. Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ],
569
					$benefitList
570
				)
571
			);
572
		}
573
574
		$html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
575
			$loginPrompt
576
			. $languageLinks
577
			. $signupStart
578
			. Html::rawElement( 'div', [ 'id' => 'userloginForm' ],
579
				$formHtml
580
			)
581
			. $benefitsContainer
582
		);
583
584
		return $html;
585
	}
586
587
	/**
588
	 * Generates a form from the given request.
589
	 * @param AuthenticationRequest[] $requests
590
	 * @param string $action AuthManager action name
591
	 * @param string|Message $msg
592
	 * @param string $msgType
593
	 * @return HTMLForm
594
	 */
595
	protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) {
596
		global $wgSecureLogin, $wgLoginLanguageSelector;
597
		// FIXME merge this with parent
598
599
		if ( isset( $this->authForm ) ) {
600
			return $this->authForm;
601
		}
602
603
		$usingHTTPS = $this->getRequest()->getProtocol() === 'https';
604
605
		// get basic form description from the auth logic
606
		$fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
607
		$fakeTemplate = $this->getFakeTemplate( $msg, $msgType );
608
		$this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
609
		// this will call onAuthChangeFormFields()
610
		$formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
611
		$this->postProcessFormDescriptor( $formDescriptor, $requests );
612
613
		$context = $this->getContext();
614 View Code Duplication
		if ( $context->getRequest() !== $this->getRequest() ) {
615
			// We have overridden the request, need to make sure the form uses that too.
616
			$context = new DerivativeContext( $this->getContext() );
617
			$context->setRequest( $this->getRequest() );
618
		}
619
		$form = HTMLForm::factory( 'vform', $formDescriptor, $context );
620
621
		$form->addHiddenField( 'authAction', $this->authAction );
622
		if ( $wgLoginLanguageSelector && $this->mLanguage ) {
623
			$form->addHiddenField( 'uselang', $this->mLanguage );
624
		}
625
		$form->addHiddenField( 'force', $this->securityLevel );
626
		$form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
627
		if ( $wgSecureLogin ) {
628
			// If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
629
			if ( !$this->isSignup() ) {
630
				$form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
631
				$form->addHiddenField( 'wpFromhttp', $usingHTTPS );
632
			}
633
		}
634
635
		// set properties of the form itself
636
		$form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) );
637
		$form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
638
		if ( $this->isSignup() ) {
639
			$form->setId( 'userlogin2' );
640
		}
641
642
		$form->suppressDefaultSubmit();
643
644
		$this->authForm = $form;
645
646
		return $form;
647
	}
648
649
	/**
650
	 * Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks.
651
	 * @param string|Message $msg
652
	 * @param string $msgType
653
	 * @return FakeAuthTemplate
654
	 */
655
	protected function getFakeTemplate( $msg, $msgType ) {
656
		global $wgAuth, $wgEnableEmail, $wgHiddenPrefs, $wgEmailConfirmToEdit, $wgEnableUserEmail,
657
			   $wgSecureLogin, $wgLoginLanguageSelector, $wgPasswordResetRoutes;
658
659
		// make a best effort to get the value of fields which used to be fixed in the old login
660
		// template but now might or might not exist depending on what providers are used
661
		$request = $this->getRequest();
662
		$data = (object)[
663
			'mUsername' => $request->getText( 'wpName' ),
664
			'mPassword' => $request->getText( 'wpPassword' ),
665
			'mRetype' => $request->getText( 'wpRetype' ),
666
			'mEmail' => $request->getText( 'wpEmail' ),
667
			'mRealName' => $request->getText( 'wpRealName' ),
668
			'mDomain' => $request->getText( 'wpDomain' ),
669
			'mReason' => $request->getText( 'wpReason' ),
670
			'mRemember' => $request->getCheck( 'wpRemember' ),
671
		];
672
673
		// Preserves a bunch of logic from the old code that was rewritten in getAuthForm().
674
		// There is no code reuse to make this easier to remove .
675
		// If an extension tries to change any of these values, they are out of luck - we only
676
		// actually use the domain/usedomain/domainnames, extraInput and extrafields keys.
677
678
		$titleObj = $this->getPageTitle();
679
		$user = $this->getUser();
680
		$template = new FakeAuthTemplate();
681
682
		// Pre-fill username (if not creating an account, bug 44775).
683
		if ( $data->mUsername == '' && $this->isSignup() ) {
684
			if ( $user->isLoggedIn() ) {
685
				$data->mUsername = $user->getName();
686
			} else {
687
				$data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername();
688
			}
689
		}
690
691
		if ( $this->isSignup() ) {
692
			// Must match number of benefits defined in messages
693
			$template->set( 'benefitCount', 3 );
694
695
			$q = 'action=submitlogin&type=signup';
696
			$linkq = 'type=login';
697
		} else {
698
			$q = 'action=submitlogin&type=login';
699
			$linkq = 'type=signup';
700
		}
701
702
		if ( $this->mReturnTo !== '' ) {
703
			$returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
704
			if ( $this->mReturnToQuery !== '' ) {
705
				$returnto .= '&returntoquery=' .
706
							 wfUrlencode( $this->mReturnToQuery );
707
			}
708
			$q .= $returnto;
709
			$linkq .= $returnto;
710
		}
711
712
		# Don't show a "create account" link if the user can't.
713
		if ( $this->showCreateAccountLink() ) {
714
			# Pass any language selection on to the mode switch link
715
			if ( $wgLoginLanguageSelector && $this->mLanguage ) {
716
				$linkq .= '&uselang=' . $this->mLanguage;
717
			}
718
			// Supply URL, login template creates the button.
719
			$template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
720
		} else {
721
			$template->set( 'link', '' );
722
		}
723
724
		$resetLink = $this->isSignup()
725
			? null
726
			: is_array( $wgPasswordResetRoutes )
727
			  && in_array( true, array_values( $wgPasswordResetRoutes ), true );
728
729
		$template->set( 'header', '' );
730
		$template->set( 'formheader', '' );
731
		$template->set( 'skin', $this->getSkin() );
732
733
		$template->set( 'name', $data->mUsername );
734
		$template->set( 'password', $data->mPassword );
735
		$template->set( 'retype', $data->mRetype );
736
		$template->set( 'createemailset', false ); // no easy way to get that from AuthManager
737
		$template->set( 'email', $data->mEmail );
738
		$template->set( 'realname', $data->mRealName );
739
		$template->set( 'domain', $data->mDomain );
740
		$template->set( 'reason', $data->mReason );
741
		$template->set( 'remember', $data->mRemember );
742
743
		$template->set( 'action', $titleObj->getLocalURL( $q ) );
744
		$template->set( 'message', $msg );
745
		$template->set( 'messagetype', $msgType );
746
		$template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
747
		$template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) );
748
		$template->set( 'useemail', $wgEnableEmail );
749
		$template->set( 'emailrequired', $wgEmailConfirmToEdit );
750
		$template->set( 'emailothers', $wgEnableUserEmail );
751
		$template->set( 'canreset', $wgAuth->allowPasswordChange() );
752
		$template->set( 'resetlink', $resetLink );
753
		$template->set( 'canremember', $request->getSession()->getProvider()
754
			->getRememberUserDuration() !== null );
755
		$template->set( 'usereason', $user->isLoggedIn() );
756
		$template->set( 'cansecurelogin', ( $wgSecureLogin ) );
757
		$template->set( 'stickhttps', (int)$this->mStickHTTPS );
758
		$template->set( 'loggedin', $user->isLoggedIn() );
759
		$template->set( 'loggedinuser', $user->getName() );
760
		$template->set( 'token', $this->getToken()->toString() );
761
762
		$action = $this->isSignup() ? 'signup' : 'login';
763
		$wgAuth->modifyUITemplate( $template, $action );
764
765
		$oldTemplate = $template;
766
767
		// Both Hooks::run are explicit here to make findHooks.php happy
768
		if ( $this->isSignup() ) {
769
			Hooks::run( 'UserCreateForm', [ &$template ] );
770
			if ( $oldTemplate !== $template ) {
771
				wfDeprecated( "reference in UserCreateForm hook", '1.27' );
772
			}
773
		} else {
774
			Hooks::run( 'UserLoginForm', [ &$template ] );
775
			if ( $oldTemplate !== $template ) {
776
				wfDeprecated( "reference in UserLoginForm hook", '1.27' );
777
			}
778
		}
779
780
		return $template;
781
	}
782
783
	public function onAuthChangeFormFields(
784
		array $requests, array $fieldInfo, array &$formDescriptor, $action
785
	) {
786
		$coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate );
787
		$specialFields = array_merge( [ 'extraInput' ],
788
			array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) );
789
790
		// keep the ordering from getCoreFieldDescriptors() where there is no explicit weight
791
		foreach ( $coreFieldDescriptors as $fieldName => $coreField ) {
792
			$requestField = isset( $formDescriptor[$fieldName] ) ?
793
				$formDescriptor[$fieldName] : [];
794
795
			// remove everything that is not in the fieldinfo, is not marked as a supplemental field
796
			// to something in the fieldinfo, is not B/C for the pre-AuthManager templates,
797
			// and is not an info field or a submit button
798
			if (
799
				!isset( $fieldInfo[$fieldName] )
800
				&& (
801
					!isset( $coreField['baseField'] )
802
					|| !isset( $fieldInfo[$coreField['baseField']] )
803
				)
804
				&& !in_array( $fieldName, $specialFields, true )
805
				&& (
806
					!isset( $coreField['type'] )
807
					|| !in_array( $coreField['type'], [ 'submit', 'info' ], true )
808
				)
809
			) {
810
				$coreFieldDescriptors[$fieldName] = null;
811
				continue;
812
			}
813
814
			// core message labels should always take priority
815
			if (
816
				isset( $coreField['label'] )
817
				|| isset( $coreField['label-message'] )
818
				|| isset( $coreField['label-raw'] )
819
			) {
820
				unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] );
821
			}
822
823
			$coreFieldDescriptors[$fieldName] += $requestField;
824
		}
825
826
		$formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor );
827
		return true;
828
	}
829
830
	/**
831
	 * Show extra information such as password recovery information, link from login to signup,
832
	 * CTA etc? Such information should only be shown on the "landing page", ie. when the user
833
	 * is at the first step of the authentication process.
834
	 * @return bool
835
	 */
836
	protected function showExtraInformation() {
837
		return $this->authAction !== $this->getContinueAction( $this->authAction )
838
			&& !$this->securityLevel;
839
	}
840
841
	/**
842
	 * Create a HTMLForm descriptor for the core login fields.
843
	 * @param FakeAuthTemplate $template B/C data (not used but needed by getBCFieldDefinitions)
844
	 * @return array
845
	 */
846
	protected function getFieldDefinitions( $template ) {
847
		global $wgEmailConfirmToEdit, $wgLoginLanguageSelector;
848
849
		$isLoggedIn = $this->getUser()->isLoggedIn();
850
		$continuePart = $this->isContinued() ? 'continue-' : '';
851
		$anotherPart = $isLoggedIn ? 'another-' : '';
852
		$expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
853
		$expirationDays = ceil( $expiration / ( 3600 * 24 ) );
854
		$secureLoginLink = '';
855
		if ( $this->mSecureLoginUrl ) {
856
			$secureLoginLink = Html::element( 'a', [
857
				'href' => $this->mSecureLoginUrl,
858
				'class' => 'mw-ui-flush-right mw-secure',
859
			], $this->msg( 'userlogin-signwithsecure' )->text() );
860
		}
861
		$usernameHelpLink = '';
862
		if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) {
863
			$usernameHelpLink = Html::rawElement( 'span', [
864
				'class' => 'mw-ui-flush-right',
865
			], $this->msg( 'createacct-helpusername' )->parse() );
866
		}
867
868
		if ( $this->isSignup() ) {
869
			$fieldDefinitions = [
870
				'statusarea' => [
871
					// used by the mediawiki.special.userlogin.signup.js module for error display
872
					// FIXME merge this with HTMLForm's normal status (error) area
873
					'type' => 'info',
874
					'raw' => true,
875
					'default' => Html::element( 'div', [ 'id' => 'mw-createacct-status-area' ] ),
876
					'weight' => -105,
877
				],
878
				'username' => [
879
					'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
880
					'id' => 'wpName2',
881
					'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
882
						: 'userlogin-yourname-ph',
883
				],
884
				'mailpassword' => [
885
					// create account without providing password, a temporary one will be mailed
886
					'type' => 'check',
887
					'label-message' => 'createaccountmail',
888
					'name' => 'wpCreateaccountMail',
889
					'id' => 'wpCreateaccountMail',
890
				],
891
				'password' => [
892
					'id' => 'wpPassword2',
893
					'placeholder-message' => 'createacct-yourpassword-ph',
894
					'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
895
				],
896
				'domain' => [],
897
				'retype' => [
898
					'baseField' => 'password',
899
					'type' => 'password',
900
					'label-message' => 'createacct-yourpasswordagain',
901
					'id' => 'wpRetype',
902
					'cssclass' => 'loginPassword',
903
					'size' => 20,
904
					'validation-callback' => function ( $value, $alldata ) {
905
						if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
906
							if ( !$value ) {
907
								return $this->msg( 'htmlform-required' );
908
							} elseif ( $value !== $alldata['password'] ) {
909
								return $this->msg( 'badretype' );
910
							}
911
						}
912
						return true;
913
					},
914
					'hide-if' => [ '===', 'wpCreateaccountMail', '1' ],
915
					'placeholder-message' => 'createacct-yourpasswordagain-ph',
916
				],
917
				'email' => [
918
					'type' => 'email',
919
					'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired'
920
						: 'createacct-emailoptional',
921
					'id' => 'wpEmail',
922
					'cssclass' => 'loginText',
923
					'size' => '20',
924
					// FIXME will break non-standard providers
925
					'required' => $wgEmailConfirmToEdit,
926
					'validation-callback' => function ( $value, $alldata ) {
927
						global $wgEmailConfirmToEdit;
928
929
						// AuthManager will check most of these, but that will make the auth
930
						// session fail and this won't, so nicer to do it this way
931
						if ( !$value && $wgEmailConfirmToEdit ) {
932
							// no point in allowing registration without email when email is
933
							// required to edit
934
							return $this->msg( 'noemailtitle' );
935
						} elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
936
							// cannot send password via email when there is no email address
937
							return $this->msg( 'noemailcreate' );
938
						} elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
0 ignored issues
show
Bug Best Practice introduced by Gergő Tisza
The expression \Sanitizer::validateEmail($value) of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
939
							return $this->msg( 'invalidemailaddress' );
940
						}
941
						return true;
942
					},
943
					'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
944
				],
945
				'realname' => [
946
					'type' => 'text',
947
					'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
948
						: 'prefs-help-realname',
949
					'label-message' => 'createacct-realname',
950
					'cssclass' => 'loginText',
951
					'size' => 20,
952
					'id' => 'wpRealName',
953
				],
954
				'reason' => [
955
					// comment for the user creation log
956
					'type' => 'text',
957
					'label-message' => 'createacct-reason',
958
					'cssclass' => 'loginText',
959
					'id' => 'wpReason',
960
					'size' => '20',
961
					'placeholder-message' => 'createacct-reason-ph',
962
				],
963
				'extrainput' => [], // placeholder for fields coming from the template
964
				'createaccount' => [
965
					// submit button
966
					'type' => 'submit',
967
					'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
968
						'submit' )->text(),
969
					'name' => 'wpCreateaccount',
970
					'id' => 'wpCreateaccount',
971
					'weight' => 100,
972
				],
973
			];
974
		} else {
975
			$fieldDefinitions = [
976
				'username' => [
977
					'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
978
					'id' => 'wpName1',
979
					'placeholder-message' => 'userlogin-yourname-ph',
980
				],
981
				'password' => [
982
					'id' => 'wpPassword1',
983
					'placeholder-message' => 'userlogin-yourpassword-ph',
984
				],
985
				'domain' => [],
986
				'extrainput' => [],
987
				'rememberMe' => [
988
					// option for saving the user token to a cookie
989
					'type' => 'check',
990
					'name' => 'wpRemember',
991
					'label-message' => $this->msg( 'userlogin-remembermypassword' )
992
						->numParams( $expirationDays ),
993
					'id' => 'wpRemember',
994
				],
995
				'loginattempt' => [
996
					// submit button
997
					'type' => 'submit',
998
					'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
999
					'id' => 'wpLoginAttempt',
1000
					'weight' => 100,
1001
				],
1002
				'linkcontainer' => [
1003
					// help link
1004
					'type' => 'info',
1005
					'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1006
					// 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1007
					'raw' => true,
1008
					'default' => Html::element( 'a', [
1009
						'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' )
1010
							->inContentLanguage()
1011
							->text() ),
1012
					], $this->msg( 'userlogin-helplink2' )->text() ),
1013
					'weight' => 200,
1014
				],
1015
				// button for ResetPasswordSecondaryAuthenticationProvider
1016
				'skipReset' => [
1017
					'weight' => 110,
1018
					'flags' => [],
1019
				],
1020
			];
1021
		}
1022
1023
		$fieldDefinitions['username'] += [
1024
			'type' => 'text',
1025
			'name' => 'wpName',
1026
			'cssclass' => 'loginText',
1027
			'size' => 20,
1028
			// 'required' => true,
1029
		];
1030
		$fieldDefinitions['password'] += [
1031
			'type' => 'password',
1032
			// 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1033
			'name' => 'wpPassword',
1034
			'cssclass' => 'loginPassword',
1035
			'size' => 20,
1036
			// 'required' => true,
1037
		];
1038
1039
		if ( $template->get( 'header' ) || $template->get( 'formheader' ) ) {
1040
			// B/C for old extensions that haven't been converted to AuthManager (or have been
1041
			// but somebody is using the old version) and still use templates via the
1042
			// UserCreateForm/UserLoginForm hook.
1043
			// 'header' used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
1044
			// 'formheader' used by MobileFrontend
1045
			$fieldDefinitions['header'] = [
1046
				'type' => 'info',
1047
				'raw' => true,
1048
				'default' => $template->get( 'header' ) ?: $template->get( 'formheader' ),
1049
				'weight' => - 110,
1050
			];
1051
		}
1052
		if ( $this->mEntryError ) {
1053
			$fieldDefinitions['entryError'] = [
1054
				'type' => 'info',
1055
				'default' => Html::rawElement( 'div', [ 'class' => $this->mEntryErrorType . 'box', ],
1056
					$this->mEntryError ),
1057
				'raw' => true,
1058
				'rawrow' => true,
1059
				'weight' => -100,
1060
			];
1061
		}
1062
		if ( !$this->showExtraInformation() ) {
1063
			unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
1064
		}
1065
		if ( $this->isSignup() && $this->showExtraInformation() ) {
1066
			// blank signup footer for site customization
1067
			// uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
1068
			$signupendMsg = $this->msg( 'signupend' );
1069
			$signupendHttpsMsg = $this->msg( 'signupend-https' );
1070
			if ( !$signupendMsg->isDisabled() ) {
1071
				$usingHTTPS = $this->getRequest()->getProtocol() === 'https';
1072
				$signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
1073
					? $signupendHttpsMsg ->parse() : $signupendMsg->parse();
1074
				$fieldDefinitions['signupend'] = [
1075
					'type' => 'info',
1076
					'raw' => true,
1077
					'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ),
1078
					'weight' => 225,
1079
				];
1080
			}
1081
		}
1082
		if ( !$this->isSignup() && $this->showExtraInformation() ) {
1083
			$passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() );
1084
			if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
1085
				$fieldDefinitions['passwordReset'] = [
1086
					'type' => 'info',
1087
					'raw' => true,
1088
					'cssclass' => 'mw-form-related-link-container',
1089
					'default' => Linker::link(
1090
						SpecialPage::getTitleFor( 'PasswordReset' ),
1091
						$this->msg( 'userlogin-resetpassword-link' )->escaped()
1092
					),
1093
					'weight' => 230,
1094
				];
1095
			}
1096
1097
			// Don't show a "create account" link if the user can't.
1098
			if ( $this->showCreateAccountLink() ) {
1099
				// link to the other action
1100
				$linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' );
1101
				$linkq = $this->getReturnToQueryStringFragment();
1102
				// Pass any language selection on to the mode switch link
1103
				if ( $wgLoginLanguageSelector && $this->mLanguage ) {
1104
					$linkq .= '&uselang=' . $this->mLanguage;
1105
				}
1106
				$loggedIn = $this->getUser()->isLoggedIn();
1107
1108
				$fieldDefinitions['createOrLogin'] = [
1109
					'type' => 'info',
1110
					'raw' => true,
1111
					'linkQuery' => $linkq,
1112
					'default' => function ( $params ) use ( $loggedIn, $linkTitle ) {
1113
						return Html::rawElement( 'div',
1114
							[ 'id' => 'mw-createaccount' . ( !$loggedIn ? '-cta' : '' ),
1115
								'class' => ( $loggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
1116
							( $loggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
1117
							. Html::element( 'a',
1118
								[