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/SiteConfiguration.php (3 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
 * Configuration holder, particularly for multi-wiki sites.
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
 */
22
23
/**
24
 * This is a class for holding configuration settings, particularly for
25
 * multi-wiki sites.
26
 *
27
 * A basic synopsis:
28
 *
29
 * Consider a wikifarm having three sites: two production sites, one in English
30
 * and one in German, and one testing site. You can assign them easy-to-remember
31
 * identifiers - ISO 639 codes 'en' and 'de' for language wikis, and 'beta' for
32
 * the testing wiki.
33
 *
34
 * You would thus initialize the site configuration by specifying the wiki
35
 * identifiers:
36
 *
37
 * @code
38
 * $conf = new SiteConfiguration;
39
 * $conf->wikis = [ 'de', 'en', 'beta' ];
40
 * @endcode
41
 *
42
 * When configuring the MediaWiki global settings (the $wg variables),
43
 * the identifiers will be available to specify settings on a per wiki basis.
44
 *
45
 * @code
46
 * $conf->settings = [
47
 *	'wgSomeSetting' => [
48
 *
49
 *		# production:
50
 *		'de'     => false,
51
 *		'en'     => false,
52
 *
53
 *		# test:
54
 *		'beta    => true,
55
 *	],
56
 * ];
57
 * @endcode
58
 *
59
 * With three wikis, that is easy to manage. But what about a farm with
60
 * hundreds of wikis? Site configuration provides a special keyword named
61
 * 'default' which is the value used when a wiki is not found. Hence
62
 * the above code could be written:
63
 *
64
 * @code
65
 * $conf->settings = [
66
 *	'wgSomeSetting' => [
67
 *
68
 *		'default' => false,
69
 *
70
 *		# Enable feature on test
71
 *		'beta'    => true,
72
 *	],
73
 * ];
74
 * @endcode
75
 *
76
 *
77
 * Since settings can contain arrays, site configuration provides a way
78
 * to merge an array with the default. This is very useful to avoid
79
 * repeating settings again and again while still maintaining specific changes
80
 * on a per wiki basis.
81
 *
82
 * @code
83
 * $conf->settings = [
84
 *	'wgMergeSetting' = [
85
 *		# Value that will be shared among all wikis:
86
 *		'default' => [ NS_USER => true ],
87
 *
88
 *		# Leading '+' means merging the array of value with the defaults
89
 *		'+beta' => [ NS_HELP => true ],
90
 *	],
91
 * ];
92
 *
93
 * # Get configuration for the German site:
94
 * $conf->get( 'wgMergeSetting', 'de' );
95
 * // --> [ NS_USER => true ];
96
 *
97
 * # Get configuration for the testing site:
98
 * $conf->get( 'wgMergeSetting', 'beta' );
99
 * // --> [ NS_USER => true, NS_HELP => true ];
100
 * @endcode
101
 *
102
 * Finally, to load all configuration settings, extract them in global context:
103
 *
104
 * @code
105
 * # Name / identifier of the wiki as set in $conf->wikis
106
 * $wikiID = 'beta';
107
 * $globals = $conf->getAll( $wikiID );
108
 * extract( $globals );
109
 * @endcode
110
 *
111
 * @note For WikiMap to function, the configuration must define string values for
112
 *  $wgServer (or $wgCanonicalServer) and $wgArticlePath, even if these are the
113
 *  same for all wikis or can be correctly determined by the logic in
114
 *  Setup.php.
115
 *
116
 * @todo Give examples for,
117
 * suffixes:
118
 * $conf->suffixes = [ 'wiki' ];
119
 * localVHosts
120
 * callbacks!
121
 */
122
class SiteConfiguration {
123
124
	/**
125
	 * Array of suffixes, for self::siteFromDB()
126
	 */
127
	public $suffixes = [];
128
129
	/**
130
	 * Array of wikis, should be the same as $wgLocalDatabases
131
	 */
132
	public $wikis = [];
133
134
	/**
135
	 * The whole array of settings
136
	 */
137
	public $settings = [];
138
139
	/**
140
	 * Array of domains that are local and can be handled by the same server
141
	 *
142
	 * @deprecated since 1.25; use $wgLocalVirtualHosts instead.
143
	 */
144
	public $localVHosts = [];
145
146
	/**
147
	 * Optional callback to load full configuration data.
148
	 * @var string|array
149
	 */
150
	public $fullLoadCallback = null;
151
152
	/** Whether or not all data has been loaded */
153
	public $fullLoadDone = false;
154
155
	/**
156
	 * A callback function that returns an array with the following keys (all
157
	 * optional):
158
	 * - suffix: site's suffix
159
	 * - lang: site's lang
160
	 * - tags: array of wiki tags
161
	 * - params: array of parameters to be replaced
162
	 * The function will receive the SiteConfiguration instance in the first
163
	 * argument and the wiki in the second one.
164
	 * if suffix and lang are passed they will be used for the return value of
165
	 * self::siteFromDB() and self::$suffixes will be ignored
166
	 *
167
	 * @var string|array
168
	 */
169
	public $siteParamsCallback = null;
170
171
	/**
172
	 * Configuration cache for getConfig()
173
	 * @var array
174
	 */
175
	protected $cfgCache = [];
176
177
	/**
178
	 * Retrieves a configuration setting for a given wiki.
179
	 * @param string $settingName ID of the setting name to retrieve
180
	 * @param string $wiki Wiki ID of the wiki in question.
181
	 * @param string $suffix The suffix of the wiki in question.
182
	 * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
183
	 * @param array $wikiTags The tags assigned to the wiki.
184
	 * @return mixed The value of the setting requested.
185
	 */
186
	public function get( $settingName, $wiki, $suffix = null, $params = [],
187
		$wikiTags = []
188
	) {
189
		$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
190
		return $this->getSetting( $settingName, $wiki, $params );
191
	}
192
193
	/**
194
	 * Really retrieves a configuration setting for a given wiki.
195
	 *
196
	 * @param string $settingName ID of the setting name to retrieve.
197
	 * @param string $wiki Wiki ID of the wiki in question.
198
	 * @param array $params Array of parameters.
199
	 * @return mixed The value of the setting requested.
200
	 */
201
	protected function getSetting( $settingName, $wiki, array $params ) {
202
		$retval = null;
203
		if ( array_key_exists( $settingName, $this->settings ) ) {
204
			$thisSetting =& $this->settings[$settingName];
205
			do {
206
				// Do individual wiki settings
207
				if ( array_key_exists( $wiki, $thisSetting ) ) {
208
					$retval = $thisSetting[$wiki];
209
					break;
210
				} elseif ( array_key_exists( "+$wiki", $thisSetting ) && is_array( $thisSetting["+$wiki"] ) ) {
211
					$retval = $thisSetting["+$wiki"];
212
				}
213
214
				// Do tag settings
215
				foreach ( $params['tags'] as $tag ) {
216 View Code Duplication
					if ( array_key_exists( $tag, $thisSetting ) ) {
217
						if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
218
							$retval = self::arrayMerge( $retval, $thisSetting[$tag] );
219
						} else {
220
							$retval = $thisSetting[$tag];
221
						}
222
						break 2;
223
					} elseif ( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) {
224
						if ( $retval === null ) {
225
							$retval = [];
226
						}
227
						$retval = self::arrayMerge( $retval, $thisSetting["+$tag"] );
228
					}
229
				}
230
				// Do suffix settings
231
				$suffix = $params['suffix'];
232 View Code Duplication
				if ( !is_null( $suffix ) ) {
233
					if ( array_key_exists( $suffix, $thisSetting ) ) {
234
						if ( is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) {
235
							$retval = self::arrayMerge( $retval, $thisSetting[$suffix] );
236
						} else {
237
							$retval = $thisSetting[$suffix];
238
						}
239
						break;
240
					} elseif ( array_key_exists( "+$suffix", $thisSetting )
241
						&& is_array( $thisSetting["+$suffix"] )
242
					) {
243
						if ( $retval === null ) {
244
							$retval = [];
245
						}
246
						$retval = self::arrayMerge( $retval, $thisSetting["+$suffix"] );
247
					}
248
				}
249
250
				// Fall back to default.
251
				if ( array_key_exists( 'default', $thisSetting ) ) {
252
					if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
253
						$retval = self::arrayMerge( $retval, $thisSetting['default'] );
254
					} else {
255
						$retval = $thisSetting['default'];
256
					}
257
					break;
258
				}
259
			} while ( false );
260
		}
261
262
		if ( !is_null( $retval ) && count( $params['params'] ) ) {
263
			foreach ( $params['params'] as $key => $value ) {
264
				$retval = $this->doReplace( '$' . $key, $value, $retval );
265
			}
266
		}
267
		return $retval;
268
	}
