Move dynamic defaults into MainConfigSchema

The goal is to keep the actual default values for settings in the same
place as the setting is declared, and applied using the regular means
for loading the settings -- not in a separate piece of code that needs
to be loaded through some entirely different mechanism.

SetupDynamicConfig.php now contains a few categories of things:

* Post-processing of configuration settings, where already-set settings
  are altered. This could be moved to MainConfigSchema too as a separate
  set of methods.
* Processing of old aliases of settings (blacklist, slave) that are not
  registered as settings anymore and therefore are not available to
  MainConfigSchema. This could perhaps be moved to LocalSettings
  processing somehow?
* Setting $wgUseEnotif, which is also not registered as a setting.
  Easiest would be just to declare it as a setting and have it set
  unconditionally.
* Setting the actual timezone to $wgLocaltimezone. This is not related
  to configuration and should just be in Setup.php.

Bug: T305093
Change-Id: Ia5c23b52dbbfcb3d07ffcf5d3b7f2d7befba2a26
This commit is contained in:
Aryeh Gregor 2022-04-24 19:14:51 +03:00 committed by Tim Starling
parent 14d324c15f
commit b72b9a8c43
22 changed files with 1654 additions and 511 deletions

View file

@ -79,7 +79,8 @@ config-schema:
Other paths will be set to defaults based on it unless they are directly
set in LocalSettings.php
UsePathInfo:
default: null
dynamicDefault:
callback: [MediaWiki\MainConfigSchema, getDefaultUsePathInfo]
description: |-
Whether to support URLs like index.php/Page_title.
The effective default value is determined at runtime:
@ -90,37 +91,56 @@ config-schema:
you have customized it, having this incorrectly set to true can cause
redirect loops when "pretty URLs" are used.
@since 1.2.1
default: null
Script:
default: false
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultScript]
description: |-
The URL path to index.php.
Defaults to "{$wgScriptPath}/index.php".
LoadScript:
default: false
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultLoadScript]
description: |-
The URL path to load.php.
Defaults to "{$wgScriptPath}/load.php".
@since 1.17
RestPath:
default: false
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultRestPath]
description: |-
The URL path to the REST API
Defaults to "{$wgScriptPath}/rest.php"
@since 1.34
StylePath:
default: false
dynamicDefault:
use: [ResourceBasePath]
callback: [MediaWiki\MainConfigSchema, getDefaultStylePath]
description: |-
The URL path of the skins directory.
Defaults to "{$wgResourceBasePath}/skins".
@since 1.3
LocalStylePath:
default: false
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultLocalStylePath]
description: |-
The URL path of the skins directory. Should not point to an external domain.
Defaults to "{$wgScriptPath}/skins".
@since 1.17
ExtensionAssetsPath:
default: false
dynamicDefault:
use: [ResourceBasePath]
callback: [MediaWiki\MainConfigSchema, getDefaultExtensionAssetsPath]
description: |-
The URL path of the extensions directory.
Defaults to "{$wgResourceBasePath}/extensions".
@ -156,6 +176,9 @@ config-schema:
@since 1.38
ArticlePath:
default: false
dynamicDefault:
use: [Script, UsePathInfo]
callback: [MediaWiki\MainConfigSchema, getDefaultArticlePath]
description: |-
The URL path for primary article page views. This path should contain $1,
which is replaced by the article title.
@ -163,6 +186,9 @@ config-schema:
depending on $wgUsePathInfo.
UploadPath:
default: false
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultUploadPath]
description: |-
The URL path for the images directory.
Defaults to "{$wgScriptPath}/images".
@ -185,14 +211,23 @@ config-schema:
@since 1.36
UploadDirectory:
default: false
dynamicDefault:
use: [BaseDirectory]
callback: [MediaWiki\MainConfigSchema, getDefaultUploadDirectory]
description: 'The filesystem path of the images directory. Defaults to "{$IP}/images".'
FileCacheDirectory:
default: false
dynamicDefault:
use: [UploadDirectory]
callback: [MediaWiki\MainConfigSchema, getDefaultFileCacheDirectory]
description: |-
Directory where the cached page will be saved.
Defaults to "{$wgUploadDirectory}/cache".
Logo:
default: false
dynamicDefault:
use: [ResourceBasePath]
callback: [MediaWiki\MainConfigSchema, getDefaultLogo]
description: |-
The URL path of the wiki logo. The logo size should be 135x135 pixels.
Defaults to "$wgResourceBasePath/resources/assets/change-your-logo.svg".
@ -398,6 +433,9 @@ config-schema:
completeness.
DeletedDirectory:
default: false
dynamicDefault:
use: [UploadDirectory]
callback: [MediaWiki\MainConfigSchema, getDefaultDeletedDirectory]
description: |-
What directory to place deleted uploads in.
Defaults to "{$wgUploadDirectory}/deleted".
@ -423,6 +461,9 @@ config-schema:
type:
- object
- boolean
dynamicDefault:
use: [UploadDirectory, ScriptPath, Favicon, UploadBaseUrl, UploadPath, HashedUploadDirectory, ThumbnailScriptPath, GenerateThumbnailOnParse, DeletedDirectory, UpdateCompatibleMetadata]
callback: [MediaWiki\MainConfigSchema, getDefaultLocalFileRepo]
description: |-
File repository structures
$wgLocalFileRepo is a single repository structure, and $wgForeignFileRepos is
@ -716,7 +757,8 @@ config-schema:
Additional parameters are specific to the lock manager class used.
These settings should be global to all wikis.
ShowEXIF:
default: null
dynamicDefault:
callback: [MediaWiki\MainConfigSchema, getDefaultShowEXIF]
description: |-
Whether to show Exif data.
The effective default value is determined at runtime:
@ -728,6 +770,7 @@ config-schema:
```{.ini}
extension=extensions/php_exif.dll
```
default: null
UpdateCompatibleMetadata:
default: false
description: 'Shortcut for the ''updateCompatibleMetadata'' setting of $wgLocalFileRepo.'
@ -1767,6 +1810,9 @@ config-schema:
configuration of $wgLBFactoryConf.
SharedPrefix:
default: false
dynamicDefault:
use: [DBprefix]
callback: [MediaWiki\MainConfigSchema, getDefaultSharedPrefix]
description: '@see $wgSharedDB'
SharedTables:
default:
@ -1779,6 +1825,9 @@ config-schema:
The installer will add 'actor' to this list for all new wikis.
SharedSchema:
default: false
dynamicDefault:
use: [DBmwschema]
callback: [MediaWiki\MainConfigSchema, getDefaultSharedSchema]
description: |-
@see $wgSharedDB
@since 1.23
@ -1858,6 +1907,9 @@ config-schema:
description: 'File to log database errors to'
DBerrorLogTZ:
default: false
dynamicDefault:
use: [Localtimezone]
callback: [MediaWiki\MainConfigSchema, getDefaultDBerrorLogTZ]
description: |-
Timezone to use in the error log.
Defaults to the wiki timezone ($wgLocaltimezone).
@ -2414,6 +2466,7 @@ config-schema:
@since 1.39
@unstable Per MediaWiki 1.39, the structure of this configuration is still subject to
change.
default: null
ChronologyProtectorStash:
default: null
type:
@ -2531,6 +2584,7 @@ config-schema:
$wgCacheDirectory is used instead.
manualRecache: Set this to true to disable cache updates on web requests.
Use maintenance/rebuildLocalisationCache.php instead.
default: null
CachePages:
default: true
description: 'Allow client-side caching of pages'
@ -2973,7 +3027,8 @@ config-schema:
letter.
@since 1.32
Localtimezone:
default: null
dynamicDefault:
callback: [MediaWiki\MainConfigSchema, getDefaultLocaltimezone]
description: |-
Fake out the timezone that the server thinks it's in. This will be used for
date display and not for what's stored in the DB. Leave to null to retain
@ -2992,14 +3047,18 @@ config-schema:
$wgLocaltimezone = 'Europe/Sweden';
$wgLocaltimezone = 'CET';
```
LocalTZoffset:
default: null
LocalTZoffset:
dynamicDefault:
use: [Localtimezone]
callback: [MediaWiki\MainConfigSchema, getDefaultLocalTZoffset]
description: |-
Set an offset from UTC in minutes to use for the default timezone setting
for anonymous users and new user accounts.
This setting is used for most date/time displays in the software, and is
overridable in user preferences. It is *not* used for signature timestamps.
By default, this will be set to match $wgLocaltimezone.
default: null
OverrideUcfirstCharacters:
default: { }
type: object
@ -3554,6 +3613,9 @@ config-schema:
```
ResourceBasePath:
default: null
dynamicDefault:
use: [ScriptPath]
callback: [MediaWiki\MainConfigSchema, getDefaultResourceBasePath]
description: |-
The default 'remoteBasePath' value for instances of MediaWiki\ResourceLoader\FileModule.
Defaults to $wgScriptPath.
@ -3646,6 +3708,9 @@ config-schema:
@since 1.35
MetaNamespace:
default: false
dynamicDefault:
use: [Sitename]
callback: [MediaWiki\MainConfigSchema, getDefaultMetaNamespace]
description: |-
Name of the project namespace. If left set to false, $wgSitename will be
used instead.
@ -4685,6 +4750,7 @@ config-schema:
- uppercase: (bool) With "filtered-radix", whether to use uppercase
letters, default false.
@since 1.39
default: null
AutoblockExpiry:
default: 86400
description: 'Number of seconds before autoblock entries expire. Default 86400 = 1 day.'
@ -5537,6 +5603,9 @@ config-schema:
the domain specified by $wgCookieDomain.
CookieSecure:
default: detect
dynamicDefault:
use: [ForceHTTPS]
callback: [MediaWiki\MainConfigSchema, getDefaultCookieSecure]
description: |-
Whether the "secure" flag should be set on the cookie. This can be:
- true: Set secure flag
@ -5554,6 +5623,9 @@ config-schema:
check.
CookiePrefix:
default: false
dynamicDefault:
use: [SharedDB, SharedPrefix, SharedTables, DBname, DBprefix]
callback: [MediaWiki\MainConfigSchema, getDefaultCookiePrefix]
description: |-
Cookies generated by MediaWiki have names starting with this prefix. Set it
to a string to use a custom prefix. Setting it to false causes the database
@ -6090,6 +6162,9 @@ config-schema:
@since 1.31
ReadOnlyFile:
default: false
dynamicDefault:
use: [UploadDirectory]
callback: [MediaWiki\MainConfigSchema, getDefaultReadOnlyFile]
description: |-
If this lock file exists (size > 0), the wiki will be forced into read-only mode.
Its contents will be shown to users as part of the read-only warning

