Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.99% |
89 / 129 |
|
26.67% |
4 / 15 |
CRAP | |
0.00% |
0 / 1 |
SiteConfiguration | |
69.53% |
89 / 128 |
|
26.67% |
4 / 15 |
234.11 | |
0.00% |
0 / 1 |
get | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
processSetting | |
72.22% |
13 / 18 |
|
0.00% |
0 / 1 |
13.59 | |||
doReplacements | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
getAll | |
84.21% |
16 / 19 |
|
0.00% |
0 / 1 |
10.39 | |||
getBool | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLocalDatabases | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
extractVar | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
extractGlobal | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
extractGlobalSetting | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
72 | |||
extractAllGlobals | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getWikiParams | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
7.12 | |||
mergeParams | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
6.02 | |||
siteFromDB | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
7 | |||
arrayMerge | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
7.29 | |||
loadFullData | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Config; |
22 | |
23 | /** |
24 | * Configuration holder, particularly for multi-wiki sites. |
25 | * |
26 | * A basic synopsis: |
27 | * |
28 | * Consider a wikifarm having three sites: two production sites, one in English |
29 | * and one in German, and one testing site. You can assign them easy-to-remember |
30 | * identifiers - ISO 639 codes 'en' and 'de' for language wikis, and 'beta' for |
31 | * the testing wiki. |
32 | * |
33 | * You would thus initialize the site configuration by specifying the wiki |
34 | * identifiers: |
35 | * |
36 | * @code |
37 | * $conf = new SiteConfiguration; |
38 | * $conf->wikis = [ 'de', 'en', 'beta' ]; |
39 | * @endcode |
40 | * |
41 | * When configuring the MediaWiki global settings (the $wg variables), |
42 | * the identifiers will be available to specify settings on a per wiki basis. |
43 | * |
44 | * @code |
45 | * $conf->settings = [ |
46 | * 'wgSomeSetting' => [ |
47 | * |
48 | * # production: |
49 | * 'de' => false, |
50 | * 'en' => false, |
51 | * |
52 | * # test: |
53 | * 'beta => true, |
54 | * ], |
55 | * ]; |
56 | * @endcode |
57 | * |
58 | * With three wikis, that is easy to manage. But what about a farm with |
59 | * hundreds of wikis? Site configuration provides a special keyword named |
60 | * 'default' which is the value used when a wiki is not found. Hence |
61 | * the above code could be written: |
62 | * |
63 | * @code |
64 | * $conf->settings = [ |
65 | * 'wgSomeSetting' => [ |
66 | * |
67 | * 'default' => false, |
68 | * |
69 | * # Enable feature on test |
70 | * 'beta' => true, |
71 | * ], |
72 | * ]; |
73 | * @endcode |
74 | * |
75 | * |
76 | * Since settings can contain arrays, site configuration provides a way |
77 | * to merge an array with the default. This is very useful to avoid |
78 | * repeating settings again and again while still maintaining specific changes |
79 | * on a per wiki basis. |
80 | * |
81 | * @code |
82 | * $conf->settings = [ |
83 | * 'wgMergeSetting' = [ |
84 | * # Value that will be shared among all wikis: |
85 | * 'default' => [ NS_USER => true ], |
86 | * |
87 | * # Leading '+' means merging the array of value with the defaults |
88 | * '+beta' => [ NS_HELP => true ], |
89 | * ], |
90 | * ]; |
91 | * |
92 | * # Get configuration for the German site: |
93 | * $conf->get( 'wgMergeSetting', 'de' ); |
94 | * // --> [ NS_USER => true ]; |
95 | * |
96 | * # Get configuration for the testing site: |
97 | * $conf->get( 'wgMergeSetting', 'beta' ); |
98 | * // --> [ NS_USER => true, NS_HELP => true ]; |
99 | * @endcode |
100 | * |
101 | * Finally, to load all configuration settings, extract them in global context: |
102 | * |
103 | * @code |
104 | * # Name / identifier of the wiki as set in $conf->wikis |
105 | * $wikiID = 'beta'; |
106 | * $globals = $conf->getAll( $wikiID ); |
107 | * extract( $globals ); |
108 | * @endcode |
109 | * |
110 | * Simple suffix system where "pt_brwiki" becomes lang="pt-br", site="wiki": |
111 | * |
112 | * @code |
113 | * $conf->suffixes[] = 'wiki'; |
114 | * @endcode |
115 | * |
116 | * Suffix is resolved as an alias, so "dewiki" becomes lang="de", site="wikipedia": |
117 | * @code |
118 | * $conf->suffixes['wikipedia'] = 'wiki'; |
119 | * @endcode |
120 | * |
121 | * @note For WikiMap to function, the configuration must define string values for |
122 | * $wgServer (or $wgCanonicalServer) and $wgArticlePath, even if these are the |
123 | * same for all wikis or can be correctly determined by the logic in |
124 | * Setup.php. |
125 | */ |
126 | class SiteConfiguration { |
127 | |
128 | /** |
129 | * Array of suffixes, for self::siteFromDB() |
130 | */ |
131 | public $suffixes = []; |
132 | |
133 | /** |
134 | * Array of wikis, should be the same as $wgLocalDatabases |
135 | */ |
136 | public $wikis = []; |
137 | |
138 | /** |
139 | * The whole array of settings. |
140 | * |
141 | * If the key "@replaceableSettings" exists, it contains a list of setting |
142 | * names that are subject to string replacement of $params. |
143 | */ |
144 | public $settings = []; |
145 | |
146 | /** |
147 | * Optional callback to load full configuration data. |
148 | * @var string|array |
149 | */ |
150 | public $fullLoadCallback = null; |
151 | |
152 | /** Whether or not all data has been loaded */ |
153 | public $fullLoadDone = false; |
154 | |
155 | /** |
156 | * A callback function that returns an array with the following keys (all |
157 | * optional): |
158 | * - suffix: site's suffix |
159 | * - lang: site's lang |
160 | * - tags: array of wiki tags |
161 | * - params: array of parameters to be replaced |
162 | * The function will receive the SiteConfiguration instance in the first |
163 | * argument and the wiki in the second one. |
164 | * if suffix and lang are passed they will be used for the return value of |
165 | * self::siteFromDB() and self::$suffixes will be ignored |
166 | * |
167 | * @var callable|null |
168 | */ |
169 | public $siteParamsCallback = null; |
170 | |
171 | /** |
172 | * Configuration cache for getConfig() |
173 | * @var array |
174 | */ |
175 | protected $cfgCache = []; |
176 | |
177 | /** |
178 | * Retrieves a configuration setting for a given wiki. |
179 | * @param string $settingName ID of the setting name to retrieve |
180 | * @param string $wiki Wiki ID of the wiki in question. |
181 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
182 | * @param array $params List of parameters. $.'key' is replaced by $value in all returned data. |
183 | * @param array $wikiTags The tags assigned to the wiki. |
184 | * @return mixed The value of the setting requested. |
185 | */ |
186 | public function get( |
187 | $settingName, |
188 | $wiki, |
189 | $site = null, |
190 | $params = [], |
191 | $wikiTags = [] |
192 | ) { |
193 | $params = $this->mergeParams( $wiki, $site, $params, $wikiTags ); |
194 | $overrides = $this->settings[$settingName] ?? null; |
195 | $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null; |
196 | if ( !array_key_exists( '@replaceableSettings', $this->settings ) |
197 | || in_array( $settingName, $this->settings['@replaceableSettings'] ) |
198 | ) { |
199 | $this->doReplacements( $value, $params['replacements'] ); |
200 | } |
201 | return $value; |
202 | } |
203 | |
204 | /** |
205 | * Retrieve the configuration setting for a given wiki, based on an overrides array. |
206 | * |
207 | * General order of precedence: |
208 | * |
209 | * 1. Wiki ID, an override specific to the given wiki. |
210 | * 2. Tag, an override specific to a group of wikis (e.g. wiki family, or db |
211 | * shard). It is unsupported for the same setting to be set for multiple |
212 | * tags of which the wiki groups overlap. In that case, whichever is |
213 | * iterated and matched first wins, where the tag iteration order |
214 | * is NOT guaranteed. |
215 | * 3. Default, the default value for all wikis in this wiki farm. |
216 | * |
217 | * If the "+" operator is used, with any of these, then the merges will follow the |
218 | * following order (earlier entries have precedence on clashing sub keys): |
219 | * |
220 | * 1. "+wiki" |
221 | * 2. "tag" |
222 | * Only one may match here. And upon match, the merge cascade stops. |
223 | * 3. "+tag" |
224 | * These are only considered if there was no "tag" match. |
225 | * Multiple matches are allowed here, although the array values from |
226 | * multiple tags that contain the same wiki must not overlap, as it is |
227 | * undocumented how key conflicts among them would be handled. |
228 | * 4. "default" |
229 | * |
230 | * @param array $thisSetting An array of overrides for a given setting. |
231 | * @param string $wiki Wiki ID of the wiki in question. |
232 | * @param string[] $tags Array of tags. |
233 | * @return mixed The value of the setting requested. |
234 | */ |
235 | private function processSetting( $thisSetting, $wiki, $tags ) { |
236 | // Optimization: Avoid native type hint on private method called by hot getAll() |
237 | // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244> |
238 | |
239 | $retval = null; |
240 | |
241 | if ( array_key_exists( $wiki, $thisSetting ) ) { |
242 | // Found override by Wiki ID. |
243 | $retval = $thisSetting[$wiki]; |
244 | } else { |
245 | if ( array_key_exists( "+$wiki", $thisSetting ) ) { |
246 | // Found mergable override by Wiki ID. |
247 | // We continue to look for more merge candidates. |
248 | $retval = $thisSetting["+$wiki"]; |
249 | } |
250 | |
251 | foreach ( $tags as $tag ) { |
252 | if ( array_key_exists( $tag, $thisSetting ) ) { |
253 | if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) { |
254 | // Found a mergable override by Tag, without "+" operator. |
255 | // Merge it with any "+wiki" match from before, and stop the cascade. |
256 | $retval = self::arrayMerge( $retval, $thisSetting[$tag] ); |
257 | } else { |
258 | // Found a non-mergable override by Tag. |
259 | // This could in theory replace a "+wiki" match, but it should never happen |
260 | // that a setting uses both mergable array values and non-array values. |
261 | $retval = $thisSetting[$tag]; |
262 | } |
263 | return $retval; |
264 | } elseif ( array_key_exists( "+$tag", $thisSetting ) ) { |
265 | // Found a mergable override by Tag with "+" operator. |
266 | // Merge it with any "+wiki" or "+tag" matches from before, |
267 | // and keep looking for more merge candidates. |
268 | $retval = self::arrayMerge( $retval ?? [], $thisSetting["+$tag"] ); |
269 | } |
270 | } |
271 | |
272 | if ( array_key_exists( 'default', $thisSetting ) ) { |
273 | if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) { |
274 | // Found a mergable default |
275 | // Merge it with any "+wiki" or "+tag" matches from before. |
276 | $retval = self::arrayMerge( $retval, $thisSetting['default'] ); |
277 | } else { |
278 | // Found a default |
279 | // If any array-based values were built up via "+wiki" or "+tag" matches, |
280 | // these are thrown away here. We don't support merging array values into |
281 | // non-array values, and the fallback here is to use the default. |
282 | $retval = $thisSetting['default']; |
283 | } |
284 | } |
285 | } |
286 | return $retval; |
287 | } |
288 | |
289 | /** |
290 | * Do string replacements |
291 | * |
292 | * @param string &$value |
293 | * @param array $replacements |
294 | */ |
295 | private function doReplacements( &$value, $replacements ) { |
296 | // Optimization: Avoid native type hint on private method called by hot getAll() |
297 | // <https://gerrit.wikimedia.org/r/c/mediawiki/core/+/820244> |
298 | |
299 | if ( is_string( $value ) ) { |
300 | $value = strtr( $value, $replacements ); |
301 | } elseif ( is_array( $value ) ) { |
302 | foreach ( $value as &$val ) { |
303 | if ( is_string( $val ) ) { |
304 | $val = strtr( $val, $replacements ); |
305 | } |
306 | } |
307 | } |
308 | } |
309 | |
310 | /** |
311 | * Gets all settings for a wiki |
312 | * @param string $wiki Wiki ID of the wiki in question. |
313 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
314 | * @param array $params List of parameters. $.'key' is replaced by $value in all returned data. |
315 | * @param array $wikiTags The tags assigned to the wiki. |
316 | * @return array Array of settings requested. |
317 | */ |
318 | public function getAll( $wiki, $site = null, $params = [], $wikiTags = [] ) { |
319 | $params = $this->mergeParams( $wiki, $site, $params, $wikiTags ); |
320 | $tags = $params['tags']; |
321 | $localSettings = []; |
322 | foreach ( $this->settings as $varname => $overrides ) { |
323 | $value = $this->processSetting( $overrides, $wiki, $tags ); |
324 | if ( $varname[0] === '+' ) { |
325 | $varname = substr( $varname, 1 ); |
326 | if ( is_array( $value ) && is_array( $GLOBALS[$varname] ) ) { |
327 | $value = self::arrayMerge( $value, $GLOBALS[$varname] ); |
328 | } |
329 | } |
330 | if ( $value !== null ) { |
331 | $localSettings[$varname] = $value; |
332 | } |
333 | } |
334 | |
335 | $replacements = $params['replacements']; |
336 | if ( array_key_exists( '@replaceableSettings', $this->settings ) ) { |
337 | foreach ( $this->settings['@replaceableSettings'] as $varname ) { |
338 | if ( array_key_exists( $varname, $localSettings ) ) { |
339 | $this->doReplacements( $localSettings[$varname], $replacements ); |
340 | } |
341 | } |
342 | } else { |
343 | foreach ( $localSettings as &$value ) { |
344 | $this->doReplacements( $value, $replacements ); |
345 | } |
346 | } |
347 | return $localSettings; |
348 | } |
349 | |
350 | /** |
351 | * Retrieves a configuration setting for a given wiki, forced to a boolean. |
352 | * |
353 | * @param string $setting ID of the setting name to retrieve |
354 | * @param string $wiki Wiki ID of the wiki in question. |
355 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
356 | * @param array $wikiTags The tags assigned to the wiki. |
357 | * @return bool The value of the setting requested. |
358 | */ |
359 | public function getBool( $setting, $wiki, $site = null, $wikiTags = [] ) { |
360 | return (bool)$this->get( $setting, $wiki, $site, [], $wikiTags ); |
361 | } |
362 | |
363 | /** |
364 | * Retrieves an array of local databases |
365 | * |
366 | * @return array |
367 | */ |
368 | public function getLocalDatabases() { |
369 | return $this->wikis; |
370 | } |
371 | |
372 | /** |
373 | * Retrieves the value of a given setting, and places it in a variable passed by reference. |
374 | * |
375 | * @deprecated since 1.41 Use SiteConfiguration::get() instead. |
376 | * @param string $setting ID of the setting name to retrieve |
377 | * @param string $wiki Wiki ID of the wiki in question. |
378 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
379 | * @param array &$var Reference The variable to insert the value into. |
380 | * @param array $params List of parameters. $.'key' is replaced by $value in all returned data. |
381 | * @param array $wikiTags The tags assigned to the wiki. |
382 | */ |
383 | public function extractVar( |
384 | $setting, |
385 | $wiki, |
386 | $site, |
387 | &$var, |
388 | $params = [], |
389 | $wikiTags = [] |
390 | ) { |
391 | wfDeprecated( __METHOD__, '1.41' ); |
392 | $value = $this->get( $setting, $wiki, $site, $params, $wikiTags ); |
393 | if ( $value !== null ) { |
394 | $var = $value; |
395 | } |
396 | } |
397 | |
398 | /** |
399 | * Retrieves the value of a given setting, and places it in its corresponding global variable. |
400 | * |
401 | * @deprecated since 1.41 Use SiteConfiguration::get() instead. |
402 | * @param string $setting ID of the setting name to retrieve |
403 | * @param string $wiki Wiki ID of the wiki in question. |
404 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
405 | * @param array $params List of parameters. $.'key' is replaced by $value in all returned data. |
406 | * @param array $wikiTags The tags assigned to the wiki. |
407 | */ |
408 | public function extractGlobal( |
409 | $setting, |
410 | $wiki, |
411 | $site = null, |
412 | $params = [], |
413 | $wikiTags = [] |
414 | ) { |
415 | wfDeprecated( __METHOD__, '1.41' ); |
416 | $params = $this->mergeParams( $wiki, $site, $params, $wikiTags ); |
417 | $this->extractGlobalSetting( $setting, $wiki, $params ); |
418 | } |
419 | |
420 | /** |
421 | * @param string $setting |
422 | * @param string $wiki |
423 | * @param array $params |
424 | */ |
425 | public function extractGlobalSetting( $setting, $wiki, $params ) { |
426 | $overrides = $this->settings[$setting] ?? null; |
427 | $value = $overrides ? $this->processSetting( $overrides, $wiki, $params['tags'] ) : null; |
428 | if ( !array_key_exists( '@replaceableSettings', $this->settings ) |
429 | || in_array( $setting, $this->settings['@replaceableSettings'] ) |
430 | ) { |
431 | $this->doReplacements( $value, $params['replacements'] ); |
432 | } |
433 | if ( $value !== null ) { |
434 | if ( substr( $setting, 0, 1 ) == '+' && is_array( $value ) ) { |
435 | $setting = substr( $setting, 1 ); |
436 | if ( is_array( $GLOBALS[$setting] ) ) { |
437 | $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value ); |
438 | } else { |
439 | $GLOBALS[$setting] = $value; |
440 | } |
441 | } else { |
442 | $GLOBALS[$setting] = $value; |
443 | } |
444 | } |
445 | } |
446 | |
447 | /** |
448 | * Retrieves the values of all settings, and places them in their corresponding global variables. |
449 | * @param string $wiki Wiki ID of the wiki in question. |
450 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
451 | * @param array $params List of parameters. $.'key' is replaced by $value in all returned data. |
452 | * @param array $wikiTags The tags assigned to the wiki. |
453 | */ |
454 | public function extractAllGlobals( |
455 | $wiki, |
456 | $site = null, |
457 | $params = [], |
458 | $wikiTags = [] |
459 | ) { |
460 | $params = $this->mergeParams( $wiki, $site, $params, $wikiTags ); |
461 | foreach ( $this->settings as $varName => $setting ) { |
462 | $this->extractGlobalSetting( $varName, $wiki, $params ); |
463 | } |
464 | } |
465 | |
466 | /** |
467 | * Return specific settings for $wiki |
468 | * See the documentation of self::$siteParamsCallback for more in-depth |
469 | * documentation about this function |
470 | * |
471 | * @param string $wiki |
472 | * @return array |
473 | */ |
474 | protected function getWikiParams( $wiki ) { |
475 | static $default = [ |
476 | 'suffix' => null, |
477 | 'lang' => null, |
478 | 'tags' => [], |
479 | 'params' => [], |
480 | ]; |
481 | |
482 | if ( !is_callable( $this->siteParamsCallback ) ) { |
483 | return $default; |
484 | } |
485 | |
486 | $ret = ( $this->siteParamsCallback )( $this, $wiki ); |
487 | # Validate the returned value |
488 | if ( !is_array( $ret ) ) { |
489 | return $default; |
490 | } |
491 | |
492 | foreach ( $default as $name => $def ) { |
493 | if ( !isset( $ret[$name] ) || ( is_array( $def ) && !is_array( $ret[$name] ) ) ) { |
494 | $ret[$name] = $def; |
495 | } |
496 | } |
497 | |
498 | return $ret; |
499 | } |
500 | |
501 | /** |
502 | * Merge params between the ones passed to the function and the ones given |
503 | * by self::$siteParamsCallback for backward compatibility |
504 | * Values returned by self::getWikiParams() have the priority. |
505 | * |
506 | * @param string $wiki Wiki ID of the wiki in question. |
507 | * @param string|null $site The site from ::siteFromDB(), or db suffix. |
508 | * @param array $params List of parameters. $.'key' is replaced by $value in |
509 | * all returned data. |
510 | * @param array $wikiTags The tags assigned to the wiki. |
511 | * @return array |
512 | */ |
513 | protected function mergeParams( $wiki, $site, array $params, array $wikiTags ) { |
514 | $ret = $this->getWikiParams( $wiki ); |
515 | |
516 | $ret['suffix'] ??= $site; |
517 | |
518 | // Make tags based on the db suffix (e.g. wiki family) automatically |
519 | // available for use in wgConf. The user does not have to maintain |
520 | // wiki tag lookups (e.g. dblists at WMF) for the wiki family. |
521 | $wikiTags[] = $ret['suffix']; |
522 | |
523 | $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) ); |
524 | |
525 | $ret['params'] += $params; |
526 | |
527 | // Make the $lang and $site parameters automatically available if they |
528 | // were provided by `siteParamsCallback` via getWikiParams() |
529 | if ( !isset( $ret['params']['lang'] ) && $ret['lang'] !== null ) { |
530 | $ret['params']['lang'] = $ret['lang']; |
531 | } |
532 | if ( !isset( $ret['params']['site'] ) && $ret['suffix'] !== null ) { |
533 | $ret['params']['site'] = $ret['suffix']; |
534 | } |
535 | |
536 | // Optimization: For hot getAll() code path, precompute replacements to re-use |
537 | // over hundreds of processSetting() calls. |
538 | $ret['replacements'] = []; |
539 | // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive |
540 | foreach ( $ret['params'] as $key => $value ) { |
541 | $ret['replacements']['$' . $key] = $value; |
542 | } |
543 | |
544 | return $ret; |
545 | } |
546 | |
547 | /** |
548 | * Work out the site and language name from a database name |
549 | * |
550 | * @param string $wiki Wiki ID |
551 | * @return array [ string|null $site, string|null $languageCode ] |
552 | */ |
553 | public function siteFromDB( $wiki ) { |
554 | // Allow override |
555 | $def = $this->getWikiParams( $wiki ); |
556 | if ( $def['suffix'] !== null && $def['lang'] !== null ) { |
557 | return [ $def['suffix'], $def['lang'] ]; |
558 | } |
559 | |
560 | $languageCode = str_replace( '_', '-', $wiki ); |
561 | foreach ( $this->suffixes as $altSite => $suffix ) { |
562 | if ( $suffix === '' ) { |
563 | return [ '', $languageCode ]; |
564 | } elseif ( str_ends_with( $wiki, $suffix ) ) { |
565 | $site = is_string( $altSite ) ? $altSite : $suffix; |
566 | $languageCode = substr( $languageCode, 0, -strlen( $suffix ) ); |
567 | return [ $site, $languageCode ]; |
568 | } |
569 | } |
570 | |
571 | return [ null, null ]; |
572 | } |
573 | |
574 | /** |
575 | * Merge multiple arrays together. |
576 | * On encountering duplicate keys, merge the two, but ONLY if they're arrays. |
577 | * PHP's array_merge_recursive() merges ANY duplicate values into arrays, |
578 | * which is not fun |
579 | * |
580 | * @param array $array1 |
581 | * @param array $array2 |
582 | * @return array |
583 | */ |
584 | private static function arrayMerge( array $array1, array $array2 ) { |
585 | $out = $array1; |
586 | foreach ( $array2 as $key => $value ) { |
587 | if ( isset( $out[$key] ) ) { |
588 | if ( is_array( $out[$key] ) && is_array( $value ) ) { |
589 | // Merge the new array into the existing one |
590 | $out[$key] = self::arrayMerge( $out[$key], $value ); |
591 | } elseif ( is_numeric( $key ) ) { |
592 | // A numerical key is taken, append the value at the end instead. |
593 | // It is important that we generally preserve numerical keys and only |
594 | // fallback to appending values if there are conflicts. This is needed |
595 | // by configuration variables that hold associative arrays with |
596 | // meaningful numerical keys, such as $wgNamespacesWithSubpages, |
597 | // $wgNamespaceProtection, $wgNamespacesToBeSearchedDefault, etc. |
598 | $out[] = $value; |
599 | } elseif ( $out[$key] === false ) { |
600 | // A non-numerical key is taken and holds a false value, |
601 | // allow it to be overridden always. This exists mainly for the purpose |
602 | // merging permissions arrays, such as $wgGroupPermissions. |
603 | $out[$key] = $value; |
604 | } |
605 | // Else: The key is already taken and we keep the current value |
606 | |
607 | } else { |
608 | // Add a new key. |
609 | $out[$key] = $value; |
610 | } |
611 | } |
612 | |
613 | return $out; |
614 | } |
615 | |
616 | public function loadFullData() { |
617 | if ( $this->fullLoadCallback && !$this->fullLoadDone ) { |
618 | ( $this->fullLoadCallback )( $this ); |
619 | $this->fullLoadDone = true; |
620 | } |
621 | } |
622 | } |
623 | |
624 | /** @deprecated class alias since 1.41 */ |
625 | class_alias( SiteConfiguration::class, 'SiteConfiguration' ); |