MediaWiki master
SiteConfiguration.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Config;
8
9use function array_key_exists;
10
115
120 public $suffixes = [];
121
126 public $wikis = [];
127
135 public $settings = [];
136
141 public $fullLoadCallback = null;
142
144 public $fullLoadDone = false;
145
160 public $siteParamsCallback = null;
161
171 public function get(
172 $settingName,
173 $wiki,
174 $site = null,
175 $params = [],
176 $wikiTags = []
177 ) {
178 if ( !is_string( $settingName ) ) {
179 wfDeprecated( __METHOD__ . ' with non-string $settingName', '1.44' );
180 $settingName = (string)$settingName;
181 }
182 if ( $wiki === null ) {
183 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
184 }
185 if ( !is_string( $wiki ) ) {
186 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
187 }
188 $wiki = (string)$wiki;
189 if ( $site !== null && !is_string( $site ) ) {
190 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
191 $site = (string)$site;
192 }
193 if ( !is_array( $params ) ) {
194 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
195 $params = (array)$params;
196 }
197 if ( !is_array( $wikiTags ) ) {
198 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
199 $wikiTags = (array)$wikiTags;
200 }
201 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
202 $overrides = $this->settings[$settingName] ?? null;
203 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
204 if ( !array_key_exists( '@replaceableSettings', $this->settings )
205 || in_array( $settingName, $this->settings['@replaceableSettings'] )
206 ) {
207 $this->doReplacements( $value, $params['replacements'] );
208 }
209 return $value;
210 }
211
243 private function processSetting( $thisSetting, $wiki, $tags ) {
244 // Optimization: Avoid native type hint on private method called by hot getAll()
245 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
246
247 // Optimization: Fast path for when there is only a default value and no overrides
248 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1220661>
249 if ( count( $thisSetting ) === 1 && array_key_exists( 'default', $thisSetting ) ) {
250 return $thisSetting['default'];
251 }
252
253 $retval = null;
254
255 if ( array_key_exists( $wiki, $thisSetting ) ) {
256 // Found override by Wiki ID.
257 $retval = $thisSetting[$wiki];
258 } else {
259 if ( array_key_exists( "+$wiki", $thisSetting ) ) {
260 // Found mergable override by Wiki ID.
261 // We continue to look for more merge candidates.
262 $retval = $thisSetting["+$wiki"];
263 }
264
265 foreach ( $tags as $tag ) {
266 if ( array_key_exists( $tag, $thisSetting ) ) {
267 if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
268 // Found a mergable override by Tag, without "+" operator.
269 // Merge it with any "+wiki" match from before, and stop the cascade.
270 $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
271 } else {
272 // Found a non-mergable override by Tag.
273 // This could in theory replace a "+wiki" match, but it should never happen
274 // that a setting uses both mergable array values and non-array values.
275 $retval = $thisSetting[$tag];
276 }
277 return $retval;
278 } elseif ( array_key_exists( "+$tag", $thisSetting ) ) {
279 // Found a mergable override by Tag with "+" operator.
280 // Merge it with any "+wiki" or "+tag" matches from before,
281 // and keep looking for more merge candidates.
282 $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] );
283 }
284 }
285
286 if ( array_key_exists( 'default', $thisSetting ) ) {
287 if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
288 // Found a mergable default
289 // Merge it with any "+wiki" or "+tag" matches from before.
290 $retval = self::arrayMerge( $retval, $thisSetting['default'] );
291 } else {
292 // Found a default
293 // If any array-based values were built up via "+wiki" or "+tag" matches,
294 // these are thrown away here. We don't support merging array values into
295 // non-array values, and the fallback here is to use the default.
296 $retval = $thisSetting['default'];
297 }
298 }
299 }
300 return $retval;
301 }
302
309 private function doReplacements( &$value, $replacements ) {
310 // Optimization: Avoid native type hint on private method called by hot getAll()
311 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
312
313 if ( is_string( $value ) ) {
314 $value = strtr( $value, $replacements );
315 } elseif ( is_array( $value ) ) {
316 foreach ( $value as &$val ) {
317 if ( is_string( $val ) ) {
318 $val = strtr( $val, $replacements );
319 }
320 }
321 }
322 }
323
332 public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) {
333 if ( $wiki === null ) {
334 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
335 }
336 if ( !is_string( $wiki ) ) {
337 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
338 }
339 $wiki = (string)$wiki;
340 if ( $site !== null && !is_string( $site ) ) {
341 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
342 $site = (string)$site;
343 }
344 if ( !is_array( $params ) ) {
345 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
346 $params = (array)$params;
347 }
348 if ( !is_array( $wikiTags ) ) {
349 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
350 $wikiTags = (array)$wikiTags;
351 }
352 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
353 $tags = $params['tags'];
354 $localSettings = [];
355 foreach ( $this->settings as $varname => $overrides ) {
356 $value = $this->processSetting( $overrides, $wiki, $tags );
357 if ( $varname[0] === '+' ) {
358 $varname = substr( $varname, 1 );
359 if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) {
360 $value = self::arrayMerge( $value, $GLOBALS[$varname] );
361 }
362 }
363 if ( $value !== null ) {
364 $localSettings[$varname] = $value;
365 }
366 }
367
368 $replacements = $params['replacements'];
369 if ( array_key_exists( '@replaceableSettings', $this->settings ) ) {
370 foreach ( $this->settings['@replaceableSettings'] as $varname ) {
371 if ( array_key_exists( $varname, $localSettings ) ) {
372 $this->doReplacements( $localSettings[$varname], $replacements );
373 }
374 }
375 } else {
376 foreach ( $localSettings as &$value ) {
377 $this->doReplacements( $value, $replacements );
378 }
379 }
380 return $localSettings;
381 }
382
392 public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) {
393 if ( !is_string( $setting ) ) {
394 wfDeprecated( __METHOD__ . ' with non-string $setting', '1.44' );
395 $setting = (string)$setting;
396 }
397 if ( $wiki === null ) {
398 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
399 }
400 if ( !is_string( $wiki ) ) {
401 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
402 }
403 $wiki = (string)$wiki;
404 if ( $site !== null && !is_string( $site ) ) {
405 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
406 $site = (string)$site;
407 }
408 if ( !is_array( $wikiTags ) ) {
409 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
410 $wikiTags = (array)$wikiTags;
411 }
412 return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags );
413 }
414
420 public function getLocalDatabases() {
421 return $this->wikis;
422 }
423
429 private function extractGlobalSetting( $setting, $wiki, $params ) {
430 $overrides = $this->settings[$setting] ?? null;
431 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
432 if ( !array_key_exists( '@replaceableSettings', $this->settings )
433 || in_array( $setting, $this->settings['@replaceableSettings'] )
434 ) {
435 $this->doReplacements( $value, $params['replacements'] );
436 }
437 if ( $value !== null ) {
438 if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
439 $setting = substr( $setting, 1 );
440 if ( is_array( $GLOBALS[$setting] ) ) {
441 $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
442 } else {
443 $GLOBALS[$setting] = $value;
444 }
445 } else {
446 $GLOBALS[$setting] = $value;
447 }
448 }
449 }
450
458 public function extractAllGlobals(
459 $wiki,
460 $site = null,
461 $params = [],
462 $wikiTags = []
463 ) {
464 if ( $wiki === null ) {
465 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
466 }
467 if ( !is_string( $wiki ) ) {
468 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
469 }
470 $wiki = (string)$wiki;
471 if ( $site !== null && !is_string( $site ) ) {
472 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
473 $site = (string)$site;
474 }
475 if ( !is_array( $params ) ) {
476 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
477 $params = (array)$params;
478 }
479 if ( !is_array( $wikiTags ) ) {
480 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
481 $wikiTags = (array)$wikiTags;
482 }
483 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
484 foreach ( $this->settings as $setting => $overrides ) {
485 $this->extractGlobalSetting( $setting, $wiki, $params );
486 }
487 }
488
497 protected function getWikiParams( $wiki ) {
498 static $default = [
499 'suffix' => null,
500 'lang' => null,
501 'tags' => [],
502 'params' => [],
503 ];
504
505 if ( !is_callable( $this->siteParamsCallback ) ) {
506 return $default;
507 }
508
509 $ret = ( $this->siteParamsCallback )( $this, $wiki );
510 # Validate the returned value
511 if ( !is_array( $ret ) ) {
512 return $default;
513 }
514
515 foreach ( $default as $name => $def ) {
516 if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) {
517 $ret[$name] = $def;
518 }
519 }
520
521 return $ret;
522 }
523
536 protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) {
537 $ret = $this->getWikiParams( $wiki );
538
539 $ret['suffix'] ??= $site;
540
541 // Make tags based on the db suffix (e.g. wiki family) automatically
542 // available for use in wgConf. The user does not have to maintain
543 // wiki tag lookups (e.g. dblists at WMF) for the wiki family.
544 if ( $ret['suffix'] !== null ) {
545 $wikiTags[] = $ret['suffix'];
546 }
547
548 $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
549
550 $ret['params'] += $params;
551
552 // Make the $lang and $site parameters automatically available if they
553 // were provided by `siteParamsCallback` via getWikiParams()
554 if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) {
555 $ret['params']['lang'] = $ret['lang'];
556 }
557 if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) {
558 $ret['params']['site'] = $ret['suffix'];
559 }
560
561 // Optimization: For hot getAll() code path, precompute replacements to re-use
562 // over hundreds of processSetting() calls.
563 $ret['replacements'] = [];
564 foreach ( $ret['params'] as $key => $value ) {
565 $ret['replacements']['$' . $key] = $value;
566 }
567
568 return $ret;
569 }
570
577 public function siteFromDB( $wiki ) {
578 if ( $wiki === null ) {
579 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
580 }
581 if ( !is_string( $wiki ) ) {
582 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
583 }
584 $wiki = (string)$wiki;
585 // Allow override
586 $def = $this->getWikiParams( $wiki );
587 if ( $def['suffix'] !== null && $def['lang'] !== null ) {
588 return [ $def['suffix'], $def['lang'] ];
589 }
590
591 $languageCode = str_replace( '_', '-', $wiki );
592 foreach ( $this->suffixes as $altSite => $suffix ) {
593 if ( $suffix === '' ) {
594 return [ '', $languageCode ];
595 } elseif ( str_ends_with( $wiki, $suffix ) ) {
596 $site = is_string( $altSite ) ? $altSite : $suffix;
597 $languageCode = substr( $languageCode, 0, -strlen( $suffix ) );
598 return [ $site, $languageCode ];
599 }
600 }
601
602 return [ null, null ];
603 }
604
615 private static function arrayMerge( array $array1, array $array2 ) {
616 $out = $array1;
617 foreach ( $array2 as $key => $value ) {
618 if ( isset( $out[$key] ) ) {
619 if ( is_array( $out[$key] ) && is_array( $value ) ) {
620 // Merge the new array into the existing one
621 $out[$key] = self::arrayMerge( $out[$key], $value );
622 } elseif ( is_numeric( $key ) ) {
623 // A numerical key is taken, append the value at the end instead.
624 // It is important that we generally preserve numerical keys and only
625 // fallback to appending values if there are conflicts. This is needed
626 // by configuration variables that hold associative arrays with
627 // meaningful numerical keys, such as $wgNamespacesWithSubpages,
628 // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc.
629 $out[] = $value;
630 } elseif ( $out[$key] === false ) {
631 // A non-numerical key is taken and holds a false value,
632 // allow it to be overridden always. This exists mainly for the purpose
633 // merging permissions arrays, such as $wgGroupPermissions.
634 $out[$key] = $value;
635 }
636 // Else: The key is already taken and we keep the current value
637
638 } else {
639 // Add a new key.
640 $out[$key] = $value;
641 }
642 }
643
644 return $out;
645 }
646
647 public function loadFullData() {
648 if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
649 ( $this->fullLoadCallback )( $this );
650 $this->fullLoadDone = true;
651 }
652 }
653}
654
656class_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.
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.