View file

@ -23,6 +23,8 @@ use ClearUserWatchlistJob;
use ClearWatchlistNotificationsJob;
use ContentModelLogFormatter;
use CssContentHandler;
use DateTime;
use DateTimeZone;
use DeleteLinksJob;
use DeleteLogFormatter;
use DeletePageJob;
@ -44,6 +46,7 @@ use JsonContentHandler;
use LayeredParameterizedPassword;
use LocalIdLookup;
use LocalisationCache;
use LocalRepo;
use LogFormatter;
use MediaWiki\Settings\Source\JsonSchemaTrait;
use MediaWikiSite;
@ -78,6 +81,7 @@ use UserGroupExpiryJob;
use UserOptionsUpdateJob;
use WANObjectCache;
use WatchlistExpiryJob;
use WebRequest;
use WikitextContentHandler;
use WinCacheBagOStuff;
@ -98,9 +102,14 @@ use WinCacheBagOStuff;
* with uniform values. The 'object' type should be used for structures that have a known
* set of meaningful properties, especially if each property may have a different kind
* of value.
* See {@link MediaWiki\Settings\Source\JsonTypeHelper} for details.
*
* The following additional keys are used by MediaWiki:
* - mergeStrategy: see the {@link MediaWiki\Settings\Config\MergeStrategy}.
* - dynamicDefault: Specified a callback that computes the effective default at runtime, based
* on the value of other config variables or on the system environment.
* See {@link MediaWiki\Settings\Source\ReflectionSchemaSource}
* and {@link MediaWiki\Settings\DynamicDefaultValues} for details.
*
* @note After changing this file, run maintenance/generateConfigSchema.php to update
* all the files derived from the information in MainConfigSchema.
@ -328,9 +337,22 @@ class MainConfigSchema {
* @since 1.2.1
*/
public const UsePathInfo = [
'default' => null,
'dynamicDefault' => true,
];
/**
* @return bool
*/
public static function getDefaultUsePathInfo(): bool {
// These often break when PHP is set up in CGI mode.
// PATH_INFO *may* be correct if cgi.fix_pathinfo is set, but then again it may not;
// lighttpd converts incoming path data to lowercase on systems
// with case-insensitive filesystems, and there have been reports of
// problems on Apache as well.
return !str_contains( PHP_SAPI, 'cgi' ) && !str_contains( PHP_SAPI, 'apache2filter' ) &&
!str_contains( PHP_SAPI, 'isapi' );
}
/**
* The URL path to index.php.
*
@ -338,8 +360,17 @@ class MainConfigSchema {
*/
public const Script = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultScript( $scriptPath ): string {
return "$scriptPath/index.php";
}
/**
* The URL path to load.php.
*
@ -349,8 +380,17 @@ class MainConfigSchema {
*/
public const LoadScript = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultLoadScript( $scriptPath ): string {
return "$scriptPath/load.php";
}
/**
* The URL path to the REST API
* Defaults to "{$wgScriptPath}/rest.php"
@ -359,8 +399,17 @@ class MainConfigSchema {
*/
public const RestPath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultRestPath( $scriptPath ): string {
return "$scriptPath/rest.php";
}
/**
* The URL path of the skins directory.
*
@ -370,8 +419,17 @@ class MainConfigSchema {
*/
public const StylePath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ResourceBasePath' ] ]
];
/**
* @param mixed $resourceBasePath Value of ResourceBasePath
* @return string
*/
public static function getDefaultStylePath( $resourceBasePath ): string {
return "$resourceBasePath/skins";
}
/**
* The URL path of the skins directory. Should not point to an external domain.
*
@ -381,8 +439,18 @@ class MainConfigSchema {
*/
public const LocalStylePath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultLocalStylePath( $scriptPath ): string {
// Avoid ResourceBasePath here since that may point to a different domain (e.g. CDN)
return "$scriptPath/skins";
}
/**
* The URL path of the extensions directory.
*
@ -392,8 +460,17 @@ class MainConfigSchema {
*/
public const ExtensionAssetsPath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ResourceBasePath' ] ]
];
/**
* @param mixed $resourceBasePath Value of ResourceBasePath
* @return string
*/
public static function getDefaultExtensionAssetsPath( $resourceBasePath ): string {
return "$resourceBasePath/extensions";
}
/**
* Extensions directory in the file system.
*
@ -440,8 +517,21 @@ class MainConfigSchema {
*/
public const ArticlePath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'Script', 'UsePathInfo' ] ]
];
/**
* @param string $script Value of Script
* @param mixed $usePathInfo Value of UsePathInfo
* @return string
*/
public static function getDefaultArticlePath( string $script, $usePathInfo ): string {
if ( $usePathInfo ) {
return "$script/$1";
}
return "$script?title=$1";
}
/**
* The URL path for the images directory.
*
@ -449,8 +539,17 @@ class MainConfigSchema {
*/
public const UploadPath = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultUploadPath( $scriptPath ): string {
return "$scriptPath/images";
}
/**
* The base path for img_auth.php. This is used to interpret the request URL
* for requests to img_auth.php that do not match the base upload path. If
@ -482,8 +581,17 @@ class MainConfigSchema {
*/
public const UploadDirectory = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'BaseDirectory' ] ]
];
/**
* @param mixed $baseDirectory Value of BaseDirectory
* @return string
*/
public static function getDefaultUploadDirectory( $baseDirectory ): string {
return "$baseDirectory/images";
}
/**
* Directory where the cached page will be saved.
*
@ -491,8 +599,17 @@ class MainConfigSchema {
*/
public const FileCacheDirectory = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'UploadDirectory' ] ]
];
/**
* @param mixed $uploadDirectory Value of UploadDirectory
* @return string
*/
public static function getDefaultFileCacheDirectory( $uploadDirectory ): string {
return "$uploadDirectory/cache";
}
/**
* The URL path of the wiki logo. The logo size should be 135x135 pixels.
*
@ -503,8 +620,17 @@ class MainConfigSchema {
*/
public const Logo = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'ResourceBasePath' ] ]
];
/**
* @param mixed $resourceBasePath Value of ResourceBasePath
* @return string
*/
public static function getDefaultLogo( $resourceBasePath ): string {
return "$resourceBasePath/resources/assets/change-your-logo.svg";
}
/**
* Specification for different versions of the wiki logo.
*
@ -785,8 +911,17 @@ class MainConfigSchema {
*/
public const DeletedDirectory = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'UploadDirectory' ] ]
];
/**
* @param mixed $uploadDirectory Value of UploadDirectory
* @return string
*/
public static function getDefaultDeletedDirectory( $uploadDirectory ): string {
return "$uploadDirectory/deleted";
}
/**
* Set this to true if you use img_auth and want the user to see details on why access failed.
*/
@ -951,8 +1086,33 @@ class MainConfigSchema {
public const LocalFileRepo = [
'default' => false,
'type' => 'map|false',
'dynamicDefault' => [ 'use' => [ 'UploadDirectory', 'ScriptPath', 'Favicon', 'UploadBaseUrl',
'UploadPath', 'HashedUploadDirectory', 'ThumbnailScriptPath',
'GenerateThumbnailOnParse', 'DeletedDirectory', 'UpdateCompatibleMetadata' ] ],
];
public static function getDefaultLocalFileRepo(
$uploadDirectory, $scriptPath, $favicon, $uploadBaseUrl, $uploadPath,
$hashedUploadDirectory, $thumbnailScriptPath, $generateThumbnailOnParse, $deletedDirectory,
$updateCompatibleMetadata
) {
return [
'class' => LocalRepo::class,
'name' => 'local',
'directory' => $uploadDirectory,
'scriptDirUrl' => $scriptPath,
'favicon' => $favicon,
'url' => $uploadBaseUrl ? $uploadBaseUrl . $uploadPath : $uploadPath,
'hashLevels' => $hashedUploadDirectory ? 2 : 0,
'thumbScriptUrl' => $thumbnailScriptPath,
'transformVia404' => !$generateThumbnailOnParse,
'deletedDir' => $deletedDirectory,
'deletedHashLevels' => $hashedUploadDirectory ? 3 : 0,
'updateCompatibleMetadata' => $updateCompatibleMetadata,
'reserializeMetadata' => $updateCompatibleMetadata,
];
}
/**
* Enable the use of files from one or more other wikis.
*
@ -1240,9 +1400,16 @@ class MainConfigSchema {
* ```
*/
public const ShowEXIF = [
'default' => null,
'dynamicDefault' => [ 'callback' => [ self::class, 'getDefaultShowEXIF' ] ],
];
/**
* @return bool
*/
public static function getDefaultShowEXIF(): bool {
return function_exists( 'exif_read_data' );
}
/**
* Shortcut for the 'updateCompatibleMetadata' setting of $wgLocalFileRepo.
*/
@ -2811,8 +2978,17 @@ class MainConfigSchema {
*/
public const SharedPrefix = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'DBprefix' ] ]
];
/**
* @param mixed $dbPrefix Value of DBprefix
* @return mixed
*/
public static function getDefaultSharedPrefix( $dbPrefix ) {
return $dbPrefix;
}
/**
* @see $wgSharedDB
* The installer will add 'actor' to this list for all new wikis.
@ -2832,8 +3008,17 @@ class MainConfigSchema {
*/
public const SharedSchema = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'DBmwschema' ] ]
];
/**
* @param mixed $dbMwschema Value of DBmwschema
* @return mixed
*/
public static function getDefaultSharedSchema( $dbMwschema ) {
return $dbMwschema;
}
/**
* Database load balancer
* This is a two-dimensional array, a list of server info structures
@ -2956,8 +3141,13 @@ class MainConfigSchema {
*/
public const DBerrorLogTZ = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'Localtimezone' ] ]
];
public static function getDefaultDBerrorLogTZ( $localtimezone ) {
return $localtimezone;
}
/**
* Other wikis on this site, can be administered from a single developer account.
*
@ -4676,9 +4866,22 @@ class MainConfigSchema {
* ```
*/
public const Localtimezone = [
'default' => null,
'dynamicDefault' => true,
];
public static function getDefaultLocaltimezone(): string {
// This defaults to the `date.timezone` value of the PHP INI option. If this option is not set,
// it falls back to UTC. Prior to PHP 7.0, this fallback produced a warning.
$localtimezone = date_default_timezone_get();
if ( !$localtimezone ) {
// Make doubly sure we have a valid time zone, even if date_default_timezone_get()
// returned garbage.
$localtimezone = 'UTC';
}
return $localtimezone;
}
/**
* Set an offset from UTC in minutes to use for the default timezone setting
* for anonymous users and new user accounts.
@ -4689,9 +4892,14 @@ class MainConfigSchema {
* By default, this will be set to match $wgLocaltimezone.
*/
public const LocalTZoffset = [
'default' => null,
'dynamicDefault' => [ 'use' => [ 'Localtimezone' ] ]
];
public static function getDefaultLocalTZoffset( $localtimezone ): int {
$offset = ( new DateTimeZone( $localtimezone ) )->getOffset( new DateTime() );
return (int)( $offset / 60 );
}
/**
* Map of Unicode characters for which capitalization is overridden in
* Language::ucfirst. The characters should be
@ -5487,8 +5695,17 @@ class MainConfigSchema {
*/
public const ResourceBasePath = [
'default' => null,
'dynamicDefault' => [ 'use' => [ 'ScriptPath' ] ]
];
/**
* @param mixed $scriptPath Value of ScriptPath
* @return string
*/
public static function getDefaultResourceBasePath( $scriptPath ): string {
return $scriptPath;
}
/**
* Override how long a CDN or browser may cache a ResourceLoader HTTP response.
*
@ -5634,8 +5851,17 @@ class MainConfigSchema {
*/
public const MetaNamespace = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'Sitename' ] ]
];
/**
* @param mixed $sitename Value of Sitename
* @return string
*/
public static function getDefaultMetaNamespace( $sitename ): string {
return str_replace( ' ', '_', $sitename );
}
/**
* Name of the project talk namespace.
*
@ -8757,8 +8983,13 @@ class MainConfigSchema {
*/
public const CookieSecure = [
'default' => 'detect',
'dynamicDefault' => [ 'use' => [ 'ForceHTTPS' ] ]
];
public static function getDefaultCookieSecure( $forceHTTPS ): bool {
return $forceHTTPS || ( WebRequest::detectProtocol() === 'https' );
}
/**
* By default, MediaWiki checks if the client supports cookies during the
* login process, so that it can display an informative error message if
@ -8776,8 +9007,20 @@ class MainConfigSchema {
*/
public const CookiePrefix = [
'default' => false,
'dynamicDefault' => [
'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix' ]
],
];
public static function getDefaultCookiePrefix(
$sharedDB, $sharedPrefix, $sharedTables, $dbName, $dbPrefix
): string {
if ( $sharedDB && in_array( 'user', $sharedTables ) ) {
return $sharedDB . ( $sharedPrefix ? "_$sharedPrefix" : '' );
}
return $dbName . ( $dbPrefix ? "_$dbPrefix" : '' );
}
/**
* Set authentication cookies to HttpOnly to prevent access by JavaScript,
* in browsers that support this feature. This can mitigates some classes of
@ -9662,8 +9905,17 @@ class MainConfigSchema {
*/
public const ReadOnlyFile = [
'default' => false,
'dynamicDefault' => [ 'use' => [ 'UploadDirectory' ] ]
];
/**
* @param mixed $uploadDirectory Value of UploadDirectory
* @return string
*/
public static function getDefaultReadOnlyFile( $uploadDirectory ): string {
return "$uploadDirectory/lock_yBgMBwiR";
}
/**
* When you run the web-based upgrade utility, it will tell you what to set
* this to in order to authorize the upgrade process. It will subsequently be

View file

@ -16,7 +16,7 @@ class ArrayConfigBuilder extends ConfigBuilderBase {
return array_key_exists( $key, $this->config );
}
protected function get( string $key ) {
public function get( string $key ) {
return $this->config[$key] ?? null;
}

View file

@ -56,6 +56,14 @@ interface ConfigBuilder {
*/
public function setMultiDefault( array $defaults, array $mergeStrategies ): ConfigBuilder;
/**
* Get the current value for $key.
*
* @param string $key
* @return mixed
*/
public function get( string $key );
/**
* Build the resulting Config object.
*

View file

@ -6,8 +6,6 @@ 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 );
/**

View file

@ -34,6 +34,14 @@ interface ConfigSchema {
*/
public function getDefaults(): array;
/**
* Get all dynamic default declarations.
* @see DynamicDefaultValues.
*
* @return array<string,array>
*/
public function getDynamicDefaults(): array;
/**
* Check if the $key has a default value set in the schema.
*

View file

@ -5,6 +5,7 @@ namespace MediaWiki\Settings\Config;
use Config;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use MediaWiki\Settings\DynamicDefaultValues;
use MediaWiki\Settings\SettingsBuilderException;
use MediaWiki\Settings\Source\JsonSchemaTrait;
use StatusValue;
@ -26,18 +27,21 @@ class ConfigSchemaAggregator implements ConfigSchema {
/** @var array Map of config keys to default values, for optimized access */
private $defaults = [];
/** @var array Map of config keys to dynamic default declaration ararys, for optimized access */
private $dynamicDefaults = [];
/** @var array Map of config keys to types, for optimized access */
private $types = [];
/** @var array Map of config keys to merge strategies, for optimized access */
private $mergeStrategies = [];
/** @var Validator */
private $validator;
/** @var MergeStrategy[]|null */
private $mergeStrategyCache;
/** @var Validator */
private $validator;
/**
* Add a config schema to the aggregator.
*
@ -56,6 +60,7 @@ class ConfigSchemaAggregator implements ConfigSchema {
$this->setListValueInternal( $schema, $this->defaults, $key, 'default', $sourceName );
$this->setListValueInternal( $schema, $this->types, $key, 'type', $sourceName );
$this->setListValueInternal( $schema, $this->mergeStrategies, $key, 'mergeStrategy', $sourceName );
$this->setListValueInternal( $schema, $this->dynamicDefaults, $key, 'dynamicDefault', $sourceName );
if ( isset( $schema['mergeStrategy'] ) ) {
// TODO: mark cache as incomplete rather than throwing it away
@ -91,6 +96,20 @@ class ConfigSchemaAggregator implements ConfigSchema {
}
}
/**
* Add multiple schema definitions.
*
* @see addSchema()
*
* @param array[] $schemas An associative array mapping config variable
* names to their respective schemas.
*/
public function addSchemaMulti( array $schemas ) {
foreach ( $schemas as $key => $sch ) {
$this->addSchema( $key, $sch );
}
}
/**
* Update a map with the given values.
*
@ -155,6 +174,23 @@ class ConfigSchemaAggregator implements ConfigSchema {
$this->mergeStrategyCache = null;
}
/**
* Declare dynamic defaults
*
* @see DynamicDefaultValues.
*
* @param array $dynamicDefaults
* @param string $sourceName
*/
public function addDynamicDefaults( array $dynamicDefaults, string $sourceName = 'unknown' ) {
$this->mergeListInternal(
$dynamicDefaults,
$this->dynamicDefaults,
'dynamicDefaults',
$sourceName
);
}
/**
* Get a list of all defined keys
*
@ -162,7 +198,13 @@ class ConfigSchemaAggregator implements ConfigSchema {
*/
public function getDefinedKeys(): array {
return array_keys(
array_merge( $this->schemas, $this->defaults, $this->types, $this->mergeStrategies )
array_merge(
$this->schemas,
$this->defaults,
$this->types,
$this->mergeStrategies,
$this->dynamicDefaults
)
);
}
@ -188,6 +230,10 @@ class ConfigSchemaAggregator implements ConfigSchema {
$schema['mergeStrategy'] = $this->mergeStrategies[$key];
}
if ( isset( $this->dynamicDefaults[$key] ) ) {
$schema['dynamicDefault'] = $this->dynamicDefaults[$key];
}
return $schema;
}
@ -201,7 +247,8 @@ class ConfigSchemaAggregator implements ConfigSchema {
return isset( $this->schemas[ $key ] )
|| array_key_exists( $key, $this->defaults )
|| isset( $this->types[ $key ] )
|| isset( $this->mergeStrategies[ $key ] );
|| isset( $this->mergeStrategies[ $key ] )
|| isset( $this->dynamicDefaults[ $key ] );
}
/**
@ -231,6 +278,16 @@ class ConfigSchemaAggregator implements ConfigSchema {
return $this->mergeStrategies;
}
/**
* Get all dynamic default declarations.
* @see DynamicDefaultValues.
*
* @return array<string,array>
*/
public function getDynamicDefaults(): array {
return $this->dynamicDefaults;
}
/**
* Check if the $key has a default values set in the schema.
*
@ -243,7 +300,7 @@ class ConfigSchemaAggregator implements ConfigSchema {
/**
* Get default value for the $key.
* For keys that do not define a default, null is assumed.
* If no default value was declared, this returns null.
*
* @param string $key
* @return mixed
@ -262,6 +319,17 @@ class ConfigSchemaAggregator implements ConfigSchema {
return $this->types[$key] ?? null;
}
/**
* Get a dynamic default declaration for $key.
* If no dynamic default is declared, this returns null.
*
* @param string $key
* @return ?array An associative array of the form expected by DynamicDefaultValues.
*/
public function getDynamicDefaultDeclarationFor( string $key ): ?array {
return $this->dynamicDefaults[$key] ?? null;
}
/**
* Get the merge strategy defined for the $key, or null if none defined.
*

View file

@ -26,7 +26,7 @@ class GlobalConfigBuilder extends ConfigBuilderBase {
return array_key_exists( $var, $GLOBALS );
}
protected function get( string $key ) {
public function get( string $key ) {
$var = $this->getVarName( $key );
return $GLOBALS[ $var ] ?? null;
}

View file

@ -0,0 +1,99 @@
<?php
namespace MediaWiki\Settings;
use LogicException;
use MediaWiki\Settings\Config\ConfigBuilder;
use MediaWiki\Settings\Config\ConfigSchema;
class DynamicDefaultValues {
/**
* @var ConfigSchema
*/
private $configSchema;
/**
* @var array
*/
private $declarations;
/**
* @param ConfigSchema $configSchema
*/
public function __construct( ConfigSchema $configSchema ) {
$this->configSchema = $configSchema;
$this->declarations = $this->configSchema->getDynamicDefaults();
}
/**
* Compute dynamic defaults for settings that have them defined.
*
* @param ConfigBuilder $configBuilder
*
* @return void
*/
public function applyDynamicDefaults( ConfigBuilder $configBuilder ): void {
$alreadyComputed = [];
foreach ( $this->declarations as $key => $unused ) {
$this->computeDefaultFor( $key, $configBuilder, $alreadyComputed );
}
}
/**
* Compute dynamic default for a setting, recursively computing any dependencies.
*
* @param string $key Name of setting
* @param ConfigBuilder $configBuilder
* @param array &$alreadyComputed Map whose keys are the names of settings whose dynamic
* defaults have already been computed
* @param array $currentlyComputing Ordered map whose keys are the names of settings whose
* dynamic defaults are currently being computed, for cycle detection.
*/
private function computeDefaultFor(
string $key,
ConfigBuilder $configBuilder,
array &$alreadyComputed = [],
array $currentlyComputing = []
): void {
if ( !isset( $this->declarations[ $key ] ) || isset( $alreadyComputed[ $key ] ) ) {
return;
}
if ( isset( $currentlyComputing[ $key ] ) ) {
throw new LogicException(
'Cyclic dependency when computing dynamic default: ' .
implode( ' -> ', array_keys( $currentlyComputing ) ) . " -> $key"
);
}
if (
$configBuilder->get( $key ) !==
$this->configSchema->getDefaultFor( $key )
) {
// Default was already overridden, nothing more to do
$alreadyComputed[ $key ] = true;
return;
}
$currentlyComputing[ $key ] = true;
$callback = $this->declarations[ $key ]['callback'];
$argNames = $this->declarations[ $key ]['use'] ?? [];
$args = [];
foreach ( $argNames as $argName ) {
$this->computeDefaultFor(
$argName,
$configBuilder,
$alreadyComputed,
$currentlyComputing
);
$args[] = $configBuilder->get( $argName );
}
$configBuilder->set( $key, $callback( ...$args ) );
$alreadyComputed[ $key ] = true;
}
}

