MediaWiki master
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 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
191 $overrides = $this->settings[$settingName] ?? null;
192 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
193 if ( !array_key_exists( '@replaceableSettings', $this->settings )
194 || in_array( $settingName, $this->settings['@replaceableSettings'] )
195 ) {
196 $this->doReplacements( $value, $params['replacements'] );
197 }
198 return $value;
199 }
200
232 private function processSetting( $thisSetting, $wiki, $tags ) {
233 // Optimization: Avoid native type hint on private method called by hot getAll()
234 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
235
236 $retval = null;
237
238 if ( array_key_exists( $wiki, $thisSetting ) ) {
239 // Found override by Wiki ID.
240 $retval = $thisSetting[$wiki];
241 } else {
242 if ( array_key_exists( "+$wiki", $thisSetting ) ) {
243 // Found mergable override by Wiki ID.
244 // We continue to look for more merge candidates.
245 $retval = $thisSetting["+$wiki"];
246 }
247
248 foreach ( $tags as $tag ) {
249 if ( array_key_exists( $tag, $thisSetting ) ) {
250 if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
251 // Found a mergable override by Tag, without "+" operator.
252 // Merge it with any "+wiki" match from before, and stop the cascade.
253 $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
254 } else {
255 // Found a non-mergable override by Tag.
256 // This could in theory replace a "+wiki" match, but it should never happen
257 // that a setting uses both mergable array values and non-array values.
258 $retval = $thisSetting[$tag];
259 }
260 return $retval;
261 } elseif ( array_key_exists( "+$tag", $thisSetting ) ) {
262 // Found a mergable override by Tag with "+" operator.
263 // Merge it with any "+wiki" or "+tag" matches from before,
264 // and keep looking for more merge candidates.
265 $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] );
266 }
267 }
268
269 if ( array_key_exists( 'default', $thisSetting ) ) {
270 if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
271 // Found a mergable default
272 // Merge it with any "+wiki" or "+tag" matches from before.
273 $retval = self::arrayMerge( $retval, $thisSetting['default'] );
274 } else {
275 // Found a default
276 // If any array-based values were built up via "+wiki" or "+tag" matches,
277 // these are thrown away here. We don't support merging array values into
278 // non-array values, and the fallback here is to use the default.
279 $retval = $thisSetting['default'];
280 }
281 }
282 }
283 return $retval;
284 }
285
292 private function doReplacements( &$value, $replacements ) {
293 // Optimization: Avoid native type hint on private method called by hot getAll()
294 // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244>
295
296 if ( is_string( $value ) ) {
297 $value = strtr( $value, $replacements );
298 } elseif ( is_array( $value ) ) {
299 foreach ( $value as &$val ) {
300 if ( is_string( $val ) ) {
301 $val = strtr( $val, $replacements );
302 }
303 }
304 }
305 }
306
315 public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) {
316 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
317 $tags = $params['tags'];
318 $localSettings = [];
319 foreach ( $this->settings as $varname => $overrides ) {
320 $value = $this->processSetting( $overrides, $wiki, $tags );
321 if ( $varname[0] === '+' ) {
322 $varname = substr( $varname, 1 );
323 if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) {
324 $value = self::arrayMerge( $value, $GLOBALS[$varname] );
325 }
326 }
327 if ( $value !== null ) {
328 $localSettings[$varname] = $value;
329 }
330 }
331
332 $replacements = $params['replacements'];
333 if ( array_key_exists( '@replaceableSettings', $this->settings ) ) {
334 foreach ( $this->settings['@replaceableSettings'] as $varname ) {
335 if ( array_key_exists( $varname, $localSettings ) ) {
336 $this->doReplacements( $localSettings[$varname], $replacements );
337 }
338 }
339 } else {
340 foreach ( $localSettings as &$value ) {
341 $this->doReplacements( $value, $replacements );
342 }
343 }
344 return $localSettings;
345 }
346
356 public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) {
357 return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags );
358 }
359
365 public function getLocalDatabases() {
366 return $this->wikis;
367 }
368
374 public function extractGlobalSetting( $setting, $wiki, $params ) {
375 $overrides = $this->settings[$setting] ?? null;
376 $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null;
377 if ( !array_key_exists( '@replaceableSettings', $this->settings )
378 || in_array( $setting, $this->settings['@replaceableSettings'] )
379 ) {
380 $this->doReplacements( $value, $params['replacements'] );
381 }
382 if ( $value !== null ) {
383 if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) {
384 $setting = substr( $setting, 1 );
385 if ( is_array( $GLOBALS[$setting] ) ) {
386 $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
387 } else {
388 $GLOBALS[$setting] = $value;
389 }
390 } else {
391 $GLOBALS[$setting] = $value;
392 }
393 }
394 }
395
403 public function extractAllGlobals(
404 $wiki,
405 $site = null,
406 $params = [],
407 $wikiTags = []
408 ) {
409 $params = $this->mergeParams( $wiki, $site, $params, $wikiTags );
410 foreach ( $this->settings as $varName => $setting ) {
411 $this->extractGlobalSetting( $varName, $wiki, $params );
412 }
413 }
414
423 protected function getWikiParams( $wiki ) {
424 static $default = [
425 'suffix' => null,
426 'lang' => null,
427 'tags' => [],
428 'params' => [],
429 ];
430
431 if ( !is_callable( $this->siteParamsCallback ) ) {
432 return $default;
433 }
434
435 $ret = ( $this->siteParamsCallback )( $this, $wiki );
436 # Validate the returned value
437 if ( !is_array( $ret ) ) {
438 return $default;
439 }
440
441 foreach ( $default as $name => $def ) {
442 if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) {
443 $ret[$name] = $def;
444 }
445 }
446
447 return $ret;
448 }
449
462 protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) {
463 $ret = $this->getWikiParams( $wiki );
464
465 $ret['suffix'] ??= $site;
466
467 // Make tags based on the db suffix (e.g. wiki family) automatically
468 // available for use in wgConf. The user does not have to maintain
469 // wiki tag lookups (e.g. dblists at WMF) for the wiki family.
470 $wikiTags[] = $ret['suffix'];
471
472 $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
473
474 $ret['params'] += $params;
475
476 // Make the $lang and $site parameters automatically available if they
477 // were provided by `siteParamsCallback` via getWikiParams()
478 if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) {
479 $ret['params']['lang'] = $ret['lang'];
480 }
481 if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) {
482 $ret['params']['site'] = $ret['suffix'];
483 }
484
485 // Optimization: For hot getAll() code path, precompute replacements to re-use
486 // over hundreds of processSetting() calls.
487 $ret['replacements'] = [];
488 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
489 foreach ( $ret['params'] as $key => $value ) {
490 $ret['replacements']['$' . $key] = $value;
491 }
492
493 return $ret;
494 }
495
502 public function siteFromDB( $wiki ) {
503 // Allow override
504 $def = $this->getWikiParams( $wiki );
505 if ( $def['suffix'] !== null && $def['lang'] !== null ) {
506 return [ $def['suffix'], $def['lang'] ];
507 }
508
509 $languageCode = str_replace( '_', '-', $wiki );
510 foreach ( $this->suffixes as $altSite => $suffix ) {
511 if ( $suffix === '' ) {
512 return [ '', $languageCode ];
513 } elseif ( str_ends_with( $wiki, $suffix ) ) {
514 $site = is_string( $altSite ) ? $altSite : $suffix;
515 $languageCode = substr( $languageCode, 0, -strlen( $suffix ) );
516 return [ $site, $languageCode ];
517 }
518 }
519
520 return [ null, null ];
521 }
522
533 private static function arrayMerge( array $array1, array $array2 ) {
534 $out = $array1;
535 foreach ( $array2 as $key => $value ) {
536 if ( isset( $out[$key] ) ) {
537 if ( is_array( $out[$key] ) && is_array( $value ) ) {
538 // Merge the new array into the existing one
539 $out[$key] = self::arrayMerge( $out[$key], $value );
540 } elseif ( is_numeric( $key ) ) {
541 // A numerical key is taken, append the value at the end instead.
542 // It is important that we generally preserve numerical keys and only
543 // fallback to appending values if there are conflicts. This is needed
544 // by configuration variables that hold associative arrays with
545 // meaningful numerical keys, such as $wgNamespacesWithSubpages,
546 // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc.
547 $out[] = $value;
548 } elseif ( $out[$key] === false ) {
549 // A non-numerical key is taken and holds a false value,
550 // allow it to be overridden always. This exists mainly for the purpose
551 // merging permissions arrays, such as $wgGroupPermissions.
552 $out[$key] = $value;
553 }
554 // Else: The key is already taken and we keep the current value
555
556 } else {
557 // Add a new key.
558 $out[$key] = $value;
559 }
560 }
561
562 return $out;
563 }
564
565 public function loadFullData() {
566 if ( $this->fullLoadCallback && !$this->fullLoadDone ) {
567 ( $this->fullLoadCallback )( $this );
568 $this->fullLoadDone = true;
569 }
570 }
571}
572
574class_alias( SiteConfiguration::class, 'SiteConfiguration' );
array $params
The job parameters.
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.