269
270
	/**
271
	 * Type-safe string replace; won't do replacements on non-strings
272
	 * private?
273
	 *
274
	 * @param string $from
275
	 * @param string $to
276
	 * @param string|array $in
277
	 * @return string
278
	 */
279
	function doReplace( $from, $to, $in ) {
280
		if ( is_string( $in ) ) {
281
			return str_replace( $from, $to, $in );
282
		} elseif ( is_array( $in ) ) {
283
			foreach ( $in as $key => $val ) {
284
				$in[$key] = $this->doReplace( $from, $to, $val );
285
			}
286
			return $in;
287
		} else {
288
			return $in;
289
		}
290
	}
291
292
	/**
293
	 * Gets all settings for a wiki
294
	 * @param string $wiki Wiki ID of the wiki in question.
295
	 * @param string $suffix The suffix of the wiki in question.
296
	 * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
297
	 * @param array $wikiTags The tags assigned to the wiki.
298
	 * @return array Array of settings requested.
299
	 */
300
	public function getAll( $wiki, $suffix = null, $params = [], $wikiTags = [] ) {
0 ignored issues
show
getAll 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...
301
		$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
302
		$localSettings = [];
303
		foreach ( $this->settings as $varname => $stuff ) {
304
			$append = false;
305
			$var = $varname;
306
			if ( substr( $varname, 0, 1 ) == '+' ) {
307
				$append = true;
308
				$var = substr( $varname, 1 );
309
			}
310
311
			$value = $this->getSetting( $varname, $wiki, $params );
312
			if ( $append && is_array( $value ) && is_array( $GLOBALS[$var] ) ) {
313
				$value = self::arrayMerge( $value, $GLOBALS[$var] );
314
			}
315
			if ( !is_null( $value ) ) {
316
				$localSettings[$var] = $value;
317
			}
318
		}
319
		return $localSettings;
320
	}