View file

@ -373,6 +373,10 @@ class SettingsBuilder {
$settings['config-schema-inverse']['type'] ?? [],
$settings['source-name']
);
$this->configSchema->addDynamicDefaults(
$settings['config-schema-inverse']['dynamicDefault'] ?? [],
$settings['source-name']
);
}
if ( isset( $settings['config-schema'] ) ) {
@ -554,7 +558,7 @@ class SettingsBuilder {
}
/**
* Settings can't be loaded & applied after calling this
* Settings can't be loaded and applied after calling this
* method.
*
* @internal Most likely called only in Setup.php.
@ -566,4 +570,13 @@ class SettingsBuilder {
$this->finished = true;
}
/**
* @internal For use in Setup.php, pending a better solution.
* @return ConfigBuilder
*/
public function getConfigBuilder(): ConfigBuilder {
$this->apply();
return $this->configSink;
}
}

View file

@ -2,6 +2,7 @@
namespace MediaWiki\Settings\Source;
use Closure;
use MediaWiki\Settings\SettingsBuilderException;
use ReflectionClass;
use ReflectionException;
@ -10,6 +11,27 @@ use ReflectionException;
* Constructs a settings array based on a PHP class by inspecting class
* members to construct a schema.
*
* The value of each constant must be an array structured like a JSON Schema.
* For convenience, type declarations support PHPDoc style types in addition to
* JSON types. To avoid confusion, use 'list' for sequential arrays and 'map'
* for associative arrays.
*
* Dynamic default values can be declared using the 'dynamicDefault' key.
* The structure of the dynamic default declaration is an array with two keys:
* - 'callback': this is a PHP callable string or array, closures are not supported.
* - 'use': A list of other config variables that the dynamic default depends on.
* The values of these variables will be passed to the callback as parameters.
*
* The following shorthands can be used with dynamic default declarations:
* - if the value for 'use' is empty, it can be omitted.
* - if 'callback' is omitted, it is assumed to be a static method "getDefault$name" on
* the same class where $name is the name of the variable.
* - if the dynamic default declaration is not an array but a string, that
* string is taken to be the callback, with no parameters.
* - if the dynamic default declaration is the boolean value true,
* the callback is assumed to be a static method "getDefault$name" on
* the same class where $name is the name of the variable.
*
* @since 1.39
*/
class ReflectionSchemaSource implements SettingsSource {
@ -63,6 +85,15 @@ class ReflectionSchemaSource implements SettingsSource {
}
}
if ( isset( $schema['dynamicDefault'] ) ) {
$schema['dynamicDefault'] =
$this->normalizeDynamicDefault( $name, $schema['dynamicDefault'] );
}
if ( !array_key_exists( 'default', $schema ) ) {
$schema['default'] = null;
}
$schema = self::normalizeJsonSchema( $schema );
$schemas[ $name ] = $schema;
@ -97,4 +128,38 @@ class ReflectionSchemaSource implements SettingsSource {
return $doc;
}
private function normalizeDynamicDefault( string $name, $spec ) {
if ( $spec === true ) {
$spec = [ 'callback' => [ $this->class, "getDefault{$name}" ] ];
}
if ( is_string( $spec ) ) {
$spec = [ 'callback' => $spec ];
}
if ( !isset( $spec['callback'] ) ) {
$spec['callback'] = [ $this->class, "getDefault{$name}" ];
}
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset per fallback above.
if ( $spec['callback'] instanceof Closure ) {
throw new SettingsBuilderException(
"dynamicDefaults callback for $name must be JSON serializable. " .
"Closures are not supported."
);
}
if ( !is_callable( $spec['callback'] ) ) {
$pretty = var_export( $spec['callback'], true );
$pretty = preg_replace( '/\s+/', ' ', $pretty );
throw new SettingsBuilderException(
"dynamicDefaults callback for $name is not callable: " .
$pretty
);
}
return $spec;
}
}

