MediaWiki master
SiteConfiguration.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Config;
8
113
118 public $suffixes = [];
119
124 public $wikis = [];
125
133 public $settings = [];
134
139 public $fullLoadCallback = null;
140
142 public $fullLoadDone = false;
143
158 public $siteParamsCallback = null;
159
169 public function get(
170 $settingName,
171 $wiki,
172 $site = null,
173 $params = [],
174 $wikiTags = []
175 ) {
176 if ( !is_string( $settingName ) ) {
177 wfDeprecated( __METHOD__ . ' with non-string $settingName', '1.44' );
178 $settingName = (string)$settingName;
179 }
180 if ( $wiki === null ) {
181 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
182 }
183 if ( !is_string( $wiki ) ) {
184 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
185 }
186 $wiki = (string)$wiki;
187 if ( $site !== null && !is_string( $site ) ) {
188 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
189 $site = (string)$site;
190 }
191 if ( !is_array( $params ) ) {
192 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
193 $params = (array)$params;
194 }
195 if ( !is_array( $wikiTags ) ) {
196 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
197 $wikiTags = (array)$wikiTags;
198 }
199 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
200 $overrides = $this->settings[$settingName] ?? null;
201 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
202 if ( !array_key_exists( '@replaceableSettings', $this->settings )
203 || in_array( $settingName, $this->settings['@replaceableSettings'] )
204 ) {
205 $this->doReplacements( $value, $params['replacements'] );
206 }
207 return $value;
208 }
209
241 private function processSetting( $thisSetting, $wiki, $tags ) {
242 // Optimization: Avoid native type hint on private method called by hot getAll()
243 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
244
245 $retval = null;
246
247 if ( array_key_exists( $wiki, $thisSetting ) ) {
248 // Found override by Wiki ID.
249 $retval = $thisSetting[$wiki];
250 } else {
251 if ( array_key_exists( "+$wiki", $thisSetting ) ) {
252 // Found mergable override by Wiki ID.
253 // We continue to look for more merge candidates.
254 $retval = $thisSetting["+$wiki"];
255 }
256
257 foreach ( $tags as $tag ) {
258 if ( array_key_exists( $tag, $thisSetting ) ) {
259 if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
260 // Found a mergable override by Tag, without "+" operator.
261 // Merge it with any "+wiki" match from before, and stop the cascade.
262 $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
263 } else {
264 // Found a non-mergable override by Tag.
265 // This could in theory replace a "+wiki" match, but it should never happen
266 // that a setting uses both mergable array values and non-array values.
267 $retval = $thisSetting[$tag];
268 }
269 return $retval;
270 } elseif ( array_key_exists( "+$tag", $thisSetting ) ) {
271 // Found a mergable override by Tag with "+" operator.
272 // Merge it with any "+wiki" or "+tag" matches from before,
273 // and keep looking for more merge candidates.
274 $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] );
275 }
276 }
277
278 if ( array_key_exists( 'default', $thisSetting ) ) {
279 if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
280 // Found a mergable default
281 // Merge it with any "+wiki" or "+tag" matches from before.
282 $retval = self::arrayMerge( $retval, $thisSetting['default'] );
283 } else {
284 // Found a default
285 // If any array-based values were built up via "+wiki" or "+tag" matches,
286 // these are thrown away here. We don't support merging array values into
287 // non-array values, and the fallback here is to use the default.
288 $retval = $thisSetting['default'];
289 }
290 }
291 }
292 return $retval;
293 }
294
301 private function doReplacements( &$value, $replacements ) {
302 // Optimization: Avoid native type hint on private method called by hot getAll()
303 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
304
305 if ( is_string( $value ) ) {
306 $value = strtr( $value, $replacements );
307 } elseif ( is_array( $value ) ) {
308 foreach ( $value as &$val ) {
309 if ( is_string( $val ) ) {
310 $val = strtr( $val, $replacements );
311 }
312 }
313 }
314 }
315
324 public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) {
325 if ( $wiki === null ) {
326 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
327 }
328 if ( !is_string( $wiki ) ) {
329 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
330 }
331 $wiki = (string)$wiki;
332 if ( $site !== null && !is_string( $site ) ) {
333 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
334 $site = (string)$site;
335 }
336 if ( !is_array( $params ) ) {
337 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
338 $params = (array)$params;
339 }
340 if ( !is_array( $wikiTags ) ) {
341 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
342 $wikiTags = (array)$wikiTags;
343 }
344 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
345 $tags = $params['tags'];
346 $localSettings = [];
347 foreach ( $this->settings as $varname => $overrides ) {
348 $value = $this->processSetting( $overrides, $wiki, $tags );
349 if ( $varname[0] === '+' ) {
350 $varname = substr( $varname, 1 );
351 if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) {
352 $value = self::arrayMerge( $value, $GLOBALS[$varname] );
353 }
354 }
355 if ( $value !== null ) {
356 $localSettings[$varname] = $value;
357 }
358 }
359
360 $replacements = $params['replacements'];
361 if ( array_key_exists( '@replaceableSettings', $this->settings ) ) {
362 foreach ( $this->settings['@replaceableSettings'] as $varname ) {
363 if ( array_key_exists( $varname, $localSettings ) ) {
364 $this->doReplacements( $localSettings[$varname], $replacements );
365 }
366 }
367 } else {
368 foreach ( $localSettings as &$value ) {
369 $this->doReplacements( $value, $replacements );
370 }
371 }
372 return $localSettings;
373 }
374
384 public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) {
385 if ( !is_string( $setting ) ) {
386 wfDeprecated( __METHOD__ . ' with non-string $setting', '1.44' );
387 $setting = (string)$setting;
388 }
389 if ( $wiki === null ) {
390 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
391 }
392 if ( !is_string( $wiki ) ) {
393 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
394 }
395 $wiki = (string)$wiki;
396 if ( $site !== null && !is_string( $site ) ) {
397 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
398 $site = (string)$site;
399 }
400 if ( !is_array( $wikiTags ) ) {
401 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
402 $wikiTags = (array)$wikiTags;
403 }
404 return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags );
405 }
406
412 public function getLocalDatabases() {
413 return $this->wikis;
414 }
415
421 private function extractGlobalSetting( $setting, $wiki, $params ) {
422 $overrides = $this->settings[$setting] ?? null;
423 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
424 if ( !array_key_exists( '@replaceableSettings', $this->settings )
425 || in_array( $setting, $this->settings['@replaceableSettings'] )
426 ) {
427 $this->doReplacements( $value, $params['replacements'] );
428 }
429 if ( $value !== null ) {
430 if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
431 $setting = substr( $setting, 1 );
432 if ( is_array( $GLOBALS[$setting] ) ) {
433 $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
434 } else {
435 $GLOBALS[$setting] = $value;
436 }
437 } else {
438 $GLOBALS[$setting] = $value;
439 }
440 }
441 }
442
450 public function extractAllGlobals(
451 $wiki,
452 $site = null,
453 $params = [],
454 $wikiTags = []
455 ) {
456 if ( $wiki === null ) {
457 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
458 }
459 if ( !is_string( $wiki ) ) {
460 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
461 }
462 $wiki = (string)$wiki;
463 if ( $site !== null && !is_string( $site ) ) {
464 wfDeprecated( __METHOD__ . ' with non-string $site', '1.44' );
465 $site = (string)$site;
466 }
467 if ( !is_array( $params ) ) {
468 wfDeprecated( __METHOD__ . ' with non-array $params', '1.44' );
469 $params = (array)$params;
470 }
471 if ( !is_array( $wikiTags ) ) {
472 wfDeprecated( __METHOD__ . ' with non-array $wikiTags', '1.44' );
473 $wikiTags = (array)$wikiTags;
474 }
475 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
476 foreach ( $this->settings as $setting => $overrides ) {
477 $this->extractGlobalSetting( $setting, $wiki, $params );
478 }
479 }
480
489 protected function getWikiParams( $wiki ) {
490 static $default = [
491 'suffix' => null,
492 'lang' => null,
493 'tags' => [],
494 'params' => [],
495 ];
496
497 if ( !is_callable( $this->siteParamsCallback ) ) {
498 return $default;
499 }
500
501 $ret = ( $this->siteParamsCallback )( $this, $wiki );
502 # Validate the returned value
503 if ( !is_array( $ret ) ) {
504 return $default;
505 }
506
507 foreach ( $default as $name => $def ) {
508 if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) {
509 $ret[$name] = $def;
510 }
511 }
512
513 return $ret;
514 }
515
528 protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) {
529 $ret = $this->getWikiParams( $wiki );
530
531 $ret['suffix'] ??= $site;
532
533 // Make tags based on the db suffix (e.g. wiki family) automatically
534 // available for use in wgConf. The user does not have to maintain
535 // wiki tag lookups (e.g. dblists at WMF) for the wiki family.
536 $wikiTags[] = $ret['suffix'];
537
538 $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
539
540 $ret['params'] += $params;
541
542 // Make the $lang and $site parameters automatically available if they
543 // were provided by `siteParamsCallback` via getWikiParams()
544 if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) {
545 $ret['params']['lang'] = $ret['lang'];
546 }
547 if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) {
548 $ret['params']['site'] = $ret['suffix'];
549 }
550
551 // Optimization: For hot getAll() code path, precompute replacements to re-use
552 // over hundreds of processSetting() calls.
553 $ret['replacements'] = [];
554 foreach ( $ret['params'] as $key => $value ) {
555 $ret['replacements']['$' . $key] = $value;
556 }
557
558 return $ret;
559 }
560
567 public function siteFromDB( $wiki ) {
568 if ( $wiki === null ) {
569 wfDeprecated( __METHOD__ . ' with null $wiki', '1.44' );
570 }
571 if ( !is_string( $wiki ) ) {
572 wfDeprecated( __METHOD__ . ' with non-string $wiki', '1.44' );
573 }
574 $wiki = (string)$wiki;
575 // Allow override
576 $def = $this->getWikiParams( $wiki );
577 if ( $def['suffix'] !== null && $def['lang'] !== null ) {
578 return [ $def['suffix'], $def['lang'] ];
579 }
580
581 $languageCode = str_replace( '_', '-', $wiki );
582 foreach ( $this->suffixes as $altSite => $suffix ) {
583 if ( $suffix === '' ) {
584 return [ '', $languageCode ];
585 } elseif ( str_ends_with( $wiki, $suffix ) ) {
586 $site = is_string( $altSite ) ? $altSite : $suffix;
587 $languageCode = substr( $languageCode, 0, -strlen( $suffix ) );
588 return [ $site, $languageCode ];
589 }
590 }
591
592 return [ null, null ];
593 }
594
605 private static function arrayMerge( array $array1, array $array2 ) {
606 $out = $array1;
607 foreach ( $array2 as $key => $value ) {
608 if ( isset( $out[$key] ) ) {
609 if ( is_array( $out[$key] ) && is_array( $value ) ) {
610 // Merge the new array into the existing one
611 $out[$key] = self::arrayMerge( $out[$key], $value );
612 } elseif ( is_numeric( $key ) ) {
613 // A numerical key is taken, append the value at the end instead.
614 // It is important that we generally preserve numerical keys and only
615 // fallback to appending values if there are conflicts. This is needed
616 // by configuration variables that hold associative arrays with
617 // meaningful numerical keys, such as $wgNamespacesWithSubpages,
618 // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc.
619 $out[] = $value;
620 } elseif ( $out[$key] === false ) {
621 // A non-numerical key is taken and holds a false value,
622 // allow it to be overridden always. This exists mainly for the purpose
623 // merging permissions arrays, such as $wgGroupPermissions.
624 $out[$key] = $value;
625 }
626 // Else: The key is already taken and we keep the current value
627
628 } else {
629 // Add a new key.
630 $out[$key] = $value;
631 }
632 }
633
634 return $out;
635 }
636
637 public function loadFullData() {
638 if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
639 ( $this->fullLoadCallback )( $this );
640 $this->fullLoadDone = true;
641 }
642 }
643}
644
646class_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.