321
322
	/**
323
	 * Retrieves a configuration setting for a given wiki, forced to a boolean.
324
	 * @param string $setting ID of the setting name to retrieve
325
	 * @param string $wiki Wiki ID of the wiki in question.
326
	 * @param string $suffix The suffix of the wiki in question.
327
	 * @param array $wikiTags The tags assigned to the wiki.
328
	 * @return bool The value of the setting requested.
329
	 */
330
	public function getBool( $setting, $wiki, $suffix = null, $wikiTags = [] ) {
331
		return (bool)$this->get( $setting, $wiki, $suffix, [], $wikiTags );
332
	}
333
334
	/**
335
	 * Retrieves an array of local databases
336
	 *
337
	 * @return array
338
	 */
339
	function &getLocalDatabases() {
340
		return $this->wikis;
341
	}
342
343
	/**
344
	 * Retrieves the value of a given setting, and places it in a variable passed by reference.
345
	 * @param string $setting ID of the setting name to retrieve
346
	 * @param string $wiki Wiki ID of the wiki in question.
347
	 * @param string $suffix The suffix of the wiki in question.
348
	 * @param array $var Reference The variable to insert the value into.
349
	 * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
350
	 * @param array $wikiTags The tags assigned to the wiki.
351
	 */
352
	public function extractVar( $setting, $wiki, $suffix, &$var,
353
		$params = [], $wikiTags = []
354
	) {
355
		$value = $this->get( $setting, $wiki, $suffix, $params, $wikiTags );
356
		if ( !is_null( $value ) ) {
357
			$var = $value;
358
		}
359
	}
360
361
	/**
362
	 * Retrieves the value of a given setting, and places it in its corresponding global variable.
363
	 * @param string $setting ID of the setting name to retrieve
364
	 * @param string $wiki Wiki ID of the wiki in question.
365
	 * @param string $suffix The suffix of the wiki in question.
366
	 * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
367
	 * @param array $wikiTags The tags assigned to the wiki.
368
	 */
369
	public function extractGlobal( $setting, $wiki, $suffix = null,
370
		$params = [], $wikiTags = []
371
	) {
372
		$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
373
		$this->extractGlobalSetting( $setting, $wiki, $params );
374
	}
375
376
	/**
377
	 * @param string $setting
378
	 * @param string $wiki
379
	 * @param array $params
380
	 */