View file

@ -58,6 +58,7 @@ use MediaWiki\MainConfigSchema;
use MediaWiki\MediaWikiServices;
use MediaWiki\Settings\Config\GlobalConfigBuilder;
use MediaWiki\Settings\Config\PhpIniSink;
use MediaWiki\Settings\DynamicDefaultValues;
use MediaWiki\Settings\LocalSettingsLoader;
use MediaWiki\Settings\SettingsBuilder;
use MediaWiki\Settings\Source\PhpSettingsSource;
@ -244,6 +245,18 @@ if ( $wgSettings->getConfig()->get( MainConfigNames::WikiFarmSettingsDirectory )
unset( $wikiFarmSettingsLoader );
}
// Apply dynamic defaults declared in config schema callbacks.
$dynamicDefaults = new DynamicDefaultValues( $wgSettings->getConfigSchema() );
$dynamicDefaults->applyDynamicDefaults( $wgSettings->getConfigBuilder() );
// Make updated config available in global scope.
$wgSettings->apply();
// Apply dynamic defaults implemented in SetupDynamicConfig.php.
// Ideally, all logic in SetupDynamicConfig would be converted to
// callbacks in the config schema.
require __DIR__ . '/SetupDynamicConfig.php';
// All settings should be loaded now.
$wgSettings->finalize();
if ( $wgBaseDirectory !== MW_INSTALL_PATH ) {
@ -274,11 +287,6 @@ ExtensionRegistry::getInstance()->finish();
// with Command::environment().
putenv( "LC_ALL=" . setlocale( LC_ALL, 'C.UTF-8', 'C' ) );
/**
* Expand dynamic defaults and shortcuts
*/
require __DIR__ . '/SetupDynamicConfig.php';
MWDebug::setup();
// Enable the global service locator.

View file

@ -5,80 +5,12 @@
*/
use Wikimedia\AtEase\AtEase;
if ( $wgScript === false ) {
$wgScript = "$wgScriptPath/index.php";
}
if ( $wgLoadScript === false ) {
$wgLoadScript = "$wgScriptPath/load.php";
}
if ( $wgRestPath === false ) {
$wgRestPath = "$wgScriptPath/rest.php";
}
if ( $wgUsePathInfo === null ) {
// These often break when PHP is set up in CGI mode.
// PATH_INFO *may* be correct if cgi.fix_pathinfo is set, but then again it may not;
// lighttpd converts incoming path data to lowercase on systems
// with case-insensitive filesystems, and there have been reports of
// problems on Apache as well.
$wgUsePathInfo = ( strpos( PHP_SAPI, 'cgi' ) === false ) &&
( strpos( PHP_SAPI, 'apache2filter' ) === false ) &&
( strpos( PHP_SAPI, 'isapi' ) === false );
}
if ( $wgArticlePath === false ) {
if ( $wgUsePathInfo ) {
$wgArticlePath = "$wgScript/$1";
} else {
$wgArticlePath = "$wgScript?title=$1";
}
}
if ( $wgResourceBasePath === null ) {
$wgResourceBasePath = $wgScriptPath;
}
if ( $wgStylePath === false ) {
$wgStylePath = "$wgResourceBasePath/skins";
}
if ( $wgLocalStylePath === false ) {
// Avoid wgResourceBasePath here since that may point to a different domain (e.g. CDN)
$wgLocalStylePath = "$wgScriptPath/skins";
}
if ( $wgExtensionAssetsPath === false ) {
$wgExtensionAssetsPath = "$wgResourceBasePath/extensions";
}
// For backwards compatibility, the value of wgLogos is copied to wgLogo.
// This is because some extensions/skins may be using $config->get('Logo')
// to access the value.
if ( $wgLogos !== false && isset( $wgLogos['1x'] ) ) {
$wgLogo = $wgLogos['1x'];
}
if ( $wgLogo === false ) {
$wgLogo = "$wgResourceBasePath/resources/assets/change-your-logo.svg";
}
if ( $wgUploadPath === false ) {
$wgUploadPath = "$wgScriptPath/images";
}
if ( $wgUploadDirectory === false ) {
$wgUploadDirectory = "$IP/images";
}
if ( $wgReadOnlyFile === false ) {
$wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
}
if ( $wgFileCacheDirectory === false ) {
$wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
}
if ( $wgDeletedDirectory === false ) {
$wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
}
if ( $wgSharedPrefix === false ) {
$wgSharedPrefix = $wgDBprefix;
}
if ( $wgSharedSchema === false ) {
$wgSharedSchema = $wgDBmwschema;
}
if ( $wgMetaNamespace === false ) {
$wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
}
if ( $wgMainWANCache === false ) {
// Create a WAN cache from $wgMainCacheType
@ -169,13 +101,6 @@ $wgLockManagers[] = [
'class' => NullLockManager::class,
];
/**
* Determine whether EXIF info can be shown
*/
if ( $wgShowEXIF === null ) {
$wgShowEXIF = function_exists( 'exif_read_data' );
}
/**
* Default parameters for the "<gallery>" tag.
* @see docs/Configuration.md for description of the fields.
@ -190,28 +115,7 @@ $wgGalleryOptions += [
'mode' => 'traditional',
];
/**
* Shortcuts for $wgLocalFileRepo
*/
if ( !$wgLocalFileRepo ) {
$wgLocalFileRepo = [
'class' => LocalRepo::class,
'name' => 'local',
'directory' => $wgUploadDirectory,
'scriptDirUrl' => $wgScriptPath,
'favicon' => $wgFavicon,
'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
'thumbScriptUrl' => $wgThumbnailScriptPath,
'transformVia404' => !$wgGenerateThumbnailOnParse,
'deletedDir' => $wgDeletedDirectory,
'deletedHashLevels' => $wgHashedUploadDirectory ? 3 : 0,
'updateCompatibleMetadata' => $wgUpdateCompatibleMetadata,
'reserializeMetadata' => $wgUpdateCompatibleMetadata,
];
}
if ( !isset( $wgLocalFileRepo['backend'] ) ) {
if ( isset( $wgLocalFileRepo['name'] ) && !isset( $wgLocalFileRepo['backend'] ) ) {
// Create a default FileBackend name.
// FileBackendGroup will register a default, if absent from $wgFileBackends.
$wgLocalFileRepo['backend'] = $wgLocalFileRepo['name'] . '-backend';
@ -291,17 +195,6 @@ $wgDefaultUserOptions['watchlistdays'] = min(
);
unset( $rcMaxAgeDays );
if ( !$wgCookiePrefix ) {
if ( $wgSharedDB && $wgSharedPrefix && in_array( 'user', $wgSharedTables ) ) {
$wgCookiePrefix = $wgSharedDB . '_' . $wgSharedPrefix;
} elseif ( $wgSharedDB && in_array( 'user', $wgSharedTables ) ) {
$wgCookiePrefix = $wgSharedDB;
} elseif ( $wgDBprefix ) {
$wgCookiePrefix = $wgDBname . '_' . $wgDBprefix;
} else {
$wgCookiePrefix = $wgDBname;
}
}
$wgCookiePrefix = strtr( $wgCookiePrefix, '=,; +."\'\\[', '__________' );
if ( $wgEnableEmail ) {
@ -326,27 +219,10 @@ if ( $wgEnableEmail ) {
$wgUsersNotifiedOnAllChanges = [];
}
if ( !$wgLocaltimezone ) {
// This defaults to the `date.timezone` value of the PHP INI option. If this option is not set,
// it falls back to UTC. Prior to PHP 7.0, this fallback produced a warning.
$wgLocaltimezone = date_default_timezone_get();
}
if ( !$wgLocaltimezone ) {
// Make doubly sure we have a valid time zone, even if date_default_timezone_get()
// returned garbage.
$wgLocaltimezone = 'UTC';
}
date_default_timezone_set( $wgLocaltimezone );
if ( $wgLocalTZoffset === null ) {
$wgLocalTZoffset = (int)date( 'Z' ) / 60;
}
// The part after the System| is ignored, but rest of MW fills it out as the local offset.
$wgDefaultUserOptions['timecorrection'] = "System|$wgLocalTZoffset";
if ( !$wgDBerrorLogTZ ) {
$wgDBerrorLogTZ = $wgLocaltimezone;
}
/**
* Definitions of the NS_ constants are in Defines.php
* @internal
@ -439,10 +315,6 @@ if ( $wgPageLanguageUseDB ) {
$wgLogActionsHandlers['pagelang/pagelang'] = PageLangLogFormatter::class;
}
if ( $wgCookieSecure === 'detect' ) {
$wgCookieSecure = $wgForceHTTPS || ( WebRequest::detectProtocol() === 'https' );
}
// Backwards compatibility with old password limits
if ( $wgMinimalPasswordLength !== false ) {
$wgPasswordPolicy['policies']['default']['MinimalPasswordLength'] = $wgMinimalPasswordLength;

View file

@ -2880,6 +2880,238 @@ return [
'Hooks' => 'array_merge_recursive',
'VirtualRestConfig' => 'array_plus_2d',
],
'dynamicDefault' => [
'UsePathInfo' => [
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultUsePathInfo',
],
],
'Script' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultScript',
],
],
'LoadScript' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLoadScript',
],
],
'RestPath' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultRestPath',
],
],
'StylePath' => [
'use' => [
0 => 'ResourceBasePath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultStylePath',
],
],
'LocalStylePath' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLocalStylePath',
],
],
'ExtensionAssetsPath' => [
'use' => [
0 => 'ResourceBasePath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultExtensionAssetsPath',
],
],
'ArticlePath' => [
'use' => [
0 => 'Script',
1 => 'UsePathInfo',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultArticlePath',
],
],
'UploadPath' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultUploadPath',
],
],
'UploadDirectory' => [
'use' => [
0 => 'BaseDirectory',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultUploadDirectory',
],
],
'FileCacheDirectory' => [
'use' => [
0 => 'UploadDirectory',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultFileCacheDirectory',
],
],
'Logo' => [
'use' => [
0 => 'ResourceBasePath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLogo',
],
],
'DeletedDirectory' => [
'use' => [
0 => 'UploadDirectory',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultDeletedDirectory',
],
],
'LocalFileRepo' => [
'use' => [
0 => 'UploadDirectory',
1 => 'ScriptPath',
2 => 'Favicon',
3 => 'UploadBaseUrl',
4 => 'UploadPath',
5 => 'HashedUploadDirectory',
6 => 'ThumbnailScriptPath',
7 => 'GenerateThumbnailOnParse',
8 => 'DeletedDirectory',
9 => 'UpdateCompatibleMetadata',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLocalFileRepo',
],
],
'ShowEXIF' => [
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultShowEXIF',
],
],
'SharedPrefix' => [
'use' => [
0 => 'DBprefix',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultSharedPrefix',
],
],
'SharedSchema' => [
'use' => [
0 => 'DBmwschema',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultSharedSchema',
],
],
'DBerrorLogTZ' => [
'use' => [
0 => 'Localtimezone',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultDBerrorLogTZ',
],
],
'Localtimezone' => [
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLocaltimezone',
],
],
'LocalTZoffset' => [
'use' => [
0 => 'Localtimezone',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultLocalTZoffset',
],
],
'ResourceBasePath' => [
'use' => [
0 => 'ScriptPath',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultResourceBasePath',
],
],
'MetaNamespace' => [
'use' => [
0 => 'Sitename',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultMetaNamespace',
],
],
'CookieSecure' => [
'use' => [
0 => 'ForceHTTPS',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultCookieSecure',
],
],
'CookiePrefix' => [
'use' => [
0 => 'SharedDB',
1 => 'SharedPrefix',
2 => 'SharedTables',
3 => 'DBname',
4 => 'DBprefix',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultCookiePrefix',
],
],
'ReadOnlyFile' => [
'use' => [
0 => 'UploadDirectory',
],
'callback' => [
0 => 'MediaWiki\\MainConfigSchema',
1 => 'getDefaultReadOnlyFile',
],
],
],
],
'config-schema' => [
'LogoHD' => [

View file

@ -124,6 +124,9 @@ class BenchmarkSettings extends Benchmarker {
'function' => static function () {
$IP = MW_INSTALL_PATH;
include MW_INSTALL_PATH . '/includes/DefaultSettings.php';
// phpcs:ignore MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgLocaltimezone
$wgLocaltimezone = 'utc';
include MW_INSTALL_PATH . '/includes/SetupDynamicConfig.php';
}
];

View file

@ -199,12 +199,14 @@ class GenerateConfigSchema extends Maintenance {
'default' => $aggregator->getDefaults(),
'type' => $aggregator->getTypes(),
'mergeStrategy' => $aggregator->getMergeStrategyNames(),
'dynamicDefault' => $aggregator->getDynamicDefaults(),
];
$keyMask = array_flip( [
'default',
'type',
'mergeStrategy',
'dynamicDefault',
'description',
'properties'
] );
@ -293,6 +295,9 @@ class GenerateConfigSchema extends Maintenance {
}
}
// Dynamic defaults are not relevant to yaml consumers
unset( $sch['dynamicDefault'] );
$yamlFlags = Yaml::DUMP_OBJECT_AS_MAP
| Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
| Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;

View file

@ -23,6 +23,17 @@ class ConfigSchemaAggregatorTest extends TestCase {
$this->assertSame( [ 'type' => 'number', ], $aggregator->getSchemaFor( 'bar' ) );
}
public function testAddSchemaMulti() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemaMulti( [
'foo' => [ 'type' => 'string', ],
'bar' => [ 'type' => 'number', ],
] );
$this->assertTrue( $aggregator->hasSchemaFor( 'foo' ) );
$this->assertFalse( $aggregator->hasSchemaFor( 'xyzzy' ) );
$this->assertSame( [ 'type' => 'number', ], $aggregator->getSchemaFor( 'bar' ) );
}
public function testCombineSchema() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addTypes( [ 'foo' => 'string', ] );
@ -60,6 +71,23 @@ class ConfigSchemaAggregatorTest extends TestCase {
], $aggregator->getDefaults() );
}
public function testSetAndGetDynamicDefaults() {
$dyn1 = [ 'callback' => 'function1' ];
$dyn2 = [ 'callback' => 'function2', 'use' => [ 'Stuff' ] ];
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchema( 'no_dynamicDefault', [ 'default' => 'xyz', ] );
$aggregator->addSchema( 'with_dynamicDefault', [ 'dynamicDefault' => $dyn1, 'default' => 'bla', ] );
$aggregator->addDynamicDefaults( [ 'another_with_dynamicDefault' => $dyn2 ] );
$this->assertNull( $aggregator->getDynamicDefaultDeclarationFor( 'no_dynamicDefault' ) );
$this->assertSame( $dyn1, $aggregator->getDynamicDefaultDeclarationFor( 'with_dynamicDefault' ) );
$this->assertSame( [ 'dynamicDefault' => $dyn2 ], $aggregator->getSchemaFor( 'another_with_dynamicDefault' ) );
$this->assertEquals( [
'with_dynamicDefault' => $dyn1,
'another_with_dynamicDefault' => $dyn2,
], $aggregator->getDynamicDefaults() );
}
public function testGetDefaults() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchema( 'no_default', [ 'type' => 'string', ] );

View file

@ -0,0 +1,224 @@
<?php
namespace MediaWiki\Tests\Unit\Settings;
use LogicException;
use MediaWiki\Settings\Config\ArrayConfigBuilder;
use MediaWiki\Settings\Config\ConfigSchemaAggregator;
use MediaWiki\Settings\DynamicDefaultValues;
use PHPUnit\Framework\TestCase;
/**
* @covers \MediaWiki\Settings\DynamicDefauoltValuesTest
*/
class DynamicDefaultValuesTest extends TestCase {
private const DEFAULT_VALUE = 'this is the default value';
public static function getDefaultTestValue() {
return self::DEFAULT_VALUE;
}
public static function identity( $v ) {
return $v;
}
public static function provideDynamicDefaults() {
yield 'global function callback' => [
[
'Static' => [ 'default' => 'abcd' ],
'Dynamic' => [
'dynamicDefault' => [
'callback' => 'strtoupper',
'use' => [ 'Static' ]
]
],
],
[
'Static' => 'xyz',
],
[
'Static' => 'xyz',
'Dynamic' => 'XYZ',
]
];
yield 'static method callback (string)' => [
[
'Dynamic' => [
'dynamicDefault' => [ 'callback' => self::class . '::getDefaultTestValue' ]
],
],
[],
[
'Dynamic' => self::DEFAULT_VALUE,
]
];
yield 'static method callback (array)' => [
[
'Dynamic' => [
'dynamicDefault' => [ 'callback' => [ self::class, 'getDefaultTestValue' ] ]
],
],
[],
[
'Dynamic' => self::DEFAULT_VALUE,
]
];
yield 'interrupted chain' => [
[
'A' => [ 'default' => 'a' ],
'B' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'A' ]
]
],
'C' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'B' ]
]
],
'D' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'C' ]
]
],
'E' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'D' ]
]
],
],
[ 'D' => 'x' ],
[
'A' => 'a',
'B' => 'a',
'C' => 'a',
'D' => 'x',
'E' => 'x',
]
];
yield 'do not trigger on null if the default is false' => [
[
'Dynamic' => [
'default' => false,
'dynamicDefault' => [ 'callback' => self::class . '::getDefaultTestValue' ]
],
],
[ 'Dynamic' => null ],
[ 'Dynamic' => null ]
];
yield 'trigger on 1 if that is the default' => [
[
'Dynamic' => [
'default' => 1,
'dynamicDefault' => [ 'callback' => self::class . '::getDefaultTestValue' ]
],
],
[ 'Dynamic' => 1 ],
[ 'Dynamic' => self::DEFAULT_VALUE ]
];
}
/**
* @dataProvider provideDynamicDefaults
*
* @param array $schema
* @param array $config
* @param array $expected
*/
public function testApply( array $schema, array $config, array $expected ) {
$schemaAggregator = new ConfigSchemaAggregator();
$schemaAggregator->addSchemaMulti( $schema );
$configBuilder = new ArrayConfigBuilder();
$configBuilder->setMulti( $schemaAggregator->getDefaults() );
$configBuilder->setMulti( $config );
$dynamicDefaults = new DynamicDefaultValues( $schemaAggregator );
$dynamicDefaults->applyDynamicDefaults( $configBuilder );
$result = $configBuilder->build();
foreach ( $expected as $key => $value ) {
$this->assertSame( $value, $result->get( $key ), $key );
}
}
public static function provideBadDynamicDefaults() {
yield 'loop' => [
[
'X' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'X' ]
]
],
],
];
yield 'cycle' => [
[
'A' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'E' ]
]
],
'B' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'A' ]
]
],
'C' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'B' ]
]
],
'D' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'C' ]
]
],
'E' => [
'dynamicDefault' => [
'callback' => [ self::class, 'identity' ],
'use' => [ 'D' ]
]
],
],
];
}
/**
* @dataProvider provideBadDynamicDefaults
*
* @param array $schema
* @param array $config
*/
public function testApplyFails( array $schema, array $config = [] ) {
$schemaAggregator = new ConfigSchemaAggregator();
$schemaAggregator->addSchemaMulti( $schema );
$configBuilder = new ArrayConfigBuilder();
$configBuilder->setMulti( $schemaAggregator->getDefaults() );
$configBuilder->setMulti( $config );
$dynamicDefaults = new DynamicDefaultValues( $schemaAggregator );
$this->expectException( LogicException::class );
$dynamicDefaults->applyDynamicDefaults( $configBuilder );
}
}

