Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/installer/WebInstaller.php (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Core installer web interface.
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 Deployment
22
 */
23
24
/**
25
 * Class for the core installer web interface.
26
 *
27
 * @ingroup Deployment
28
 * @since 1.17
29
 */
30
class WebInstaller extends Installer {
31
32
	/**
33
	 * @var WebInstallerOutput
34
	 */
35
	public $output;
36
37
	/**
38
	 * WebRequest object.
39
	 *
40
	 * @var WebRequest
41
	 */
42
	public $request;
43
44
	/**
45
	 * Cached session array.
46
	 *
47
	 * @var array[]
48
	 */
49
	protected $session;
50
51
	/**
52
	 * Captured PHP error text. Temporary.
53
	 *
54
	 * @var string[]
55
	 */
56
	protected $phpErrors;
57
58
	/**
59
	 * The main sequence of page names. These will be displayed in turn.
60
	 *
61
	 * To add a new installer page:
62
	 *    * Add it to this WebInstaller::$pageSequence property
63
	 *    * Add a "config-page-<name>" message
64
	 *    * Add a "WebInstaller<name>" class
65
	 *
66
	 * @var string[]
67
	 */
68
	public $pageSequence = [
69
		'Language',
70
		'ExistingWiki',
71
		'Welcome',
72
		'DBConnect',
73
		'Upgrade',
74
		'DBSettings',
75
		'Name',
76
		'Options',
77
		'Install',
78
		'Complete',
79
	];
80
81
	/**
82
	 * Out of sequence pages, selectable by the user at any time.
83
	 *
84
	 * @var string[]
85
	 */
86
	protected $otherPages = [
87
		'Restart',
88
		'Readme',
89
		'ReleaseNotes',
90
		'Copying',
91
		'UpgradeDoc', // Can't use Upgrade due to Upgrade step
92
	];
93
94
	/**
95
	 * Array of pages which have declared that they have been submitted, have validated
96
	 * their input, and need no further processing.
97
	 *
98
	 * @var bool[]
99
	 */
100
	protected $happyPages;
101
102
	/**
103
	 * List of "skipped" pages. These are pages that will automatically continue
104
	 * to the next page on any GET request. To avoid breaking the "back" button,
105
	 * they need to be skipped during a back operation.
106
	 *
107
	 * @var bool[]
108
	 */
109
	protected $skippedPages;
110
111
	/**
112
	 * Flag indicating that session data may have been lost.
113
	 *
114
	 * @var bool
115
	 */
116
	public $showSessionWarning = false;
117
118
	/**
119
	 * Numeric index of the page we're on
120
	 *
121
	 * @var int
122
	 */
123
	protected $tabIndex = 1;
124
125
	/**
126
	 * Name of the page we're on
127
	 *
128
	 * @var string
129
	 */
130
	protected $currentPageName;
131
132
	/**
133
	 * Constructor.
134
	 *
135
	 * @param WebRequest $request
136
	 */
137
	public function __construct( WebRequest $request ) {
138
		parent::__construct();
139
		$this->output = new WebInstallerOutput( $this );
140
		$this->request = $request;
141
142
		// Add parser hooks
143
		global $wgParser;
144
		$wgParser->setHook( 'downloadlink', [ $this, 'downloadLinkHook' ] );
145
		$wgParser->setHook( 'doclink', [ $this, 'docLink' ] );
146
	}
147
148
	/**
149
	 * Main entry point.
150
	 *
151
	 * @param array[] $session Initial session array
152
	 *
153
	 * @return array[] New session array
154
	 */
155
	public function execute( array $session ) {
156
		$this->session = $session;
157
158
		if ( isset( $session['settings'] ) ) {
159
			$this->settings = $session['settings'] + $this->settings;
160
		}
161
162
		$this->setupLanguage();
163
164
		if ( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) )
165
			&& $this->request->getVal( 'localsettings' )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request->getVal('localsettings') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
166
		) {
167
			$this->request->response()->header( 'Content-type: application/x-httpd-php' );
168
			$this->request->response()->header(
169
				'Content-Disposition: attachment; filename="LocalSettings.php"'
170
			);
171
172
			$ls = InstallerOverrides::getLocalSettingsGenerator( $this );
173
			$rightsProfile = $this->rightsProfiles[$this->getVar( '_RightsProfile' )];
174
			foreach ( $rightsProfile as $group => $rightsArr ) {
175
				$ls->setGroupRights( $group, $rightsArr );
176
			}
177
			echo $ls->getText();
178
179
			return $this->session;
180
		}
181
182
		$isCSS = $this->request->getVal( 'css' );
183
		if ( $isCSS ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isCSS of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
184
			$this->outputCss();
185
			return $this->session;
186
		}
187
188
		if ( isset( $session['happyPages'] ) ) {
189
			$this->happyPages = $session['happyPages'];
190
		} else {
191
			$this->happyPages = [];
192
		}
193
194
		if ( isset( $session['skippedPages'] ) ) {
195
			$this->skippedPages = $session['skippedPages'];
196
		} else {
197
			$this->skippedPages = [];
198
		}
199
200
		$lowestUnhappy = $this->getLowestUnhappy();
201
202
		# Special case for Creative Commons partner chooser box.
203 View Code Duplication
		if ( $this->request->getVal( 'SubmitCC' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request->getVal('SubmitCC') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
204
			$page = $this->getPageByName( 'Options' );
205
			$this->output->useShortHeader();
206
			$this->output->allowFrames();
207
			$page->submitCC();
208
209
			return $this->finish();
210
		}
211
212 View Code Duplication
		if ( $this->request->getVal( 'ShowCC' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request->getVal('ShowCC') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
213
			$page = $this->getPageByName( 'Options' );
214
			$this->output->useShortHeader();
215
			$this->output->allowFrames();
216
			$this->output->addHTML( $page->getCCDoneBox() );
217
218
			return $this->finish();
219
		}
220
221
		# Get the page name.
222
		$pageName = $this->request->getVal( 'page' );
223
224
		if ( in_array( $pageName, $this->otherPages ) ) {
225
			# Out of sequence
226
			$pageId = false;
227
			$page = $this->getPageByName( $pageName );
228
		} else {
229
			# Main sequence
230
			if ( !$pageName || !in_array( $pageName, $this->pageSequence ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pageName of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
231
				$pageId = $lowestUnhappy;
232
			} else {
233
				$pageId = array_search( $pageName, $this->pageSequence );
234
			}
235
236
			# If necessary, move back to the lowest-numbered unhappy page
237
			if ( $pageId > $lowestUnhappy ) {
238
				$pageId = $lowestUnhappy;
239
				if ( $lowestUnhappy == 0 ) {
240
					# Knocked back to start, possible loss of session data.
241
					$this->showSessionWarning = true;
242
				}
243
			}
244
245
			$pageName = $this->pageSequence[$pageId];
246
			$page = $this->getPageByName( $pageName );
247
		}
248
249
		# If a back button was submitted, go back without submitting the form data.
250
		if ( $this->request->wasPosted() && $this->request->getBool( 'submit-back' ) ) {
251
			if ( $this->request->getVal( 'lastPage' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request->getVal('lastPage') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
252
				$nextPage = $this->request->getVal( 'lastPage' );
253
			} elseif ( $pageId !== false ) {
254
				# Main sequence page
255
				# Skip the skipped pages
256
				$nextPageId = $pageId;
257
258
				do {
259
					$nextPageId--;
260
					$nextPage = $this->pageSequence[$nextPageId];
261
				} while ( isset( $this->skippedPages[$nextPage] ) );
262
			} else {
263
				$nextPage = $this->pageSequence[$lowestUnhappy];
264
			}
265
266
			$this->output->redirect( $this->getUrl( [ 'page' => $nextPage ] ) );
267
268
			return $this->finish();
269
		}
270
271
		# Execute the page.
272
		$this->currentPageName = $page->getName();
273
		$this->startPageWrapper( $pageName );
274
275
		if ( $page->isSlow() ) {
276
			$this->disableTimeLimit();
277
		}
278
279
		$result = $page->execute();
280
281
		$this->endPageWrapper();
282
283
		if ( $result == 'skip' ) {
284
			# Page skipped without explicit submission.
285
			# Skip it when we click "back" so that we don't just go forward again.
286
			$this->skippedPages[$pageName] = true;
287
			$result = 'continue';
288
		} else {
289
			unset( $this->skippedPages[$pageName] );
290
		}
291
292
		# If it was posted, the page can request a continue to the next page.
293
		if ( $result === 'continue' && !$this->output->headerDone() ) {
294
			if ( $pageId !== false ) {
295
				$this->happyPages[$pageId] = true;
296
			}
297
298
			$lowestUnhappy = $this->getLowestUnhappy();
299
300
			if ( $this->request->getVal( 'lastPage' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->request->getVal('lastPage') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
301
				$nextPage = $this->request->getVal( 'lastPage' );
302
			} elseif ( $pageId !== false ) {
303
				$nextPage = $this->pageSequence[$pageId + 1];
304
			} else {
305
				$nextPage = $this->pageSequence[$lowestUnhappy];
306
			}
307
308
			if ( array_search( $nextPage, $this->pageSequence ) > $lowestUnhappy ) {
309
				$nextPage = $this->pageSequence[$lowestUnhappy];
310
			}
311
312
			$this->output->redirect( $this->getUrl( [ 'page' => $nextPage ] ) );
313
		}
314
315
		return $this->finish();
316
	}
317
318
	/**
319
	 * Find the next page in sequence that hasn't been completed
320
	 * @return int
321
	 */
322
	public function getLowestUnhappy() {
323
		if ( count( $this->happyPages ) == 0 ) {
324
			return 0;
325
		} else {
326
			return max( array_keys( $this->happyPages ) ) + 1;
327
		}
328
	}
329
330
	/**
331
	 * Start the PHP session. This may be called before execute() to start the PHP session.
332
	 *
333
	 * @throws Exception
334
	 * @return bool
335
	 */
336
	public function startSession() {
337
		if ( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
338
			// Done already
339
			return true;
340
		}
341
342
		$this->phpErrors = [];
343
		set_error_handler( [ $this, 'errorHandler' ] );
344
		try {
345
			session_name( 'mw_installer_session' );
346
			session_start();
347
		} catch ( Exception $e ) {
348
			restore_error_handler();
349
			throw $e;
350
		}
351
		restore_error_handler();
352
353
		if ( $this->phpErrors ) {
354
			return false;
355
		}
356
357
		return true;
358
	}
359
360
	/**
361
	 * Get a hash of data identifying this MW installation.
362
	 *
363
	 * This is used by mw-config/index.php to prevent multiple installations of MW
364
	 * on the same cookie domain from interfering with each other.
365
	 *
366
	 * @return string
367
	 */
368
	public function getFingerprint() {
0 ignored issues
show
getFingerprint uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
369
		// Get the base URL of the installation
370
		$url = $this->request->getFullRequestURL();
371
		if ( preg_match( '!^(.*\?)!', $url, $m ) ) {
372
			// Trim query string
373
			$url = $m[1];
374
		}
375
		if ( preg_match( '!^(.*)/[^/]*/[^/]*$!', $url, $m ) ) {
376
			// This... seems to try to get the base path from
377
			// the /mw-config/index.php. Kinda scary though?
378
			$url = $m[1];
379
		}
380
381
		return md5( serialize( [
382
			'local path' => dirname( __DIR__ ),
383
			'url' => $url,
384
			'version' => $GLOBALS['wgVersion']
385
		] ) );
386
	}
387
388
	/**
389
	 * Show an error message in a box. Parameters are like wfMessage(), or
390
	 * alternatively, pass a Message object in.
391
	 * @param string|Message $msg
392
	 */
393
	public function showError( $msg /*...*/ ) {
394
		if ( !( $msg instanceof Message ) ) {
395
			$args = func_get_args();
396
			array_shift( $args );
397
			$args = array_map( 'htmlspecialchars', $args );
398
			$msg = wfMessage( $msg, $args );
399
		}
400
		$text = $msg->useDatabase( false )->plain();
401
		$this->output->addHTML( $this->getErrorBox( $text ) );
402
	}
403
404
	/**
405
	 * Temporary error handler for session start debugging.
406
	 *
407
	 * @param int $errno Unused
408
	 * @param string $errstr
409
	 */
410
	public function errorHandler( $errno, $errstr ) {
411
		$this->phpErrors[] = $errstr;
412
	}
413
414
	/**
415
	 * Clean up from execute()
416
	 *
417
	 * @return array[]
418
	 */
419
	public function finish() {
420
		$this->output->output();
421
422
		$this->session['happyPages'] = $this->happyPages;
423
		$this->session['skippedPages'] = $this->skippedPages;
424
		$this->session['settings'] = $this->settings;
425
426
		return $this->session;
427
	}
428
429
	/**
430
	 * We're restarting the installation, reset the session, happyPages, etc
431
	 */
432
	public function reset() {
433
		$this->session = [];
434
		$this->happyPages = [];
435
		$this->settings = [];
436
	}
437
438
	/**
439
	 * Get a URL for submission back to the same script.
440
	 *
441
	 * @param string[] $query
442
	 *
443
	 * @return string
444
	 */
445
	public function getUrl( $query = [] ) {
446
		$url = $this->request->getRequestURL();
447
		# Remove existing query
448
		$url = preg_replace( '/\?.*$/', '', $url );
449
450
		if ( $query ) {
451
			$url .= '?' . wfArrayToCgi( $query );
452
		}
453
454
		return $url;
455
	}
456
457
	/**
458
	 * Get a WebInstallerPage by name.
459
	 *
460
	 * @param string $pageName
461
	 * @return WebInstallerPage
462
	 */
463
	public function getPageByName( $pageName ) {
464
		$pageClass = 'WebInstaller' . $pageName;
465
466
		return new $pageClass( $this );
467
	}
468
469
	/**
470
	 * Get a session variable.
471
	 *
472
	 * @param string $name
473
	 * @param array $default
474
	 *
475
	 * @return array
476
	 */
477 View Code Duplication
	public function getSession( $name, $default = null ) {
478
		if ( !isset( $this->session[$name] ) ) {
479
			return $default;
480
		} else {
481
			return $this->session[$name];
482
		}
483
	}
484
485
	/**
486
	 * Set a session variable.
487
	 *
488
	 * @param string $name Key for the variable
489
	 * @param mixed $value
490
	 */
491
	public function setSession( $name, $value ) {
492
		$this->session[$name] = $value;
493
	}
494
495
	/**
496
	 * Get the next tabindex attribute value.
497
	 *
498
	 * @return int
499
	 */
500
	public function nextTabIndex() {
501
		return $this->tabIndex++;
502
	}
503
504
	/**
505
	 * Initializes language-related variables.
506
	 */
507
	public function setupLanguage() {
508
		global $wgLang, $wgContLang, $wgLanguageCode;
509
510
		if ( $this->getSession( 'test' ) === null && !$this->request->wasPosted() ) {
511
			$wgLanguageCode = $this->getAcceptLanguage();
512
			$wgLang = $wgContLang = Language::factory( $wgLanguageCode );
513
			RequestContext::getMain()->setLanguage( $wgLang );
514
			$this->setVar( 'wgLanguageCode', $wgLanguageCode );
515
			$this->setVar( '_UserLang', $wgLanguageCode );
516
		} else {
517
			$wgLanguageCode = $this->getVar( 'wgLanguageCode' );
518
			$wgContLang = Language::factory( $wgLanguageCode );
519
		}
520
	}
521
522
	/**
523
	 * Retrieves MediaWiki language from Accept-Language HTTP header.
524
	 *
525
	 * @return string
526
	 */
527
	public function getAcceptLanguage() {
528
		global $wgLanguageCode, $wgRequest;
529
530
		$mwLanguages = Language::fetchLanguageNames();
531
		$headerLanguages = array_keys( $wgRequest->getAcceptLang() );
532
533
		foreach ( $headerLanguages as $lang ) {
534
			if ( isset( $mwLanguages[$lang] ) ) {
535
				return $lang;
536
			}
537
		}
538
539
		return $wgLanguageCode;
540
	}
541
542
	/**
543
	 * Called by execute() before page output starts, to show a page list.
544
	 *
545
	 * @param string $currentPageName
546
	 */
547
	private function startPageWrapper( $currentPageName ) {
548
		$s = "<div class=\"config-page-wrapper\">\n";
549
		$s .= "<div class=\"config-page\">\n";
550
		$s .= "<div class=\"config-page-list\"><ul>\n";
551
		$lastHappy = -1;
552
553
		foreach ( $this->pageSequence as $id => $pageName ) {
554
			$happy = !empty( $this->happyPages[$id] );
555
			$s .= $this->getPageListItem(
556
				$pageName,
557
				$happy || $lastHappy == $id - 1,
558
				$currentPageName
559
			);
560
561
			if ( $happy ) {
562
				$lastHappy = $id;
563
			}
564
		}
565
566
		$s .= "</ul><br/><ul>\n";
567
		$s .= $this->getPageListItem( 'Restart', true, $currentPageName );
568
		// End list pane
569
		$s .= "</ul></div>\n";
570
571
		// Messages:
572
		// config-page-language, config-page-welcome, config-page-dbconnect, config-page-upgrade,
573
		// config-page-dbsettings, config-page-name, config-page-options, config-page-install,
574
		// config-page-complete, config-page-restart, config-page-readme, config-page-releasenotes,
575
		// config-page-copying, config-page-upgradedoc, config-page-existingwiki
576
		$s .= Html::element( 'h2', [],
577
			wfMessage( 'config-page-' . strtolower( $currentPageName ) )->text() );
578
579
		$this->output->addHTMLNoFlush( $s );
580
	}
581
582
	/**
583
	 * Get a list item for the page list.
584
	 *
585
	 * @param string $pageName
586
	 * @param bool $enabled
587
	 * @param string $currentPageName
588
	 *
589
	 * @return string
590
	 */
591
	private function getPageListItem( $pageName, $enabled, $currentPageName ) {
592
		$s = "<li class=\"config-page-list-item\">";
593
594
		// Messages:
595
		// config-page-language, config-page-welcome, config-page-dbconnect, config-page-upgrade,
596
		// config-page-dbsettings, config-page-name, config-page-options, config-page-install,
597
		// config-page-complete, config-page-restart, config-page-readme, config-page-releasenotes,
598
		// config-page-copying, config-page-upgradedoc, config-page-existingwiki
599
		$name = wfMessage( 'config-page-' . strtolower( $pageName ) )->text();
600
601
		if ( $enabled ) {
602
			$query = [ 'page' => $pageName ];
603
604
			if ( !in_array( $pageName, $this->pageSequence ) ) {
605
				if ( in_array( $currentPageName, $this->pageSequence ) ) {
606
					$query['lastPage'] = $currentPageName;
607
				}
608
609
				$link = Html::element( 'a',
610
					[
611
						'href' => $this->getUrl( $query )
612
					],
613
					$name
614
				);
615
			} else {
616
				$link = htmlspecialchars( $name );
617
			}
618
619
			if ( $pageName == $currentPageName ) {
620
				$s .= "<span class=\"config-page-current\">$link</span>";
621
			} else {
622
				$s .= $link;
623
			}
624
		} else {
625
			$s .= Html::element( 'span',
626
				[
627
					'class' => 'config-page-disabled'
628
				],
629
				$name
630
			);
631
		}
632
633
		$s .= "</li>\n";
634
635
		return $s;
636
	}
637
638
	/**
639
	 * Output some stuff after a page is finished.
640
	 */
641
	private function endPageWrapper() {
642
		$this->output->addHTMLNoFlush(
643
			"<div class=\"visualClear\"></div>\n" .
644
			"</div>\n" .
645
			"<div class=\"visualClear\"></div>\n" .
646
			"</div>" );
647
	}
648
649
	/**
650
	 * Get HTML for an error box with an icon.
651
	 *
652
	 * @param string $text Wikitext, get this with wfMessage()->plain()
653
	 *
654
	 * @return string
655
	 */
656
	public function getErrorBox( $text ) {
657
		return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' );
658
	}
659
660
	/**
661
	 * Get HTML for a warning box with an icon.
662
	 *
663
	 * @param string $text Wikitext, get this with wfMessage()->plain()
664
	 *
665
	 * @return string
666
	 */
667
	public function getWarningBox( $text ) {
668
		return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' );
669
	}
670
671
	/**
672
	 * Get HTML for an info box with an icon.
673
	 *
674
	 * @param string $text Wikitext, get this with wfMessage()->plain()
675
	 * @param string|bool $icon Icon name, file in mw-config/images. Default: false
676
	 * @param string|bool $class Additional class name to add to the wrapper div. Default: false.
677
	 *
678
	 * @return string
679
	 */
680
	public function getInfoBox( $text, $icon = false, $class = false ) {
681
		$text = $this->parse( $text, true );
682
		$icon = ( $icon == false ) ?
683
			'images/info-32.png' :
684
			'images/' . $icon;
685
		$alt = wfMessage( 'config-information' )->text();
686
687
		return Html::infoBox( $text, $icon, $alt, $class );
0 ignored issues
show
It seems like $class defined by parameter $class on line 680 can also be of type boolean; however, Html::infoBox() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
688
	}
689
690
	/**
691
	 * Get small text indented help for a preceding form field.
692
	 * Parameters like wfMessage().
693
	 *
694
	 * @param string $msg
695
	 * @return string
696
	 */
697
	public function getHelpBox( $msg /*, ... */ ) {
698
		$args = func_get_args();
699
		array_shift( $args );
700
		$args = array_map( 'htmlspecialchars', $args );
701
		$text = wfMessage( $msg, $args )->useDatabase( false )->plain();
702
		$html = $this->parse( $text, true );
703
704
		return "<div class=\"config-help-field-container\">\n" .
705
			"<span class=\"config-help-field-hint\" title=\"" .
706
			wfMessage( 'config-help-tooltip' )->escaped() . "\">" .
707
			wfMessage( 'config-help' )->escaped() . "</span>\n" .
708
			"<span class=\"config-help-field-data\">" . $html . "</span>\n" .
709
			"</div>\n";
710
	}
711
712
	/**
713
	 * Output a help box.
714
	 * @param string $msg Key for wfMessage()
715
	 */
716
	public function showHelpBox( $msg /*, ... */ ) {
717
		$args = func_get_args();
718
		$html = call_user_func_array( [ $this, 'getHelpBox' ], $args );
719
		$this->output->addHTML( $html );
720
	}
721
722
	/**
723
	 * Show a short informational message.
724
	 * Output looks like a list.
725
	 *
726
	 * @param string $msg
727
	 */
728
	public function showMessage( $msg /*, ... */ ) {
729
		$args = func_get_args();
730
		array_shift( $args );
731
		$html = '<div class="config-message">' .
732
			$this->parse( wfMessage( $msg, $args )->useDatabase( false )->plain() ) .
733
			"</div>\n";
734
		$this->output->addHTML( $html );
735
	}
736
737
	/**
738
	 * @param Status $status
739
	 */
740
	public function showStatusMessage( Status $status ) {
741
		$errors = array_merge( $status->getErrorsArray(), $status->getWarningsArray() );
742
		foreach ( $errors as $error ) {
743
			call_user_func_array( [ $this, 'showMessage' ], $error );
744
		}
745
	}
746
747
	/**
748
	 * Label a control by wrapping a config-input div around it and putting a
749
	 * label before it.
750
	 *
751
	 * @param string $msg
752
	 * @param string $forId
753
	 * @param string $contents
754
	 * @param string $helpData
755
	 * @return string
756
	 */
757
	public function label( $msg, $forId, $contents, $helpData = "" ) {
758
		if ( strval( $msg ) == '' ) {
759
			$labelText = '&#160;';
760
		} else {
761
			$labelText = wfMessage( $msg )->escaped();
762
		}
763
764
		$attributes = [ 'class' => 'config-label' ];
765
766
		if ( $forId ) {
767
			$attributes['for'] = $forId;
768
		}
769
770
		return "<div class=\"config-block\">\n" .
771
			"  <div class=\"config-block-label\">\n" .
772
			Xml::tags( 'label',
773
				$attributes,
774
				$labelText
775
			) . "\n" .
776
			$helpData .
777
			"  </div>\n" .
778
			"  <div class=\"config-block-elements\">\n" .
779
			$contents .
780
			"  </div>\n" .
781
			"</div>\n";
782
	}
783
784
	/**
785
	 * Get a labelled text box to configure a variable.
786
	 *
787
	 * @param mixed[] $params
788
	 *    Parameters are:
789
	 *      var:         The variable to be configured (required)
790
	 *      label:       The message name for the label (required)
791
	 *      attribs:     Additional attributes for the input element (optional)
792
	 *      controlName: The name for the input element (optional)
793
	 *      value:       The current value of the variable (optional)
794
	 *      help:        The html for the help text (optional)
795
	 *
796
	 * @return string
797
	 */
798 View Code Duplication
	public function getTextBox( $params ) {
799
		if ( !isset( $params['controlName'] ) ) {
800
			$params['controlName'] = 'config_' . $params['var'];
801
		}
802
803
		if ( !isset( $params['value'] ) ) {
804
			$params['value'] = $this->getVar( $params['var'] );
805
		}
806
807
		if ( !isset( $params['attribs'] ) ) {
808
			$params['attribs'] = [];
809
		}
810
		if ( !isset( $params['help'] ) ) {
811
			$params['help'] = "";
812
		}
813
814
		return $this->label(
815
			$params['label'],
816
			$params['controlName'],
817
			Xml::input(
818
				$params['controlName'],
819
				30, // intended to be overridden by CSS
820
				$params['value'],
821
				$params['attribs'] + [
822
					'id' => $params['controlName'],
823
					'class' => 'config-input-text',
824
					'tabindex' => $this->nextTabIndex()
825
				]
826
			),
827
			$params['help']
828
		);
829
	}
830
831
	/**
832
	 * Get a labelled textarea to configure a variable
833
	 *
834
	 * @param mixed[] $params
835
	 *    Parameters are:
836
	 *      var:         The variable to be configured (required)
837
	 *      label:       The message name for the label (required)
838
	 *      attribs:     Additional attributes for the input element (optional)
839
	 *      controlName: The name for the input element (optional)
840
	 *      value:       The current value of the variable (optional)
841
	 *      help:        The html for the help text (optional)
842
	 *
843
	 * @return string
844
	 */
845 View Code Duplication
	public function getTextArea( $params ) {
846
		if ( !isset( $params['controlName'] ) ) {
847
			$params['controlName'] = 'config_' . $params['var'];
848
		}
849
850
		if ( !isset( $params['value'] ) ) {
851
			$params['value'] = $this->getVar( $params['var'] );
852
		}
853
854
		if ( !isset( $params['attribs'] ) ) {
855
			$params['attribs'] = [];
856
		}
857
		if ( !isset( $params['help'] ) ) {
858
			$params['help'] = "";
859
		}
860
861
		return $this->label(
862
			$params['label'],
863
			$params['controlName'],
864
			Xml::textarea(
865
				$params['controlName'],
866
				$params['value'],
867
				30,
868
				5,
869
				$params['attribs'] + [
870
					'id' => $params['controlName'],
871
					'class' => 'config-input-text',
872
					'tabindex' => $this->nextTabIndex()
873
				]
874
			),
875
			$params['help']
876
		);
877
	}
878
879
	/**
880
	 * Get a labelled password box to configure a variable.
881
	 *
882
	 * Implements password hiding
883
	 * @param mixed[] $params
884
	 *    Parameters are:
885
	 *      var:         The variable to be configured (required)
886
	 *      label:       The message name for the label (required)
887
	 *      attribs:     Additional attributes for the input element (optional)
888
	 *      controlName: The name for the input element (optional)
889
	 *      value:       The current value of the variable (optional)
890
	 *      help:        The html for the help text (optional)
891
	 *
892
	 * @return string
893
	 */
894
	public function getPasswordBox( $params ) {
895
		if ( !isset( $params['value'] ) ) {
896
			$params['value'] = $this->getVar( $params['var'] );
897
		}
898
899
		if ( !isset( $params['attribs'] ) ) {
900
			$params['attribs'] = [];
901
		}
902
903
		$params['value'] = $this->getFakePassword( $params['value'] );
904
		$params['attribs']['type'] = 'password';
905
906
		return $this->getTextBox( $params );
907
	}
908
909
	/**
910
	 * Get a labelled checkbox to configure a boolean variable.
911
	 *
912
	 * @param mixed[] $params
913
	 *    Parameters are:
914
	 *      var:         The variable to be configured (required)
915
	 *      label:       The message name for the label (required)
916
	 *      attribs:     Additional attributes for the input element (optional)
917
	 *      controlName: The name for the input element (optional)
918
	 *      value:       The current value of the variable (optional)
919
	 *      help:        The html for the help text (optional)
920
	 *
921
	 * @return string
922
	 */
923
	public function getCheckBox( $params ) {
924
		if ( !isset( $params['controlName'] ) ) {
925
			$params['controlName'] = 'config_' . $params['var'];
926
		}
927
928
		if ( !isset( $params['value'] ) ) {
929
			$params['value'] = $this->getVar( $params['var'] );
930
		}
931
932
		if ( !isset( $params['attribs'] ) ) {
933
			$params['attribs'] = [];
934
		}
935
		if ( !isset( $params['help'] ) ) {
936
			$params['help'] = "";
937
		}
938
		if ( isset( $params['rawtext'] ) ) {
939
			$labelText = $params['rawtext'];
940
		} else {
941
			$labelText = $this->parse( wfMessage( $params['label'] )->text() );
942
		}
943
944
		return "<div class=\"config-input-check\">\n" .
945
			$params['help'] .
946
			"<label>\n" .
947
			Xml::check(
948
				$params['controlName'],
949
				$params['value'],
950
				$params['attribs'] + [
951
					'id' => $params['controlName'],
952
					'tabindex' => $this->nextTabIndex(),
953
				]
954
			) .
955
			$labelText . "\n" .
956
			"</label>\n" .
957
			"</div>\n";
958
	}
959
960
	/**
961
	 * Get a set of labelled radio buttons.
962
	 *
963
	 * @param mixed[] $params
964
	 *    Parameters are:
965
	 *      var:             The variable to be configured (required)
966
	 *      label:           The message name for the label (required)
967
	 *      itemLabelPrefix: The message name prefix for the item labels (required)
968
	 *      itemLabels:      List of message names to use for the item labels instead
969
	 *                       of itemLabelPrefix, keyed by values
970
	 *      values:          List of allowed values (required)
971
	 *      itemAttribs:     Array of attribute arrays, outer key is the value name (optional)
972
	 *      commonAttribs:   Attribute array applied to all items
973
	 *      controlName:     The name for the input element (optional)
974
	 *      value:           The current value of the variable (optional)
975
	 *      help:            The html for the help text (optional)
976
	 *
977
	 * @return string
978
	 */
979
	public function getRadioSet( $params ) {
980
		$items = $this->getRadioElements( $params );
981
982
		if ( !isset( $params['label'] ) ) {
983
			$label = '';
984
		} else {
985
			$label = $params['label'];
986
		}
987
988
		if ( !isset( $params['controlName'] ) ) {
989
			$params['controlName'] = 'config_' . $params['var'];
990
		}
991
992
		if ( !isset( $params['help'] ) ) {
993
			$params['help'] = "";
994
		}
995
996
		$s = "<ul>\n";
997
		foreach ( $items as $value => $item ) {
998
			$s .= "<li>$item</li>\n";
999
		}
1000
		$s .= "</ul>\n";
1001
1002
		return $this->label( $label, $params['controlName'], $s, $params['help'] );
1003
	}
1004
1005
	/**
1006
	 * Get a set of labelled radio buttons. You probably want to use getRadioSet(), not this.
1007
	 *
1008
	 * @see getRadioSet
1009
	 *
1010
	 * @return array
1011
	 */
1012
	public function getRadioElements( $params ) {
1013
		if ( !isset( $params['controlName'] ) ) {
1014
			$params['controlName'] = 'config_' . $params['var'];
1015
		}
1016
1017
		if ( !isset( $params['value'] ) ) {
1018
			$params['value'] = $this->getVar( $params['var'] );
1019
		}
1020
1021
		$items = [];
1022
1023
		foreach ( $params['values'] as $value ) {
1024
			$itemAttribs = [];
1025
1026
			if ( isset( $params['commonAttribs'] ) ) {
1027
				$itemAttribs = $params['commonAttribs'];
1028
			}
1029
1030
			if ( isset( $params['itemAttribs'][$value] ) ) {
1031
				$itemAttribs = $params['itemAttribs'][$value] + $itemAttribs;
1032
			}
1033
1034
			$checked = $value == $params['value'];
1035
			$id = $params['controlName'] . '_' . $value;
1036
			$itemAttribs['id'] = $id;
1037
			$itemAttribs['tabindex'] = $this->nextTabIndex();
1038
1039
			$items[$value] =
1040
				Xml::radio( $params['controlName'], $value, $checked, $itemAttribs ) .
1041
				'&#160;' .
1042
				Xml::tags( 'label', [ 'for' => $id ], $this->parse(
1043
					isset( $params['itemLabels'] ) ?
1044
						wfMessage( $params['itemLabels'][$value] )->plain() :
1045
						wfMessage( $params['itemLabelPrefix'] . strtolower( $value ) )->plain()
1046
				) );
1047
		}
1048
1049
		return $items;
1050
	}
1051
1052
	/**
1053
	 * Output an error or warning box using a Status object.
1054
	 *
1055
	 * @param Status $status
1056
	 */
1057
	public function showStatusBox( $status ) {
1058
		if ( !$status->isGood() ) {
1059
			$text = $status->getWikiText();
1060
1061
			if ( $status->isOK() ) {
1062
				$box = $this->getWarningBox( $text );
1063
			} else {
1064
				$box = $this->getErrorBox( $text );
1065
			}
1066
1067
			$this->output->addHTML( $box );
1068
		}
1069
	}
1070
1071
	/**
1072
	 * Convenience function to set variables based on form data.
1073
	 * Assumes that variables containing "password" in the name are (potentially
1074
	 * fake) passwords.
1075
	 *
1076
	 * @param string[] $varNames
1077
	 * @param string $prefix The prefix added to variables to obtain form names
1078
	 *
1079
	 * @return string[]
1080
	 */
1081
	public function setVarsFromRequest( $varNames, $prefix = 'config_' ) {
1082
		$newValues = [];
1083
1084
		foreach ( $varNames as $name ) {
1085
			$value = $this->request->getVal( $prefix . $name );
1086
			// bug 30524, do not trim passwords
1087
			if ( stripos( $name, 'password' ) === false ) {
1088
				$value = trim( $value );
1089
			}
1090
			$newValues[$name] = $value;
1091
1092
			if ( $value === null ) {
1093
				// Checkbox?
1094
				$this->setVar( $name, false );
1095
			} else {
1096
				if ( stripos( $name, 'password' ) !== false ) {
1097
					$this->setPassword( $name, $value );
1098
				} else {
1099
					$this->setVar( $name, $value );
1100
				}
1101
			}
1102
		}
1103
1104
		return $newValues;
1105
	}
1106
1107
	/**
1108
	 * Helper for Installer::docLink()
1109
	 *
1110
	 * @param string $page
1111
	 *
1112
	 * @return string
1113
	 */
1114
	protected function getDocUrl( $page ) {
0 ignored issues
show
getDocUrl uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1115
		$url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1116
1117
		if ( in_array( $this->currentPageName, $this->pageSequence ) ) {
1118
			$url .= '&lastPage=' . urlencode( $this->currentPageName );
1119
		}
1120
1121
		return $url;
1122
	}
1123
1124
	/**
1125
	 * Extension tag hook for a documentation link.
1126
	 *
1127
	 * @param string $linkText
1128
	 * @param string[] $attribs
1129
	 * @param Parser $parser Unused
1130
	 *
1131
	 * @return string
1132
	 */
1133
	public function docLink( $linkText, $attribs, $parser ) {
1134
		$url = $this->getDocUrl( $attribs['href'] );
1135
1136
		return '<a href="' . htmlspecialchars( $url ) . '">' .
1137
			htmlspecialchars( $linkText ) .
1138
			'</a>';
1139
	}
1140
1141
	/**
1142
	 * Helper for "Download LocalSettings" link on WebInstall_Complete
1143
	 *
1144
	 * @param string $text Unused
1145
	 * @param string[] $attribs Unused
1146
	 * @param Parser $parser Unused
1147
	 *
1148
	 * @return string Html for download link
1149
	 */
1150
	public function downloadLinkHook( $text, $attribs, $parser ) {
1151
		$anchor = Html::rawElement( 'a',
1152
			[ 'href' => $this->getUrl( [ 'localsettings' => 1 ] ) ],
1153
			wfMessage( 'config-download-localsettings' )->parse()
1154
		);
1155
1156
		return Html::rawElement( 'div', [ 'class' => 'config-download-link' ], $anchor );
1157
	}
1158
1159
	/**
1160
	 * If the software package wants the LocalSettings.php file
1161
	 * to be placed in a specific location, override this function
1162
	 * (see mw-config/overrides/README) to return the path of
1163
	 * where the file should be saved, or false for a generic
1164
	 * "in the base of your install"
1165
	 *
1166
	 * @since 1.27
1167
	 * @return string|bool
1168
	 */
1169
	public function getLocalSettingsLocation() {
1170
		return false;
1171
	}
1172
1173
	/**
1174
	 * @return bool
1175
	 */
1176
	public function envCheckPath() {
0 ignored issues
show
envCheckPath uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1177
		// PHP_SELF isn't available sometimes, such as when PHP is CGI but
1178
		// cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
1179
		// to get the path to the current script... hopefully it's reliable. SIGH
1180
		$path = false;
1181 View Code Duplication
		if ( !empty( $_SERVER['PHP_SELF'] ) ) {
1182
			$path = $_SERVER['PHP_SELF'];
1183
		} elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
1184
			$path = $_SERVER['SCRIPT_NAME'];
1185
		}
1186
		if ( $path === false ) {
1187
			$this->showError( 'config-no-uri' );
1188
			return false;
1189
		}
1190
1191
		return parent::envCheckPath();
1192
	}
1193
1194
	public function envPrepPath() {
0 ignored issues
show
envPrepPath uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1195
		parent::envPrepPath();
1196
		// PHP_SELF isn't available sometimes, such as when PHP is CGI but
1197
		// cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
1198
		// to get the path to the current script... hopefully it's reliable. SIGH
1199
		$path = false;
1200 View Code Duplication
		if ( !empty( $_SERVER['PHP_SELF'] ) ) {
1201
			$path = $_SERVER['PHP_SELF'];
1202
		} elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
1203
			$path = $_SERVER['SCRIPT_NAME'];
1204
		}
1205
		if ( $path !== false ) {
1206
			$scriptPath = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
1207
1208
			$this->setVar( 'wgScriptPath', "$scriptPath" );
1209
			// Update variables set from Setup.php that are derived from wgScriptPath
1210
			$this->setVar( 'wgScript', "$scriptPath/index.php" );
1211
			$this->setVar( 'wgLoadScript', "$scriptPath/load.php" );
1212
			$this->setVar( 'wgStylePath', "$scriptPath/skins" );
1213
			$this->setVar( 'wgLocalStylePath', "$scriptPath/skins" );
1214
			$this->setVar( 'wgExtensionAssetsPath', "$scriptPath/extensions" );
1215
			$this->setVar( 'wgUploadPath', "$scriptPath/images" );
1216
			$this->setVar( 'wgResourceBasePath', "$scriptPath" );
1217
		}
1218
	}
1219
1220
	/**
1221
	 * @return string
1222
	 */
1223
	protected function envGetDefaultServer() {
1224
		return WebRequest::detectServer();
1225
	}
1226
1227
	/**
1228
	 * Output stylesheet for web installer pages
1229
	 */
1230
	public function outputCss() {
1231
		$this->request->response()->header( 'Content-type: text/css' );
1232
		echo $this->output->getCSS();
1233
	}
1234
1235
	/**
1236
	 * @return string[]
1237
	 */
1238
	public function getPhpErrors() {
1239
		return $this->phpErrors;
1240
	}
1241
1242
}
1243