381
	public function extractGlobalSetting( $setting, $wiki, $params ) {
0 ignored issues
show
extractGlobalSetting 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...
382
		$value = $this->getSetting( $setting, $wiki, $params );
383
		if ( !is_null( $value ) ) {
384
			if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
385
				$setting = substr( $setting, 1 );
386
				if ( is_array( $GLOBALS[$setting] ) ) {
387
					$GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
388
				} else {
389
					$GLOBALS[$setting] = $value;
390
				}
391
			} else {
392
				$GLOBALS[$setting] = $value;
393
			}
394
		}
395
	}
396
397
	/**
398
	 * Retrieves the values of all settings, and places them in their corresponding global variables.
399
	 * @param string $wiki Wiki ID of the wiki in question.
400
	 * @param string $suffix The suffix of the wiki in question.
401
	 * @param array $params List of parameters. $.'key' is replaced by $value in all returned data.
402
	 * @param array $wikiTags The tags assigned to the wiki.
403
	 */
404
	public function extractAllGlobals( $wiki, $suffix = null, $params = [],
405
		$wikiTags = []
406
	) {
407
		$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
408
		foreach ( $this->settings as $varName => $setting ) {
409
			$this->extractGlobalSetting( $varName, $wiki, $params );
410
		}
411
	}
412
413
	/**
414
	 * Return specific settings for $wiki
415
	 * See the documentation of self::$siteParamsCallback for more in-depth
416
	 * documentation about this function
417
	 *
418
	 * @param string $wiki
419
	 * @return array
420
	 */
421
	protected function getWikiParams( $wiki ) {
422
		static $default = [
423
			'suffix' => null,
424
			'lang' => null,
425
			'tags' => [],
426
			'params' => [],
427
		];
428
429
		if ( !is_callable( $this->siteParamsCallback ) ) {
430
			return $default;
431
		}
432
433
		$ret = call_user_func_array( $this->siteParamsCallback, [ $this, $wiki ] );
434
		# Validate the returned value
435
		if ( !is_array( $ret ) ) {
436
			return $default;
437
		}
438
439
		foreach ( $default as $name => $def ) {
440 View Code Duplication
			if ( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) ) {
441
				$ret[$name] = $default[$name];
442
			}
443
		}
444
445
		return $ret;
446
	}
447
448
	/**
449
	 * Merge params between the ones passed to the function and the ones given
450
	 * by self::$siteParamsCallback for backward compatibility
451
	 * Values returned by self::getWikiParams() have the priority.
452
	 *
453
	 * @param string $wiki Wiki ID of the wiki in question.
454
	 * @param string $suffix The suffix of the wiki in question.
455
	 * @param array $params List of parameters. $.'key' is replaced by $value in
456
	 *   all returned data.
457
	 * @param array $wikiTags The tags assigned to the wiki.
458
	 * @return array
459
	 */
460
	protected function mergeParams( $wiki, $suffix, array $params, array $wikiTags ) {
461
		$ret = $this->getWikiParams( $wiki );
462
463
		if ( is_null( $ret['suffix'] ) ) {
464
			$ret['suffix'] = $suffix;
465
		}
466
467
		$ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
468
469
		$ret['params'] += $params;
470
471
		// Automatically fill that ones if needed
472 View Code Duplication
		if ( !isset( $ret['params']['lang'] ) && !is_null( $ret['lang'] ) ) {
473
			$ret['params']['lang'] = $ret['lang'];
474
		}
475 View Code Duplication
		if ( !isset( $ret['params']['site'] ) && !is_null( $ret['suffix'] ) ) {
476
			$ret['params']['site'] = $ret['suffix'];
477
		}
478
479
		return $ret;
480
	}
481
482
	/**
483
	 * Work out the site and language name from a database name
484
	 * @param string $db
485
	 *
486
	 * @return array
487
	 */
488
	public function siteFromDB( $db ) {
489
		// Allow override
490
		$def = $this->getWikiParams( $db );
491
		if ( !is_null( $def['suffix'] ) && !is_null( $def['lang'] ) ) {
492
			return [ $def['suffix'], $def['lang'] ];
493
		}
494
495
		$site = null;
496
		$lang = null;
497
		foreach ( $this->suffixes as $altSite => $suffix ) {
498
			if ( $suffix === '' ) {
499
				$site = '';
500
				$lang = $db;
501
				break;
502
			} elseif ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
503
				$site = is_numeric( $altSite ) ? $suffix : $altSite;
504
				$lang = substr( $db, 0, strlen( $db ) - strlen( $suffix ) );
505
				break;
506
			}
507
		}