View file

@ -569,4 +569,44 @@ class SettingsBuilderTest extends TestCase {
$this->assertTrue( $configSchema->hasSchemaFor( 'MySetting' ) );
$this->assertSame( 'bla', $configSchema->getDefaultFor( 'MySetting' ) );
}
/**
* Make sure that the 'config-schema' and 'config-schema-inverse' keys
* are fully processed and combined appropriately.
*/
public function testSchemaLoading() {
$settings = $this->newSettingsBuilder();
$settings->loadFile( 'fixtures/default-schema.json' );
$schema = $settings->getConfigSchema();
$this->assertSame(
'/DEFAULT/',
$schema->getDefaultFor( 'StyleDirectory' )
);
$this->assertSame(
[ 'callback' => [ 'MainConfigSchema', 'getDefaultUsePathInfo' ] ],
$schema->getDynamicDefaultDeclarationFor( 'UsePathInfo' )
);
$this->assertSame(
'replace',
$schema->getMergeStrategyFor( 'LBFactoryConf' )->getName()
);
$this->assertSame(
'/DEFAULT/',
$schema->getDefaultFor( 'ExtensionDirectory' )
);
$this->assertSame(
[ 'use' => [ 'ScriptPath' ], 'callback' => [ 'MainConfigSchema', 'getDefaultRestPath' ] ],
$schema->getDynamicDefaultDeclarationFor( 'RestPath' )
);
$this->assertSame(
[ 'use' => [ 'ScriptPath' ], 'callback' => [ 'MainConfigSchema', 'getDefaultRestPath' ] ],
$schema->getDynamicDefaultDeclarationFor( 'RestPath' )
);
$this->assertSame(
'replace',
$schema->getMergeStrategyFor( 'TiffThumbnailType' )->getName()
);
}
}

