diff options
author | daniel <dkinzler@wikimedia.org> | 2022-02-10 22:12:10 +0100 |
---|---|---|
committer | daniel <dkinzler@wikimedia.org> | 2022-02-23 14:09:41 +0100 |
commit | f9b589f556aabd62477aee6734ab93cc300d2b49 (patch) | |
tree | d3e710028cf732c9e4fcbb7bbec2277703195ebf | |
parent | 176194cef87bb222d5ad842a514b61d8bc7f9491 (diff) | |
download | mediawikicore-f9b589f556aabd62477aee6734ab93cc300d2b49.tar.gz mediawikicore-f9b589f556aabd62477aee6734ab93cc300d2b49.zip |
config-schema: Define types for all arrays.
This patch ensures that we know which arrays are lists (JsonSchema type
"array") and which are maps (JsonSchema type "object"). We can then
default to array_merge for lists and to array_plus for maps. This seems
clearer than requiring an explicit merge strategy to be declared for all
arrays.
This patch specified a mergeTrategy for some config variables that need
behavior different from the default.
This patch also changes the merging behavior to allow non-array values
to replace arrays and vice versa. It also changes the behavior of
defaults to allow falsy values to override non-falsy defaults.
Bug: T300129
Change-Id: Ia7b0c0250af6a957eac1efb554fb47511f5e093f
-rw-r--r-- | docs/Configuration.md | 11 | ||||
-rw-r--r-- | includes/Settings/Config/ArrayConfigBuilder.php | 23 | ||||
-rw-r--r-- | includes/Settings/Config/ConfigBuilderBase.php | 51 | ||||
-rw-r--r-- | includes/Settings/Config/ConfigBuilderTrait.php | 54 | ||||
-rw-r--r-- | includes/Settings/Config/ConfigSchemaAggregator.php | 36 | ||||
-rw-r--r-- | includes/Settings/Config/GlobalConfigBuilder.php | 23 | ||||
-rw-r--r-- | includes/Settings/Config/MergeStrategy.php | 5 | ||||
-rw-r--r-- | includes/config-schema.php | 327 | ||||
-rw-r--r-- | includes/config-schema.yaml | 331 | ||||
-rw-r--r-- | tests/phpunit/structure/SettingsTest.php | 406 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php | 54 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Settings/Config/ConfigSinkTestTrait.php | 140 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Settings/Config/GlobalConfigBuilderTest.php | 11 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Settings/Config/MergeStrategyTest.php | 6 |
14 files changed, 1138 insertions, 340 deletions
diff --git a/docs/Configuration.md b/docs/Configuration.md index 560615e12bf1..8b9610d9110f 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1009,6 +1009,7 @@ Force thumbnailing of animated GIFs above this size to a single frame instead of an animated thumbnail. As of MW 1.17 this limit is checked against the total size of all frames in the animation. + It probably makes sense to keep this equal to $wgMaxImageArea. # TiffThumbnailType {#TiffThumbnailType} @@ -1602,7 +1603,7 @@ The installer will add 'actor' to this list for all new wikis. # DBservers {#DBservers} Database load balancer -This is a two-dimensional array, an array of server info structures +This is a two-dimensional array, a list of server info structures Fields are: - host: Host name - dbname: Default database name @@ -1611,7 +1612,7 @@ Fields are: - type: DB type - driver: DB driver (when there are multiple drivers) -- load: Ratio of DB_REPLICA load, must be >=0, the sum of all loads must be >0. + - load: Ratio of DB_REPLICA load, must be >=0, the sum of all loads must be >0. If this is zero for any given server, no normal query traffic will be sent to it. It will be excluded from lag checks in maintenance scripts. The only way it can receive traffic is if groupLoads is used. @@ -2701,7 +2702,7 @@ overridable in user preferences. It is *not* used for signature timestamps. By default, this will be set to match $wgLocaltimezone. # OverrideUcfirstCharacters {#OverrideUcfirstCharacters} -List of Unicode characters for which capitalization is overridden in +Map of Unicode characters for which capitalization is overridden in Language::ucfirst. The characters should be represented as char_to_convert => conversion_override. See T219279 for details on why this is useful during php version transitions. @@ -4629,7 +4630,7 @@ May be an array of regexes or a single string for backwards compatibility. @note Each regex needs a beginning/end delimiter, eg: # or / # SummarySpamRegex {#SummarySpamRegex} -Same as the above except for edit summaries +Same as SpamRegex except for edit summaries # EnableDnsBlacklist {#EnableDnsBlacklist} Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open @@ -4660,7 +4661,7 @@ eventual domain search suffixes. @since 1.16 # ProxyList {#ProxyList} -Big list of banned IP addresses. +List of banned IP addresses. This can have the following formats: - An array of addresses diff --git a/includes/Settings/Config/ArrayConfigBuilder.php b/includes/Settings/Config/ArrayConfigBuilder.php index 61790eba7b83..9df71cac68bd 100644 --- a/includes/Settings/Config/ArrayConfigBuilder.php +++ b/includes/Settings/Config/ArrayConfigBuilder.php @@ -6,26 +6,21 @@ use Config; use HashConfig; use MediaWiki\Config\IterableConfig; -class ArrayConfigBuilder implements ConfigBuilder { - - use ConfigBuilderTrait; +class ArrayConfigBuilder extends ConfigBuilderBase { /** @var array */ protected $config = []; - public function set( string $key, $value, MergeStrategy $mergeStrategy = null ): ConfigBuilder { - $this->config[ $key ] = - $this->getNewValue( $key, $this->config[ $key ] ?? null, $value, $mergeStrategy ); - return $this; + protected function has( string $key ): bool { + return array_key_exists( $key, $this->config ); + } + + protected function get( string $key ) { + return $this->config[$key] ?? null; } - public function setDefault( string $key, $value, MergeStrategy $mergeStrategy = null ): ConfigBuilder { - if ( $mergeStrategy ) { - $this->set( $key, $value, $mergeStrategy->reverse() ); - } elseif ( !array_key_exists( $key, $this->config ) ) { - $this->config[$key] = $value; - } - return $this; + protected function update( string $key, $value ) { + $this->config[$key] = $value; } /** diff --git a/includes/Settings/Config/ConfigBuilderBase.php b/includes/Settings/Config/ConfigBuilderBase.php new file mode 100644 index 000000000000..8524c2e047fd --- /dev/null +++ b/includes/Settings/Config/ConfigBuilderBase.php @@ -0,0 +1,51 @@ +<?php + +namespace MediaWiki\Settings\Config; + +abstract class ConfigBuilderBase implements ConfigBuilder { + + abstract protected function has( string $key ): bool; + + abstract protected function get( string $key ); + + abstract protected function update( string $key, $value ); + + /** + * @inheritDoc + */ + public function set( + string $key, + $newValue, + MergeStrategy $mergeStrategy = null + ): ConfigBuilder { + if ( $mergeStrategy && is_array( $newValue ) ) { + $oldValue = $this->get( $key ); + if ( $oldValue && is_array( $oldValue ) ) { + $newValue = $mergeStrategy->merge( $oldValue, $newValue ); + } + } + $this->update( $key, $newValue ); + return $this; + } + + public function setDefault( + string $key, + $defaultValue, + MergeStrategy $mergeStrategy = null + ): ConfigBuilder { + if ( $this->has( $key ) ) { + if ( $mergeStrategy && $defaultValue && is_array( $defaultValue ) ) { + $customValue = $this->get( $key ); + if ( is_array( $customValue ) ) { + $newValue = $mergeStrategy->merge( $defaultValue, $customValue ); + $this->update( $key, $newValue ); + } + } + } else { + $this->update( $key, $defaultValue ); + } + + return $this; + } + +} diff --git a/includes/Settings/Config/ConfigBuilderTrait.php b/includes/Settings/Config/ConfigBuilderTrait.php deleted file mode 100644 index dfec6d47181a..000000000000 --- a/includes/Settings/Config/ConfigBuilderTrait.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -namespace MediaWiki\Settings\Config; - -use MediaWiki\Settings\SettingsBuilderException; - -/** - * Trait for sharing code between implementations of ConfigBuilder - */ -trait ConfigBuilderTrait { - - /** - * Determine the new value given an old value and a merge strategy. - * - * @param string $key - * @param mixed $oldValue - * @param mixed $newValue - * @param ?MergeStrategy $mergeStrategy - * - * @return mixed - */ - private function getNewValue( string $key, $oldValue, $newValue, ?MergeStrategy $mergeStrategy = null ) { - if ( $mergeStrategy ) { - if ( !is_array( $newValue ) ) { - throw new SettingsBuilderException( - 'Cannot merge non-array value of type {value_type} under {key}', - [ - 'value_type' => get_debug_type( $newValue ), - 'key' => $key, - ] - ); - } elseif ( $oldValue === null ) { - // Optimization: If there is no old value, no need to merge. - return $newValue; - } elseif ( !is_array( $oldValue ) ) { - throw new SettingsBuilderException( - 'Cannot merge into non-array value of type {value_type} under {key}', - [ - 'value_type' => get_debug_type( $oldValue ), - 'key' => $key, - ] - ); - // @phan-suppress-next-line PhanImpossibleCondition False positive - } elseif ( !$oldValue ) { - // Optimization: If the old value is an empty array, no need to merge. - return $newValue; - } else { - return $mergeStrategy->merge( $oldValue, $newValue ); - } - } - return $newValue; - } - -} diff --git a/includes/Settings/Config/ConfigSchemaAggregator.php b/includes/Settings/Config/ConfigSchemaAggregator.php index 1e0644a09b99..989967969a4c 100644 --- a/includes/Settings/Config/ConfigSchemaAggregator.php +++ b/includes/Settings/Config/ConfigSchemaAggregator.php @@ -94,9 +94,41 @@ class ConfigSchemaAggregator { */ public function getMergeStrategyFor( string $key ): ?MergeStrategy { $strategyName = $this->schema[$key]['mergeStrategy'] ?? null; + if ( $strategyName === null ) { - return null; + $type = $this->schema[ $key ]['type'] ?? null; + $strategyName = $type ? $this->getStrategyForType( $type ) : null; } - return MergeStrategy::newFromName( $strategyName ); + + return $strategyName ? MergeStrategy::newFromName( $strategyName ) : null; + } + + /** + * Returns an appropriate merge strategy for the given type. + * + * @param string|array $type + * + * @return string + */ + private function getStrategyForType( $type ): string { + if ( is_array( $type ) ) { + if ( in_array( 'array', $type ) ) { + $type = 'array'; + } elseif ( in_array( 'object', $type ) ) { + $type = 'object'; + } + } + + if ( $type === 'array' ) { + // In JSON Schema, "array" means a list. + // Use array_merge to append. + return 'array_merge'; + } elseif ( $type === 'object' ) { + // In JSON Schema, "object" means a map. + // Use array_plus to replace keys, even if they are numeric. + return 'array_plus'; + } + + return 'replace'; } } diff --git a/includes/Settings/Config/GlobalConfigBuilder.php b/includes/Settings/Config/GlobalConfigBuilder.php index 2413fe750fc6..c75d6896ba65 100644 --- a/includes/Settings/Config/GlobalConfigBuilder.php +++ b/includes/Settings/Config/GlobalConfigBuilder.php @@ -5,8 +5,7 @@ namespace MediaWiki\Settings\Config; use Config; use GlobalVarConfig; -class GlobalConfigBuilder implements ConfigBuilder { - use ConfigBuilderTrait; +class GlobalConfigBuilder extends ConfigBuilderBase { /** @var string */ public const DEFAULT_PREFIX = 'wg'; @@ -21,23 +20,19 @@ class GlobalConfigBuilder implements ConfigBuilder { $this->prefix = $prefix; } - public function set( string $key, $value, MergeStrategy $mergeStrategy = null ): ConfigBuilder { + protected function has( string $key ): bool { $var = $this->getVarName( $key ); - - $GLOBALS[ $var ] = - $this->getNewValue( $key, $GLOBALS[ $var ] ?? null, $value, $mergeStrategy ); - return $this; + return array_key_exists( $var, $GLOBALS ); } - public function setDefault( string $key, $value, MergeStrategy $mergeStrategy = null ): ConfigBuilder { + protected function get( string $key ) { $var = $this->getVarName( $key ); + return $GLOBALS[ $var ] ?? null; + } - if ( $mergeStrategy ) { - $this->set( $key, $value, $mergeStrategy->reverse() ); - } elseif ( !array_key_exists( $var, $GLOBALS ) ) { - $GLOBALS[ $var ] = $value; - } - return $this; + protected function update( string $key, $value ) { + $var = $this->getVarName( $key ); + $GLOBALS[ $var ] = $value; } private function getVarName( string $key ): string { diff --git a/includes/Settings/Config/MergeStrategy.php b/includes/Settings/Config/MergeStrategy.php index 41c816c7fe5d..164aa58ba563 100644 --- a/includes/Settings/Config/MergeStrategy.php +++ b/includes/Settings/Config/MergeStrategy.php @@ -22,6 +22,9 @@ class MergeStrategy { public const ARRAY_MERGE = 'array_merge'; /** @var string */ + public const REPLACE = 'replace'; + + /** @var string */ private $name; /** @var bool */ @@ -77,6 +80,8 @@ class MergeStrategy { } switch ( $this->name ) { + case self::REPLACE: + return $source; case self::ARRAY_MERGE_RECURSIVE: return array_merge_recursive( $destination, $source ); case self::ARRAY_REPLACE_RECURSIVE: diff --git a/includes/config-schema.php b/includes/config-schema.php index 13774d69618d..470f7775fcfd 100644 --- a/includes/config-schema.php +++ b/includes/config-schema.php @@ -7,7 +7,7 @@ return [ 'default' => [ 'main' => 'GlobalVarConfig::newInstance', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'Sitename' => [ 'default' => 'MediaWiki', @@ -58,9 +58,17 @@ return [ ], 'ExtensionDirectory' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'StyleDirectory' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'ArticlePath' => [ 'default' => false, @@ -86,14 +94,14 @@ return [ 'Logos' => [ 'default' => false, 'type' => [ - 0 => 'array', + 0 => 'object', 1 => 'boolean', ], ], 'LogoHD' => [ 'default' => false, 'type' => [ - 0 => 'array', + 0 => 'object', 1 => 'boolean', ], ], @@ -123,6 +131,7 @@ return [ 'ActionPaths' => [ 'default' => [ ], + 'type' => 'object', ], 'MainPageIsDomainRoot' => [ 'default' => false, @@ -152,13 +161,19 @@ return [ 'ImgAuthUrlPathMap' => [ 'default' => [ ], + 'type' => 'object', ], 'LocalFileRepo' => [ 'default' => false, + 'type' => [ + 0 => 'object', + 1 => 'boolean', + ], ], 'ForeignFileRepos' => [ 'default' => [ ], + 'type' => 'array', ], 'UseInstantCommons' => [ 'default' => false, @@ -211,6 +226,7 @@ return [ 'default' => [ 0 => 'local', ], + 'type' => 'array', ], 'UploadDialog' => [ 'default' => [ @@ -235,14 +251,17 @@ return [ 'uncategorized' => '', ], ], + 'type' => 'object', ], 'FileBackends' => [ 'default' => [ ], + 'type' => 'object', ], 'LockManagers' => [ 'default' => [ ], + 'type' => 'object', ], 'ShowEXIF' => [ 'default' => true, @@ -256,6 +275,7 @@ return [ 'CopyUploadsDomains' => [ 'default' => [ ], + 'type' => 'array', ], 'CopyUploadsFromSpecialUpload' => [ 'default' => false, @@ -304,7 +324,7 @@ return [ 3 => 'jpeg', 4 => 'webp', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'ProhibitedFileExtensions' => [ 'default' => [ @@ -340,6 +360,7 @@ return [ 29 => 'vxd', 30 => 'cpl', ], + 'type' => 'array', ], 'MimeTypeExclusions' => [ 'default' => [ @@ -359,6 +380,7 @@ return [ 13 => 'application/x-msdownload', 14 => 'application/x-msmetafile', ], + 'type' => 'array', ], 'AllowJavaUploads' => [ 'default' => false, @@ -383,18 +405,16 @@ return [ 3 => 'image/svg+xml', 4 => 'application/pdf', ], + 'type' => 'array', ], 'MediaHandlers' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'NativeImageLazyLoading' => [ 'default' => false, - 'type' => [ - 0 => 'array', - 1 => 'boolean', - ], + 'type' => 'boolean', ], 'ParserTestMediaHandlers' => [ 'default' => [ @@ -409,6 +429,7 @@ return [ 'image/svg+xml' => 'MockSvgHandler', 'image/vnd.djvu' => 'MockDjVuHandler', ], + 'type' => 'object', ], 'UseImageResize' => [ 'default' => true, @@ -422,6 +443,7 @@ return [ 'MaxInterlacingAreas' => [ 'default' => [ ], + 'type' => 'object', ], 'SharpenParameter' => [ 'default' => '0x0.4', @@ -462,6 +484,7 @@ return [ 0 => 'SvgHandler::rasterizeImagickExt', ], ], + 'type' => 'object', ], 'SVGConverter' => [ 'default' => 'ImageMagick', @@ -492,6 +515,8 @@ return [ 'TiffThumbnailType' => [ 'default' => [ ], + 'type' => 'array', + 'mergeStrategy' => 'replace', ], 'ThumbnailEpoch' => [ 'default' => '20030516000000', @@ -511,9 +536,17 @@ return [ ], 'EnableAutoRotation' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'boolean', + ], ], 'Antivirus' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'AntivirusSetup' => [ 'default' => [ @@ -528,6 +561,7 @@ return [ 'messagepattern' => '/.*?:(.*)/sim', ], ], + 'type' => 'object', ], 'AntivirusRequired' => [ 'default' => true, @@ -546,6 +580,10 @@ return [ ], 'MimeDetectorCommand' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'TrivialMimeDetection' => [ 'default' => false, @@ -558,6 +596,7 @@ return [ 'http://www.w3.org/1999/xhtml:html' => 'text/html', 'html' => 'text/html', ], + 'type' => 'object', ], 'ImageLimits' => [ 'default' => [ @@ -586,6 +625,7 @@ return [ 1 => 2048, ], ], + 'type' => 'array', ], 'ThumbLimits' => [ 'default' => [ @@ -596,9 +636,14 @@ return [ 4 => 250, 5 => 300, ], + 'type' => 'array', ], 'ThumbnailBuckets' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'array', + ], ], 'ThumbnailMinimumBucketDistance' => [ 'default' => 50, @@ -606,6 +651,7 @@ return [ 'UploadThumbnailRenderMap' => [ 'default' => [ ], + 'type' => 'object', ], 'UploadThumbnailRenderMethod' => [ 'default' => 'jobqueue', @@ -622,6 +668,7 @@ return [ 'GalleryOptions' => [ 'default' => [ ], + 'type' => 'object', ], 'ThumbUpright' => [ 'default' => 0.75, @@ -637,15 +684,31 @@ return [ ], 'DjvuDump' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'DjvuRenderer' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'DjvuTxt' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'DjvuPostProcessor' => [ 'default' => 'pnmtojpeg', + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'DjvuOutputExtension' => [ 'default' => 'jpg', @@ -691,6 +754,10 @@ return [ ], 'SMTP' => [ 'default' => false, + 'type' => [ + 0 => 'boolean', + 1 => 'object', + ], ], 'AdditionalMailParams' => [ 'default' => null, @@ -730,12 +797,17 @@ return [ 'UsersNotifiedOnAllChanges' => [ 'default' => [ ], + 'type' => 'object', ], 'DBname' => [ 'default' => 'my_wiki', ], 'DBmwschema' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'string', + ], ], 'DBprefix' => [ 'default' => '', @@ -796,17 +868,24 @@ return [ 0 => 'user', 1 => 'user_properties', ], + 'type' => 'array', ], 'SharedSchema' => [ 'default' => false, ], 'DBservers' => [ 'default' => false, + 'type' => [ + 0 => 'boolean', + 1 => 'array', + ], ], 'LBFactoryConf' => [ 'default' => [ 'class' => 'Wikimedia\\Rdbms\\LBFactorySimple', ], + 'type' => 'object', + 'mergeStrategy' => 'replace', ], 'DataCenterUpdateStickTTL' => [ 'default' => 10, @@ -851,12 +930,12 @@ return [ 'text' => 'TextContentHandler', 'unknown' => 'FallbackContentHandler', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'NamespaceContentModels' => [ 'default' => [ ], - 'mergeStrategy' => 'array_plus', + 'type' => 'object', ], 'ContentHandlerTextFallback' => [ 'default' => 'ignore', @@ -867,6 +946,7 @@ return [ 1 => 'javascript', 2 => 'css', ], + 'type' => 'array', ], 'CompressRevisions' => [ 'default' => false, @@ -879,6 +959,7 @@ return [ 'ExternalServers' => [ 'default' => [ ], + 'type' => 'object', ], 'DefaultExternalStore' => [ 'default' => false, @@ -949,6 +1030,10 @@ return [ ], 'PoolCounterConf' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'object', + ], ], 'MaxUserDBWriteDuration' => [ 'default' => false, @@ -1055,9 +1140,15 @@ return [ 'reportDupes' => false, ], ], + 'mergeStrategy' => 'array_plus', ], 'MainWANCache' => [ 'default' => false, + 'type' => [ + 0 => 'integer', + 1 => 'string', + 2 => 'boolean', + ], ], 'WANObjectCaches' => [ 'default' => [ @@ -1066,6 +1157,7 @@ return [ 'cacheId' => 0, ], ], + 'mergeStrategy' => 'array_plus', ], 'EnableWANCacheReaper' => [ 'default' => false, @@ -1108,6 +1200,7 @@ return [ 'default' => [ 0 => '127.0.0.1:11211', ], + 'type' => 'array', ], 'MemCachedPersistent' => [ 'default' => false, @@ -1132,6 +1225,7 @@ return [ 'forceRecache' => false, 'manualRecache' => false, ], + 'type' => 'object', ], 'CachePages' => [ 'default' => true, @@ -1209,10 +1303,12 @@ return [ 'CdnServers' => [ 'default' => [ ], + 'type' => 'object', ], 'CdnServersNoPurge' => [ 'default' => [ ], + 'type' => 'object', ], 'SquidPurgeUseHostHeader' => [ 'default' => true, @@ -1220,6 +1316,7 @@ return [ 'HTCPRouting' => [ 'default' => [ ], + 'type' => 'object', ], 'HTCPMulticastTTL' => [ 'default' => 1, @@ -1233,6 +1330,7 @@ return [ 'GrammarForms' => [ 'default' => [ ], + 'type' => 'object', ], 'InterwikiMagic' => [ 'default' => true, @@ -1243,14 +1341,17 @@ return [ 'ExtraInterlanguageLinkPrefixes' => [ 'default' => [ ], + 'type' => 'object', ], 'InterlanguageLinkCodeMap' => [ 'default' => [ ], + 'type' => 'object', ], 'ExtraLanguageNames' => [ 'default' => [ ], + 'type' => 'object', ], 'ExtraLanguageCodes' => [ 'default' => [ @@ -1258,10 +1359,12 @@ return [ 'no' => 'nb', 'simple' => 'en', ], + 'type' => 'object', ], 'DummyLanguageCodes' => [ 'default' => [ ], + 'type' => 'object', ], 'AllUnicodeFixes' => [ 'default' => false, @@ -1299,6 +1402,7 @@ return [ 'DisabledVariants' => [ 'default' => [ ], + 'type' => 'object', ], 'VariantArticlePath' => [ 'default' => false, @@ -1309,6 +1413,7 @@ return [ 'ForceUIMsgAsContentMsg' => [ 'default' => [ ], + 'type' => 'object', ], 'RawHtmlMessages' => [ 'default' => [ @@ -1318,7 +1423,6 @@ return [ 3 => 'feedback-terms', 4 => 'feedback-termsofuse', ], - 'mergeStrategy' => 'array_merge', 'type' => 'array', 'items' => [ 'type' => 'string', @@ -1333,7 +1437,7 @@ return [ 'OverrideUcfirstCharacters' => [ 'default' => [ ], - 'type' => 'array', + 'type' => 'object', ], 'MimeType' => [ 'default' => 'text/html', @@ -1353,6 +1457,7 @@ return [ 'XhtmlNamespaces' => [ 'default' => [ ], + 'type' => 'object', ], 'SiteNotice' => [ 'default' => '', @@ -1364,6 +1469,7 @@ return [ 'SkinMetaTags' => [ 'default' => [ ], + 'type' => 'object', ], 'DefaultSkin' => [ 'default' => 'vector', @@ -1374,6 +1480,7 @@ return [ 'SkipSkins' => [ 'default' => [ ], + 'type' => 'object', ], 'DisableOutputCompression' => [ 'default' => false, @@ -1383,6 +1490,7 @@ return [ 0 => 'html5', 1 => 'legacy', ], + 'type' => 'array', ], 'ExternalInterwikiFragmentMode' => [ 'default' => 'legacy', @@ -1401,6 +1509,7 @@ return [ ], ], ], + 'type' => 'object', ], 'UseCombinedLoginLink' => [ 'default' => false, @@ -1423,15 +1532,17 @@ return [ 'ResourceModules' => [ 'default' => [ ], + 'type' => 'object', ], 'ResourceModuleSkinStyles' => [ 'default' => [ ], + 'type' => 'object', ], 'ResourceLoaderSources' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'ResourceBasePath' => [ 'default' => null, @@ -1441,6 +1552,7 @@ return [ 'versioned' => 2592000, 'unversioned' => 300, ], + 'type' => 'object', ], 'ResourceLoaderUseObjectCacheForDeps' => [ 'default' => false, @@ -1503,19 +1615,22 @@ return [ 14 => 'Category', 15 => 'Category_talk', ], + 'type' => 'object', ], 'ExtraNamespaces' => [ 'default' => [ ], + 'type' => 'object', ], 'ExtraGenderNamespaces' => [ 'default' => [ ], - 'mergeStrategy' => 'array_plus', + 'type' => 'object', ], 'NamespaceAliases' => [ 'default' => [ ], + 'type' => 'object', ], 'LegalTitleChars' => [ 'default' => ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', @@ -1526,7 +1641,7 @@ return [ 'CapitalLinkOverrides' => [ 'default' => [ ], - 'mergeStrategy' => 'array_plus', + 'type' => 'object', ], 'NamespacesWithSubpages' => [ 'default' => [ @@ -1544,21 +1659,23 @@ return [ 13 => true, 15 => true, ], - 'mergeStrategy' => 'array_plus', + 'type' => 'object', ], 'ContentNamespaces' => [ 'default' => [ 0 => 0, ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'ShortPagesNamespaceExclusions' => [ 'default' => [ ], + 'type' => 'array', ], 'ExtraSignatureNamespaces' => [ 'default' => [ ], + 'type' => 'array', ], 'MaxRedirects' => [ 'default' => 1, @@ -1570,6 +1687,7 @@ return [ 2 => 'Mytalk', 3 => 'Redirect', ], + 'type' => 'array', ], 'DisableHardRedirects' => [ 'default' => false, @@ -1580,10 +1698,12 @@ return [ 'InterwikiPrefixDisplayTypes' => [ 'default' => [ ], + 'type' => 'object', ], 'LocalInterwikis' => [ 'default' => [ ], + 'type' => 'array', ], 'InterwikiExpiry' => [ 'default' => 10800, @@ -1592,9 +1712,10 @@ return [ 'default' => false, 'type' => [ 0 => 'boolean', - 1 => 'array', + 1 => 'object', 2 => 'string', ], + 'mergeStrategy' => 'replace', ], 'InterwikiScopes' => [ 'default' => 3, @@ -1609,6 +1730,7 @@ return [ 'default' => [ 'mediawiki' => 'MediaWikiSite', ], + 'type' => 'object', ], 'MaxTocLevel' => [ 'default' => 999, @@ -1653,6 +1775,7 @@ return [ 26 => 'xmpp:', 27 => '//', ], + 'type' => 'array', ], 'CleanSignatures' => [ 'default' => true, @@ -1672,6 +1795,7 @@ return [ 'TidyConfig' => [ 'default' => [ ], + 'type' => 'object', ], 'ParserEnableLegacyMediaDOM' => [ 'default' => true, @@ -1691,11 +1815,13 @@ return [ 'NoFollowNsExceptions' => [ 'default' => [ ], + 'type' => 'array', ], 'NoFollowDomainExceptions' => [ 'default' => [ 0 => 'mediawiki.org', ], + 'type' => 'array', ], 'RegisterInternalExternals' => [ 'default' => false, @@ -1724,6 +1850,7 @@ return [ 'PMID' => false, 'RFC' => false, ], + 'type' => 'object', ], 'ArticleCountMethod' => [ 'default' => 'link', @@ -1761,7 +1888,7 @@ return [ ], ], ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'CentralIdLookupProvider' => [ 'default' => 'local', @@ -1818,10 +1945,15 @@ return [ 'PasswordNotInCommonList' => 'PasswordPolicyChecks::checkPasswordNotInCommonList', ], ], - 'mergeStrategy' => 'array_merge_recursive', + 'type' => 'object', + 'mergeStrategy' => 'array_replace_recursive', ], 'AuthManagerConfig' => [ 'default' => null, + 'type' => [ + 0 => 'object', + 1 => 'null', + ], ], 'AuthManagerAutoConfig' => [ 'default' => [ @@ -1875,6 +2007,7 @@ return [ ], ], ], + 'type' => 'object', 'mergeStrategy' => 'array_plus_2d', ], 'RememberMe' => [ @@ -1885,7 +2018,6 @@ return [ 'default' => [ 'default' => 300, ], - 'mergeStrategy' => 'array_merge', 'type' => 'object', 'additionalProperties' => [ 'type' => 'integer', @@ -1904,7 +2036,6 @@ return [ 'default' => [ 0 => 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], - 'mergeStrategy' => 'array_merge', 'type' => 'array', 'items' => [ 'type' => 'string', @@ -1914,7 +2045,6 @@ return [ 'default' => [ 0 => 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], - 'mergeStrategy' => 'array_merge', 'type' => 'array', 'items' => [ 'type' => 'string', @@ -1969,12 +2099,14 @@ return [ 'algo' => 'auto', ], ], + 'type' => 'object', ], 'PasswordResetRoutes' => [ 'default' => [ 'username' => true, 'email' => true, ], + 'type' => 'object', ], 'MaxSigChars' => [ 'default' => 255, @@ -1986,6 +2118,7 @@ return [ 'default' => [ 0 => 'obsolete-tag', ], + 'type' => 'array', ], 'MaxNameChars' => [ 'default' => 255, @@ -2005,6 +2138,7 @@ return [ 10 => 'msg:spambot_username', 11 => 'msg:autochange-username', ], + 'type' => 'array', ], 'DefaultUserOptions' => [ 'default' => [ @@ -2071,12 +2205,12 @@ return [ 'requireemail' => 0, 'skin-responsive' => 1, ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'HiddenPrefs' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'InvalidUsernameCharacters' => [ 'default' => '@:', @@ -2114,7 +2248,7 @@ return [ ], ], ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'AllowRequiringEmailForResets' => [ 'default' => false, @@ -2130,6 +2264,7 @@ return [ 'IPv4' => 16, 'IPv6' => 19, ], + 'type' => 'object', ], 'BlockDisablesLogin' => [ 'default' => false, @@ -2279,6 +2414,7 @@ return [ 'RevokePermissions' => [ 'default' => [ ], + 'type' => 'object', 'mergeStrategy' => 'array_plus_2d', ], 'GroupInheritsPermissions' => [ @@ -2295,17 +2431,17 @@ return [ 1 => 'user', 2 => 'autoconfirmed', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'GroupsAddToSelf' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'GroupsRemoveFromSelf' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'RestrictionTypes' => [ 'default' => [ @@ -2314,6 +2450,7 @@ return [ 2 => 'move', 3 => 'upload', ], + 'type' => 'array', ], 'RestrictionLevels' => [ 'default' => [ @@ -2321,26 +2458,29 @@ return [ 1 => 'autoconfirmed', 2 => 'sysop', ], + 'type' => 'array', ], 'CascadingRestrictionLevels' => [ 'default' => [ 0 => 'sysop', ], + 'type' => 'array', ], 'SemiprotectedRestrictionLevels' => [ 'default' => [ 0 => 'autoconfirmed', ], + 'type' => 'array', ], 'NamespaceProtection' => [ 'default' => [ ], - 'mergeStrategy' => 'array_plus', + 'type' => 'object', ], 'NonincludableNamespaces' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'AutoConfirmAge' => [ 'default' => 0, @@ -2362,12 +2502,14 @@ return [ ], ], ], + 'type' => 'object', ], 'AutopromoteOnce' => [ 'default' => [ 'onEdit' => [ ], ], + 'type' => 'object', ], 'AutopromoteOnceLogInRC' => [ 'default' => true, @@ -2375,17 +2517,17 @@ return [ 'AddGroups' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'RemoveGroups' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'AvailableRights' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'DeleteRevisionsLimit' => [ 'default' => 0, @@ -2403,14 +2545,17 @@ return [ 'seconds' => 86400, ], ], + 'type' => 'array', ], 'SpamRegex' => [ 'default' => [ ], + 'type' => 'array', ], 'SummarySpamRegex' => [ 'default' => [ ], + 'type' => 'array', ], 'EnableDnsBlacklist' => [ 'default' => false, @@ -2419,14 +2564,20 @@ return [ 'default' => [ 0 => 'http.dnsbl.sorbs.net.', ], + 'type' => 'array', ], 'ProxyList' => [ 'default' => [ ], + 'type' => [ + 0 => 'string', + 1 => 'array', + ], ], 'ProxyWhitelist' => [ 'default' => [ ], + 'type' => 'array', ], 'SoftBlockRanges' => [ 'default' => [ @@ -2596,11 +2747,13 @@ return [ ], ], ], + 'type' => 'object', 'mergeStrategy' => 'array_plus_2d', ], 'RateLimitsExcludedIPs' => [ 'default' => [ ], + 'type' => 'array', ], 'PutIPinRC' => [ 'default' => true, @@ -2619,6 +2772,7 @@ return [ 'seconds' => 172800, ], ], + 'type' => 'array', ], 'GrantPermissions' => [ 'default' => [ @@ -2830,7 +2984,6 @@ return [ 'privateinfo' => 'private-information', ], 'type' => 'object', - 'mergeStrategy' => 'array_merge', 'additionalProperties' => [ 'type' => 'string', ], @@ -2912,6 +3065,7 @@ return [ 'https://t.lkqd.net/t' => true, 'chrome-extension' => true, ], + 'type' => 'object', ], 'AllowCrossOrigin' => [ 'default' => false, @@ -2971,6 +3125,7 @@ return [ 'CacheVaryCookies' => [ 'default' => [ ], + 'type' => 'array', ], 'SessionName' => [ 'default' => false, @@ -3043,15 +3198,18 @@ return [ 'maxAffected' => 1000, ], ], + 'type' => 'object', ], 'DebugLogGroups' => [ 'default' => [ ], + 'type' => 'object', ], 'MWLoggerDefaultSpi' => [ 'default' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], + 'mergeStrategy' => 'replace', 'type' => 'object', ], 'ShowDebug' => [ @@ -3084,6 +3242,8 @@ return [ 'Profiler' => [ 'default' => [ ], + 'type' => 'object', + 'mergeStrategy' => 'replace', ], 'StatsdServer' => [ 'default' => false, @@ -3094,6 +3254,7 @@ return [ 'StatsdSamplingRates' => [ 'default' => [ ], + 'type' => 'object', ], 'MetricsTarget' => [ 'default' => null, @@ -3119,7 +3280,7 @@ return [ 'ParserTestFiles' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'EnableJavaScriptTest' => [ 'default' => false, @@ -3147,6 +3308,7 @@ return [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], + 'type' => 'object', ], 'EnableOpenSearchSuggest' => [ 'default' => true, @@ -3167,6 +3329,7 @@ return [ 'default' => [ 0 => true, ], + 'type' => 'array', ], 'DisableInternalSearch' => [ 'default' => false, @@ -3176,9 +3339,17 @@ return [ ], 'SitemapNamespaces' => [ 'default' => false, + 'type' => [ + 0 => 'boolean', + 1 => 'array', + ], ], 'SitemapNamespacesPriorities' => [ 'default' => false, + 'type' => [ + 0 => 'boolean', + 1 => 'object', + ], ], 'EnableSearchContributorsByIP' => [ 'default' => true, @@ -3186,7 +3357,7 @@ return [ 'SpecialSearchFormOptions' => [ 'default' => [ ], - 'type' => 'array', + 'type' => 'object', ], 'SearchMatchRedirectPreference' => [ 'default' => false, @@ -3206,6 +3377,7 @@ return [ 'default' => [ 14 => true, ], + 'type' => 'object', ], 'UniversalEditButton' => [ 'default' => true, @@ -3240,6 +3412,7 @@ return [ 'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)' => 'https://gerrit.wikimedia.org/g/%R/+/%H', 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' => 'https://gerrit.wikimedia.org/g/%R/+/%H', ], + 'type' => 'object', ], 'RCMaxAge' => [ 'default' => 7776000, @@ -3260,6 +3433,7 @@ return [ 2 => 250, 3 => 500, ], + 'type' => 'array', ], 'RCLinkDays' => [ 'default' => [ @@ -3269,16 +3443,19 @@ return [ 3 => 14, 4 => 30, ], + 'type' => 'array', ], 'RCFeeds' => [ 'default' => [ ], + 'type' => 'object', ], 'RCEngines' => [ 'default' => [ 'redis' => 'RedisPubSubFeedEngine', 'udp' => 'UDPRCFeedEngine', ], + 'type' => 'object', ], 'RCWatchCategoryMembership' => [ 'default' => false, @@ -3310,18 +3487,20 @@ return [ 'OverrideSiteFeed' => [ 'default' => [ ], + 'type' => 'object', ], 'FeedClasses' => [ 'default' => [ 'rss' => 'RSSFeed', 'atom' => 'AtomFeed', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'AdvertisedFeedTypes' => [ 'default' => [ 0 => 'atom', ], + 'type' => 'array', ], 'RCShowWatchingUsers' => [ 'default' => false, @@ -3394,7 +3573,7 @@ return [ 'grouping' => 'any', ], ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'WatchlistExpiry' => [ 'default' => false, @@ -3435,6 +3614,7 @@ return [ 'ImportSources' => [ 'default' => [ ], + 'type' => 'object', ], 'ImportTargetNamespace' => [ 'default' => null, @@ -3466,26 +3646,27 @@ return [ 'ExtensionFunctions' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'ExtensionMessagesFiles' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'MessagesDirs' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'ExtensionEntryPointListFiles' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'ParserOutputHooks' => [ 'default' => [ ], + 'type' => 'object', ], 'EnableParserLimitReporting' => [ 'default' => true, @@ -3493,12 +3674,12 @@ return [ 'ValidSkinNames' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'SpecialPages' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'AutoloadAttemptLowercase' => [ 'default' => false, @@ -3506,16 +3687,18 @@ return [ 'ExtensionCredits' => [ 'default' => [ ], + 'type' => 'object', ], 'Hooks' => [ 'default' => [ ], + 'type' => 'object', 'mergeStrategy' => 'array_merge_recursive', ], 'ServiceWiringFiles' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'JobClasses' => [ 'default' => [ @@ -3545,13 +3728,14 @@ return [ 'null' => 'NullJob', 'userEditCountInit' => 'UserEditCountInitJob', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'JobTypesExcludedFromDefaultQueue' => [ 'default' => [ 0 => 'AssembleUploadChunks', 1 => 'PublishStashedFile', ], + 'type' => 'array', ], 'JobBackoffThrottling' => [ 'default' => [ @@ -3576,6 +3760,7 @@ return [ 'claimTTL' => 3600, ], ], + 'type' => 'object', ], 'JobQueueIncludeInMaxLagFactor' => [ 'default' => false, @@ -3587,11 +3772,13 @@ return [ 1 => 'cacheUpdate', ], ], + 'type' => 'object', ], 'PagePropLinkInvalidations' => [ 'default' => [ 'hiddencat' => 'categorylinks', ], + 'type' => 'object', ], 'CategoryMagicGallery' => [ 'default' => true, @@ -3605,10 +3792,12 @@ return [ 'TempCategoryCollations' => [ 'default' => [ ], + 'type' => 'array', ], 'TrackingCategories' => [ 'default' => [ ], + 'type' => 'array', ], 'LogTypes' => [ 'default' => [ @@ -3627,13 +3816,13 @@ return [ 12 => 'managetags', 13 => 'contentmodel', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'LogRestrictions' => [ 'default' => [ 'suppress' => 'suppressionlog', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'FilterLogTypes' => [ 'default' => [ @@ -3641,7 +3830,7 @@ return [ 'tag' => true, 'newusers' => false, ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'LogNames' => [ 'default' => [ @@ -3657,7 +3846,7 @@ return [ 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'LogHeaders' => [ 'default' => [ @@ -3673,12 +3862,12 @@ return [ 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'LogActions' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'LogActionsHandlers' => [ 'default' => [ @@ -3720,7 +3909,7 @@ return [ 'upload/revert' => 'UploadLogFormatter', 'upload/upload' => 'UploadLogFormatter', ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'ActionFilteredLogs' => [ 'default' => [ @@ -3857,7 +4046,7 @@ return [ ], ], ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'NewUserLog' => [ 'default' => true, @@ -3890,7 +4079,7 @@ return [ 'Actions' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'array', ], 'DefaultRobotPolicy' => [ 'default' => 'index,follow', @@ -3898,13 +4087,19 @@ return [ 'NamespaceRobotPolicies' => [ 'default' => [ ], + 'type' => 'object', ], 'ArticleRobotPolicies' => [ 'default' => [ ], + 'type' => 'object', ], 'ExemptFromUserRobotsControl' => [ 'default' => null, + 'type' => [ + 0 => 'null', + 1 => 'array', + ], ], 'DebugAPI' => [ 'default' => false, @@ -3912,27 +4107,27 @@ return [ 'APIModules' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'APIFormatModules' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'APIMetaModules' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'APIPropModules' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'APIListModules' => [ 'default' => [ ], - 'mergeStrategy' => 'array_merge', + 'type' => 'object', ], 'APIMaxDBRows' => [ 'default' => 5000, @@ -3957,6 +4152,7 @@ return [ 0 => 'MIMEsearch', 1 => 'LinkSearch', ], + 'type' => 'array', ], 'AjaxUploadDestCheck' => [ 'default' => true, @@ -3967,10 +4163,12 @@ return [ 'CrossSiteAJAXdomains' => [ 'default' => [ ], + 'type' => 'object', ], 'CrossSiteAJAXdomainExceptions' => [ 'default' => [ ], + 'type' => 'object', ], 'AllowedCorsHeaders' => [ 'default' => [ @@ -3985,10 +4183,12 @@ return [ 8 => 'Api-User-Agent', 9 => 'Access-Control-Max-Age', ], + 'type' => 'array', ], 'RestAPIAdditionalRouteFiles' => [ 'default' => [ ], + 'type' => 'array', ], 'MaxShellMemory' => [ 'default' => 307200, @@ -4066,6 +4266,7 @@ return [ 'LocalVirtualHosts' => [ 'default' => [ ], + 'type' => 'object', ], 'LocalHTTPProxy' => [ 'default' => false, @@ -4104,6 +4305,7 @@ return [ 'HTTPProxy' => null, ], ], + 'mergeStrategy' => 'array_plus_2d', 'type' => 'object', ], 'EventRelayerConfig' => [ @@ -4112,6 +4314,7 @@ return [ 'class' => 'EventRelayerNull', ], ], + 'type' => 'object', ], 'Pingback' => [ 'default' => false, diff --git a/includes/config-schema.yaml b/includes/config-schema.yaml index a823c8e850bf..187f226d9b29 100644 --- a/includes/config-schema.yaml +++ b/includes/config-schema.yaml @@ -2,7 +2,7 @@ config-schema: ConfigRegistry: default: main: GlobalVarConfig::newInstance - mergeStrategy: array_merge + type: object description: |- Registry of factory functions to create config objects: The 'main' key must be set, and the value should be a valid @@ -148,6 +148,7 @@ config-schema: @since 1.16 ExtensionDirectory: default: null + type: [ "null", string ] description: |- Extensions directory. @note Set to "{$IP}/extensions" by Setup.php before loading local settings. @@ -155,6 +156,7 @@ config-schema: @since 1.25 StyleDirectory: default: null + type: [ "null", string ] description: |- Skins directory. @note Set to "{$IP}/skins" by Setup.php before loading local settings. @@ -212,7 +214,7 @@ config-schema: Ignored if $wgLogos is set. Logos: default: false - type: [ array, boolean ] + type: [ object, boolean ] description: |- Specification for different versions of the wiki logo. @@ -262,7 +264,7 @@ config-schema: @since 1.35 LogoHD: default: false - type: [array, boolean] + type: [object, boolean] description: |- Array with URL paths to HD versions of the wiki logo. The scaled logo size should be under 135x155 pixels. @@ -365,6 +367,7 @@ config-schema: @since 1.17 ActionPaths: default: { } + type: object description: |- To set 'pretty' URL paths for actions other than plain page views, add to this array. @@ -427,6 +430,7 @@ config-schema: description: 'Set this to true if you use img_auth and want the user to see details on why access failed.' ImgAuthUrlPathMap: default: { } + type: object description: |- Map of relative URL directories to match to internal mwstore:// base storage paths. @@ -442,6 +446,7 @@ config-schema: @see $wgFileBackends LocalFileRepo: default: false + type: [ object, boolean ] description: |- File repository structures @@ -573,7 +578,8 @@ config-schema: @see \FileRepo::__construct for the default options. @see Setup.php for an example usage and default initialization. ForeignFileRepos: - default: { } + default: [] + type: array description: |- Enable the use of files from one or more other wikis. @@ -688,6 +694,7 @@ config-schema: @since 1.5 ForeignUploadTargets: default: [local] + type: array description: |- Array of foreign file repo names (set in $wgForeignFileRepos above) that are allowable upload targets. These wikis must have some method of @@ -699,6 +706,7 @@ config-schema: $wgForeignUploadTargets = [ 'shared' ]; UploadDialog: default: { fields: { description: true, date: false, categories: false }, licensemessages: { local: generic-local, foreign: generic-foreign }, comment: { local: '', foreign: '' }, format: { filepage: $DESCRIPTION, description: $TEXT, ownwork: '', license: '', uncategorized: '' } } + type: object description: |- Configuration for file uploads using the embeddable upload dialog (https://www.mediawiki.org/wiki/Upload_dialog). @@ -709,6 +717,7 @@ config-schema: See below for documentation of each property. None of the properties may be omitted. FileBackends: default: { } + type: object description: |- File backend structure configuration. @@ -746,6 +755,7 @@ config-schema: would need a fully qualified backend that is defined on all wikis in the wiki farm. LockManagers: default: { } + type: object description: |- Array of configuration arrays for each lock manager. @@ -781,7 +791,8 @@ config-schema: The timeout for copy uploads is set by $wgCopyUploadTimeout. You have to assign the user right 'upload_by_url' to a user group, to use this. CopyUploadsDomains: - default: { } + default: [] + type: array description: |- A list of domains copy uploads can come from @since 1.20 @@ -888,7 +899,7 @@ config-schema: @note Only used if $wgLocalFileRepo is not set. FileExtensions: default: [png, gif, jpg, jpeg, webp] - mergeStrategy: array_merge + type: array description: |- This is the list of preferred extensions for uploading files. Uploading files with extensions not in this list will trigger a warning. @@ -897,6 +908,7 @@ config-schema: your wiki will be vulnerable to cross-site request forgery (CSRF). ProhibitedFileExtensions: default: [html, htm, js, jsb, mhtml, mht, xhtml, xht, php, phtml, php3, php4, php5, phps, phar, shtml, jhtml, pl, py, cgi, exe, scr, dll, msi, vbs, bat, com, pif, cmd, vxd, cpl] + type: array description: |- Files with these extensions will never be allowed as uploads. @@ -905,6 +917,7 @@ config-schema: @since 1.37; previously $wgFileBlacklist MimeTypeExclusions: default: [text/html, application/javascript, text/javascript, text/x-javascript, application/x-shellscript, application/x-php, text/x-php, text/x-python, text/x-perl, text/x-bash, text/x-sh, text/x-csh, text/scriptlet, application/x-msdownload, application/x-msmetafile] + type: array description: |- Files with these MIME types will never be allowed as uploads if $wgVerifyMimeType is enabled. @@ -938,6 +951,7 @@ config-schema: description: 'Warn if uploaded files are larger than this (in bytes), or false to disable' TrustedMediaFormats: default: [BITMAP, AUDIO, VIDEO, image/svg+xml, application/pdf] + type: array description: |- list of trusted media-types and MIME types. @@ -950,7 +964,7 @@ config-schema: [[media:...]] links for non-trusted formats. MediaHandlers: default: { } - mergeStrategy: array_merge + type: object description: |- Plugins for media file type handling. @@ -960,13 +974,14 @@ config-schema: and extensions should use extension.json. NativeImageLazyLoading: default: false - type: [ array, boolean ] + type: boolean description: |- Toggles native image lazy loading, via the "loading" attribute. @warning EXPERIMENTAL! @since 1.34 ParserTestMediaHandlers: default: { image/jpeg: MockBitmapHandler, image/png: MockBitmapHandler, image/gif: MockBitmapHandler, image/tiff: MockBitmapHandler, image/webp: MockBitmapHandler, image/x-ms-bmp: MockBitmapHandler, image/x-bmp: MockBitmapHandler, image/x-xcf: MockBitmapHandler, image/svg+xml: MockSvgHandler, image/vnd.djvu: MockDjVuHandler } + type: object description: |- Media handler overrides for parser tests (they don't need to generate actual thumbnails, so a mock will do) @@ -991,6 +1006,7 @@ config-schema: description: 'The convert command shipped with ImageMagick' MaxInterlacingAreas: default: { } + type: object description: |- Array of max pixel areas for interlacing per MIME type @since 1.27 @@ -1061,6 +1077,7 @@ config-schema: @since 1.26 SVGConverters: default: { ImageMagick: '$path/convert -background "#ffffff00" -thumbnail $widthx$height\! $input PNG:$output', sodipodi: '$path/sodipodi -z -w $width -f $input -e $output', inkscape: '$path/inkscape -z -w $width -f $input -e $output', batik: 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', rsvg: '$path/rsvg-convert -w $width -h $height -o $output $input', imgserv: '$path/imgserv-wrapper -i svg -o png -w$width $input $output', ImagickExt: ['SvgHandler::rasterizeImagickExt'] } + type: object description: |- Scalable Vector Graphics (SVG) may be uploaded as images. @@ -1120,9 +1137,12 @@ config-schema: frame instead of an animated thumbnail. As of MW 1.17 this limit is checked against the total size of all frames in the animation. + It probably makes sense to keep this equal to $wgMaxImageArea. TiffThumbnailType: - default: { } + default: [] + type: array + mergeStrategy: replace description: |- Browsers don't support TIFF inline generally... For inline display, we need to convert to PNG or JPEG. @@ -1191,18 +1211,21 @@ config-schema: description: 'Show thumbnails for old images on the image description page' EnableAutoRotation: default: null + type: [ "null", boolean ] description: |- If set to true, images that contain certain the exif orientation tag will be rotated accordingly. If set to null, try to auto-detect whether a scaler is available that can rotate. Antivirus: default: null + type: [ "null", string ] description: |- Internal name of virus scanner. This serves as a key to the $wgAntivirusSetup array. Set this to NULL to disable virus scanning. If not null, every file uploaded will be scanned for viruses. AntivirusSetup: default: { clamav: { command: 'clamscan --no-summary ', codemap: { 0: 0, 1: 1, 52: -1, '*': false }, messagepattern: '/.*?:(.*)/sim' } } + type: object description: |- Configuration for different virus scanners. This an associative array of associative arrays. It contains one setup array per known scanner type. @@ -1271,6 +1294,7 @@ config-schema: Set to null to use the minimum set of built-in defaults only. MimeDetectorCommand: default: null + type: [ "null", string ] description: |- Sets an external MIME detector program. The command must print only the MIME type to standard output. @@ -1291,12 +1315,14 @@ config-schema: can be trusted. XMLMimeTypes: default: { 'http://www.w3.org/2000/svg:svg': image/svg+xml, svg: image/svg+xml, 'http://www.lysator.liu.se/~alla/dia/:diagram': application/x-dia-diagram, 'http://www.w3.org/1999/xhtml:html': text/html, html: text/html } + type: object description: |- Additional XML types we can allow via MIME-detection. array = [ 'rootElement' => 'associatedMimeType' ] ImageLimits: default: [[320, 240], [640, 480], [800, 600], [1024, 768], [1280, 1024], [2560, 2048]] + type: array description: |- Limit images on image description pages to a user-selectable limit. @@ -1308,12 +1334,14 @@ config-schema: This list is also used by ImagePage for alternate size links. ThumbLimits: default: [120, 150, 180, 200, 250, 300] + type: array description: |- Adjust thumbnails on image pages according to a user setting. In order to reduce disk usage, the values can only be selected from a list. This is the list of settings the user can choose from: ThumbnailBuckets: default: null + type: [ "null", array ] description: |- When defined, is an array of image widths used as buckets for thumbnail generation. @@ -1341,6 +1369,7 @@ config-schema: because 220 + 50 = 270 and the closest bucket bigger than 270px is 512. UploadThumbnailRenderMap: default: { } + type: object description: |- When defined, is an array of thumbnail widths to be rendered at upload time. The idea is to prerender common thumbnail sizes, in order to avoid the necessity to render them on demand, which @@ -1378,6 +1407,7 @@ config-schema: @since 1.26 GalleryOptions: default: { } + type: object description: |- Parameters for the "<gallery>" tag. @@ -1418,24 +1448,28 @@ config-schema: @warning EXPERIMENTAL! DjvuDump: default: null + type: [ "null", string ] description: |- Path of the djvudump executable Enable this and $wgDjvuRenderer to enable djvu rendering example: $wgDjvuDump = 'djvudump'; DjvuRenderer: default: null + type: [ "null", string ] description: |- Path of the ddjvu DJVU renderer Enable this and $wgDjvuDump to enable djvu rendering example: $wgDjvuRenderer = 'ddjvu'; DjvuTxt: default: null + type: [ "null", string ] description: |- Path of the djvutxt DJVU text extraction utility Enable this and $wgDjvuDump to enable text layer extraction from djvu files example: $wgDjvuTxt = 'djvutxt'; DjvuPostProcessor: default: pnmtojpeg + type: [ "null", string ] description: |- Shell command for the DJVU post processor Default: pnmtojpeg, since ddjvu generates ppm output @@ -1522,6 +1556,7 @@ config-schema: and cancel their password change, but are sent to the password change form on each login. SMTP: default: false + type: [ boolean, object ] description: |- SMTP Mode. @@ -1621,6 +1656,7 @@ config-schema: description: 'Use real name instead of username in e-mail "from" field.' UsersNotifiedOnAllChanges: default: { } + type: object description: |- Array of usernames who will be sent a notification email for every change which occurs on a wiki. Users will not be notified of their own changes. @@ -1635,6 +1671,7 @@ config-schema: This should still be set even if $wgLBFactoryConf is configured. DBmwschema: default: null + type: [ "null", string ] description: |- Current wiki database schema name @@ -1768,6 +1805,7 @@ config-schema: @see $wgSharedDB SharedTables: default: [user, user_properties] + type: array description: |- @see $wgSharedDB @@ -1780,9 +1818,10 @@ config-schema: @since 1.23 DBservers: default: false + type: [ boolean, array ] description: |- Database load balancer - This is a two-dimensional array, an array of server info structures + This is a two-dimensional array, a list of server info structures Fields are: - host: Host name - dbname: Default database name @@ -1791,7 +1830,7 @@ config-schema: - type: DB type - driver: DB driver (when there are multiple drivers) - - load: Ratio of DB_REPLICA load, must be >=0, the sum of all loads must be >0. + - load: Ratio of DB_REPLICA load, must be >=0, the sum of all loads must be >0. If this is zero for any given server, no normal query traffic will be sent to it. It will be excluded from lag checks in maintenance scripts. The only way it can receive traffic is if groupLoads is used. @@ -1834,6 +1873,8 @@ config-schema: our primaries, and then set read_only=0 on primaries at runtime. LBFactoryConf: default: { class: Wikimedia\Rdbms\LBFactorySimple } + type: object + mergeStrategy: replace description: |- Load balancer factory configuration To set up a multi-primary wiki farm, set the class here to something that @@ -1879,7 +1920,7 @@ config-schema: ``` @since 1.20 LocalDatabases: - default: {} + default: [] type: array items: type: 'string' @@ -1946,7 +1987,7 @@ config-schema: a new migration which removes temporary tables. ContentHandlers: default: { wikitext: WikitextContentHandler, javascript: JavaScriptContentHandler, json: JsonContentHandler, css: CssContentHandler, text: TextContentHandler, unknown: FallbackContentHandler } - mergeStrategy: array_merge + type: object description: |- Plugins for page content model handling. @@ -1955,7 +1996,7 @@ config-schema: @since 1.21 NamespaceContentModels: default: { } - mergeStrategy: array_plus + type: object description: |- Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by default (use the CONTENT_MODEL_XXX constants). If no special content type is @@ -1977,6 +2018,7 @@ config-schema: @deprecated since 1.37 TextModelsToParse: default: [wikitext, javascript, css] + type: array description: |- Determines which types of text are parsed as wikitext. This does not imply that these kinds of texts are also rendered as wikitext, it only means that links, magic words, etc will have @@ -2004,6 +2046,7 @@ config-schema: ``` ExternalServers: default: {} + type: object description: |- Shortcut for setting `$wgLBFactoryConf["externalClusters"]`. @@ -2130,6 +2173,7 @@ config-schema: raise PHP's memory limit if it's below this amount. PoolCounterConf: default: null + type: [ "null", object ] description: |- Configuration for processing pool control, for use in high-traffic wikis. @@ -2258,6 +2302,10 @@ config-schema: @since 1.20 ObjectCaches: default: { 0: { class: EmptyBagOStuff, reportDupes: false }, 1: { class: SqlBagOStuff, loggroup: SQLBagOStuff }, -1: { factory: 'ObjectCache::newAnything' }, 3: { factory: 'ObjectCache::getLocalServerInstance' }, db-replicated: { class: ReplicatedBagOStuff, readFactory: { factory: 'ObjectCache::newFromParams', args: [{ class: SqlBagOStuff, replicaOnly: true }] }, writeFactory: { factory: 'ObjectCache::newFromParams', args: [{ class: SqlBagOStuff, replicaOnly: false }] }, loggroup: SQLBagOStuff, reportDupes: false }, memcached-php: { class: MemcachedPhpBagOStuff, loggroup: memcached }, memcached-pecl: { class: MemcachedPeclBagOStuff, loggroup: memcached }, hash: { class: HashBagOStuff, reportDupes: false }, apc: { class: APCUBagOStuff, reportDupes: false }, apcu: { class: APCUBagOStuff, reportDupes: false }, wincache: { class: WinCacheBagOStuff, reportDupes: false } } + # NOTE: this should have type object, but the JSON schema validator doesn't like 0 as a + # field name. It assumes that if the 0 key is set, it's a list (JSON array) rather + # than a JSON object, and complains. + mergeStrategy: array_plus description: |- Advanced object cache configuration. @@ -2272,6 +2320,7 @@ config-schema: given, giving a callable function which will generate a suitable cache object. MainWANCache: default: false + type: [ integer, string, boolean ] description: |- Main Wide-Area-Network cache type. @@ -2293,7 +2342,11 @@ config-schema: configuration in $wgWANObjectCaches @since 1.26 WANObjectCaches: - default: [{ class: WANObjectCache, cacheId: 0 }] + default: { 0: { class: WANObjectCache, cacheId: 0 } } + # NOTE: this should have type object, but the JSON schema validator doesn't like 0 as a + # field name. It assumes that if the 0 key is set, it's a list (JSON array) rather + # than a JSON object, and complains. + mergeStrategy: array_plus description: |- Advanced WAN object cache configuration. @@ -2414,6 +2467,7 @@ config-schema: @since 1.28 MemCachedServers: default: ['127.0.0.1:11211'] + type: array description: 'The list of MemCached servers and port numbers' MemCachedPersistent: default: false @@ -2445,6 +2499,7 @@ config-schema: This option is probably only useful for translatewiki.net. LocalisationCacheConf: default: { class: LocalisationCache, store: detect, storeClass: false, storeDirectory: false, storeServer: { }, forceRecache: false, manualRecache: false } + type: object description: |- Localisation cache configuration. @@ -2658,6 +2713,7 @@ config-schema: 300 seconds = 5 minutes. CdnServers: default: { } + type: object description: |- List of proxy servers to purge on changes; default port is 80. Use IP addresses. @@ -2668,6 +2724,7 @@ config-schema: @since 1.34 Renamed from $wgSquidServers. CdnServersNoPurge: default: { } + type: object description: |- As with $wgCdnServers, except these servers aren't purged on page changes; use to set a list of trusted proxies, etc. Supports both individual IP @@ -2696,6 +2753,7 @@ config-schema: @deprecated since 1.33, will always be true in a future release. HTCPRouting: default: { } + type: object description: |- Routing configuration for HTCP multicast purging. Add elements here to enable HTCP and determine which purges are sent where. If set to an empty @@ -2769,6 +2827,7 @@ config-schema: via hooks, see Title::getPageLanguage. GrammarForms: default: { } + type: object description: |- Some languages need different word forms, usually for different cases. @@ -2787,6 +2846,7 @@ config-schema: description: 'Hide interlanguage links from the sidebar' ExtraInterlanguageLinkPrefixes: default: { } + type: object description: |- List of additional interwiki prefixes that should be treated as interlanguage links (i.e. placed in the sidebar). @@ -2802,6 +2862,7 @@ config-schema: the prefix in this array. InterlanguageLinkCodeMap: default: { } + type: object description: |- Map of interlanguage link codes to language codes. This is useful to override what is shown as the language name when the interwiki code does not match it @@ -2809,9 +2870,11 @@ config-schema: @since 1.35 ExtraLanguageNames: default: { } + type: object description: 'List of language names or overrides for default names in Names.php' ExtraLanguageCodes: default: { bh: bho, 'no': nb, simple: en } + type: object description: |- List of mappings from one language code to another. @@ -2826,6 +2889,7 @@ config-schema: @since 1.29 DummyLanguageCodes: default: { } + type: object description: |- Functionally the same as $wgExtraLanguageCodes, but deprecated. Instead of appending values to this array, append them to $wgExtraLanguageCodes. @@ -2895,6 +2959,7 @@ config-schema: used to ease variant development work. DisabledVariants: default: { } + type: object description: |- Disabled variants array of language variant conversion. @@ -2931,6 +2996,7 @@ config-schema: customise these. ForceUIMsgAsContentMsg: default: { } + type: object description: |- When translating messages with wfMessage(), it is not always clear what should be considered UI messages and what should be content messages. @@ -2952,7 +3018,6 @@ config-schema: ``` RawHtmlMessages: default: [copyright, history_copyright, googlesearch, feedback-terms, feedback-termsofuse] - mergeStrategy: array_merge type: array items: type: 'string' @@ -3001,10 +3066,10 @@ config-schema: By default, this will be set to match $wgLocaltimezone. OverrideUcfirstCharacters: - default: { } - type: array + default: {} + type: object description: |- - List of Unicode characters for which capitalization is overridden in + Map of Unicode characters for which capitalization is overridden in Language::ucfirst. The characters should be represented as char_to_convert => conversion_override. See T219279 for details on why this is useful during php version transitions. @@ -3048,6 +3113,7 @@ config-schema: @since 1.28 XhtmlNamespaces: default: { } + type: object description: |- Permit other namespaces in addition to the w3.org default. @@ -3085,6 +3151,7 @@ config-schema: @see https://developer.apple.com/ library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html SkinMetaTags: default: { } + type: object description: |- An array of open graph tags which should be added by all skins. @@ -3103,6 +3170,7 @@ config-schema: @since 1.24 SkipSkins: default: { } + type: object description: |- Specify the names of skins that should not be presented in the list of available skins in user preferences. @@ -3116,6 +3184,7 @@ config-schema: description: 'Disable output compression (enabled by default if zlib is available)' FragmentMode: default: [html5, legacy] + type: array description: |- How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of: @@ -3153,6 +3222,7 @@ config-schema: @since 1.30 FooterIcons: default: { copyright: { copyright: { } }, poweredby: { mediawiki: { src: null, url: 'https://www.mediawiki.org/', alt: 'Powered by MediaWiki' } } } + type: object description: |- Abstract list of footer icons for skins in place of old copyrightico and poweredbyico code You can add new icons to the built in copyright or poweredby, or you can create @@ -3232,6 +3302,7 @@ config-schema: @since 1.25 ResourceModules: default: { } + type: object description: |- Define extra client-side modules to be registered with ResourceLoader. @note It is recommended to define modules using the `ResourceModule` attribute @@ -3550,6 +3621,7 @@ config-schema: @since 1.17 ResourceModuleSkinStyles: default: { } + type: object description: |- Add extra skin-specific styles to a resource module. @@ -3645,7 +3717,7 @@ config-schema: ``` ResourceLoaderSources: default: { } - mergeStrategy: array_merge + type: object description: |- Extensions should register foreign module sources here. 'local' is a built-in source that is not in this array, but defined by @@ -3664,6 +3736,7 @@ config-schema: Defaults to $wgScriptPath. ResourceLoaderMaxage: default: { versioned: 2592000, unversioned: 300 } + type: object description: |- How long a CDN or browser may cache a ResourceLoader HTTP response. @@ -3773,12 +3846,14 @@ config-schema: manually for grammatical reasons. CanonicalNamespaceNames: default: { -2: Media, -1: Special, 0: '', 1: Talk, 2: User, 3: User_talk, 4: Project, 5: Project_talk, 6: File, 7: File_talk, 8: MediaWiki, 9: MediaWiki_talk, 10: Template, 11: Template_talk, 12: Help, 13: Help_talk, 14: Category, 15: Category_talk } + type: object description: |- Canonical namespace names. Must not be changed directly in configuration or by extensions, use $wgExtraNamespaces instead. ExtraNamespaces: default: { } + type: object description: |- Additional namespaces. If the namespaces defined in Language.php and Namespace.php are insufficient, you can create new ones here, for example, @@ -3805,7 +3880,7 @@ config-schema: @todo Add a note about maintenance/namespaceDupes.php ExtraGenderNamespaces: default: { } - mergeStrategy: array_plus + type: object description: |- Same as above, but for namespaces with gender distinction. @@ -3814,6 +3889,7 @@ config-schema: @since 1.18 NamespaceAliases: default: { } + type: object description: |- Define extra namespace aliases. @@ -3868,7 +3944,7 @@ config-schema: same place as links in the middle of a sentence using a lowercase initial. CapitalLinkOverrides: default: { } - mergeStrategy: array_plus + type: object description: |- @since 1.16 - This can now be set per-namespace. Some special namespaces (such as Special, see @@ -3885,20 +3961,21 @@ config-schema: ``` NamespacesWithSubpages: default: { 1: true, 2: true, 3: true, 4: true, 5: true, 7: true, 8: true, 9: true, 10: true, 11: true, 12: true, 13: true, 15: true } - mergeStrategy: array_plus + type: object description: |- Which namespaces should support subpages? See Language.php for a list of namespaces. ContentNamespaces: default: [0] - mergeStrategy: array_merge + type: array description: |- Array of namespaces which can be deemed to contain valid "content", as far as the site statistics are concerned. Useful if additional namespaces also contain "content" which should be considered when generating a count of the number of articles in the wiki. ShortPagesNamespaceExclusions: - default: { } + default: [] + type: array description: |- Optional array of namespaces which should be excluded from Special:ShortPages. @@ -3906,7 +3983,8 @@ config-schema: be shown on that page. @since 1.37; previously $wgShortPagesNamespaceBlacklist ExtraSignatureNamespaces: - default: { } + default: [] + type: array description: |- Array of namespaces, in addition to the talk namespaces, where signatures (~~~~) are likely to be used. This determines whether to display the @@ -3923,6 +4001,7 @@ config-schema: 0 or less means no redirects are followed. InvalidRedirectTargets: default: [Filepath, Mypage, Mytalk, Redirect] + type: array description: |- Array of invalid page redirect targets. @@ -3949,6 +4028,7 @@ config-schema: Tends to conflict with page move vandalism, use only on a private wiki. InterwikiPrefixDisplayTypes: default: { } + type: object description: |- Mapping of interwiki index prefixes to descriptors that can be used to change the display of interwiki search results. @@ -3967,7 +4047,8 @@ config-schema: ]; ``` LocalInterwikis: - default: { } + default: [] + type: array description: |- Array for local interwiki values, for each of the interwiki prefixes that point to the current wiki. @@ -3978,7 +4059,8 @@ config-schema: description: 'Expiry time for cache of interwiki table' InterwikiCache: default: false - type: [boolean, array, string] + type: [boolean, object, string] + mergeStrategy: replace description: |- Interwiki cache, either as an associative array or a path to a constant database (.cdb) file. @@ -4024,6 +4106,7 @@ config-schema: the URL. SiteTypes: default: { mediawiki: MediaWikiSite } + type: object description: |- Register handlers for specific types of sites. @since 1.21 @@ -4049,7 +4132,8 @@ config-schema: @see $wgMaxTemplateDepth UrlProtocols: - default: ['bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://', 'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:', 'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://', 'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:', //] + default: ['bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://', 'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:', 'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://', 'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:', '//'] + type: array description: |- URL schemes that should be recognized as valid by wfParseUrl(). @@ -4103,6 +4187,7 @@ config-schema: @deprecated since 1.35; register an extension tag named <img> instead. TidyConfig: default: { } + type: object description: |- Configuration for HTML postprocessing tool. Set this to a configuration array to enable an external tool. By default, we now use the RemexHtml @@ -4160,13 +4245,15 @@ config-schema: they should not be followed for ranking purposes as they are user-supplied and thus subject to spamming. NoFollowNsExceptions: - default: { } + default: [] + type: array description: |- Namespaces in which $wgNoFollowLinks doesn't apply. See Language.php for a list of namespaces. NoFollowDomainExceptions: default: [mediawiki.org] + type: array description: |- If this is set to an array of domains, external links to these domain names (or any subdomains) will not be set to rel="nofollow" regardless of the @@ -4214,6 +4301,7 @@ config-schema: Only used $wgEnableInterwikiTranscluding is set to true. EnableMagicLinks: default: { ISBN: false, PMID: false, RFC: false } + type: object description: |- Enable the magic links feature of automatically turning ISBN xxx, PMID xxx, RFC xxx into links @@ -4306,7 +4394,7 @@ config-schema: @since 1.36 CentralIdLookupProviders: default: { local: { class: LocalIdLookup, services: [MainConfig, DBLoadBalancer] } } - mergeStrategy: array_merge + type: object description: |- Central ID lookup providers Key is the provider ID, value is a specification for ObjectFactory @@ -4317,7 +4405,8 @@ config-schema: description: 'Central ID lookup provider to use by default' PasswordPolicy: default: { policies: { bureaucrat: { MinimalPasswordLength: 10, MinimumPasswordLengthToLogin: 1 }, sysop: { MinimalPasswordLength: 10, MinimumPasswordLengthToLogin: 1 }, interface-admin: { MinimalPasswordLength: 10, MinimumPasswordLengthToLogin: 1 }, bot: { MinimalPasswordLength: 10, MinimumPasswordLengthToLogin: 1 }, default: { MinimalPasswordLength: { value: 1, suggestChangeOnLogin: true }, PasswordCannotBeSubstringInUsername: { value: true, suggestChangeOnLogin: true }, PasswordCannotMatchDefaults: { value: true, suggestChangeOnLogin: true }, MaximalPasswordLength: { value: 4096, suggestChangeOnLogin: true }, PasswordNotInCommonList: { value: true, suggestChangeOnLogin: true } } }, checks: { MinimalPasswordLength: 'PasswordPolicyChecks::checkMinimalPasswordLength', MinimumPasswordLengthToLogin: 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', PasswordCannotBeSubstringInUsername: 'PasswordPolicyChecks::checkPasswordCannotBeSubstringInUsername', PasswordCannotMatchDefaults: 'PasswordPolicyChecks::checkPasswordCannotMatchDefaults', MaximalPasswordLength: 'PasswordPolicyChecks::checkMaximalPasswordLength', PasswordNotInCommonList: 'PasswordPolicyChecks::checkPasswordNotInCommonList' } } - mergeStrategy: array_merge_recursive + type: object + mergeStrategy: array_replace_recursive description: |- Password policy for the wiki. @@ -4381,6 +4470,7 @@ config-schema: @see \User::checkPasswordValidity() AuthManagerConfig: default: null + type: [object, 'null'] description: |- Configure AuthManager @@ -4400,6 +4490,7 @@ config-schema: auto-configure themselves should use $wgAuthManagerAutoConfig instead. AuthManagerAutoConfig: default: { preauth: { MediaWiki\Auth\ThrottlePreAuthenticationProvider: { class: MediaWiki\Auth\ThrottlePreAuthenticationProvider, sort: 0 } }, primaryauth: { MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider: { class: MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider, services: [DBLoadBalancer], args: [{ authoritative: false }], sort: 0 }, MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider: { class: MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider, services: [DBLoadBalancer], args: [{ authoritative: true }], sort: 100 } }, secondaryauth: { MediaWiki\Auth\CheckBlocksSecondaryAuthenticationProvider: { class: MediaWiki\Auth\CheckBlocksSecondaryAuthenticationProvider, sort: 0 }, MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider: { class: MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider, sort: 100 }, MediaWiki\Auth\EmailNotificationSecondaryAuthenticationProvider: { class: MediaWiki\Auth\EmailNotificationSecondaryAuthenticationProvider, services: [DBLoadBalancer], sort: 200 } } } + type: object mergeStrategy: array_plus_2d description: |- @see $wgAuthManagerConfig @@ -4416,7 +4507,6 @@ config-schema: @since 1.36 ReauthenticateTime: default: { default: 300 } - mergeStrategy: array_merge type: object additionalProperties: type: 'integer' @@ -4474,7 +4564,6 @@ config-schema: @see $wgReauthenticateTime ChangeCredentialsBlacklist: default: [MediaWiki\Auth\TemporaryPasswordAuthenticationRequest] - mergeStrategy: array_merge type: array items: type: 'string' @@ -4488,7 +4577,6 @@ config-schema: @since 1.27 RemoveCredentialsBlacklist: default: [MediaWiki\Auth\PasswordAuthenticationRequest] - mergeStrategy: array_merge type: array items: type: 'string' @@ -4533,6 +4621,7 @@ config-schema: @since 1.24 PasswordConfig: default: { A: { class: MWOldPassword }, B: { class: MWSaltedPassword }, pbkdf2-legacyA: { class: LayeredParameterizedPassword, types: [A, pbkdf2] }, pbkdf2-legacyB: { class: LayeredParameterizedPassword, types: [B, pbkdf2] }, bcrypt: { class: BcryptPassword, cost: 9 }, pbkdf2: { class: Pbkdf2Password, algo: sha512, cost: '30000', length: '64' }, argon2: { class: Argon2Password, algo: auto } } + type: object description: |- Configuration for built-in password types. Maps the password type to an array of options. The 'class' option is the Password class to @@ -4553,6 +4642,7 @@ config-schema: @since 1.24 PasswordResetRoutes: default: { username: true, email: true } + type: object description: |- Whether to allow password resets ("enter some identifying data, and we'll send an email with a temporary password you can use to get back into the account") identified by @@ -4573,6 +4663,7 @@ config-schema: @since 1.35 SignatureAllowedLintErrors: default: [obsolete-tag] + type: array description: |- List of lint error codes which don't cause signature validation to fail. @see https://www.mediawiki.org/wiki/Help:Lint_errors @@ -4584,6 +4675,7 @@ config-schema: script ./maintenance/checkUsernames.php once you have changed this value. ReservedUsernames: default: ['MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', ScriptImporter, 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username'] + type: array description: |- Array of usernames which may not be registered or logged in from Maintenance scripts can still use these @@ -4652,7 +4744,7 @@ config-schema: prefershttps: 1 requireemail: 0 skin-responsive: 1 - mergeStrategy: array_merge + type: object description: |- Settings added to this array will override the default globals for the user preferences used by anonymous visitors and newly created accounts. @@ -4660,8 +4752,8 @@ config-schema: For instance, to disable editing on double clicks: $wgDefaultUserOptions ['editondblclick'] = 0; HiddenPrefs: - default: { } - mergeStrategy: array_merge + default: [] + type: array description: 'An array of preferences to not show for the user' InvalidUsernameCharacters: default: '@:' @@ -4700,7 +4792,7 @@ config-schema: @since 1.27 SessionProviders: default: { MediaWiki\Session\CookieSessionProvider: { class: MediaWiki\Session\CookieSessionProvider, args: [{ priority: 30, callUserSetCookiesHook: true }] }, MediaWiki\Session\BotPasswordSessionProvider: { class: MediaWiki\Session\BotPasswordSessionProvider, args: [{ priority: 75 }] } } - mergeStrategy: array_merge + type: object description: |- MediaWiki\Session\SessionProvider configuration. @@ -4727,6 +4819,7 @@ config-schema: restrictions. BlockCIDRLimit: default: { IPv4: 16, IPv6: 19 } + type: object description: |- Limits on the possible sizes of range blocks. @@ -4966,6 +5059,7 @@ config-schema: This replaces $wgWhitelistAccount and $wgWhitelistEdit RevokePermissions: default: { } + type: object mergeStrategy: array_plus_2d description: |- Permission keys revoked from users in each group. @@ -4999,11 +5093,11 @@ config-schema: @since 1.38 ImplicitGroups: default: ['*', user, autoconfirmed] - mergeStrategy: array_merge + type: array description: 'Implicit groups, aren''t shown on Special:Listusers or somewhere else' GroupsAddToSelf: default: { } - mergeStrategy: array_merge + type: object description: |- A map of group names that the user is in, to group names that those users are allowed to add or revoke. @@ -5029,12 +5123,13 @@ config-schema: any group that they happen to be in. GroupsRemoveFromSelf: default: { } - mergeStrategy: array_merge + type: object description: |- @see $wgGroupsAddToSelf RestrictionTypes: default: [create, edit, move, upload] + type: array description: |- Set of available actions that can be restricted via action=protect You probably shouldn't change this. @@ -5044,6 +5139,7 @@ config-schema: applicable to a specific title (create and upload) RestrictionLevels: default: ['', autoconfirmed, sysop] + type: array description: |- Rights which can be required for each protection level (via action=protect) @@ -5056,6 +5152,7 @@ config-schema: - 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility CascadingRestrictionLevels: default: [sysop] + type: array description: |- Restriction levels that can be used with cascading protection @@ -5066,6 +5163,7 @@ config-schema: 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility. SemiprotectedRestrictionLevels: default: [autoconfirmed] + type: array description: |- Restriction levels that should be considered "semiprotected" @@ -5079,7 +5177,7 @@ config-schema: 'sysop' is not changed, since it really shouldn't be here. NamespaceProtection: default: { } - mergeStrategy: array_plus + type: object description: |- Set the minimum permissions required to edit pages in each namespace. If you list more than one permission, a user must @@ -5087,7 +5185,7 @@ config-schema: @note NS_MEDIAWIKI is implicitly restricted to 'editinterface'. NonincludableNamespaces: default: { } - mergeStrategy: array_merge + type: object description: |- Pages in namespaces in this array can not be used as templates. @@ -5134,6 +5232,7 @@ config-schema: ``` Autopromote: default: { autoconfirmed: ['&', [1, null], [2, null]] } + type: object description: |- Array containing the conditions of automatic promotion of a user to specific groups. @@ -5192,6 +5291,7 @@ config-schema: user who has provided an e-mail address. AutopromoteOnce: default: { onEdit: { } } + type: object description: |- Automatically add a usergroup to any user who matches certain conditions. @@ -5217,7 +5317,7 @@ config-schema: @since 1.18 AddGroups: default: { } - mergeStrategy: array_merge + type: object description: |- $wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups at Special:Userrights. @@ -5248,13 +5348,13 @@ config-schema: ``` RemoveGroups: default: { } - mergeStrategy: array_merge + type: object description: |- @see $wgAddGroups AvailableRights: - default: { } - mergeStrategy: array_merge + default: [] + type: array description: |- A list of available rights, in addition to the ones defined by the core. @@ -5281,6 +5381,7 @@ config-schema: @since 1.23 AccountCreationThrottle: default: [{ count: 0, seconds: 86400 }] + type: array description: |- Number of accounts each IP address may create per specified period(s). @@ -5302,7 +5403,8 @@ config-schema: ``` @warning Requires $wgMainCacheType to be enabled SpamRegex: - default: { } + default: [] + type: array description: |- Edits matching these regular expressions in body text will be recognised as spam and rejected automatically. @@ -5312,8 +5414,9 @@ config-schema: @see https://en.wikipedia.org/wiki/Regular_expression @note Each regex needs a beginning/end delimiter, eg: # or / SummarySpamRegex: - default: { } - description: 'Same as the above except for edit summaries' + default: [] + type: array + description: 'Same as SpamRegex except for edit summaries' EnableDnsBlacklist: default: false description: |- @@ -5322,6 +5425,7 @@ config-schema: @since 1.16 DnsBlacklistUrls: default: [http.dnsbl.sorbs.net.] + type: array description: |- List of DNS blacklists to use, if $wgEnableDnsBlacklist is true. @@ -5345,16 +5449,18 @@ config-schema: eventual domain search suffixes. @since 1.16 ProxyList: - default: { } + default: [] + type: [string, array] description: |- - Big list of banned IP addresses. + List of banned IP addresses. This can have the following formats: - An array of addresses - A string, in which case this is the path to a file containing the list of IP addresses, one per line ProxyWhitelist: - default: { } + default: [] + type: array description: |- Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods might say. @@ -5376,6 +5482,7 @@ config-schema: (transparent) proxies without needing to block the proxies themselves. RateLimits: default: { edit: { ip: [8, 60], newbie: [8, 60], user: [90, 60] }, move: { newbie: [2, 120], user: [8, 60] }, upload: { ip: [8, 60], newbie: [8, 60] }, rollback: { user: [10, 60], newbie: [5, 120] }, mailpassword: { ip: [5, 3600] }, emailuser: { ip: [5, 86400], newbie: [5, 86400], user: [20, 86400] }, changeemail: { ip-all: [10, 3600], user: [4, 86400] }, confirmemail: { ip-all: [10, 3600], user: [4, 86400] }, purge: { ip: [30, 60], user: [30, 60] }, linkpurge: { ip: [30, 60], user: [30, 60] }, renderfile: { ip: [700, 30], user: [700, 30] }, renderfile-nonstandard: { ip: [70, 30], user: [70, 30] }, stashedit: { ip: [30, 60], newbie: [30, 60] }, changetag: { ip: [8, 60], newbie: [8, 60] }, editcontentmodel: { newbie: [2, 120], user: [8, 60] } } + type: object mergeStrategy: array_plus_2d description: |- Simple rate limiter options to brake edit floods. @@ -5416,7 +5523,8 @@ config-schema: ``` @warning Requires that $wgMainCacheType is set to something persistent RateLimitsExcludedIPs: - default: { } + default: [] + type: array description: |- Array of IPs / CIDR ranges which should be excluded from rate limits. @@ -5434,6 +5542,7 @@ config-schema: special pages which are query-pages such as Special:Whatlinkshere. PasswordAttemptThrottle: default: [{ count: 5, seconds: 300 }, { count: 150, seconds: 172800 }] + type: array description: |- Limit password attempts to X attempts per Y seconds per IP per account. @@ -5632,7 +5741,6 @@ config-schema: highvolume: high-volume privateinfo: private-information type: object - mergeStrategy: array_merge additionalProperties: type: string description: |- @@ -5740,6 +5848,7 @@ config-schema: @since 1.32 CSPFalsePositiveUrls: default: { 'https://3hub.co': true, 'https://morepro.info': true, 'https://p.ato.mx': true, 'https://s.ato.mx': true, 'https://adserver.adtech.de': true, 'https://ums.adtechus.com': true, 'https://cas.criteo.com': true, 'https://cat.nl.eu.criteo.com': true, 'https://atpixel.alephd.com': true, 'https://rtb.metrigo.com': true, 'https://d5p.de17a.com': true, 'https://ad.lkqd.net/vpaid/vpaid.js': true, 'https://ad.lkqd.net/vpaid/vpaid.js?fusion=1.0': true, 'https://t.lkqd.net/t': true, chrome-extension: true } + type: object description: |- List of urls which appear often to be triggering CSP reports but do not appear to be caused by actual content, but by client @@ -5871,7 +5980,8 @@ config-schema: is a workaround for broken behaviour in Chrome 51-66 and similar browsers. @since 1.35 CacheVaryCookies: - default: { } + default: [] + type: array description: 'A list of cookies that vary the cache (for use by extensions)' SessionName: default: false @@ -5933,11 +6043,13 @@ config-schema: the 'flags' option of the database connection to achieve the same functionality. TrxProfilerLimits: default: { GET: { masterConns: 0, writes: 0, readQueryTime: 5, readQueryRows: 10000 }, POST: { readQueryTime: 5, writeQueryTime: 1, readQueryRows: 100000, maxAffected: 1000 }, POST-nonwrite: { writes: 0, readQueryTime: 5, readQueryRows: 10000 }, PostSend-GET: { readQueryTime: 5, writeQueryTime: 1, readQueryRows: 10000, maxAffected: 1000, masterConns: 0, writes: 0 }, PostSend-POST: { readQueryTime: 5, writeQueryTime: 1, readQueryRows: 100000, maxAffected: 1000 }, JobRunner: { readQueryTime: 30, writeQueryTime: 5, readQueryRows: 100000, maxAffected: 500 }, Maintenance: { writeQueryTime: 5, maxAffected: 1000 } } + type: object description: |- Performance expectations for DB usage @since 1.26 DebugLogGroups: default: { } + type: object description: |- Map of string log group names to log destinations. @@ -5971,6 +6083,7 @@ config-schema: ``` MWLoggerDefaultSpi: default: { class: MediaWiki\Logger\LegacySpi } + mergeStrategy: replace type: object description: |- Default service provider for creating Psr\Log\LoggerInterface instances. @@ -6038,6 +6151,8 @@ config-schema: after the limit. Profiler: default: { } + type: object + mergeStrategy: replace description: |- Profiler configuration. @@ -6121,6 +6236,7 @@ config-schema: @since 1.25 StatsdSamplingRates: default: { } + type: object description: |- Sampling rate for statsd metrics as an associative array of patterns and rates. @@ -6165,7 +6281,7 @@ config-schema: templates. ParserTestFiles: default: { } - mergeStrategy: array_merge + type: object description: |- Parser test suite files to be run by parserTests.php when no specific filename is passed to it. @@ -6218,6 +6334,7 @@ config-schema: @deprecated since 1.25 Use $wgOpenSearchTemplates['application/x-suggestions+json'] instead OpenSearchTemplates: default: { application/x-suggestions+json: false, application/x-suggestions+xml: false } + type: object description: |- Templates for OpenSearch suggestions, defaults to API action=opensearch @@ -6249,6 +6366,7 @@ config-schema: table. If you ever re-enable, be sure to rebuild the search table. NamespacesToBeSearchedDefault: default: [true] + type: array description: |- List of namespaces which are searched by default. @@ -6283,12 +6401,14 @@ config-schema: ``` SitemapNamespaces: default: false + type: [ boolean, array ] description: |- Array of namespaces to generate a Google sitemap for when the maintenance/generateSitemap.php script is run, or false if one is to be generated for all namespaces. SitemapNamespacesPriorities: default: false + type: [ boolean, object ] description: |- Custom namespace priorities for sitemaps. Setting this will allow you to set custom priorities to namespaces when sitemaps are generated using the @@ -6312,7 +6432,7 @@ config-schema: [[Special:Contributions/1.2.3.4]] SpecialSearchFormOptions: default: { } - type: array + type: object description: |- Options for Special:Search completion widget form created by SearchFormWidget class. @@ -6347,6 +6467,7 @@ config-schema: description: 'Path to the GNU diff utility.' PreviewOnOpenNamespaces: default: { 14: true } + type: object description: |- Which namespaces have special treatment where they should be preview-on-open Internally only Category: pages apply, but using this extensions (e.g. Semantic MediaWiki) @@ -6407,6 +6528,7 @@ config-schema: description: 'Fully specified path to git binary' GitRepositoryViewers: default: { 'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)': 'https://gerrit.wikimedia.org/g/%R/+/%H', 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)': 'https://gerrit.wikimedia.org/g/%R/+/%H' } + type: object description: |- Map GIT repository URLs to viewer URLs to provide links in Special:Version @@ -6450,17 +6572,20 @@ config-schema: is still there. RCLinkLimits: default: [50, 100, 250, 500] + type: array description: |- List of Limits options to list in the Special:Recentchanges and Special:Recentchangeslinked pages. RCLinkDays: default: [1, 3, 7, 14, 30] + type: array description: |- List of Days options to list in the Special:Recentchanges and Special:Recentchangeslinked pages. @see \ChangesListSpecialPage::getLinkDays RCFeeds: default: { } + type: object description: |- Configuration for feeds to which notifications about recent changes will be sent. @@ -6524,6 +6649,7 @@ config-schema: @since 1.22 RCEngines: default: { redis: RedisPubSubFeedEngine, udp: UDPRCFeedEngine } + type: object description: |- Used by RecentChange::getEngine to find the correct engine for a given URI scheme. @@ -6596,6 +6722,7 @@ config-schema: pages larger than this size. OverrideSiteFeed: default: { } + type: object description: |- Override the site's default RSS/ATOM feed for recentchanges that appears on every page. Some sites might have a different feed they'd like to promote @@ -6612,7 +6739,7 @@ config-schema: ``` FeedClasses: default: { rss: RSSFeed, atom: AtomFeed } - mergeStrategy: array_merge + type: object description: |- Available feeds objects. @@ -6620,6 +6747,7 @@ config-schema: $wgOut->isSyndicated() is true. AdvertisedFeedTypes: default: [atom] + type: array description: |- Which feed types should we provide by default? This can include 'rss', 'atom', neither, or both. @@ -6694,7 +6822,7 @@ config-schema: @since 1.21 RecentChangesFlags: default: { newpage: { letter: newpageletter, title: recentchanges-label-newpage, legend: recentchanges-legend-newpage, grouping: any }, minor: { letter: minoreditletter, title: recentchanges-label-minor, legend: recentchanges-legend-minor, class: minoredit, grouping: all }, bot: { letter: boteditletter, title: recentchanges-label-bot, legend: recentchanges-legend-bot, class: botedit, grouping: all }, unpatrolled: { letter: unpatrolledletter, title: recentchanges-label-unpatrolled, legend: recentchanges-legend-unpatrolled, grouping: any } } - mergeStrategy: array_merge + type: object description: |- Flags (letter symbols) shown in recent changes and watchlist to indicate certain types of edits. @@ -6795,6 +6923,7 @@ config-schema: Otherwise, link to a separate credits page. ImportSources: default: { } + type: object description: |- List of interwiki prefixes for wikis we'll accept as sources for Special:Import and API action=import. Since complete page history can be @@ -6871,14 +7000,14 @@ config-schema: The schema to use per default when generating XML dumps. This allows sites to control explicitly when to make breaking changes to their export and dump format. ExtensionFunctions: - default: { } - mergeStrategy: array_merge + default: [] + type: array description: |- A list of callback functions which are called once MediaWiki is fully initialised ExtensionMessagesFiles: default: { } - mergeStrategy: array_merge + type: object description: |- Extension messages files. @@ -6907,7 +7036,7 @@ config-schema: ``` MessagesDirs: default: { } - mergeStrategy: array_merge + type: object description: |- Extension messages directories. @@ -6936,13 +7065,14 @@ config-schema: @since 1.23 ExtensionEntryPointListFiles: default: { } - mergeStrategy: array_merge + type: object description: |- Array of files with list(s) of extension entry points to be used in maintenance/mergeMessageFileList.php @since 1.22 ParserOutputHooks: default: { } + type: object description: |- Parser output hooks. @@ -6963,7 +7093,7 @@ config-schema: description: 'Whether to include the NewPP limit report as a HTML comment' ValidSkinNames: default: { } - mergeStrategy: array_merge + type: object description: |- List of valid skin names @@ -6990,7 +7120,7 @@ config-schema: full list. SpecialPages: default: { } - mergeStrategy: array_merge + type: object description: |- Special page list. This is an associative array mapping the (canonical) names of special pages to either a class name to be instantiated, or a callback to use for @@ -7006,6 +7136,7 @@ config-schema: @deprecated since 1.35 ExtensionCredits: default: { } + type: object description: |- Add information about an installed extension, keyed by its type. @@ -7064,6 +7195,7 @@ config-schema: @see \SpecialVersion::getCredits Hooks: default: { } + type: object mergeStrategy: array_merge_recursive description: |- Global list of hooks. @@ -7095,7 +7227,7 @@ config-schema: handlers after file scope can lead to unexpected results due to caching. ServiceWiringFiles: default: [] - mergeStrategy: array_merge + type: array description: List of service wiring files to be loaded by the default instance of MediaWikiServices. Each file listed here is expected to return an associative array mapping service names @@ -7135,7 +7267,7 @@ config-schema: enqueue: EnqueueJob 'null': NullJob userEditCountInit: UserEditCountInitJob - mergeStrategy: array_merge + type: object description: |- Maps jobs to their handlers; extensions can add to this to provide custom jobs. @@ -7145,6 +7277,7 @@ config-schema: The callback takes (Title, array map of parameters) as arguments. JobTypesExcludedFromDefaultQueue: default: [AssembleUploadChunks, PublishStashedFile] + type: array description: |- Jobs that must be explicitly requested, i.e. aren't run by job runners unless special flags are set. The values here are keys of $wgJobClasses. @@ -7184,6 +7317,7 @@ config-schema: @since 1.26 JobTypeConf: default: { default: { class: JobQueueDB, order: random, claimTTL: 3600 } } + type: object description: |- Map of job types to configuration arrays. @@ -7204,12 +7338,14 @@ config-schema: @since 1.29 SpecialPageCacheUpdates: default: { Statistics: [SiteStatsUpdate, cacheUpdate] } + type: object description: |- Additional functions to be performed with updateSpecialPages. Expensive Querypages are already updated. PagePropLinkInvalidations: default: { hiddencat: categorylinks } + type: object description: |- Page property link table invalidation lists. When a page property changes, this may require other link tables to be updated (eg @@ -7254,6 +7390,7 @@ config-schema: and using the Collation::factory hook. TempCategoryCollations: default: [] + type: array description: |- Additional category collations to store during LinksUpdate. This can be used to perform online migration of categories from one collation to another. An @@ -7265,7 +7402,8 @@ config-schema: @since 1.38 TrackingCategories: - default: { } + default: [] + type: array description: |- Array holding default tracking category names. @@ -7280,7 +7418,7 @@ config-schema: @since 1.23 LogTypes: default: ['', block, protect, rights, delete, upload, move, import, patrol, merge, suppress, tag, managetags, contentmodel] - mergeStrategy: array_merge + type: array description: |- The logging system has two levels: an event type, which describes the general category and can be viewed as a named subset of all logs; and @@ -7291,7 +7429,7 @@ config-schema: log types instead of checking the global variable. LogRestrictions: default: { suppress: suppressionlog } - mergeStrategy: array_merge + type: object description: |- This restricts log access to those who have a certain right Users without this will not see it in the option menu and can not view it @@ -7300,7 +7438,7 @@ config-schema: Format: logtype => permissiontype FilterLogTypes: default: { patrol: true, tag: true, newusers: false } - mergeStrategy: array_merge + type: object description: |- Show/hide links on Special:Log will be shown for these log types. @@ -7320,7 +7458,7 @@ config-schema: used for the link text. LogNames: default: { '': all-logs-page, block: blocklogpage, protect: protectlogpage, rights: rightslog, delete: dellogpage, upload: uploadlogpage, move: movelogpage, import: importlogpage, patrol: patrol-log-page, merge: mergelog, suppress: suppressionlog } - mergeStrategy: array_merge + type: object description: |- Lists the message key string for each log type. The localized messages will be listed in the user interface. @@ -7330,7 +7468,7 @@ config-schema: where TYPE is your log type, you don't need to use this array. LogHeaders: default: { '': alllogstext, block: blocklogtext, delete: dellogpagetext, import: importlogpagetext, merge: mergelogpagetext, move: movelogpagetext, patrol: patrol-log-header, protect: protectlogtext, rights: rightslogtext, suppress: suppressionlogtext, upload: uploadlogpagetext } - mergeStrategy: array_merge + type: object description: |- Lists the message key string for descriptive text to be shown at the top of each log type. @@ -7339,8 +7477,8 @@ config-schema: @since 1.19, if you follow the naming convention log-description-TYPE, where TYPE is your log type, yoy don't need to use this array. LogActions: - default: { } - mergeStrategy: array_merge + default: [] + type: array description: |- Lists the message key string for formatting individual events of each type and action when listed in the logs. @@ -7348,7 +7486,7 @@ config-schema: Extensions with custom log types may add to this array. LogActionsHandlers: default: { block/block: BlockLogFormatter, block/reblock: BlockLogFormatter, block/unblock: BlockLogFormatter, contentmodel/change: ContentModelLogFormatter, contentmodel/new: ContentModelLogFormatter, delete/delete: DeleteLogFormatter, delete/delete_redir: DeleteLogFormatter, delete/delete_redir2: DeleteLogFormatter, delete/event: DeleteLogFormatter, delete/restore: DeleteLogFormatter, delete/revision: DeleteLogFormatter, import/interwiki: ImportLogFormatter, import/upload: ImportLogFormatter, managetags/activate: LogFormatter, managetags/create: LogFormatter, managetags/deactivate: LogFormatter, managetags/delete: LogFormatter, merge/merge: MergeLogFormatter, move/move: MoveLogFormatter, move/move_redir: MoveLogFormatter, patrol/patrol: PatrolLogFormatter, patrol/autopatrol: PatrolLogFormatter, protect/modify: ProtectLogFormatter, protect/move_prot: ProtectLogFormatter, protect/protect: ProtectLogFormatter, protect/unprotect: ProtectLogFormatter, rights/autopromote: RightsLogFormatter, rights/rights: RightsLogFormatter, suppress/block: BlockLogFormatter, suppress/delete: DeleteLogFormatter, suppress/event: DeleteLogFormatter, suppress/reblock: BlockLogFormatter, suppress/revision: DeleteLogFormatter, tag/update: TagLogFormatter, upload/overwrite: UploadLogFormatter, upload/revert: UploadLogFormatter, upload/upload: UploadLogFormatter } - mergeStrategy: array_merge + type: object description: |- The same as above, but here values are names of classes, not messages. @@ -7356,7 +7494,7 @@ config-schema: @see \LogFormatter ActionFilteredLogs: default: { block: { block: [block], reblock: [reblock], unblock: [unblock] }, contentmodel: { change: [change], new: [new] }, delete: { delete: [delete], delete_redir: [delete_redir, delete_redir2], restore: [restore], event: [event], revision: [revision] }, import: { interwiki: [interwiki], upload: [upload] }, managetags: { create: [create], delete: [delete], activate: [activate], deactivate: [deactivate] }, move: { move: [move], move_redir: [move_redir] }, newusers: { create: [create, newusers], create2: [create2], autocreate: [autocreate], byemail: [byemail] }, protect: { protect: [protect], modify: [modify], unprotect: [unprotect], move_prot: [move_prot] }, rights: { rights: [rights], autopromote: [autopromote] }, suppress: { event: [event], revision: [revision], delete: [delete], block: [block], reblock: [reblock] }, upload: { upload: [upload], overwrite: [overwrite], revert: [revert] } } - mergeStrategy: array_merge + type: object description: |- List of log types that can be filtered by action types @@ -7402,8 +7540,8 @@ config-schema: at Special:Contributions. @since 1.30 Actions: - default: { } - mergeStrategy: array_merge + default: [] + type: array description: |- Array of allowed values for the "title=foo&action=<action>" parameter. See ActionFactory for the syntax. Core defaults are in ActionFactory::CORE_ACTIONS, @@ -7416,6 +7554,7 @@ config-schema: basis. NamespaceRobotPolicies: default: { } + type: object description: |- Robot policies per namespaces. The default policy is given above, the array is made of namespace constants as defined in includes/Defines.php. You can- @@ -7432,6 +7571,7 @@ config-schema: ``` ArticleRobotPolicies: default: { } + type: object description: |- Robot policies per article. These override the per-namespace robot policies. @@ -7462,6 +7602,7 @@ config-schema: ``` ExemptFromUserRobotsControl: default: null + type: ["null", array] description: |- An array of namespace keys in which the __INDEX__/__NOINDEX__ magic words will not function, so users can't decide whether pages in that namespace are @@ -7487,7 +7628,7 @@ config-schema: @since 1.21 APIModules: default: { } - mergeStrategy: array_merge + type: object description: |- API module extensions. @@ -7524,7 +7665,7 @@ config-schema: See ApiMain::MODULES for a list of the core modules. APIFormatModules: default: { } - mergeStrategy: array_merge + type: object description: |- API format module extensions. @@ -7534,7 +7675,7 @@ config-schema: See ApiMain::FORMATS for a list of the core format modules. APIMetaModules: default: { } - mergeStrategy: array_merge + type: object description: |- API Query meta module extensions. @@ -7544,7 +7685,7 @@ config-schema: See ApiQuery::QUERY_META_MODULES for a list of the core meta modules. APIPropModules: default: { } - mergeStrategy: array_merge + type: object description: |- API Query prop module extensions. @@ -7554,7 +7695,7 @@ config-schema: See ApiQuery::QUERY_PROP_MODULES for a list of the core prop modules. APIListModules: default: { } - mergeStrategy: array_merge + type: object description: |- API Query list module extensions. @@ -7594,6 +7735,7 @@ config-schema: description: 'Set the timeout for the API help text cache. If set to 0, caching disabled' APIUselessQueryPages: default: [MIMEsearch, LinkSearch] + type: array description: |- The ApiQueryQueryPages module should skip pages that are redundant to true API queries. @@ -7605,6 +7747,7 @@ config-schema: description: 'Enable previewing licences via AJAX.' CrossSiteAJAXdomains: default: { } + type: object description: |- Settings for incoming cross-site AJAX requests: Newer browsers support cross-site AJAX when the target resource allows requests @@ -7628,15 +7771,18 @@ config-schema: ``` CrossSiteAJAXdomainExceptions: default: { } + type: object description: |- Domains that should not be allowed to make AJAX requests, even if they match one of the domains allowed by $wgCrossSiteAJAXdomains Uses the same syntax as $wgCrossSiteAJAXdomains AllowedCorsHeaders: default: [Accept, Accept-Language, Content-Language, Content-Type, Accept-Encoding, DNT, Origin, User-Agent, Api-User-Agent, Access-Control-Max-Age] + type: array description: 'List of allowed headers for cross-origin API requests.' RestAPIAdditionalRouteFiles: - default: { } + default: [] + type: array description: |- Additional REST API Route files. @@ -7767,6 +7913,7 @@ config-schema: description: 'Proxy to use for CURL requests.' LocalVirtualHosts: default: { } + type: object description: |- Local virtual hosts. @@ -7829,6 +7976,7 @@ config-schema: paths: {} modules: {} global: { timeout: 360, forwardCookies: false, HTTPProxy: null } + mergeStrategy: array_plus_2d type: object description: |- Global configuration variable for Virtual REST Services. @@ -7866,6 +8014,7 @@ config-schema: @since 1.25 EventRelayerConfig: default: { default: { class: EventRelayerNull } } + type: object description: |- Mapping of event channels (or channel categories) to EventRelayer configuration. @@ -7901,7 +8050,7 @@ config-schema: Aggregate pingback data is available at: https://pingback.wmflabs.org/ @since 1.28 OriginTrials: - default: { } + default: [] type: array description: |- Origin Trials tokens. @@ -7935,14 +8084,14 @@ config-schema: @warning EXPERIMENTAL! @since 1.34 ReportToEndpoints: - default: { } + default: [] type: array description: |- List of endpoints for the Reporting API. @warning EXPERIMENTAL! @since 1.34 FeaturePolicyReportOnly: - default: { } + default: [] type: array description: |- List of Feature Policy Reporting types to enable. diff --git a/tests/phpunit/structure/SettingsTest.php b/tests/phpunit/structure/SettingsTest.php index 890fa4bd8264..73d5b0c97188 100644 --- a/tests/phpunit/structure/SettingsTest.php +++ b/tests/phpunit/structure/SettingsTest.php @@ -7,6 +7,7 @@ use MediaWiki\Settings\Config\ArrayConfigBuilder; use MediaWiki\Settings\Config\PhpIniSink; use MediaWiki\Settings\SettingsBuilder; use MediaWiki\Settings\Source\FileSource; +use MediaWiki\Settings\Source\Format\YamlFormat; use MediaWiki\Settings\Source\PhpSettingsSource; use MediaWiki\Settings\Source\SettingsSource; use MediaWiki\Shell\Shell; @@ -17,7 +18,28 @@ use MediaWikiIntegrationTestCase; */ class SettingsTest extends MediaWikiIntegrationTestCase { - public function testConfigSchemaIsLoadable() { + /** + * Returns the contents of config-schema.yaml as an array. + * + * @return array + */ + private static function getSchemaData(): array { + static $data = null; + + if ( !$data ) { + $file = __DIR__ . '/../../../includes/config-schema.yaml'; + $data = file_get_contents( $file ); + $yaml = new YamlFormat(); + $data = $yaml->decode( $data ); + } + + return $data; + } + + /** + * @return SettingsBuilder + */ + private function getSettingsBuilderWithSchema(): SettingsBuilder { $configBuilder = new ArrayConfigBuilder(); $settingsBuilder = new SettingsBuilder( __DIR__ . '/../../..', @@ -25,26 +47,24 @@ class SettingsTest extends MediaWikiIntegrationTestCase { $configBuilder, $this->createNoOpMock( PhpIniSink::class ) ); - $settingsBuilder->loadFile( 'includes/config-schema.yaml' ); + $settingsBuilder->loadArray( self::getSchemaData() ); + return $settingsBuilder; + } + + public function testConfigSchemaIsLoadable() { + $settingsBuilder = $this->getSettingsBuilderWithSchema(); $settingsBuilder->apply(); // Assert we've read some random config value - $this->assertTrue( $configBuilder->build()->has( 'Server' ) ); + $this->assertTrue( $settingsBuilder->getConfig()->has( 'Server' ) ); } /** * Check that core default settings validate against the schema */ public function testConfigSchemaDefaultsValidate() { - $settingsBuilder = new SettingsBuilder( - __DIR__ . '/../../..', - $this->createNoOpMock( ExtensionRegistry::class ), - new ArrayConfigBuilder(), - $this->createNoOpMock( PhpIniSink::class ) - ); - $validationResult = $settingsBuilder->loadFile( 'includes/config-schema.yaml' ) - ->apply() - ->validate(); + $settingsBuilder = $this->getSettingsBuilderWithSchema(); + $validationResult = $settingsBuilder->apply()->validate(); $this->assertArrayEquals( [], $validationResult->getErrors() ); } @@ -132,4 +152,366 @@ class SettingsTest extends MediaWikiIntegrationTestCase { $this->assertEquals( $value, $configBuilder->build()->get( $key ), "Wrong value for $key\n" ); } } + + public function provideArraysHaveMergeStrategy() { + [ 'config-schema' => $allSchemas ] = self::getSchemaData(); + + foreach ( $allSchemas as $name => $schema ) { + yield "Schema for $name" => [ $schema ]; + } + } + + /** + * Check that the schema for each config variable contains all necessary information. + * @dataProvider provideArraysHaveMergeStrategy + */ + public function testArraysHaveMergeStrategy( $schema ) { + $this->assertArrayHasKey( + 'default', + $schema, + 'should specify a default value' + ); + + $type = $schema['type'] ?? null; + $type = (array)$type; + + // If the default is an array, the type must be declared, so we know whether + // it's a list (JS "array") or a map (JS "object"). + if ( is_array( $schema['default'] ) ) { + if ( $type ) { + $this->assertTrue( + in_array( 'array', $type ) || in_array( 'object', $type ), + 'must be of type "array" or "object", since the default is an array' + ); + } else { + $this->assertArrayHasKey( + 'mergeStrategy', + $schema, + 'must specify a type or merge strategy, since the default is an array' + ); + } + } + + // If the default value of a list is not empty, check that it is an indexed array, + // not an associative array. + if ( in_array( 'array', $type ) && !empty( $schema['default'] ) ) { + $this->assertArrayHasKey( + 0, + $schema['default'], + 'should have a default value starting with index 0, since its type is "array"' + ); + } + + $mergeStrategy = $schema['mergeStrategy'] ?? null; + + // If a merge strategy is defined, make sure it makes sense for the given type. + if ( $mergeStrategy ) { + if ( in_array( 'array', $type ) ) { + $this->assertNotSame( + 'array_merge', + $mergeStrategy, + 'should not specify redundant mergeStrategy "array_merge" since ' + . 'it is implied by the type being "array"' + ); + + $this->assertNotSame( + 'array_plus', + $mergeStrategy, + 'should not specify mergeStrategy "array_plus" since its type is "array"' + ); + + $this->assertNotSame( + 'array_plus_2d', + $mergeStrategy, + 'should not specify mergeStrategy "array_plus_2d" since its type is "array"' + ); + } elseif ( in_array( 'object', $type ) ) { + $this->assertNotSame( + 'array_plus', + $mergeStrategy, + 'should not specify redundant mergeStrategy "array_plus" since ' + . 'it is implied by the type being "object"' + ); + + $this->assertNotSame( + 'array_merge', + $mergeStrategy, + 'should not specify mergeStrategy "array_merge" since its type is not "array"' + ); + } + } + } + + public function provideConfigStructureHandling() { + yield 'NamespacesWithSubpages' => [ + 'NamespacesWithSubpages', + [ 0 => true, 1 => false, + 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, + 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, + 13 => true, 15 => true + ], + [ 0 => true, 1 => false ] + ]; + yield 'InterwikiCache array' => [ + 'InterwikiCache', + [ 'x' => [ 'foo' => 1 ] ], + [ 'x' => [ 'foo' => 1 ] ], + ]; + yield 'InterwikiCache string' => [ + 'InterwikiCache', + 'interwiki.map', + 'interwiki.map', + ]; + yield 'InterwikiCache string over array' => [ + 'InterwikiCache', + 'interwiki.map', + [ 'x' => [ 'foo' => 1 ] ], + 'interwiki.map', + ]; + yield 'ProxyList array' => [ + 'ProxyList', + [ 'a', 'b', 'c' ], + [ 'a', 'b', 'c' ], + ]; + yield 'ProxyList string' => [ + 'ProxyList', + 'interwiki.map', + 'interwiki.map', + ]; + yield 'ProxyList string over array' => [ + 'ProxyList', + 'interwiki.map', + [ 'a', 'b', 'c' ], + 'interwiki.map', + ]; + yield 'ProxyList array over array' => [ + 'ProxyList', + [ 'a', 'b', 'c', 'd' ], + [ 'a', 'b' ], + [ 'c', 'd' ], + ]; + yield 'Logos' => [ + 'Logos', + [ '1x' => 'Logo1', '2x' => 'Logo2' ], + [ '1x' => 'Logo1', '2x' => 'Logo2' ], + ]; + yield 'Logos clear' => [ + 'Logos', + false, + [ '1x' => 'Logo1', '2x' => 'Logo2' ], + false + ]; + yield 'RevokePermissions' => [ + 'RevokePermissions', + [ '*' => [ 'read' => true, 'edit' => true, ] ], + [ '*' => [ 'edit' => true ] ], + [ '*' => [ 'read' => true ] ] + ]; + } + + /** + * Ensure that some of the more complex/problematic config structures are handled + * correctly. + * + * @dataProvider provideConfigStructureHandling + */ + public function testConfigStructureHandling( $key, $expected, $value, $value2 = null ) { + $settingsBuilder = $this->getSettingsBuilderWithSchema(); + $settingsBuilder->apply(); + + $settingsBuilder->putConfigValue( $key, $value ); + + if ( $value2 !== null ) { + $settingsBuilder->putConfigValue( $key, $value2 ); + } + + $config = $settingsBuilder->getConfig(); + + $this->assertSame( $expected, $config->get( $key ) ); + } + + public function provideConfigStructurePartialReplacement() { + yield 'ObjectCaches' => [ + 'ObjectCaches', + [ // the spec for each cache should be replaced entirely + 1 => [ 'factory' => 'ObjectCache::newAnything' ], + 'test' => [ 'factory' => 'Testing' ] + ], + [ + 1 => [ 'factory' => 'ObjectCache::newAnything' ], + 'test' => [ 'factory' => 'Testing' ] + ], + ]; + yield 'GroupPermissions' => [ + 'GroupPermissions', + [ // permissions for each group should be merged + 'autoconfirmed' => [ + 'autoconfirmed' => true, + 'editsemiprotected' => false, + 'patrol' => true, + ], + 'mygroup' => [ 'test' => true ], + ], + [ + 'autoconfirmed' => [ + 'patrol' => true, + 'editsemiprotected' => false + ], + 'mygroup' => [ 'test' => true ], + ], + ]; + yield 'RateLimits' => [ + 'RateLimits', + [ // limits for each action should be merged, limits for each group get replaced + 'move' => [ 'newbie' => [ 1, 80 ], 'user' => [ 8, 60 ], 'ip' => [ 1, 60 ] ], + 'test' => [ 'ip' => [ 1, 60 ] ], + ], + [ + 'move' => [ 'ip' => [ 1, 60 ], 'newbie' => [ 1, 80 ], 'user' => [ 8, 60 ] ], + 'test' => [ 'ip' => [ 1, 60 ] ], + ] + ]; + } + + /** + * Ensure that some of the more complex/problematic config structures are + * correctly replacing parts of a complex default. + * + * @dataProvider provideConfigStructurePartialReplacement + */ + public function testConfigStructurePartialReplacement( $key, $expectedValue, $newValue ) { + $settingsBuilder = $this->getSettingsBuilderWithSchema(); + $defaultValue = $settingsBuilder->getConfig()->get( $key ); + + $settingsBuilder->putConfigValue( $key, $newValue ); + $mergedValue = $settingsBuilder->getConfig()->get( $key ); + + // Check that the keys in $mergedValue that are also present + // in $newValue now match $expectedValue. + $updatedValue = array_intersect_key( $mergedValue, $newValue ); + $this->assertArrayEquals( $expectedValue, $updatedValue, false, true ); + + // Check that the other keys in $mergedValue are still the same + // as in $defaultValue. + $mergedValue = array_diff_key( $mergedValue, $newValue ); + $defaultValue = array_diff_key( $defaultValue, $newValue ); + $this->assertArrayEquals( $defaultValue, $mergedValue, false, true ); + } + + /** + * Ensure that hook handlers are merged correctly. + */ + public function testHooksMerge() { + $settingsBuilder = $this->getSettingsBuilderWithSchema(); + + $f1 = static function () { + // noop + }; + + $hooks = [ + 'TestHook' => [ + 'TestHookHandler1', + [ 'TestHookHandler1', 'handler data' ], + $f1, + ] + ]; + $settingsBuilder->putConfigValue( 'Hooks', $hooks ); + + $f2 = static function () { + // noop + }; + + $hooks = [ + 'TestHook' => [ + 'TestHookHandler2', + [ 'TestHookHandler2', 'more handler data' ], + $f2, + ] + ]; + $settingsBuilder->putConfigValue( 'Hooks', $hooks ); + + $config = $settingsBuilder->getConfig(); + + $hooks = [ + 'TestHook' => [ + 'TestHookHandler1', + [ 'TestHookHandler1', 'handler data' ], + $f1, + 'TestHookHandler2', + [ 'TestHookHandler2', 'more handler data' ], + $f2, + ] + ]; + $this->assertSame( $hooks, $config->get( 'Hooks' ) ); + } + + /** + * Ensure that PasswordPolicy are merged correctly. + */ + public function testPasswordPolicyMerge() { + $settingsBuilder = $this->getSettingsBuilderWithSchema(); + $defaultPolicies = $settingsBuilder->getConfig()->get( 'PasswordPolicy' ); + + $newPolicies = [ + 'policies' => [ + 'sysop' => [ + 'MinimalPasswordLength' => [ + 'value' => 10, + 'suggestChangeOnLogin' => false, + ], + ], + 'bot' => [ + 'MinimumPasswordLengthToLogin' => 2, + ], + ], + 'checks' => [ + 'MinimalPasswordLength' => 'myLengthCheck', + 'SomeOtherCheck' => 'myOtherCheck', + ] + ]; + $settingsBuilder->putConfigValue( 'PasswordPolicy', $newPolicies ); + $mergedPolicies = $settingsBuilder->getConfig()->get( 'PasswordPolicy' ); + + // check that the new policies have been applied + $this->assertSame( + [ + 'MinimalPasswordLength' => [ + 'value' => 10, + 'suggestChangeOnLogin' => false, + ], + 'MinimumPasswordLengthToLogin' => 1, // from defaults + ], + $mergedPolicies['policies']['sysop'] + ); + $this->assertSame( + [ + 'MinimalPasswordLength' => 10, // from defaults + 'MinimumPasswordLengthToLogin' => 2, + ], + $mergedPolicies['policies']['bot'] + ); + $this->assertSame( + 'myLengthCheck', + $mergedPolicies['checks']['MinimalPasswordLength'] + ); + $this->assertSame( + 'myOtherCheck', + $mergedPolicies['checks']['SomeOtherCheck'] + ); + + // check that other stuff wasn't changed + $this->assertSame( + $defaultPolicies['checks']['PasswordCannotMatchDefaults'], + $mergedPolicies['checks']['PasswordCannotMatchDefaults'] + ); + $this->assertSame( + $defaultPolicies['policies']['bureaucrat'], + $mergedPolicies['policies']['bureaucrat'] + ); + $this->assertSame( + $defaultPolicies['policies']['default'], + $mergedPolicies['policies']['default'] + ); + } + } diff --git a/tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php b/tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php index 15b108d90dab..77bce3222591 100644 --- a/tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php +++ b/tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php @@ -3,7 +3,6 @@ namespace MediaWiki\Tests\Unit\Settings\Config; use MediaWiki\Settings\Config\ConfigSchemaAggregator; -use MediaWiki\Settings\Config\MergeStrategy; use MediaWiki\Settings\SettingsBuilderException; use PHPUnit\Framework\TestCase; @@ -55,17 +54,52 @@ class ConfigSchemaAggregatorTest extends TestCase { $this->assertSame( 'bla', $aggregator->getDefaultFor( 'with_default' ) ); } - public function testGetMergeStrategyFor() { + public function provideGetMergeStrategiesFor() { + yield 'no schema' => [ null, null ]; + yield 'no strategy' => [ [ 'default' => '' ], null ]; + yield 'with strategy' => [ [ 'mergeStrategy' => 'array_merge' ], 'array_merge' ]; + + yield 'with strategy and type=array' => [ + [ + 'type' => 'array', + 'mergeStrategy' => 'replace' + ], + 'replace' + ]; + + yield 'without strategy and type=array' => [ + [ 'type' => 'array' ], + 'array_merge' + ]; + + yield 'with strategy and type=object' => [ + [ + 'type' => 'object', + 'mergeStrategy' => 'array_plus_2d' + ], + 'array_plus_2d' + ]; + + yield 'without strategy and type=object' => [ + [ 'type' => 'object' ], + 'array_plus' + ]; + } + + /** + * @dataProvider provideGetMergeStrategiesFor + */ + public function testGetMergeStrategyFor( $schema, $expected ) { $aggregator = new ConfigSchemaAggregator(); - $aggregator->addSchemas( [ - 'no_strategy' => [ 'type' => 'string', ], - 'has_strategy' => [ 'type' => 'string', 'mergeStrategy' => MergeStrategy::ARRAY_MERGE, ], - ] ); - $this->assertNull( $aggregator->getMergeStrategyFor( 'not_exist' ) ); - $this->assertNull( $aggregator->getMergeStrategyFor( 'no_strategy' ) ); + + if ( $schema ) { + $aggregator->addSchemas( [ 'test' => $schema, ] ); + } + + $strategy = $aggregator->getMergeStrategyFor( 'test' ); $this->assertSame( - MergeStrategy::ARRAY_MERGE, - $aggregator->getMergeStrategyFor( 'has_strategy' )->getName() + $expected, + $strategy ? $strategy->getName() : null ); } } diff --git a/tests/phpunit/unit/includes/Settings/Config/ConfigSinkTestTrait.php b/tests/phpunit/unit/includes/Settings/Config/ConfigSinkTestTrait.php index 9469a97a21b5..415fe7f8c670 100644 --- a/tests/phpunit/unit/includes/Settings/Config/ConfigSinkTestTrait.php +++ b/tests/phpunit/unit/includes/Settings/Config/ConfigSinkTestTrait.php @@ -4,7 +4,6 @@ namespace MediaWiki\Tests\Unit\Settings\Config; use MediaWiki\Settings\Config\ConfigBuilder; use MediaWiki\Settings\Config\MergeStrategy; -use MediaWiki\Settings\SettingsBuilderException; trait ConfigSinkTestTrait { @@ -13,98 +12,95 @@ trait ConfigSinkTestTrait { abstract protected function assertKeyHasValue( string $key, $value ); public function testSet() { - $this->getConfigSink()->set( __METHOD__, 'bar' ); - $this->assertKeyHasValue( __METHOD__, 'bar' ); - } - - public function testSetOverrides() { $this->getConfigSink() - ->set( __METHOD__, 'bar' ) - ->set( __METHOD__, 'baz' ); - $this->assertKeyHasValue( __METHOD__, 'baz' ); + ->set( 'TestKey1', 'foo' ) + ->set( 'TestKey2', 'bar' ); + $this->assertKeyHasValue( 'TestKey1', 'foo' ); + $this->assertKeyHasValue( 'TestKey2', 'bar' ); } public function testSetDefault() { $this->getConfigSink() - ->set( __METHOD__, null ) - ->setDefault( 'other' . __METHOD__, 'quux' ) - ->setDefault( __METHOD__, 'baz' ); - - $this->assertKeyHasValue( 'other' . __METHOD__, 'quux' ); - $this->assertKeyHasValue( __METHOD__, null ); + ->setDefault( 'TestKey1', 'foo' ) + ->setDefault( 'TestKey2', 'bar' ); + $this->assertKeyHasValue( 'TestKey1', 'foo' ); + $this->assertKeyHasValue( 'TestKey2', 'bar' ); } - public function testMerge() { - $this->getConfigSink() - ->set( __METHOD__, [ 'bar' ] ) - ->set( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) - ); - $this->assertKeyHasValue( __METHOD__, [ 'bar', 'baz' ] ); - } + public function provideSetNewValue() { + yield 'replace 1 with 2' => [ 1, 2, null, 2 ]; + yield 'replace 1 with 0' => [ 1, 0, null, 0 ]; + yield 'replace 1 with null' => [ 1, null, null, null ]; - public function testMergeDefault() { - $this->getConfigSink() - ->set( __METHOD__, [ 'bar' ] ) - ->setDefault( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) - ); - $this->assertKeyHasValue( __METHOD__, [ 'baz', 'bar' ] ); - } + yield 'merge two arrays' => [ + [ 'a' ], [ 'b' ], MergeStrategy::ARRAY_MERGE, [ 'a', 'b' ] + ]; + yield 'merge two maps' => [ + [ 'a' => 1 ], [ 'a' => 2 ], MergeStrategy::ARRAY_MERGE, [ 'a' => 2 ] + ]; - public function testMergeOverrideEmpty() { - $this->getConfigSink() - ->set( __METHOD__, [] ) - ->set( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) - ); - $this->assertKeyHasValue( __METHOD__, [ 'baz' ] ); + yield 'empty array replaces 1' => [ 1, [], MergeStrategy::ARRAY_MERGE, [] ]; + yield '1 replaces non-empty array' => [ [ 'x' ], 1, MergeStrategy::ARRAY_MERGE, 1 ]; + yield 'null replaces non-empty array' => [ [ 'x' ], null, MergeStrategy::ARRAY_MERGE, null ]; + + yield 'empty array replaces non-empty array' => [ [ 'x' ], [], MergeStrategy::REPLACE, [] ]; } - public function testMergeOverrideNonExisting() { + /** + * @dataProvider provideSetNewValue + * + * @param mixed $first + * @param mixed $second + * @param string $strategy + * @param mixed $expected + */ + public function testSetNewValue( $first, $second, $strategy, $expected ) { $this->getConfigSink() + ->set( 'TestKey', $first ) ->set( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) + 'TestKey', + $second, $strategy ? MergeStrategy::newFromName( $strategy ) : null ); - $this->assertKeyHasValue( __METHOD__, [ 'baz' ] ); + $this->assertKeyHasValue( 'TestKey', $expected ); } - public function testMergeDefaultOverrideEmpty() { - $this->getConfigSink() - ->set( __METHOD__, [] ) - ->setDefault( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) - ); - $this->assertKeyHasValue( __METHOD__, [ 'baz' ] ); + public function provideSetDefaultValue() { + yield 'do not replace 1 with 2' => [ 1, 2, null, 1 ]; + yield 'do not replace 0 with 2' => [ 0, 2, null, 0 ]; + yield 'do not replace null with 2' => [ false, 2, null, null ]; + yield 'do not replace false with 2' => [ null, 2, null, false ]; + yield 'do not replace an empty array with 2' => [ [], 2, null, [] ]; + yield 'do not replace an empty array with a non-empty one' => [ [], [ 2 ], null, [] ]; + + yield 'merge two arrays' => [ + [ 'a' ], [ 'b' ], MergeStrategy::ARRAY_MERGE, [ 'b', 'a' ] + ]; + yield 'merge two maps' => [ + [ 'a' => 1 ], [ 'a' => 2 ], MergeStrategy::ARRAY_MERGE, [ 'a' => 1 ] + ]; + + yield 'non-empty array does not replace 1' => [ 1, [ 'x' ], MergeStrategy::ARRAY_MERGE, 1 ]; + yield '1 does not replace empty array' => [ [], 1, MergeStrategy::ARRAY_MERGE, [] ]; + yield '1 does not replace non-empty array' => [ [ 'x' ], 1, MergeStrategy::ARRAY_MERGE, [ 'x' ] ]; } - public function testMergeDefaultOverrideNonExisting() { + /** + * @dataProvider provideSetDefaultValue + * + * @param mixed $first + * @param mixed $second + * @param string $strategy + * @param mixed $expected + */ + public function testSetDefaultValue( $first, $second, $strategy, $expected ) { $this->getConfigSink() + ->set( 'TestKey', $first ) ->setDefault( - __METHOD__, - [ 'baz' ], - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) + 'TestKey', + $second, + $strategy ? MergeStrategy::newFromName( $strategy ) : null ); - $this->assertKeyHasValue( __METHOD__, [ 'baz' ] ); + $this->assertKeyHasValue( 'TestKey', $expected ); } - public function testCannotMergeNonArray() { - $this->expectException( SettingsBuilderException::class ); - $this->getConfigSink() - ->set( - __METHOD__, - 'baz', - MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) - ); - } } diff --git a/tests/phpunit/unit/includes/Settings/Config/GlobalConfigBuilderTest.php b/tests/phpunit/unit/includes/Settings/Config/GlobalConfigBuilderTest.php index b0e151a7cc21..3e5cdcdc0bfe 100644 --- a/tests/phpunit/unit/includes/Settings/Config/GlobalConfigBuilderTest.php +++ b/tests/phpunit/unit/includes/Settings/Config/GlobalConfigBuilderTest.php @@ -14,11 +14,11 @@ class GlobalConfigBuilderTest extends TestCase { use ConfigSinkTestTrait; protected function getConfigSink(): ConfigBuilder { - return new GlobalConfigBuilder( 'prefix_' ); + return new GlobalConfigBuilder( 'GlobalConfigBuilderTestPrefix_' ); } protected function assertKeyHasValue( string $key, $value ) { - $this->assertEquals( $value, $GLOBALS['prefix_' . $key] ); + $this->assertEquals( $value, $GLOBALS['GlobalConfigBuilderTestPrefix_' . $key] ); } public function testBuild() { @@ -42,7 +42,7 @@ class GlobalConfigBuilderTest extends TestCase { } public function testMergeWithGlobal() { - $GLOBALS['prefix_foo'] = [ 'a' => 1, 'b' => 2 ]; + $GLOBALS['GlobalConfigBuilderTestPrefix_foo'] = [ 'a' => 1, 'b' => 2 ]; $this->getConfigSink() ->set( @@ -51,6 +51,9 @@ class GlobalConfigBuilderTest extends TestCase { MergeStrategy::newFromName( MergeStrategy::ARRAY_MERGE ) ); $this->assertKeyHasValue( 'foo', [ 'a' => 11, 'b' => 2, 'c' => 33 ] ); - $this->assertSame( [ 'a' => 11, 'b' => 2, 'c' => 33 ], $GLOBALS['prefix_foo'] ); + $this->assertSame( + [ 'a' => 11, 'b' => 2, 'c' => 33 ], + $GLOBALS['GlobalConfigBuilderTestPrefix_foo'] + ); } } diff --git a/tests/phpunit/unit/includes/Settings/Config/MergeStrategyTest.php b/tests/phpunit/unit/includes/Settings/Config/MergeStrategyTest.php index 776d7b76212b..0fae43fb3ac3 100644 --- a/tests/phpunit/unit/includes/Settings/Config/MergeStrategyTest.php +++ b/tests/phpunit/unit/includes/Settings/Config/MergeStrategyTest.php @@ -47,6 +47,12 @@ class MergeStrategyTest extends TestCase { 'baseArray' => [ 'a' => [ 'b' => [ 'd' ] ], 'e' => [ 'f' ] ], 'expected' => [ 'a' => [ 'b' => [ 'c' ] ], 'e' => [ 'f' ] ], ]; + yield 'replace' => [ + 'strategy' => MergeStrategy::REPLACE, + 'newArray' => [ 'a' => [ 'b' => [ 'c' ] ] ], + 'baseArray' => [ 'a' => [ 'b' => [ 'd' ] ], 'e' => [ 'f' ] ], + 'expected' => [ 'a' => [ 'b' => [ 'c' ] ] ], + ]; } /** |