508
		$lang = str_replace( '_', '-', $lang );
509
		return [ $site, $lang ];
510
	}
511
512
	/**
513
	 * Get the resolved (post-setup) configuration of a potentially foreign wiki.
514
	 * For foreign wikis, this is expensive, and only works if maintenance
515
	 * scripts are setup to handle the --wiki parameter such as in wiki farms.
516
	 *
517
	 * @param string $wiki
518
	 * @param array|string $settings A setting name or array of setting names
519
	 * @return mixed|mixed[] Array if $settings is an array, otherwise the value
520
	 * @throws MWException
521
	 * @since 1.21
522
	 */
523
	public function getConfig( $wiki, $settings ) {
0 ignored issues
show
getConfig 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...
524
		global $IP;
525
526
		$multi = is_array( $settings );
527
		$settings = (array)$settings;
528
		if ( $wiki === wfWikiID() ) { // $wiki is this wiki
529
			$res = [];
530
			foreach ( $settings as $name ) {
531
				if ( !preg_match( '/^wg[A-Z]/', $name ) ) {
532
					throw new MWException( "Variable '$name' does start with 'wg'." );
533
				} elseif ( !isset( $GLOBALS[$name] ) ) {
534
					throw new MWException( "Variable '$name' is not set." );
535
				}
536
				$res[$name] = $GLOBALS[$name];
537
			}
538
		} else { // $wiki is a foreign wiki
539
			if ( isset( $this->cfgCache[$wiki] ) ) {
540
				$res = array_intersect_key( $this->cfgCache[$wiki], array_flip( $settings ) );
541
				if ( count( $res ) == count( $settings ) ) {
542
					return $multi ? $res : current( $res ); // cache hit
543
				}
544
			} elseif ( !in_array( $wiki, $this->wikis ) ) {
545
				throw new MWException( "No such wiki '$wiki'." );
546
			} else {
547
				$this->cfgCache[$wiki] = [];
548
			}
549
			$retVal = 1;
550
			$cmd = wfShellWikiCmd(
551
				"$IP/maintenance/getConfiguration.php",
552
				[
553
					'--wiki', $wiki,
554
					'--settings', implode( ' ', $settings ),
555
					'--format', 'PHP'
556
				]
557
			);
558
			// ulimit5.sh breaks this call
559
			$data = trim( wfShellExec( $cmd, $retVal, [], [ 'memory' => 0 ] ) );
560
			if ( $retVal != 0 || !strlen( $data ) ) {
561
				throw new MWException( "Failed to run getConfiguration.php." );
562
			}
563
			$res = unserialize( $data );
564
			if ( !is_array( $res ) ) {
565
				throw new MWException( "Failed to unserialize configuration array." );
566
			}
567
			$this->cfgCache[$wiki] = $this->cfgCache[$wiki] + $res;
568
		}
569
570
		return $multi ? $res : current( $res );
571
	}
572
573
	/**
574
	 * Merge multiple arrays together.
575
	 * On encountering duplicate keys, merge the two, but ONLY if they're arrays.
576
	 * PHP's array_merge_recursive() merges ANY duplicate values into arrays,
577
	 * which is not fun
578
	 *
579
	 * @param array $array1
580
	 *
581
	 * @return array
582
	 */
583
	static function arrayMerge( $array1/* ... */ ) {
584
		$out = $array1;
585
		$argsCount = func_num_args();
586
		for ( $i = 1; $i < $argsCount; $i++ ) {
587
			foreach ( func_get_arg( $i ) as $key => $value ) {
588
				if ( isset( $out[$key] ) && is_array( $out[$key] ) && is_array( $value ) ) {
589
					$out[$key] = self::arrayMerge( $out[$key], $value );
590
				} elseif ( !isset( $out[$key] ) || !$out[$key] && !is_numeric( $key ) ) {
591
					// Values that evaluate to true given precedence, for the
592
					// primary purpose of merging permissions arrays.
593
					$out[$key] = $value;
594
				} elseif ( is_numeric( $key ) ) {
595
					$out[] = $value;
596
				}
597
			}
598
		}
599
600
		return $out;
601
	}
602
603
	public function loadFullData() {
604
		if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
605
			call_user_func( $this->fullLoadCallback, $this );
606
			$this->fullLoadDone = true;
607
		}
608
	}
609
}
610