View file

@ -23,7 +23,7 @@ class ReflectionSchemaSourceTest extends TestCase {
];
public const TEST_MAP_TYPE = [
'type' => '?dict',
'type' => '?map',
'additionalProperties' => [
'type' => 'string|list',
'items' => [
@ -32,6 +32,43 @@ class ReflectionSchemaSourceTest extends TestCase {
]
];
public const TEST_DYNAMIC_DEFAULT_AUTO = [
'type' => 'string',
'dynamicDefault' => true
];
public const TEST_DYNAMIC_DEFAULT_STRING = [
'type' => 'string',
'dynamicDefault' => 'get_include_path'
];
public const TEST_DYNAMIC_DEFAULT_IMPLIED = [
'type' => 'string',
'dynamicDefault' => [
'use' => [ 'A' ],
]
];
public const TEST_DYNAMIC_DEFAULT = [
'type' => 'string',
'dynamicDefault' => [
'use' => [ 'A' ],
'callback' => [ self::class, 'getTestDefault' ]
]
];
public static function getDefaultTEST_DYNAMIC_DEFAULT_AUTO() {
// noop
}
public static function getDefaultTEST_DYNAMIC_DEFAULT_IMPLIED() {
// noop
}
public static function getTestDefault() {
// noop
}
public function testLoad() {
$source = new ReflectionSchemaSource( self::class );
$settings = $source->load();
@ -43,10 +80,12 @@ class ReflectionSchemaSourceTest extends TestCase {
$this->assertArrayNotHasKey( 'NOT_A_SCHEMA', $schemas );
$this->assertArrayHasKey( 'TEST_INTEGER', $schemas );
$this->assertArrayHasKey( 'default', $schemas['TEST_INTEGER'] );
$this->assertArrayHasKey( 'type', $schemas['TEST_INTEGER'] );
$this->assertSame( 'integer', $schemas['TEST_INTEGER']['type'] );
$this->assertArrayHasKey( 'TEST_MAP_TYPE', $schemas );
$this->assertArrayHasKey( 'default', $schemas['TEST_MAP_TYPE'] );
$this->assertArrayHasKey( 'additionalProperties', $schemas['TEST_MAP_TYPE'] );
$this->assertSame(
[ 'object', 'null' ],
@ -62,6 +101,42 @@ class ReflectionSchemaSourceTest extends TestCase {
);
}
public function testDynamicDefault() {
$source = new ReflectionSchemaSource( self::class );
$settings = $source->load();
$this->assertArrayHasKey( 'config-schema', $settings );
$schemas = $settings['config-schema'];
$this->assertSame(
[ self::class, 'getDefaultTEST_DYNAMIC_DEFAULT_AUTO' ],
$schemas['TEST_DYNAMIC_DEFAULT_AUTO']['dynamicDefault']['callback']
);
$this->assertSame(
'get_include_path',
$schemas['TEST_DYNAMIC_DEFAULT_STRING']['dynamicDefault']['callback']
);
$this->assertSame(
[ self::class, 'getDefaultTEST_DYNAMIC_DEFAULT_IMPLIED' ],
$schemas['TEST_DYNAMIC_DEFAULT_IMPLIED']['dynamicDefault']['callback']
);
$this->assertSame(
[ 'A' ],
$schemas['TEST_DYNAMIC_DEFAULT_IMPLIED']['dynamicDefault']['use']
);
$this->assertSame(
[ self::class, 'getTestDefault' ],
$schemas['TEST_DYNAMIC_DEFAULT']['dynamicDefault']['callback']
);
$this->assertSame(
[ 'A' ],
$schemas['TEST_DYNAMIC_DEFAULT']['dynamicDefault']['use']
);
}
public function testLoadInvalidClass() {
$this->expectException( SettingsBuilderException::class );

View file

@ -6,19 +6,41 @@
"HttpsPort": {
"default": 443
},
"ExtensionDirectory": {
"default": "/DEFAULT/"
},
"StyleDirectory": {
"default": "/DEFAULT/"
},
"ForeignUploadTargets": {
"default": [ "local" ],
"type": "array"
"UsePathInfo": {
"default": null,
"dynamicDefault": {
"callback": [ "MainConfigSchema", "getDefaultUsePathInfo" ]
}
},
"ExtraLanguageCodes": {
"default": { "simple": "en" },
"type": "object"
"LBFactoryConf": {
"default": { "class": "Wikimedia\\Rdbms\\LBFactorySimple" },
"type": "object",
"mergeStrategy": "replace"
}
},
"config-schema-inverse": {
"default": {
"ExtensionDirectory": "/DEFAULT/",
"ForeignUploadTargets": [ "local" ],
"ExtraLanguageCodes": { "simple": "en" },
"TiffThumbnailType": [],
"RestPath": false
},
"dynamicDefault": {
"RestPath": {
"use": [ "ScriptPath" ],
"callback": [ "MainConfigSchema", "getDefaultRestPath" ]
}
},
"mergeStrategy": {
"TiffThumbnailType": "replace"
},
"type": {
"ExtraLanguageCodes": "object",
"ForeignUploadTargets": "array"
}
}
}

File diff suppressed because it is too large Load diff