MediaWiki master
SiteConfiguration.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Config;
22
127
131 public $suffixes = [];
132
136 public $wikis = [];
137
144 public $settings = [];
145
150 public $fullLoadCallback = null;
151
153 public $fullLoadDone = false;
154
169 public $siteParamsCallback = null;
170
180 public function get(
181 $settingName,
182 $wiki,
183 $site = null,
184 $params = [],
185 $wikiTags = []
186 ) {
187 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
188 $overrides = $this->settings[$settingName] ?? null;
189 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
190 if ( !array_key_exists( '@replaceableSettings', $this->settings )
191 || in_array( $settingName, $this->settings['@replaceableSettings'] )
192 ) {
193 $this->doReplacements( $value, $params['replacements'] );
194 }
195 return $value;
196 }
197
229 private function processSetting( $thisSetting, $wiki, $tags ) {
230 // Optimization: Avoid native type hint on private method called by hot getAll()
231 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
232
233 $retval = null;
234
235 if ( array_key_exists( $wiki, $thisSetting ) ) {
236 // Found override by Wiki ID.
237 $retval = $thisSetting[$wiki];
238 } else {
239 if ( array_key_exists( "+$wiki", $thisSetting ) ) {
240 // Found mergable override by Wiki ID.
241 // We continue to look for more merge candidates.
242 $retval = $thisSetting["+$wiki"];
243 }
244
245 foreach ( $tags as $tag ) {
246 if ( array_key_exists( $tag, $thisSetting ) ) {
247 if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
248 // Found a mergable override by Tag, without "+" operator.
249 // Merge it with any "+wiki" match from before, and stop the cascade.
250 $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
251 } else {
252 // Found a non-mergable override by Tag.
253 // This could in theory replace a "+wiki" match, but it should never happen
254 // that a setting uses both mergable array values and non-array values.
255 $retval = $thisSetting[$tag];
256 }
257 return $retval;
258 } elseif ( array_key_exists( "+$tag", $thisSetting ) ) {
259 // Found a mergable override by Tag with "+" operator.
260 // Merge it with any "+wiki" or "+tag" matches from before,
261 // and keep looking for more merge candidates.
262 $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] );
263 }
264 }
265
266 if ( array_key_exists( 'default', $thisSetting ) ) {
267 if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
268 // Found a mergable default
269 // Merge it with any "+wiki" or "+tag" matches from before.
270 $retval = self::arrayMerge( $retval, $thisSetting['default'] );
271 } else {
272 // Found a default
273 // If any array-based values were built up via "+wiki" or "+tag" matches,
274 // these are thrown away here. We don't support merging array values into
275 // non-array values, and the fallback here is to use the default.
276 $retval = $thisSetting['default'];
277 }
278 }
279 }
280 return $retval;
281 }
282
289 private function doReplacements( &$value, $replacements ) {
290 // Optimization: Avoid native type hint on private method called by hot getAll()
291 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
292
293 if ( is_string( $value ) ) {
294 $value = strtr( $value, $replacements );
295 } elseif ( is_array( $value ) ) {
296 foreach ( $value as &$val ) {
297 if ( is_string( $val ) ) {
298 $val = strtr( $val, $replacements );
299 }
300 }
301 }
302 }
303
312 public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) {
313 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
314 $tags = $params['tags'];
315 $localSettings = [];
316 foreach ( $this->settings as $varname => $overrides ) {
317 $value = $this->processSetting( $overrides, $wiki, $tags );
318 if ( $varname[0] === '+' ) {
319 $varname = substr( $varname, 1 );
320 if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) {
321 $value = self::arrayMerge( $value, $GLOBALS[$varname] );
322 }
323 }
324 if ( $value !== null ) {
325 $localSettings[$varname] = $value;
326 }
327 }
328
329 $replacements = $params['replacements'];
330 if ( array_key_exists( '@replaceableSettings', $this->settings ) ) {
331 foreach ( $this->settings['@replaceableSettings'] as $varname ) {
332 if ( array_key_exists( $varname, $localSettings ) ) {
333 $this->doReplacements( $localSettings[$varname], $replacements );
334 }
335 }
336 } else {
337 foreach ( $localSettings as &$value ) {
338 $this->doReplacements( $value, $replacements );
339 }
340 }
341 return $localSettings;
342 }
343
353 public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) {
354 return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags );
355 }
356
362 public function getLocalDatabases() {
363 return $this->wikis;
364 }
365
371 public function extractGlobalSetting( $setting, $wiki, $params ) {
372 $overrides = $this->settings[$setting] ?? null;
373 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
374 if ( !array_key_exists( '@replaceableSettings', $this->settings )
375 || in_array( $setting, $this->settings['@replaceableSettings'] )
376 ) {
377 $this->doReplacements( $value, $params['replacements'] );
378 }
379 if ( $value !== null ) {
380 if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
381 $setting = substr( $setting, 1 );
382 if ( is_array( $GLOBALS[$setting] ) ) {
383 $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
384 } else {
385 $GLOBALS[$setting] = $value;
386 }
387 } else {
388 $GLOBALS[$setting] = $value;
389 }
390 }
391 }
392
400 public function extractAllGlobals(
401 $wiki,
402 $site = null,
403 $params = [],
404 $wikiTags = []
405 ) {
406 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
407 foreach ( $this->settings as $varName => $setting ) {
408 $this->extractGlobalSetting( $varName, $wiki, $params );
409 }
410 }
411
420 protected function getWikiParams( $wiki ) {
421 static $default = [
422 'suffix' => null,
423 'lang' => null,
424 'tags' => [],
425 'params' => [],
426 ];
427
428 if ( !is_callable( $this->siteParamsCallback ) ) {
429 return $default;
430 }
431
432 $ret = ( $this->siteParamsCallback )( $this, $wiki );
433 # Validate the returned value
434 if ( !is_array( $ret ) ) {
435 return $default;
436 }
437
438 foreach ( $default as $name => $def ) {
439 if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) {
440 $ret[$name] = $def;
441 }
442 }
443
444 return $ret;
445 }
446
459 protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) {
460 $ret = $this->getWikiParams( $wiki );
461
462 $ret['suffix'] ??= $site;
463
464 // Make tags based on the db suffix (e.g. wiki family) automatically
465 // available for use in wgConf. The user does not have to maintain
466 // wiki tag lookups (e.g. dblists at WMF) for the wiki family.
467 $wikiTags[] = $ret['suffix'];
468
469 $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
470
471 $ret['params'] += $params;
472
473 // Make the $lang and $site parameters automatically available if they
474 // were provided by `siteParamsCallback` via getWikiParams()
475 if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) {
476 $ret['params']['lang'] = $ret['lang'];
477 }
478 if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) {
479 $ret['params']['site'] = $ret['suffix'];
480 }
481
482 // Optimization: For hot getAll() code path, precompute replacements to re-use
483 // over hundreds of processSetting() calls.
484 $ret['replacements'] = [];
485 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
486 foreach ( $ret['params'] as $key => $value ) {
487 $ret['replacements']['$' . $key] = $value;
488 }
489
490 return $ret;
491 }
492
499 public function siteFromDB( $wiki ) {
500 // Allow override
501 $def = $this->getWikiParams( $wiki );
502 if ( $def['suffix'] !== null && $def['lang'] !== null ) {
503 return [ $def['suffix'], $def['lang'] ];
504 }
505
506 $languageCode = str_replace( '_', '-', $wiki );
507 foreach ( $this->suffixes as $altSite => $suffix ) {
508 if ( $suffix === '' ) {
509 return [ '', $languageCode ];
510 } elseif ( str_ends_with( $wiki, $suffix ) ) {
511 $site = is_string( $altSite ) ? $altSite : $suffix;
512 $languageCode = substr( $languageCode, 0, -strlen( $suffix ) );
513 return [ $site, $languageCode ];
514 }
515 }
516
517 return [ null, null ];
518 }
519
530 private static function arrayMerge( array $array1, array $array2 ) {
531 $out = $array1;
532 foreach ( $array2 as $key => $value ) {
533 if ( isset( $out[$key] ) ) {
534 if ( is_array( $out[$key] ) && is_array( $value ) ) {
535 // Merge the new array into the existing one
536 $out[$key] = self::arrayMerge( $out[$key], $value );
537 } elseif ( is_numeric( $key ) ) {
538 // A numerical key is taken, append the value at the end instead.
539 // It is important that we generally preserve numerical keys and only
540 // fallback to appending values if there are conflicts. This is needed
541 // by configuration variables that hold associative arrays with
542 // meaningful numerical keys, such as $wgNamespacesWithSubpages,
543 // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc.
544 $out[] = $value;
545 } elseif ( $out[$key] === false ) {
546 // A non-numerical key is taken and holds a false value,
547 // allow it to be overridden always. This exists mainly for the purpose
548 // merging permissions arrays, such as $wgGroupPermissions.
549 $out[$key] = $value;
550 }
551 // Else: The key is already taken and we keep the current value
552
553 } else {
554 // Add a new key.
555 $out[$key] = $value;
556 }
557 }
558
559 return $out;
560 }
561
562 public function loadFullData() {
563 if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
564 ( $this->fullLoadCallback )( $this );
565 $this->fullLoadDone = true;
566 }
567 }
568}
569
571class_alias( SiteConfiguration::class, 'SiteConfiguration' );
array $params
The job parameters.
Configuration holder, particularly for multi-wiki sites.
extractGlobalSetting( $setting, $wiki, $params)
$suffixes
Array of suffixes, for self::siteFromDB()
$fullLoadDone
Whether or not all data has been loaded.
getLocalDatabases()
Retrieves an array of local databases.
getWikiParams( $wiki)
Return specific settings for $wiki See the documentation of self::$siteParamsCallback for more in-dep...
siteFromDB( $wiki)
Work out the site and language name from a database name.
extractAllGlobals( $wiki, $site=null, $params=[], $wikiTags=[])
Retrieves the values of all settings, and places them in their corresponding global variables.
string array $fullLoadCallback
Optional callback to load full configuration data.
$wikis
Array of wikis, should be the same as $wgLocalDatabases.
mergeParams( $wiki, $site, array $params, array $wikiTags)
Merge params between the ones passed to the function and the ones given by self::$siteParamsCallback ...
$settings
The whole array of settings.
getAll( $wiki, $site=null, $params=[], $wikiTags=[])
Gets all settings for a wiki.
callable null $siteParamsCallback
A callback function that returns an array with the following keys (all optional):
getBool( $setting, $wiki, $site=null, $wikiTags=[])
Retrieves a configuration setting for a given wiki, forced to a boolean.