MediaWiki 1.44.0
SiteConfiguration.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Config;
22
127
132 public $suffixes = [];
133
138 public $wikis = [];
139
147 public $settings = [];
148
153 public $fullLoadCallback = null;
154
156 public $fullLoadDone = false;
157
172 public $siteParamsCallback = null;
173
183 public function get(
184 $settingName,
185 $wiki,
186 $site = null,
187 $params = [],
188 $wikiTags = []
189 ) {
190 if ( !is_string( $settingName ) ) {
191 wfDeprecated( __METHOD__ . ' with non-string $settingName', '1.44' );
192 $settingName = (string)$settingName;
193 }
194 if ( $wiki === null ) {
195 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
196 }
197 if ( !is_string( $wiki ) ) {
198 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
199 }
200 $wiki = (string)$wiki;
201 if ( $site !== null && !is_string( $site ) ) {
202 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
203 $site = (string)$site;
204 }
205 if ( !is_array( $params ) ) {
206 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
207 $params = (array)$params;
208 }
209 if ( !is_array( $wikiTags ) ) {
210 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
211 $wikiTags = (array)$wikiTags;
212 }
213 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
214 $overrides = $this->settings[$settingName] ?? null;
215 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
216 if ( !array_key_exists( '@replaceableSettings', $this->settings )
217 || in_array( $settingName, $this->settings['@replaceableSettings'] )
218 ) {
219 $this->doReplacements( $value, $params['replacements'] );
220 }
221 return $value;
222 }
223
255 private function processSetting( $thisSetting, $wiki, $tags ) {
256 // Optimization: Avoid native type hint on private method called by hot getAll()
257 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
258
259 $retval = null;
260
261 if ( array_key_exists( $wiki, $thisSetting ) ) {
262 // Found override by Wiki ID.
263 $retval = $thisSetting[$wiki];
264 } else {
265 if ( array_key_exists( "+$wiki", $thisSetting ) ) {
266 // Found mergable override by Wiki ID.
267 // We continue to look for more merge candidates.
268 $retval = $thisSetting["+$wiki"];
269 }
270
271 foreach ( $tags as $tag ) {
272 if ( array_key_exists( $tag, $thisSetting ) ) {
273 if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
274 // Found a mergable override by Tag, without "+" operator.
275 // Merge it with any "+wiki" match from before, and stop the cascade.
276 $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
277 } else {
278 // Found a non-mergable override by Tag.
279 // This could in theory replace a "+wiki" match, but it should never happen
280 // that a setting uses both mergable array values and non-array values.
281 $retval = $thisSetting[$tag];
282 }
283 return $retval;
284 } elseif ( array_key_exists( "+$tag", $thisSetting ) ) {
285 // Found a mergable override by Tag with "+" operator.
286 // Merge it with any "+wiki" or "+tag" matches from before,
287 // and keep looking for more merge candidates.
288 $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] );
289 }
290 }
291
292 if ( array_key_exists( 'default', $thisSetting ) ) {
293 if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
294 // Found a mergable default
295 // Merge it with any "+wiki" or "+tag" matches from before.
296 $retval = self::arrayMerge( $retval, $thisSetting['default'] );
297 } else {
298 // Found a default
299 // If any array-based values were built up via "+wiki" or "+tag" matches,
300 // these are thrown away here. We don't support merging array values into
301 // non-array values, and the fallback here is to use the default.
302 $retval = $thisSetting['default'];
303 }
304 }
305 }
306 return $retval;
307 }
308
315 private function doReplacements( &$value, $replacements ) {
316 // Optimization: Avoid native type hint on private method called by hot getAll()
317 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
318
319 if ( is_string( $value ) ) {
320 $value = strtr( $value, $replacements );
321 } elseif ( is_array( $value ) ) {
322 foreach ( $value as &$val ) {
323 if ( is_string( $val ) ) {
324 $val = strtr( $val, $replacements );
325 }
326 }
327 }
328 }
329
338 public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) {
339 if ( $wiki === null ) {
340 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
341 }
342 if ( !is_string( $wiki ) ) {
343 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
344 }
345 $wiki = (string)$wiki;
346 if ( $site !== null && !is_string( $site ) ) {
347 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
348 $site = (string)$site;
349 }
350 if ( !is_array( $params ) ) {
351 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
352 $params = (array)$params;
353 }
354 if ( !is_array( $wikiTags ) ) {
355 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
356 $wikiTags = (array)$wikiTags;
357 }
358 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
359 $tags = $params['tags'];
360 $localSettings = [];
361 foreach ( $this->settings as $varname => $overrides ) {
362 $value = $this->processSetting( $overrides, $wiki, $tags );
363 if ( $varname[0] === '+' ) {
364 $varname = substr( $varname, 1 );
365 if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) {
366 $value = self::arrayMerge( $value, $GLOBALS[$varname] );
367 }
368 }
369 if ( $value !== null ) {
370 $localSettings[$varname] = $value;
371 }
372 }
373
374 $replacements = $params['replacements'];
375 if ( array_key_exists( '@replaceableSettings', $this->settings ) ) {
376 foreach ( $this->settings['@replaceableSettings'] as $varname ) {
377 if ( array_key_exists( $varname, $localSettings ) ) {
378 $this->doReplacements( $localSettings[$varname], $replacements );
379 }
380 }
381 } else {
382 foreach ( $localSettings as &$value ) {
383 $this->doReplacements( $value, $replacements );
384 }
385 }
386 return $localSettings;
387 }
388
398 public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) {
399 if ( !is_string( $setting ) ) {
400 wfDeprecated( __METHOD__ . ' with non-string $setting', '1.44' );
401 $setting = (string)$setting;
402 }
403 if ( $wiki === null ) {
404 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
405 }
406 if ( !is_string( $wiki ) ) {
407 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
408 }
409 $wiki = (string)$wiki;
410 if ( $site !== null && !is_string( $site ) ) {
411 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
412 $site = (string)$site;
413 }
414 if ( !is_array( $wikiTags ) ) {
415 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
416 $wikiTags = (array)$wikiTags;
417 }
418 return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags );
419 }
420
426 public function getLocalDatabases() {
427 return $this->wikis;
428 }
429
435 public function extractGlobalSetting( $setting, $wiki, $params ) {
436 if ( !is_string( $setting ) ) {
437 wfDeprecated( __METHOD__ . ' with non-string $setting', '1.44' );
438 $setting = (string)$setting;
439 }
440 if ( $wiki === null ) {
441 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
442 }
443 if ( !is_string( $wiki ) ) {
444 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
445 }
446 $wiki = (string)$wiki;
447 if ( !is_array( $params ) ) {
448 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
449 $params = (array)$params;
450 }
451 $overrides = $this->settings[$setting] ?? null;
452 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
453 if ( !array_key_exists( '@replaceableSettings', $this->settings )
454 || in_array( $setting, $this->settings['@replaceableSettings'] )
455 ) {
456 $this->doReplacements( $value, $params['replacements'] );
457 }
458 if ( $value !== null ) {
459 if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
460 $setting = substr( $setting, 1 );
461 if ( is_array( $GLOBALS[$setting] ) ) {
462 $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
463 } else {
464 $GLOBALS[$setting] = $value;
465 }
466 } else {
467 $GLOBALS[$setting] = $value;
468 }
469 }
470 }
471
479 public function extractAllGlobals(
480 $wiki,
481 $site = null,
482 $params = [],
483 $wikiTags = []
484 ) {
485 if ( $wiki === null ) {
486 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
487 }
488 if ( !is_string( $wiki ) ) {
489 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
490 }
491 $wiki = (string)$wiki;
492 if ( $site !== null && !is_string( $site ) ) {
493 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
494 $site = (string)$site;
495 }
496 if ( !is_array( $params ) ) {
497 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
498 $params = (array)$params;
499 }
500 if ( !is_array( $wikiTags ) ) {
501 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
502 $wikiTags = (array)$wikiTags;
503 }
504 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
505 foreach ( $this->settings as $varName => $setting ) {
506 $this->extractGlobalSetting( $varName, $wiki, $params );
507 }
508 }
509
518 protected function getWikiParams( $wiki ) {
519 static $default = [
520 'suffix' => null,
521 'lang' => null,
522 'tags' => [],
523 'params' => [],
524 ];
525
526 if ( !is_callable( $this->siteParamsCallback ) ) {
527 return $default;
528 }
529
530 $ret = ( $this->siteParamsCallback )( $this, $wiki );
531 # Validate the returned value
532 if ( !is_array( $ret ) ) {
533 return $default;
534 }
535
536 foreach ( $default as $name => $def ) {
537 if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) {
538 $ret[$name] = $def;
539 }
540 }
541
542 return $ret;
543 }
544
557 protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) {
558 $ret = $this->getWikiParams( $wiki );
559
560 $ret['suffix'] ??= $site;
561
562 // Make tags based on the db suffix (e.g. wiki family) automatically
563 // available for use in wgConf. The user does not have to maintain
564 // wiki tag lookups (e.g. dblists at WMF) for the wiki family.
565 $wikiTags[] = $ret['suffix'];
566
567 $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
568
569 $ret['params'] += $params;
570
571 // Make the $lang and $site parameters automatically available if they
572 // were provided by `siteParamsCallback` via getWikiParams()
573 if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) {
574 $ret['params']['lang'] = $ret['lang'];
575 }
576 if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) {
577 $ret['params']['site'] = $ret['suffix'];
578 }
579
580 // Optimization: For hot getAll() code path, precompute replacements to re-use
581 // over hundreds of processSetting() calls.
582 $ret['replacements'] = [];
583 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
584 foreach ( $ret['params'] as $key => $value ) {
585 $ret['replacements']['$' . $key] = $value;
586 }
587
588 return $ret;
589 }
590
597 public function siteFromDB( $wiki ) {
598 if ( $wiki === null ) {
599 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
600 }
601 if ( !is_string( $wiki ) ) {
602 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
603 }
604 $wiki = (string)$wiki;
605 // Allow override
606 $def = $this->getWikiParams( $wiki );
607 if ( $def['suffix'] !== null && $def['lang'] !== null ) {
608 return [ $def['suffix'], $def['lang'] ];
609 }
610
611 $languageCode = str_replace( '_', '-', $wiki );
612 foreach ( $this->suffixes as $altSite => $suffix ) {
613 if ( $suffix === '' ) {
614 return [ '', $languageCode ];
615 } elseif ( str_ends_with( $wiki, $suffix ) ) {
616 $site = is_string( $altSite ) ? $altSite : $suffix;
617 $languageCode = substr( $languageCode, 0, -strlen( $suffix ) );
618 return [ $site, $languageCode ];
619 }
620 }
621
622 return [ null, null ];
623 }
624
635 private static function arrayMerge( array $array1, array $array2 ) {
636 $out = $array1;
637 foreach ( $array2 as $key => $value ) {
638 if ( isset( $out[$key] ) ) {
639 if ( is_array( $out[$key] ) && is_array( $value ) ) {
640 // Merge the new array into the existing one
641 $out[$key] = self::arrayMerge( $out[$key], $value );
642 } elseif ( is_numeric( $key ) ) {
643 // A numerical key is taken, append the value at the end instead.
644 // It is important that we generally preserve numerical keys and only
645 // fallback to appending values if there are conflicts. This is needed
646 // by configuration variables that hold associative arrays with
647 // meaningful numerical keys, such as $wgNamespacesWithSubpages,
648 // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc.
649 $out[] = $value;
650 } elseif ( $out[$key] === false ) {
651 // A non-numerical key is taken and holds a false value,
652 // allow it to be overridden always. This exists mainly for the purpose
653 // merging permissions arrays, such as $wgGroupPermissions.
654 $out[$key] = $value;
655 }
656 // Else: The key is already taken and we keep the current value
657
658 } else {
659 // Add a new key.
660 $out[$key] = $value;
661 }
662 }
663
664 return $out;
665 }
666
667 public function loadFullData() {
668 if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
669 ( $this->fullLoadCallback )( $this );
670 $this->fullLoadDone = true;
671 }
672 }
673}
674
676class_alias( SiteConfiguration::class, 'SiteConfiguration' );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Configuration holder, particularly for multi-wiki sites.
extractGlobalSetting( $setting, $wiki, $params)
string[] $suffixes
Array of suffixes, for self::siteFromDB()
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.
string[] $wikis
Array of wikis, should be the same as $wgLocalDatabases.
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.
mergeParams( $wiki, $site, array $params, array $wikiTags)
Merge params between the ones passed to the function and the ones given by self::$siteParamsCallback ...
getAll( $wiki, $site=null, $params=[], $wikiTags=[])
Gets all settings for a wiki.
array $settings
The whole array of settings.
callable null $siteParamsCallback
A callback function that returns an array with the following keys (all optional):
bool $fullLoadDone
Whether or not all data has been loaded.
getBool( $setting, $wiki, $site=null, $wikiTags=[])
Retrieves a configuration setting for a given wiki, forced to a boolean.