aboutsummaryrefslogtreecommitdiffstats
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/AutoLoader.php233
-rw-r--r--includes/HtmlHelper.php77
-rw-r--r--includes/OutputPage.php7
-rw-r--r--includes/Revision/RenderedRevision.php1
-rw-r--r--includes/Settings/Config/ArrayConfigBuilder.php43
-rw-r--r--includes/Settings/Config/ConfigBuilder.php17
-rw-r--r--includes/Settings/Config/ConfigBuilderBase.php16
-rw-r--r--includes/Settings/Config/ConfigSchemaAggregator.php72
-rw-r--r--includes/Settings/Config/GlobalConfigBuilder.php20
-rw-r--r--includes/Settings/Config/MergeStrategy.php1
-rw-r--r--includes/Settings/SettingsBuilder.php20
-rw-r--r--includes/Title.php4
-rw-r--r--includes/TrackingCategories.php2
-rw-r--r--includes/api/ApiQueryLinks.php30
-rw-r--r--includes/api/i18n/ko.json22
-rw-r--r--includes/api/i18n/nb.json1
-rw-r--r--includes/installer/DatabaseUpdater.php7
-rw-r--r--includes/installer/Installer.php5
-rw-r--r--includes/installer/MysqlUpdater.php1
-rw-r--r--includes/installer/SqliteUpdater.php1
-rw-r--r--includes/installer/i18n/it.json2
-rw-r--r--includes/installer/i18n/lij.json13
-rw-r--r--includes/languages/data/Names.php1
-rw-r--r--includes/registration/ExtensionRegistry.php39
-rw-r--r--includes/skins/Skin.php3
-rw-r--r--includes/skins/components/ComponentRegistryContext.php42
-rw-r--r--includes/skins/components/SkinComponentRegistry.php21
-rw-r--r--includes/skins/components/SkinComponentRegistryContext.php95
28 files changed, 655 insertions, 141 deletions
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index dd4e556875f2..6099aa2a97d7 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -29,13 +29,119 @@
require_once __DIR__ . '/../autoload.php';
class AutoLoader {
- protected static $autoloadLocalClassesLower = null;
/**
- * @internal Only public for ExtensionRegistry
+ * A mapping of namespace => file path for MediaWiki core.
+ * The namespaces should follow the PSR-4 standard for autoloading
+ *
+ * @see <https://www.php-fig.org/psr/psr-4/>
+ * @internal Only public for usage in AutoloadGenerator
+ */
+ public const CORE_NAMESPACES = [
+ 'MediaWiki\\' => __DIR__ . '/',
+ 'MediaWiki\\Actions\\' => __DIR__ . '/actions/',
+ 'MediaWiki\\Api\\' => __DIR__ . '/api/',
+ 'MediaWiki\\Auth\\' => __DIR__ . '/auth/',
+ 'MediaWiki\\Block\\' => __DIR__ . '/block/',
+ 'MediaWiki\\Cache\\' => __DIR__ . '/cache/',
+ 'MediaWiki\\ChangeTags\\' => __DIR__ . '/changetags/',
+ 'MediaWiki\\Config\\' => __DIR__ . '/config/',
+ 'MediaWiki\\Content\\' => __DIR__ . '/content/',
+ 'MediaWiki\\DB\\' => __DIR__ . '/db/',
+ 'MediaWiki\\Deferred\\LinksUpdate\\' => __DIR__ . '/deferred/LinksUpdate/',
+ 'MediaWiki\\Diff\\' => __DIR__ . '/diff/',
+ 'MediaWiki\\Edit\\' => __DIR__ . '/edit/',
+ 'MediaWiki\\EditPage\\' => __DIR__ . '/editpage/',
+ 'MediaWiki\\FileBackend\\LockManager\\' => __DIR__ . '/filebackend/lockmanager/',
+ 'MediaWiki\\JobQueue\\' => __DIR__ . '/jobqueue/',
+ 'MediaWiki\\Json\\' => __DIR__ . '/json/',
+ 'MediaWiki\\Http\\' => __DIR__ . '/http/',
+ 'MediaWiki\\Installer\\' => __DIR__ . '/installer/',
+ 'MediaWiki\\Interwiki\\' => __DIR__ . '/interwiki/',
+ 'MediaWiki\\Languages\\Data\\' => __DIR__ . '/languages/data/',
+ 'MediaWiki\\Linker\\' => __DIR__ . '/linker/',
+ 'MediaWiki\\Logger\\' => __DIR__ . '/debug/logger/',
+ 'MediaWiki\\Logger\Monolog\\' => __DIR__ . '/debug/logger/monolog/',
+ 'MediaWiki\\Mail\\' => __DIR__ . '/mail/',
+ 'MediaWiki\\Page\\' => __DIR__ . '/page/',
+ 'MediaWiki\\Parser\\' => __DIR__ . '/parser/',
+ 'MediaWiki\\Preferences\\' => __DIR__ . '/preferences/',
+ 'MediaWiki\\ResourceLoader\\' => __DIR__ . '/resourceloader/',
+ 'MediaWiki\\Search\\' => __DIR__ . '/search/',
+ 'MediaWiki\\Search\\SearchWidgets\\' => __DIR__ . '/search/searchwidgets/',
+ 'MediaWiki\\Session\\' => __DIR__ . '/session/',
+ 'MediaWiki\\Shell\\' => __DIR__ . '/shell/',
+ 'MediaWiki\\Site\\' => __DIR__ . '/site/',
+ 'MediaWiki\\Sparql\\' => __DIR__ . '/sparql/',
+ 'MediaWiki\\SpecialPage\\' => __DIR__ . '/specialpage/',
+ 'MediaWiki\\Tidy\\' => __DIR__ . '/tidy/',
+ 'MediaWiki\\User\\' => __DIR__ . '/user/',
+ 'MediaWiki\\Utils\\' => __DIR__ . '/utils/',
+ 'MediaWiki\\Widget\\' => __DIR__ . '/widget/',
+ 'Wikimedia\\' => __DIR__ . '/libs/',
+ 'Wikimedia\\Http\\' => __DIR__ . '/libs/http/',
+ 'Wikimedia\\Rdbms\\Platform\\' => __DIR__ . '/libs/rdbms/platform/',
+ 'Wikimedia\\UUID\\' => __DIR__ . '/libs/uuid/',
+ ];
+
+ /**
+ * Cache for lower-case version of the content of $wgAutoloadLocalClasses.
+ * @var array|null
+ */
+ private static $autoloadLocalClassesLower = null;
+
+ /**
* @var string[] Namespace (ends with \) => Path (ends with /)
+ * @internal Will become private in 1.40.
+ */
+ public static $psr4Namespaces = self::CORE_NAMESPACES;
+
+ /**
+ * @var string[] Class => File
+ */
+ private static $classFiles = [];
+
+ /**
+ * Register a directory to load the classes of a given namespace from,
+ * per PSR4.
+ *
+ * @see <https://www.php-fig.org/psr/psr-4/>
+ * @since 1.39
+ * @param string[] $dirs a map of namespace (ends with \) to path (ends with /)
+ */
+ public static function registerNamespaces( array $dirs ): void {
+ self::$psr4Namespaces += $dirs;
+ }
+
+ /**
+ * Register a file to load the given class from.
+ * @since 1.39
+ *
+ * @param string[] $files a map of qualified class names to file names
+ */
+ public static function registerClasses( array $files ): void {
+ self::$classFiles += $files;
+ }
+
+ /**
+ * Load a file that declares classes, functions, or constants.
+ * The file will be loaded immediately using require_once in function scope.
+ *
+ * @note The file to be loaded MUST NOT set global variables or otherwise
+ * affect the global state. It MAY however use conditionals to determine
+ * what to declare and how, e.g. to provide polyfills.
+ *
+ * @note The file to be loaded MUST NOT assume that MediaWiki has been
+ * initialized. In particular, it MUST NOT access configuration variables
+ * or MediaWikiServices.
+ *
+ * @since 1.39
+ *
+ * @param string $file the path of the file to load.
*/
- public static $psr4Namespaces = [];
+ public static function loadFile( string $file ): void {
+ require_once $file;
+ }
/**
* Find the file containing the given class.
@@ -46,7 +152,13 @@ class AutoLoader {
public static function find( $className ): ?string {
global $wgAutoloadLocalClasses, $wgAutoloadClasses, $wgAutoloadAttemptLowercase;
- $filename = $wgAutoloadLocalClasses[$className] ?? $wgAutoloadClasses[$className] ?? false;
+ // NOTE: $wgAutoloadClasses is supported for compatibility with old-style extension
+ // registration files.
+
+ $filename = $wgAutoloadLocalClasses[$className] ??
+ self::$classFiles[$className] ??
+ $wgAutoloadClasses[$className] ??
+ false;
if ( !$filename && $wgAutoloadAttemptLowercase ) {
// Try a different capitalisation.
@@ -62,6 +174,7 @@ class AutoLoader {
if ( function_exists( 'wfDebugLog' ) ) {
wfDebugLog( 'autoloader', "Class {$className} was loaded using incorrect case" );
}
+ // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
$filename = self::$autoloadLocalClassesLower[$lowerClass];
}
}
@@ -125,67 +238,73 @@ class AutoLoader {
self::$autoloadLocalClassesLower = null;
}
+ ///// Methods used during testing //////////////////////////////////////////////
+ private static function assertTesting( $method ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new LogicException( "$method is not supported outside phpunit tests!" );
+ }
+ }
+
/**
- * Get a mapping of namespace => file path
- * The namespaces should follow the PSR-4 standard for autoloading
- *
- * @see <https://www.php-fig.org/psr/psr-4/>
- * @internal Only public for usage in AutoloadGenerator
- * @codeCoverageIgnore
- * @since 1.31
+ * Returns a map of class names to file paths for testing.
+ * @note Will throw if called outside of phpunit tests!
* @return string[]
*/
- public static function getAutoloadNamespaces() {
+ public static function getClassFiles(): array {
+ global $wgAutoloadLocalClasses, $wgAutoloadClasses;
+
+ self::assertTesting( __METHOD__ );
+
+ // NOTE: ensure the order of preference is the same as used by find().
+ return array_merge(
+ $wgAutoloadClasses,
+ self::$classFiles,
+ $wgAutoloadLocalClasses
+ );
+ }
+
+ /**
+ * Returns a map of namespace names to directories, per PSR4.
+ * @note Will throw if called outside of phpunit tests!
+ * @return string[]
+ */
+ public static function getNamespaceDirectories(): array {
+ self::assertTesting( __METHOD__ );
+ return self::$psr4Namespaces;
+ }
+
+ /**
+ * Returns an array representing the internal state of Autoloader,
+ * so it can be remembered and later restored during testing.
+ * @internal
+ * @note Will throw if called outside of phpunit tests!
+ * @return array
+ */
+ public static function getState(): array {
+ self::assertTesting( __METHOD__ );
return [
- 'MediaWiki\\' => __DIR__ . '/',
- 'MediaWiki\\Actions\\' => __DIR__ . '/actions/',
- 'MediaWiki\\Api\\' => __DIR__ . '/api/',
- 'MediaWiki\\Auth\\' => __DIR__ . '/auth/',
- 'MediaWiki\\Block\\' => __DIR__ . '/block/',
- 'MediaWiki\\Cache\\' => __DIR__ . '/cache/',
- 'MediaWiki\\ChangeTags\\' => __DIR__ . '/changetags/',
- 'MediaWiki\\Config\\' => __DIR__ . '/config/',
- 'MediaWiki\\Content\\' => __DIR__ . '/content/',
- 'MediaWiki\\DB\\' => __DIR__ . '/db/',
- 'MediaWiki\\Deferred\\LinksUpdate\\' => __DIR__ . '/deferred/LinksUpdate/',
- 'MediaWiki\\Diff\\' => __DIR__ . '/diff/',
- 'MediaWiki\\Edit\\' => __DIR__ . '/edit/',
- 'MediaWiki\\EditPage\\' => __DIR__ . '/editpage/',
- 'MediaWiki\\FileBackend\\LockManager\\' => __DIR__ . '/filebackend/lockmanager/',
- 'MediaWiki\\JobQueue\\' => __DIR__ . '/jobqueue/',
- 'MediaWiki\\Json\\' => __DIR__ . '/json/',
- 'MediaWiki\\Http\\' => __DIR__ . '/http/',
- 'MediaWiki\\Installer\\' => __DIR__ . '/installer/',
- 'MediaWiki\\Interwiki\\' => __DIR__ . '/interwiki/',
- 'MediaWiki\\Languages\\Data\\' => __DIR__ . '/languages/data/',
- 'MediaWiki\\Linker\\' => __DIR__ . '/linker/',
- 'MediaWiki\\Logger\\' => __DIR__ . '/debug/logger/',
- 'MediaWiki\\Logger\Monolog\\' => __DIR__ . '/debug/logger/monolog/',
- 'MediaWiki\\Mail\\' => __DIR__ . '/mail/',
- 'MediaWiki\\Page\\' => __DIR__ . '/page/',
- 'MediaWiki\\Parser\\' => __DIR__ . '/parser/',
- 'MediaWiki\\Preferences\\' => __DIR__ . '/preferences/',
- 'MediaWiki\\ResourceLoader\\' => __DIR__ . '/resourceloader/',
- 'MediaWiki\\Search\\' => __DIR__ . '/search/',
- 'MediaWiki\\Search\\SearchWidgets\\' => __DIR__ . '/search/searchwidgets/',
- 'MediaWiki\\Session\\' => __DIR__ . '/session/',
- 'MediaWiki\\Shell\\' => __DIR__ . '/shell/',
- 'MediaWiki\\Site\\' => __DIR__ . '/site/',
- 'MediaWiki\\Sparql\\' => __DIR__ . '/sparql/',
- 'MediaWiki\\SpecialPage\\' => __DIR__ . '/specialpage/',
- 'MediaWiki\\Tidy\\' => __DIR__ . '/tidy/',
- 'MediaWiki\\User\\' => __DIR__ . '/user/',
- 'MediaWiki\\Utils\\' => __DIR__ . '/utils/',
- 'MediaWiki\\Widget\\' => __DIR__ . '/widget/',
- 'Wikimedia\\' => __DIR__ . '/libs/',
- 'Wikimedia\\Http\\' => __DIR__ . '/libs/http/',
- 'Wikimedia\\Rdbms\\Platform\\' => __DIR__ . '/libs/rdbms/platform/',
- 'Wikimedia\\UUID\\' => __DIR__ . '/libs/uuid/',
+ 'classFiles' => self::$classFiles,
+ 'psr4Namespaces' => self::$psr4Namespaces,
];
}
+
+ /**
+ * Returns an array representing the internal state of Autoloader,
+ * so it can be remembered and later restored during testing.
+ * @internal
+ * @note Will throw if called outside of phpunit tests!
+ *
+ * @param array $state A state array returned by getState().
+ */
+ public static function restoreState( $state ): void {
+ self::assertTesting( __METHOD__ );
+
+ self::$classFiles = $state['classFiles'];
+ self::$psr4Namespaces = $state['psr4Namespaces'];
+ }
+
}
-AutoLoader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
spl_autoload_register( [ 'AutoLoader', 'autoload' ] );
// Load composer's autoloader if present
diff --git a/includes/HtmlHelper.php b/includes/HtmlHelper.php
new file mode 100644
index 000000000000..ae67518d9ee9
--- /dev/null
+++ b/includes/HtmlHelper.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace MediaWiki;
+
+use Wikimedia\Assert\Assert;
+use Wikimedia\RemexHtml\HTMLData;
+use Wikimedia\RemexHtml\Serializer\HtmlFormatter;
+use Wikimedia\RemexHtml\Serializer\Serializer;
+use Wikimedia\RemexHtml\Serializer\SerializerNode;
+use Wikimedia\RemexHtml\Tokenizer\Tokenizer;
+use Wikimedia\RemexHtml\TreeBuilder\Dispatcher;
+use Wikimedia\RemexHtml\TreeBuilder\TreeBuilder;
+
+/**
+ * Static utilities for manipulating HTML strings.
+ */
+class HtmlHelper {
+
+ /**
+ * Modify elements of an HTML fragment via a user-provided callback.
+ * @param string $htmlFragment HTML fragment. Must be valid (ie. coming from the parser, not
+ * the user).
+ * @param callable $shouldModifyCallback A callback which takes a single
+ * RemexHtml\Serializer\SerializerNode argument, and returns true if it should be modified.
+ * @param callable $modifyCallback A callback which takes a single
+ * RemexHtml\Serializer\SerializerNode argument and actually performs the modification on it.
+ * It must return the new node (which can be the original node object).
+ * @return string
+ */
+ public static function modifyElements(
+ string $htmlFragment,
+ callable $shouldModifyCallback,
+ callable $modifyCallback
+ ) {
+ $formatter = new class( $options = [], $shouldModifyCallback, $modifyCallback ) extends HtmlFormatter {
+ /** @var callable */
+ private $shouldModifyCallback;
+
+ /** @var callable */
+ private $modifyCallback;
+
+ public function __construct( $options, $shouldModifyCallback, $modifyCallback ) {
+ parent::__construct( $options );
+ $this->shouldModifyCallback = $shouldModifyCallback;
+ $this->modifyCallback = $modifyCallback;
+ }
+
+ public function element( SerializerNode $parent, SerializerNode $node, $contents ) {
+ if ( ( $this->shouldModifyCallback )( $node ) ) {
+ $node = clone $node;
+ $node->attrs = clone $node->attrs;
+ $newNode = ( $this->modifyCallback )( $node );
+ Assert::parameterType( SerializerNode::class, $newNode, 'return value' );
+ return parent::element( $parent, $newNode, $contents );
+ } else {
+ return parent::element( $parent, $node, $contents );
+ }
+ }
+
+ public function startDocument( $fragmentNamespace, $fragmentName ) {
+ return '';
+ }
+ };
+ $serializer = new Serializer( $formatter );
+ $treeBuilder = new TreeBuilder( $serializer );
+ $dispatcher = new Dispatcher( $treeBuilder );
+ $tokenizer = new Tokenizer( $dispatcher, $htmlFragment );
+
+ $tokenizer->execute( [
+ 'fragmentNamespace' => HTMLData::NS_HTML,
+ 'fragmentName' => 'body',
+ ] );
+
+ return $serializer->getResult();
+ }
+
+}
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 16c839ff4a0b..a3f796c0bd6d 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -1137,8 +1137,13 @@ class OutputPage extends ContextSource {
* @return string HTML
*/
public function getUnprefixedDisplayTitle() {
+ $service = MediaWikiServices::getInstance();
+ $languageConverter = $service->getLanguageConverterFactory()
+ ->getLanguageConverter( $service->getContentLanguage() );
$text = $this->getDisplayTitle();
- $nsPrefix = $this->getTitle()->getNsText() . ':';
+ $nsPrefix = $languageConverter->convertNamespace(
+ $this->getTitle()->getNamespace()
+ ) . ':';
$prefix = preg_quote( $nsPrefix, '/' );
return preg_replace( "/^$prefix/i", '', $text );
diff --git a/includes/Revision/RenderedRevision.php b/includes/Revision/RenderedRevision.php
index 1ff8ab755684..ae0b84b97937 100644
--- a/includes/Revision/RenderedRevision.php
+++ b/includes/Revision/RenderedRevision.php
@@ -115,7 +115,6 @@ class RenderedRevision implements SlotRenderingProvider {
Authority $performer = null
) {
$this->options = $options;
- $this->contentRenderer = $contentRenderer;
$this->setRevisionInternal( $revision );
diff --git a/includes/Settings/Config/ArrayConfigBuilder.php b/includes/Settings/Config/ArrayConfigBuilder.php
index 79789ed35d24..40d3a478c52f 100644
--- a/includes/Settings/Config/ArrayConfigBuilder.php
+++ b/includes/Settings/Config/ArrayConfigBuilder.php
@@ -5,6 +5,7 @@ namespace MediaWiki\Settings\Config;
use Config;
use HashConfig;
use MediaWiki\Config\IterableConfig;
+use function array_key_exists;
class ArrayConfigBuilder extends ConfigBuilderBase {
@@ -23,8 +24,26 @@ class ArrayConfigBuilder extends ConfigBuilderBase {
$this->config[$key] = $value;
}
- public function setMulti( array $values ): ConfigBuilder {
- $this->config = array_merge( $this->config, $values );
+ public function setMulti( array $values, array $mergeStrategies = [] ): ConfigBuilder {
+ if ( !$mergeStrategies ) {
+ $this->config = array_merge( $this->config, $values );
+ return $this;
+ }
+
+ foreach ( $values as $key => $newValue ) {
+ // Optimization: Inlined logic from set() for performance
+ if ( array_key_exists( $key, $this->config ) ) {
+ $mergeStrategy = $mergeStrategies[$key] ?? null;
+ if ( $mergeStrategy && is_array( $newValue ) ) {
+ $oldValue = $this->config[$key];
+ if ( $oldValue && is_array( $oldValue ) ) {
+ $newValue = $mergeStrategy->merge( $oldValue, $newValue );
+ }
+ }
+ }
+ $this->config[$key] = $newValue;
+ }
+
return $this;
}
@@ -37,4 +56,24 @@ class ArrayConfigBuilder extends ConfigBuilderBase {
public function build(): Config {
return new HashConfig( $this->config );
}
+
+ public function setMultiDefault( $defaults, $mergeStrategies ): ConfigBuilder {
+ foreach ( $defaults as $key => $defaultValue ) {
+ // Optimization: Inlined logic from setDefault() for performance
+ if ( array_key_exists( $key, $this->config ) ) {
+ $mergeStrategy = $mergeStrategies[$key] ?? null;
+ if ( $mergeStrategy && $defaultValue && is_array( $defaultValue ) ) {
+ $customValue = $this->config[$key];
+ if ( is_array( $customValue ) ) {
+ $newValue = $mergeStrategy->merge( $defaultValue, $customValue );
+ $this->config[$key] = $newValue;
+ }
+ }
+ } else {
+ $this->config[$key] = $defaultValue;
+ }
+ }
+ return $this;
+ }
+
}
diff --git a/includes/Settings/Config/ConfigBuilder.php b/includes/Settings/Config/ConfigBuilder.php
index d4084894e1ae..b9636b4fb7c0 100644
--- a/includes/Settings/Config/ConfigBuilder.php
+++ b/includes/Settings/Config/ConfigBuilder.php
@@ -3,6 +3,7 @@
namespace MediaWiki\Settings\Config;
use Config;
+use MediaWiki\Settings\SettingsBuilderException;
/**
* Builder for Config objects.
@@ -22,12 +23,13 @@ interface ConfigBuilder {
public function set( string $key, $value, MergeStrategy $mergeStrategy = null ): ConfigBuilder;
/**
- * Set all values in the array, with no merge strategy applied.
+ * Set all values in the array.
*
* @param array $values
+ * @param MergeStrategy[] $mergeStrategies The merge strategies indexed by config key
* @return ConfigBuilder
*/
- public function setMulti( array $values ): ConfigBuilder;
+ public function setMulti( array $values, array $mergeStrategies = [] ): ConfigBuilder;
/**
* Set the default for the configuration $key to $defaultValue.
@@ -44,6 +46,17 @@ interface ConfigBuilder {
public function setDefault( string $key, $defaultValue, MergeStrategy $mergeStrategy = null ): ConfigBuilder;
/**
+ * Set defaults in a batch.
+ *
+ * @param array $defaults The default values
+ * @param MergeStrategy[] $mergeStrategies The merge strategies indexed by config key
+ * @return ConfigBuilder
+ * @throws SettingsBuilderException if a merge strategy is not provided and
+ * the value is not an array.
+ */
+ public function setMultiDefault( array $defaults, array $mergeStrategies ): ConfigBuilder;
+
+ /**
* Build the resulting Config object.
*
* @return Config
diff --git a/includes/Settings/Config/ConfigBuilderBase.php b/includes/Settings/Config/ConfigBuilderBase.php
index 91c249283410..e235e4aea57f 100644
--- a/includes/Settings/Config/ConfigBuilderBase.php
+++ b/includes/Settings/Config/ConfigBuilderBase.php
@@ -18,7 +18,7 @@ abstract class ConfigBuilderBase implements ConfigBuilder {
$newValue,
MergeStrategy $mergeStrategy = null
): ConfigBuilder {
- if ( $mergeStrategy && is_array( $newValue ) ) {
+ if ( $mergeStrategy && $this->has( $key ) && is_array( $newValue ) ) {
$oldValue = $this->get( $key );
if ( $oldValue && is_array( $oldValue ) ) {
$newValue = $mergeStrategy->merge( $oldValue, $newValue );
@@ -31,9 +31,9 @@ abstract class ConfigBuilderBase implements ConfigBuilder {
/**
* @inheritDoc
*/
- public function setMulti( array $values ): ConfigBuilder {
+ public function setMulti( array $values, array $mergeStrategies = [] ): ConfigBuilder {
foreach ( $values as $key => $value ) {
- $this->set( $key, $value );
+ $this->set( $key, $value, $mergeStrategies[$key] ?? null );
}
return $this;
}
@@ -61,4 +61,14 @@ abstract class ConfigBuilderBase implements ConfigBuilder {
return $this;
}
+ /**
+ * @inheritDoc
+ */
+ public function setMultiDefault( array $defaults, array $mergeStrategies ): ConfigBuilder {
+ foreach ( $defaults as $key => $defaultValue ) {
+ $this->setDefault( $key, $defaultValue, $mergeStrategies[$key] ?? null );
+ }
+ return $this;
+ }
+
}
diff --git a/includes/Settings/Config/ConfigSchemaAggregator.php b/includes/Settings/Config/ConfigSchemaAggregator.php
index 28b428e6c009..0215a24fe431 100644
--- a/includes/Settings/Config/ConfigSchemaAggregator.php
+++ b/includes/Settings/Config/ConfigSchemaAggregator.php
@@ -7,6 +7,7 @@ use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use MediaWiki\Settings\SettingsBuilderException;
use StatusValue;
+use function array_key_exists;
/**
* Aggregates multiple config schemas.
@@ -32,6 +33,9 @@ class ConfigSchemaAggregator {
/** @var Validator */
private $validator;
+ /** @var MergeStrategy[]|null */
+ private $mergeStrategyCache;
+
/**
* Add a config schema to the aggregator.
*
@@ -60,6 +64,9 @@ class ConfigSchemaAggregator {
if ( isset( $schema['mergeStrategy'] ) ) {
$this->mergeStrategies[$key] = $schema['mergeStrategy'];
}
+
+ // TODO: mark cache as incomplete rather than throwing it away
+ $this->mergeStrategyCache = null;
}
/**
@@ -121,6 +128,9 @@ class ConfigSchemaAggregator {
'mergeStrategies',
$sourceName
);
+
+ // TODO: mark cache as incomplete rather than throwing it away
+ $this->mergeStrategyCache = null;
}
/**
@@ -184,18 +194,18 @@ class ConfigSchemaAggregator {
/**
* Get all known types.
*
- * @return array
+ * @return array<string|array>
*/
public function getTypes(): array {
return $this->types;
}
/**
- * Get all known merge strategies.
+ * Get the names of all known merge strategies.
*
- * @return array
+ * @return array<string>
*/
- public function getMergeStrategies(): array {
+ public function getMergeStrategyNames(): array {
return $this->mergeStrategies;
}
@@ -227,14 +237,56 @@ class ConfigSchemaAggregator {
* @throws SettingsBuilderException if merge strategy name is invalid.
*/
public function getMergeStrategyFor( string $key ): ?MergeStrategy {
- $strategyName = $this->mergeStrategies[$key] ?? null;
+ if ( $this->mergeStrategyCache === null ) {
+ $this->initMergeStrategies();
+ }
+ return $this->mergeStrategyCache[$key] ?? null;
+ }
+
+ /**
+ * Get all merge strategies indexed by config key. If there is no merge
+ * strategy for a given key, the element will be absent.
+ *
+ * @return MergeStrategy[]
+ */
+ public function getMergeStrategies() {
+ if ( $this->mergeStrategyCache === null ) {
+ $this->initMergeStrategies();
+ }
+ return $this->mergeStrategyCache;
+ }
- if ( $strategyName === null ) {
- $type = $this->types[ $key ] ?? null;
- $strategyName = $type ? $this->getStrategyForType( $type ) : null;
+ /**
+ * Initialise $this->mergeStrategyCache
+ */
+ private function initMergeStrategies() {
+ // XXX: Keep $strategiesByName for later, in case we reset the cache?
+ // Or we could make a bulk version of MergeStrategy::newFromName(),
+ // to make use of the cache there without the overhead of a method
+ // call for each setting.
+
+ $strategiesByName = [];
+ $strategiesByKey = [];
+
+ // Explicitly defined merge strategies
+ $strategyNamesByKey = $this->mergeStrategies;
+
+ // Loop over settings for which we know a type but not a merge strategy,
+ // so we can add a merge strategy for them based on their type.
+ $types = array_diff_key( $this->types, $strategyNamesByKey );
+ foreach ( $types as $key => $type ) {
+ $strategyNamesByKey[$key] = self::getStrategyForType( $type );
+ }
+
+ // Assign MergeStrategy objects to settings. Create only one object per strategy name.
+ foreach ( $strategyNamesByKey as $key => $strategyName ) {
+ if ( !array_key_exists( $strategyName, $strategiesByName ) ) {
+ $strategiesByName[$strategyName] = MergeStrategy::newFromName( $strategyName );
+ }
+ $strategiesByKey[$key] = $strategiesByName[$strategyName];
}
- return $strategyName ? MergeStrategy::newFromName( $strategyName ) : null;
+ $this->mergeStrategyCache = $strategiesByKey;
}
/**
@@ -244,7 +296,7 @@ class ConfigSchemaAggregator {
*
* @return string
*/
- private function getStrategyForType( $type ): string {
+ private static function getStrategyForType( $type ) {
if ( is_array( $type ) ) {
if ( in_array( 'array', $type ) ) {
$type = 'array';
diff --git a/includes/Settings/Config/GlobalConfigBuilder.php b/includes/Settings/Config/GlobalConfigBuilder.php
index 2808ad395cf8..4e8a267e9f2f 100644
--- a/includes/Settings/Config/GlobalConfigBuilder.php
+++ b/includes/Settings/Config/GlobalConfigBuilder.php
@@ -4,6 +4,7 @@ namespace MediaWiki\Settings\Config;
use Config;
use GlobalVarConfig;
+use function array_key_exists;
class GlobalConfigBuilder extends ConfigBuilderBase {
@@ -35,13 +36,26 @@ class GlobalConfigBuilder extends ConfigBuilderBase {
$GLOBALS[ $var ] = $value;
}
- public function setMulti( array $values ): ConfigBuilder {
+ public function setMulti( array $values, array $mergeStrategies = [] ): ConfigBuilder {
// NOTE: It is tempting to do $GLOBALS = array_merge( $GLOBALS, $values ).
// But that no longer works in PHP 8.1!
// See https://wiki.php.net/rfc/restrict_globals_usage
- foreach ( $values as $key => $value ) {
+
+ foreach ( $values as $key => $newValue ) {
$var = $this->prefix . $key; // inline getVarName() to avoid function call
- $GLOBALS[$var] = $value;
+
+ // Optimization: Inlined logic from set() for performance
+ if ( isset( $GLOBALS[$var] ) && array_key_exists( $key, $mergeStrategies ) ) {
+ $mergeStrategy = $mergeStrategies[$key];
+ if ( $mergeStrategy && is_array( $newValue ) ) {
+ $oldValue = $GLOBALS[$var];
+ if ( $oldValue && is_array( $oldValue ) ) {
+ $newValue = $mergeStrategy->merge( $oldValue, $newValue );
+ }
+ }
+ }
+
+ $GLOBALS[$var] = $newValue;
}
return $this;
}
diff --git a/includes/Settings/Config/MergeStrategy.php b/includes/Settings/Config/MergeStrategy.php
index 164aa58ba563..2bb3bdedc8d8 100644
--- a/includes/Settings/Config/MergeStrategy.php
+++ b/includes/Settings/Config/MergeStrategy.php
@@ -3,6 +3,7 @@
namespace MediaWiki\Settings\Config;
use MediaWiki\Settings\SettingsBuilderException;
+use function array_key_exists;
class MergeStrategy {
diff --git a/includes/Settings/SettingsBuilder.php b/includes/Settings/SettingsBuilder.php
index 22dfb74de771..b09b32fd3cf4 100644
--- a/includes/Settings/SettingsBuilder.php
+++ b/includes/Settings/SettingsBuilder.php
@@ -19,6 +19,7 @@ use MediaWiki\Settings\Source\SettingsFileUtils;
use MediaWiki\Settings\Source\SettingsIncludeLocator;
use MediaWiki\Settings\Source\SettingsSource;
use StatusValue;
+use function array_key_exists;
/**
* Utility for loading settings files.
@@ -368,13 +369,8 @@ class SettingsBuilder {
}
if ( $this->defaultsNeedMerging ) {
- foreach ( $settings['config-schema'] ?? [] as $key => $schema ) {
- $this->configSink->setDefault(
- $key,
- $schema['default'],
- $this->configSchema->getMergeStrategyFor( $key )
- );
- }
+ $mergeStrategies = $this->configSchema->getMergeStrategies();
+ $this->configSink->setMultiDefault( $defaults, $mergeStrategies );
} else {
// Optimization: no merge strategy, just override in one go
$this->configSink->setMulti( $defaults );
@@ -398,12 +394,9 @@ class SettingsBuilder {
$this->applySchemas( $settings );
- foreach ( $settings['config'] ?? [] as $key => $value ) {
- $this->configSink->set(
- $key,
- $value,
- $this->configSchema->getMergeStrategyFor( $key )
- );
+ if ( isset( $settings['config'] ) ) {
+ $mergeStrategies = $this->configSchema->getMergeStrategies();
+ $this->configSink->setMulti( $settings['config'], $mergeStrategies );
}
if ( isset( $settings['config-overrides'] ) ) {
@@ -555,4 +548,5 @@ class SettingsBuilder {
$this->apply();
$this->finished = true;
}
+
}
diff --git a/includes/Title.php b/includes/Title.php
index a12db4e92b90..8ed41682759c 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -254,7 +254,8 @@ class Title implements LinkTarget, PageIdentity, IDBAccessObject {
* unless $forceClone is "clone". If $forceClone is "clone" and the given TitleValue
* is already a Title instance, that instance is copied using the clone operator.
*
- * @deprecated since 1.34, use newFromLinkTarget or castFromLinkTarget
+ * @deprecated since 1.34, use newFromLinkTarget or castFromLinkTarget. Hard
+ * deprecated in 1.39.
*
* @param TitleValue $titleValue Assumed to be safe.
* @param string $forceClone set to NEW_CLONE to ensure a fresh instance is returned.
@@ -262,6 +263,7 @@ class Title implements LinkTarget, PageIdentity, IDBAccessObject {
* @return Title
*/
public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
+ wfDeprecated( __METHOD__, '1.34' );
return self::newFromLinkTarget( $titleValue, $forceClone );
}
diff --git a/includes/TrackingCategories.php b/includes/TrackingCategories.php
index 98fd985887d1..9441af86d50e 100644
--- a/includes/TrackingCategories.php
+++ b/includes/TrackingCategories.php
@@ -157,7 +157,7 @@ class TrackingCategories {
}
// XXX: should be a better way to convert a TitleValue
// to a PageReference!
- $tempTitle = Title::newFromTitleValue( $tempTitle );
+ $tempTitle = Title::newFromLinkTarget( $tempTitle );
$catName = $msgObj->page( $tempTitle )->text();
# Allow tracking categories to be disabled by setting them to "-"
if ( $catName !== '-' ) {
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index c639fbd2d21b..d9461ebe0f56 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -21,6 +21,7 @@
*/
use MediaWiki\Cache\LinkBatchFactory;
+use MediaWiki\MediaWikiServices;
/**
* A query module to list all wiki links on a given set of pages.
@@ -88,16 +89,27 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
if ( $pages === [] ) {
return; // nothing to do
}
+ $linksMigration = MediaWikiServices::getInstance()->getLinksMigration();
$params = $this->extractRequestParams();
+ if ( isset( $linksMigration::$mapping[$this->table] ) ) {
+ list( $nsField, $titleField ) = $linksMigration->getTitleFields( $this->table );
+ $queryInfo = $linksMigration->getQueryInfo( $this->table );
+ $this->addTables( $queryInfo['tables'] );
+ $this->addJoinConds( $queryInfo['joins'] );
+ } else {
+ $this->addTables( $this->table );
+ $nsField = $this->prefix . '_namespace';
+ $titleField = $this->prefix . '_title';
+ }
+
$this->addFields( [
'pl_from' => $this->prefix . '_from',
- 'pl_namespace' => $this->prefix . '_namespace',
- 'pl_title' => $this->prefix . '_title'
+ 'pl_namespace' => $nsField,
+ 'pl_title' => $titleField,
] );
- $this->addTables( $this->table );
$this->addWhereFld( $this->prefix . '_from', array_keys( $pages ) );
$multiNS = true;
@@ -125,7 +137,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
return;
}
} elseif ( $params['namespace'] ) {
- $this->addWhereFld( $this->prefix . '_namespace', $params['namespace'] );
+ $this->addWhereFld( $nsField, $params['namespace'] );
$multiNS = $params['namespace'] === null || count( $params['namespace'] ) !== 1;
}
@@ -139,9 +151,9 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->addWhere(
"{$this->prefix}_from $op $plfrom OR " .
"({$this->prefix}_from = $plfrom AND " .
- "({$this->prefix}_namespace $op $plns OR " .
- "({$this->prefix}_namespace = $plns AND " .
- "{$this->prefix}_title $op= $pltitle)))"
+ "($nsField $op $plns OR " .
+ "($nsField = $plns AND " .
+ "$titleField $op= $pltitle)))"
);
}
@@ -156,10 +168,10 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$order[] = $this->prefix . '_from' . $sort;
}
if ( $multiNS ) {
- $order[] = $this->prefix . '_namespace' . $sort;
+ $order[] = $nsField . $sort;
}
if ( $multiTitle ) {
- $order[] = $this->prefix . '_title' . $sort;
+ $order[] = $titleField . $sort;
}
if ( $order ) {
$this->addOption( 'ORDER BY', $order );
diff --git a/includes/api/i18n/ko.json b/includes/api/i18n/ko.json
index 8e149844ba7b..7d61e8bfa3d3 100644
--- a/includes/api/i18n/ko.json
+++ b/includes/api/i18n/ko.json
@@ -35,7 +35,7 @@
"apihelp-main-param-servedby": "결과에 요청을 처리한 호스트네임을 포함합니다.",
"apihelp-main-param-curtimestamp": "결과의 타임스탬프를 포함합니다.",
"apihelp-main-param-responselanginfo": "<var>uselang</var> 및 <var>errorlang</var>에 사용되는 언어를 결과에 포함합니다.",
- "apihelp-main-param-origin": "크로스 도메인 AJAX 요청 (CORS)을 사용하여 API에 접근할 때, 이것을 발신 도메인으로 설정하십시오. 모든 pre-flight 요청에 포함되어야 하며, 이에 따라 (POST 본문이 아닌) 요청 URI의 일부여야 합니다.\n\n인증된 요청의 경우, <code>Origin</code> 헤더의 발신지들 중 하나와 정확히 일치해야 하므로 <kbd>https://en.wikipedia.org</kbd> 또는 <kbd>https://meta.wikimedia.org</kbd>와 같이 설정되어야 합니다. 이 변수가 <code>Origin</code> 헤더와 일치하지 않으면 403 응답이 반환됩니다. 이 변수가 <code>Origin</code> 헤더와 일치하고 발신지가 화이트리스트에 있을 경우 <code>Access-Control-Allow-Origin</code>과 <code>Access-Control-Allow-Credentials</code> 헤더가 설정됩니다.\n\n인증되지 않은 요청의 경우, <kbd>*</kbd> 값을 지정하십시오. 이를 통해 <code>Access-Control-Allow-Origin</code> 헤더가 설정되지만 <code>Access-Control-Allow-Credentials</code>는 <code>false</code>로 설정되어 모든 사용자 지정 데이터가 제한을 받게 됩니다.",
+ "apihelp-main-param-origin": "크로스 도메인 AJAX 요청 (CORS)을 사용하여 API에 접근할 때, 이것을 발신 도메인으로 설정하십시오. 모든 pre-flight 요청에 포함되어야 하며, 이에 따라 (POST 본문이 아닌) 요청 URI의 일부여야 합니다.\n\n인증된 요청의 경우, <code>Origin</code> 헤더의 발신지들 중 하나와 정확히 일치해야 하므로 <kbd>https://en.wikipedia.org</kbd> 또는 <kbd>https://meta.wikimedia.org</kbd>와 같이 설정되어야 합니다. 이 변수가 <code>Origin</code> 헤더와 일치하지 않으면 403 응답이 반환됩니다. 이 변수가 <code>Origin</code> 헤더와 일치하고 발신지가 허용된 경우 <code>Access-Control-Allow-Origin</code>과 <code>Access-Control-Allow-Credentials</code> 헤더가 설정됩니다.\n\n인증되지 않은 요청의 경우, <kbd>*</kbd> 값을 지정하십시오. 이를 통해 <code>Access-Control-Allow-Origin</code> 헤더가 설정되지만 <code>Access-Control-Allow-Credentials</code>는 <code>false</code>로 설정되어 모든 사용자 지정 데이터가 제한을 받게 됩니다.",
"apihelp-main-param-uselang": "메시지 번역을 위한 언어입니다. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>에 <kbd>siprop=languages</kbd>를 함께 사용하면 언어 코드의 목록을 반환하고, <kbd>user</kbd>를 지정하면 현재 사용자의 언어 환경 설정을 사용하며, <kbd>content</kbd>를 지정하면 이 위키의 콘텐츠 언어를 사용합니다.",
"apihelp-main-param-errorformat": "경고 및 오류 텍스트 출력을 위해 사용할 형식",
"apihelp-main-paramvalue-errorformat-plaintext": "HTML 태그를 제거하고 엔티티가 치환된 위키텍스트입니다.",
@@ -113,9 +113,9 @@
"apihelp-compare-paramvalue-prop-rel": "해당하는 경우 'from' 이전과 'to' 이후 판의 판 ID입니다.",
"apihelp-compare-paramvalue-prop-ids": "'from'과 'to' 판의 문서와 판 ID입니다.",
"apihelp-compare-paramvalue-prop-title": "'from'과 'to' 판의 문서 제목입니다.",
- "apihelp-compare-paramvalue-prop-user": "'from'과 'to' 판의 사용자 이름과 ID입니다.",
- "apihelp-compare-paramvalue-prop-comment": "'from'과 'to' 판의 설명입니다.",
- "apihelp-compare-paramvalue-prop-parsedcomment": "'from'과 to' 판의 변환된 설명입니다.",
+ "apihelp-compare-paramvalue-prop-user": "'from'과 'to' 판의 사용자 이름과 ID입니다. 사용자가 판을 삭제한 경우 <samp>fromuserhidden</samp> 또는 <samp>touserhidden</samp> 속성이 반환됩니다.",
+ "apihelp-compare-paramvalue-prop-comment": "'from'과 'to' 판의 설명입니다. 사용자가 판을 삭제한 경우 <samp>fromuserhidden</samp> 또는 <samp>touserhidden</samp> 속성이 반환됩니다.",
+ "apihelp-compare-paramvalue-prop-parsedcomment": "'from'과 to' 판의 변환된 설명입니다. 사용자가 판을 삭제한 경우 <samp>fromuserhidden</samp> 또는 <samp>touserhidden</samp> 속성이 반환됩니다.",
"apihelp-compare-paramvalue-prop-size": "'from'과 'to' 판의 크기입니다.",
"apihelp-compare-example-1": "판 1과 2의 차이를 생성합니다.",
"apihelp-createaccount-summary": "새 사용자 계정을 만듭니다.",
@@ -130,7 +130,7 @@
"apihelp-delete-param-reason": "삭제의 이유. 설정하지 않으면 자동 생성되는 이유를 사용합니다.",
"apihelp-delete-param-tags": "삭제 기록의 항목에 적용할 변경 태그입니다.",
"apihelp-delete-param-watch": "문서를 현재 사용자의 주시문서 목록에 추가합니다.",
- "apihelp-delete-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 주시를 변경하지 않습니다.",
+ "apihelp-delete-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 (봇 사용자는 무시됨) 주시를 변경하지 않습니다.",
"apihelp-delete-param-unwatch": "문서를 현재 사용자의 주시문서 목록에서 제거합니다.",
"apihelp-delete-param-oldimage": "[[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]]에 지정된 바대로 삭제할 오래된 그림의 이름입니다.",
"apihelp-delete-example-simple": "<kbd>Main Page</kbd>를 삭제합니다.",
@@ -139,13 +139,13 @@
"apihelp-edit-summary": "문서를 만들고 편집합니다.",
"apihelp-edit-param-title": "편집할 문서의 제목. <var>$1pageid</var>과 같이 사용할 수 없습니다.",
"apihelp-edit-param-pageid": "편집할 문서의 문서 ID입니다. <var>$1title</var>과 함께 사용할 수 없습니다.",
- "apihelp-edit-param-section": "문단 번호입니다. <kbd>0</kbd>은 최상위 문단, <kbd>new</kbd>는 새 문단입니다.",
+ "apihelp-edit-param-section": "문단 식별자입니다. <kbd>0</kbd>은 최상위 문단, <kbd>new</kbd>는 새 문단입니다. 종종 양의 정수이지만 숫자가 아닐 수도 있습니다.",
"apihelp-edit-param-sectiontitle": "새 문단을 위한 제목.",
"apihelp-edit-param-text": "문서 내용.",
"apihelp-edit-param-summary": "편집 요약. 또한 $1section=new 및 $1sectiontitle이 설정되어 있지 않을 때 문단 제목.",
"apihelp-edit-param-tags": "이 판에 적용할 태그를 변경합니다.",
"apihelp-edit-param-minor": "이 편집을 사소한 편집으로 표시합니다.",
- "apihelp-edit-param-notminor": "사소하지 않은 편집.",
+ "apihelp-edit-param-notminor": "\"{{int:tog-minordefault}}\" 사용자 환경 설정이 설정된 경우에도 이 편집을 사소한 편집으로 표시하지 않습니다.",
"apihelp-edit-param-bot": "이 편집을 봇 편집으로 표시.",
"apihelp-edit-param-basetimestamp": "기본 판의 타임스탬프이며, 편집 충돌을 발견하기 위해 사용됩니다. [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]]를 통해 가져올 수 있습니다.",
"apihelp-edit-param-starttimestamp": "편집 과정을 시작할 때의 타임스탬프이며 편집 충돌을 발견하기 위해 사용됩니다. 편집 과정을 시작할 때(예: 문서 내용을 편집으로 불러올 때) <var>[[Special:ApiHelp/main|curtimestamp]]</var>를 사용하여 적절한 값을 가져올 수 있습니다.",
@@ -154,7 +154,7 @@
"apihelp-edit-param-nocreate": "페이지가 존재하지 않으면 오류를 출력합니다.",
"apihelp-edit-param-watch": "문서를 현재 사용자의 주시문서 목록에 추가합니다.",
"apihelp-edit-param-unwatch": "문서를 현재 사용자의 주시문서 목록에서 제거합니다.",
- "apihelp-edit-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 주시를 변경하지 않습니다.",
+ "apihelp-edit-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 (봇 사용자는 무시됨) 주시를 변경하지 않습니다.",
"apihelp-edit-param-prependtext": "이 텍스를 문서의 처음에 추가합니다. $1text를 무효로 합니다.",
"apihelp-edit-param-appendtext": "이 텍스트를 문서의 끝에 추가합니다. $1text를 무효로 합니다.\n\n새 문단을 추가하려면 이 변수 대신 $1section=new를 사용하십시오.",
"apihelp-edit-param-undo": "이 판의 편집을 취소합니다. $1text, $1prependtext, $1appendtext를 무효로 합니다.",
@@ -249,7 +249,7 @@
"apihelp-move-param-noredirect": "넘겨주기 문서 만들지 않기",
"apihelp-move-param-watch": "현재 사용자의 주시 문서에 이 문서와 넘겨주기 문서를 추가하기",
"apihelp-move-param-unwatch": "현재 사용자의 주시 문서에 이 문서와 넘겨주기 문서를 제거하기",
- "apihelp-move-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 주시를 변경하지 않습니다.",
+ "apihelp-move-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 (봇 사용자는 무시됨) 주시를 변경하지 않습니다.",
"apihelp-move-param-ignorewarnings": "모든 경고 무시하기",
"apihelp-move-example-move": "<kbd>기존 제목</kbd>에서 <kbd>대상 제목</kbd>으로 넘겨주기를 만들지 않고 이동하기.",
"apihelp-opensearch-summary": "OpenSearch 프로토콜을 이용하여 위키를 검색합니다.",
@@ -299,7 +299,7 @@
"apihelp-parse-param-preview": "미리 보기 모드에서 파싱합니다.",
"apihelp-parse-param-sectionpreview": "문단 미리 보기 모드에서 파싱합니다. (미리 보기 모드도 활성화함)",
"apihelp-parse-param-disabletoc": "출력에서 목차를 제외합니다.",
- "apihelp-parse-param-useskin": "선택한 스킨을 파서 출력에 적용합니다. 다음의 속성에 영향을 줄 수 있습니다: <kbd>langlinks</kbd>, <kbd>headitems</kbd>, <kbd>modules</kbd>, <kbd>jsconfigvars</kbd>, <kbd>indicators</kbd>.",
+ "apihelp-parse-param-useskin": "선택한 스킨을 파서 출력에 적용합니다. 다음의 속성에 영향을 줄 수 있습니다: <kbd>text</kbd>, <kbd>langlinks</kbd>, <kbd>headitems</kbd>, <kbd>modules</kbd>, <kbd>jsconfigvars</kbd>, <kbd>indicators</kbd>.",
"apihelp-parse-param-contentformat": "입력 텍스트에 사용할 내용 직렬화 포맷입니다. $1text와 함께 사용할 때에만 유효합니다.",
"apihelp-parse-example-page": "페이지를 파싱합니다.",
"apihelp-parse-example-text": "위키텍스트의 구문을 분석합니다.",
@@ -311,7 +311,7 @@
"apihelp-patrol-example-revid": "판을 점검합니다.",
"apihelp-protect-summary": "문서의 보호 수준을 변경합니다.",
"apihelp-protect-param-reason": "보호 또는 보호 해제의 이유.",
- "apihelp-protect-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 주시를 변경하지 않습니다.",
+ "apihelp-protect-param-watchlist": "현재 사용자의 주시목록에서 문서를 무조건적으로 추가하거나 제거하거나, 환경 설정을 사용하거나 (봇 사용자는 무시됨) 주시를 변경하지 않습니다.",
"apihelp-protect-example-protect": "문서 보호",
"apihelp-purge-summary": "주어진 제목을 위한 캐시를 새로 고침.",
"apihelp-purge-param-forcelinkupdate": "링크 테이블을 업데이트합니다.",
diff --git a/includes/api/i18n/nb.json b/includes/api/i18n/nb.json
index eab9e23e5b05..67393f6382d8 100644
--- a/includes/api/i18n/nb.json
+++ b/includes/api/i18n/nb.json
@@ -1432,6 +1432,7 @@
"apihelp-undelete-param-fileids": "ID-ene til filrevisjonene som skal gjenopprettes. Hvis både <var>$1timestamps</var> og <var>$1fileids</var> er tomme blir alt gjenopprettet.",
"apihelp-undelete-param-watchlist": "Legg til eller fjern siden fra den gjeldende brukerens overvåkningsliste, bruk innstillinger (ignoreres for botbrukere) eller ikke endre overvåkning.",
"apihelp-undelete-param-watchlistexpiry": "Tidsstempel for utløp i overvåkningslisten. Utelat denne parameteren for å la den gjeldende utløpstiden være uendret.",
+ "apihelp-undelete-param-undeletetalk": "Gjenopprett alle revisjoner av den tilknyttede diskusjonssiden hvis det er noen.",
"apihelp-undelete-example-page": "Gjenopprett siden <kbd>Main Page</kbd>.",
"apihelp-undelete-example-revisions": "Gjenopprett to revisjoner av siden <kbd>Main Page</kbd>.",
"apihelp-unlinkaccount-summary": "Fjern en lenket tredjepartskonto fra den gjeldende brukeren.",
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 2c81a8cd021b..d3e0803e45ad 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -170,6 +170,7 @@ abstract class DatabaseUpdater {
$registry->clearQueue();
// Read extension.json files
+ // NOTE: As a side-effect, this registers classes and namespaces with the autoloader.
$data = $registry->readFromQueue( $queue );
// Merge extension attribute hooks with hooks defined by a .php
@@ -179,10 +180,10 @@ abstract class DatabaseUpdater {
$legacySchemaHooks = array_merge( $legacySchemaHooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
}
- // Merge classes from extension.json
- global $wgAutoloadClasses;
+ // Register classes defined by extensions that are loaded by including of a file that
+ // updates global variables, rather than having an extension.json manifest.
if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
- $wgAutoloadClasses += $vars['wgAutoloadClasses'];
+ AutoLoader::registerClasses( $vars['wgAutoloadClasses'] );
}
return new HookContainer(
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index db25ae4950c6..f34608580e7a 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -1563,7 +1563,7 @@ abstract class Installer {
/**
* Auto-detect extensions with an extension.json file. Load the extensions,
- * populate $wgAutoloadClasses and return the merged registry data.
+ * register classes with the autoloader and return the merged registry data.
*
* @return array
*/
@@ -1579,8 +1579,7 @@ abstract class Installer {
$registry = new ExtensionRegistry();
$data = $registry->readFromQueue( $queue );
- global $wgAutoloadClasses;
- $wgAutoloadClasses += $data['globals']['wgAutoloadClasses'];
+ AutoLoader::registerClasses( $data['globals']['wgAutoloadClasses'] );
return $data;
}
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 97f997581e78..09ab53bc7134 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -215,6 +215,7 @@ class MysqlUpdater extends DatabaseUpdater {
[ 'addTable', 'user_autocreate_serial', 'patch-user_autocreate_serial.sql' ],
[ 'modifyField', 'ipblocks_restrictions', 'ir_ipb_id', 'patch-ipblocks_restrictions-ir_ipb_id.sql' ],
[ 'modifyField', 'ipblocks', 'ipb_id', 'patch-ipblocks-ipb_id.sql' ],
+ [ 'modifyField', 'user', 'user_editcount', 'patch-user-user_editcount.sql' ],
];
}
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index ed959bc5e567..d27126cbcb8a 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -187,6 +187,7 @@ class SqliteUpdater extends DatabaseUpdater {
[ 'addTable', 'user_autocreate_serial', 'patch-user_autocreate_serial.sql' ],
[ 'modifyField', 'ipblocks_restrictions', 'ir_ipb_id', 'patch-ipblocks_restrictions-ir_ipb_id.sql' ],
[ 'modifyField', 'ipblocks', 'ipb_id', 'patch-ipblocks-ipb_id.sql' ],
+ [ 'modifyField', 'user', 'user_editcount', 'patch-user-user_editcount.sql' ],
];
}
diff --git a/includes/installer/i18n/it.json b/includes/installer/i18n/it.json
index e2a9dbc4fc85..f47859caac89 100644
--- a/includes/installer/i18n/it.json
+++ b/includes/installer/i18n/it.json
@@ -238,6 +238,7 @@
"config-logo-preview-main": "Pagina principale",
"config-logo-icon": "Logo (icona)",
"config-logo-icon-help": "La tua icona del logo dovrebbe essere quadrata e possibilmente avere o più di 50px di risoluzione, oppure essere un file SVG.",
+ "config-logo-filedrop": "Trascina un file immagine qui",
"config-instantcommons": "Abilita Instant Commons",
"config-instantcommons-help": "[https://www.mediawiki.org/wiki/Special:MyLanguage/InstantCommons Instant Commons] è una funzionalità che consente ai wiki di usare immagini, suoni e altri file multimediali che trovate sul sito [https://commons.wikimedia.org/ Wikimedia Commons].\nPer fare questo, MediaWiki richiede l'accesso a Internet.\n\nPer ulteriori informazioni su questa funzionalità, incluse le istruzioni su come configurarlo per wiki diversi da Wikimedia Commons, consultare [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos il manuale].",
"config-cc-error": "Il selettore di licenze Creative Commons non ha dato alcun risultato.\nInserisci manualmente il nome della licenza.",
@@ -300,6 +301,7 @@
"config-install-subscribe-fail": "Impossibile sottoscrivere [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ mediawiki-announce]: $1",
"config-install-subscribe-notpossible": "cURL non è installato e <code>allow_url_fopen</code> non è disponibile.",
"config-install-subscribe-alreadypending": "Una richiesta di sottoscrizione a mediawiki-announce è stata già inviata. Rispondere alla e-mail di conferma che è stata precedentemente inviata.",
+ "config-install-subscribe-possiblefail": "La richiesta di iscrizione a mediawiki-announce potrebbe non essere andata a buon fine. Se non ricevi un messaggio di posta elettronica di conferma entro pochi minuti, vai a [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ lists.wikimedia.org] per provare di nuovo a inviare l'iscrizione.",
"config-install-mainpage": "Creazione della pagina principale con contenuto predefinito",
"config-install-mainpage-exists": "La pagina principale già esiste, saltata",
"config-install-extension-tables": "Creazione delle tabelle per le estensioni attivate",
diff --git a/includes/installer/i18n/lij.json b/includes/installer/i18n/lij.json
index 30058f0ca748..5cce279e2f92 100644
--- a/includes/installer/i18n/lij.json
+++ b/includes/installer/i18n/lij.json
@@ -59,6 +59,7 @@
"config-no-fts3": "<strong>Atençión:</strong> SQLite o l'é conpilòu sénsa o [//sqlite.org/fts3.html mòdolo FTS3], e fonçionalitæ de riçèrca no saiàn disponìbili in sce 'sto backend chi.",
"config-pcre-old": "<strong>Erô fatâle:</strong> o l'é domandòu PCRE $1 ò sucesîvo.\nO tò PHP binâio o l'é colegòu con PCRE $2.\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Errors_and_symptoms/PCRE Ciù informaçioîn].",
"config-pcre-no-utf8": "<strong>Erô fatâle</strong>: o mòdolo PCRE de PHP o pâ ch'o ségge stæto conpilòu sénsa o sùporto PCRE_UTF8.\nMediaWiki o l'à bezéugno do sùporto UTF8 pe fonçionâ coretaménte.",
+ "config-pcre-invalid-newline": "<strong>Erô fatâle:</strong> o mòdolo PCRE de PHP o pâ ch'o ségge stæto conpilòu con PCRE_CONFIG_NEWLINE = -1 pe ANY.\nMediaWiki o no l'é conpatìbile con sta configuraçión chi.",
"config-memory-raised": "O valô <code>memory_limit</code> de PHP o l'é $1, aomentòu a $2.",
"config-memory-bad": "<strong>Atençión:</strong> o valô de <code>memory_limit</code> de PHP o l'é $1.\nFòscia o l'é tròppo bàsso.\nL'instalaçión a poriéiva falî!",
"config-apc": "[https://www.php.net/apc APC] o l'é instalòu",
@@ -212,6 +213,18 @@
"config-upload-help": "O caregaménto de file o poriéiva espónn-e o tò sèrver a di réizeghi de seguéssa.\nPe magioî informaçioîn, lêzi a [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security seçión in sciâ seguéssa] into manoâle.\n\nPe consentî o caregaménto de file, modìfica a modalitæ inta sotodirectory <code>images</code> da directory prinçipâ de MediaWiki coscì che o sèrver web o pòsse scrîve lì.\nDòppo ativâ quésta çèrnia.",
"config-upload-deleted": "Directory pe-i file scàssæ:",
"config-upload-deleted-help": "Çèrni 'na directory inta quæ archiviâ i file scàssæ.\nIdealménte, quésta a no doviéiva êse acescìbile da-o web.",
+ "config-personalization-settings": "Personalizaçión",
+ "config-logo-summary": "Pe ciaschedùn de sti cànpi chi o l'é necesâio caregâ 'n'inmàgine da-e dimenscioìn apropriæ, meténdo l'URL inta cazélla de sótta. Se peu dêuviâ <code>$wgStylePath</code> òpû <code>$wgScriptPath</code> se o lögo o fa riferiménto a sti percórsci chi. A ògni mòddo, e cazélle se pêuan lasciâ vêue.\n\nCo-i browser sùportæ se peu strascinâ 'n'inmàgine da-o pròpio scistêma òperatîvo inte cazélle de inseriménto ò inte l'àrea de anteprìmma.",
+ "config-logo-preview-main": "Pàgina prinçipâ",
+ "config-logo-icon": "Lögo (icónn-a):",
+ "config-logo-icon-help": "L'icónn-a do tò lögo a doviéiva êse quadrâta e co-ina resoluçión ciù âta de 50px, òpû into formâto SVG.",
+ "config-logo-wordmark": "Wordmark (facoltatîvo):",
+ "config-logo-wordmark-help": "O nómme do tò scîto. Se o no l'é indicòu, o saiâ repigiòu da-o tèsto. Idealménte 'n file SVG co-în'altéssa ciù bàssa ò pægia a 30px.",
+ "config-logo-tagline": "Tagline (facoltatîvo):",
+ "config-logo-tagline-help": "A tagline do tò scîto. Da dêuviâ sôlo se prìmma o l'é stæto definîo o wordmark. Se o no l'é indicâ, o saiâ repigiâ da-o tèsto. L'altéssa de tagline e watermark mìssi insémme a no dêve superâ i 50px.",
+ "config-logo-sidebar": "Lögo da bâra laterâle (facoltatîvo):",
+ "config-logo-filedrop": "Strascìnn-a 'n file inmàgine chi",
+ "config-logo-sidebar-help": "Çèrte skin de MediaWiki inclùddan 'n lögo co-în'altéssa de 160px mìsso inta bâra laterâle. Se o no l'é definîo o saiâ pægio a l'icónn-a za indicâ. Ti poriêsci voéi 'na gràfica dedicâ ch'a métte insémme o wordmark e l'icónn-a.",
"config-instantcommons": "Abìlita Instant Commons",
"config-instantcommons-help": "[https://www.mediawiki.org/wiki/InstantCommons Instant Commons] o l'é 'na fonçionalitæ ch'a permétte a-e wiki de dêuviâ inmàgine, soîn e âtri file multimediâli che se trêuvan in sciô scîto [https://commons.wikimedia.org/ Wikimedia Commons].\nPe fâ quésto, MediaWiki a domànda 'na conesción a l'Internétte.\n\nPe ciù informaçioîn in sce 'sta fonçionalitæ chi, con tànto de instruçioîn in sce cómme configurâlo pe de wiki despægie da Wikimedia Commons, consultâ [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos o manoâle].",
"config-cc-error": "O seletô de licénse Creative Commons o no l'à dæto nisciùn rizultâto.\nInserìsci a màn o nómme da licénsa.",
diff --git a/includes/languages/data/Names.php b/includes/languages/data/Names.php
index 811c8af02f69..2e4c94dbf6a3 100644
--- a/includes/languages/data/Names.php
+++ b/includes/languages/data/Names.php
@@ -316,6 +316,7 @@ class Names {
'mni' => 'ꯃꯤꯇꯩ ꯂꯣꯟ', # Manipuri/Meitei
'mnw' => 'ဘာသာ မန်', # Mon, T201583
'mo' => 'молдовеняскэ', # Moldovan, deprecated (ISO 639-2: ro-Cyrl-MD)
+ 'mos' => 'moore', # Mooré
'mr' => 'मराठी', # Marathi
'mrh' => 'Mara', # Mara
'mrj' => 'кырык мары', # Hill Mari
diff --git a/includes/registration/ExtensionRegistry.php b/includes/registration/ExtensionRegistry.php
index 5388f5ebce4c..fb5711945921 100644
--- a/includes/registration/ExtensionRegistry.php
+++ b/includes/registration/ExtensionRegistry.php
@@ -135,6 +135,11 @@ class ExtensionRegistry {
private static $instance;
/**
+ * @var ?BagOStuff
+ */
+ private $cache = null;
+
+ /**
* @codeCoverageIgnore
* @return ExtensionRegistry
*/
@@ -147,6 +152,18 @@ class ExtensionRegistry {
}
/**
+ * Set the cache to use for extension info.
+ * Intended for use during testing.
+ *
+ * @internal
+ *
+ * @param BagOStuff $cache
+ */
+ public function setCache( BagOStuff $cache ): void {
+ $this->cache = $cache;
+ }
+
+ /**
* @since 1.34
* @param bool $check
*/
@@ -188,9 +205,13 @@ class ExtensionRegistry {
}
private function getCache(): BagOStuff {
- // Can't call MediaWikiServices here, as we must not cause services
- // to be instantiated before extensions have loaded.
- return ObjectCache::makeLocalServerCache();
+ if ( !$this->cache ) {
+ // Can't call MediaWikiServices here, as we must not cause services
+ // to be instantiated before extensions have loaded.
+ return ObjectCache::makeLocalServerCache();
+ }
+
+ return $this->cache;
}
private function makeCacheKey( BagOStuff $cache, $component, ...$extra ) {
@@ -433,7 +454,7 @@ class ExtensionRegistry {
}
// FIXME: It was a design mistake to handle autoloading separately (T240535)
- $data['globals']['wgAutoloadClasses'] = $autoloadClasses;
+ $data['globals']['wgAutoloadClasses'] = $autoloadClasses; // NOTE: used by Installer!
$data['autoloaderPaths'] = $autoloaderPaths;
$data['autoloaderNS'] = $autoloadNamespaces;
return $data;
@@ -452,12 +473,12 @@ class ExtensionRegistry {
) {
if ( isset( $info['AutoloadClasses'] ) ) {
$autoload = self::processAutoLoader( $dir, $info['AutoloadClasses'] );
- $GLOBALS['wgAutoloadClasses'] += $autoload;
+ AutoLoader::registerClasses( $autoload );
$autoloadClasses += $autoload;
}
if ( isset( $info['AutoloadNamespaces'] ) ) {
$autoloadNamespaces += self::processAutoLoader( $dir, $info['AutoloadNamespaces'] );
- AutoLoader::$psr4Namespaces += $autoloadNamespaces;
+ AutoLoader::registerNamespaces( $autoloadNamespaces );
}
}
@@ -475,12 +496,12 @@ class ExtensionRegistry {
) {
if ( isset( $info['TestAutoloadClasses'] ) ) {
$autoload = self::processAutoLoader( $dir, $info['TestAutoloadClasses'] );
- $GLOBALS['wgAutoloadClasses'] += $autoload;
+ AutoLoader::registerClasses( $autoload );
$autoloadClasses += $autoload;
}
if ( isset( $info['TestAutoloadNamespaces'] ) ) {
$autoloadNamespaces += self::processAutoLoader( $dir, $info['TestAutoloadNamespaces'] );
- AutoLoader::$psr4Namespaces += $autoloadNamespaces;
+ AutoLoader::registerNamespaces( $autoloadNamespaces );
}
}
@@ -536,7 +557,7 @@ class ExtensionRegistry {
}
if ( isset( $info['autoloaderNS'] ) ) {
- AutoLoader::$psr4Namespaces += $info['autoloaderNS'];
+ AutoLoader::registerNamespaces( $info['autoloaderNS'] );
}
foreach ( $info['defines'] as $name => $val ) {
diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php
index ddfc42d3c8b0..8f272f41db9a 100644
--- a/includes/skins/Skin.php
+++ b/includes/skins/Skin.php
@@ -27,6 +27,7 @@ use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Skin\SkinComponent;
use MediaWiki\Skin\SkinComponentRegistry;
+use MediaWiki\Skin\SkinComponentRegistryContext;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use Wikimedia\WrappedStringList;
@@ -266,7 +267,7 @@ abstract class Skin extends ContextSource {
$this->skinname = $name;
}
$this->componentRegistry = new SkinComponentRegistry(
- $this
+ new SkinComponentRegistryContext( $this )
);
}
diff --git a/includes/skins/components/ComponentRegistryContext.php b/includes/skins/components/ComponentRegistryContext.php
new file mode 100644
index 000000000000..b1bee4a0816f
--- /dev/null
+++ b/includes/skins/components/ComponentRegistryContext.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+namespace MediaWiki\Skin;
+
+use Config;
+use Title;
+
+/**
+ * @internal for use inside Skin and SkinTemplate classes only
+ * @unstable
+ */
+interface ComponentRegistryContext {
+ /**
+ * Returns the config needed for the component.
+ *
+ * @return Config
+ */
+ public function getConfig(): Config;
+
+ /**
+ * Returns the Title object for the component.
+ *
+ * @return Title
+ */
+ public function getTitle(): Title;
+}
diff --git a/includes/skins/components/SkinComponentRegistry.php b/includes/skins/components/SkinComponentRegistry.php
index 50136fa23687..60e1941ea5f1 100644
--- a/includes/skins/components/SkinComponentRegistry.php
+++ b/includes/skins/components/SkinComponentRegistry.php
@@ -19,7 +19,6 @@
namespace MediaWiki\Skin;
use RuntimeException;
-use Skin;
use SpecialPage;
/**
@@ -30,14 +29,14 @@ class SkinComponentRegistry {
/** @var SkinComponent[]|null null if not initialized. */
private $components = null;
- /** @var Skin */
- private $skin;
+ /** @var SkinComponentRegistryContext */
+ private $skinContext;
/**
- * @param Skin $skin
+ * @param SkinComponentRegistryContext $skinContext
*/
- public function __construct( Skin $skin ) {
- $this->skin = $skin;
+ public function __construct( SkinComponentRegistryContext $skinContext ) {
+ $this->skinContext = $skinContext;
}
/**
@@ -82,27 +81,27 @@ class SkinComponentRegistry {
* @throws RuntimeException if given an unknown name
*/
private function registerComponent( string $name ) {
- $skin = $this->skin;
+ $skin = $this->skinContext;
$user = $skin->getUser();
switch ( $name ) {
case 'logos':
$component = new SkinComponentLogo(
- $this->skin->getConfig(),
- $this->skin->getLanguage()->getCode()
+ $skin->getConfig(),
+ $skin->getLanguage()
);
break;
case 'search-box':
$component = new SkinComponentSearch(
$skin->getConfig(),
$user,
- $skin->getContext(),
+ $skin->getMessageLocalizer(),
SpecialPage::newSearchPage( $user ),
$skin->getRelevantTitle()
);
break;
case 'toc':
$component = new SkinComponentTableOfContents(
- $this->skin->getOutput()
+ $skin->getOutput()
);
break;
default:
diff --git a/includes/skins/components/SkinComponentRegistryContext.php b/includes/skins/components/SkinComponentRegistryContext.php
new file mode 100644
index 000000000000..26a757944dec
--- /dev/null
+++ b/includes/skins/components/SkinComponentRegistryContext.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+namespace MediaWiki\Skin;
+
+use Config;
+use MessageLocalizer;
+use OutputPage;
+use Skin;
+use Title;
+use User;
+
+/**
+ * @internal for use inside Skin and SkinTemplate classes only
+ * @unstable
+ */
+class SkinComponentRegistryContext implements ComponentRegistryContext {
+ /** @var Skin */
+ private $skin;
+
+ /** @var MessageLocalizer */
+ private $localizer;
+
+ /**
+ * @param Skin $skin
+ */
+ public function __construct( Skin $skin ) {
+ $this->skin = $skin;
+ $this->localizer = $skin->getContext();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getConfig(): Config {
+ return $this->skin->getConfig();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTitle(): Title {
+ return $this->skin->getTitle() ?? Title::makeTitle( NS_MAIN, 'Foo' );
+ }
+
+ /**
+ * @return Title|null the "relevant" title - see Skin::getRelevantTitle
+ */
+ public function getRelevantTitle() {
+ return $this->skin->getRelevantTitle() ?? $this->getTitle();
+ }
+
+ /**
+ * @return OutputPage
+ */
+ public function getOutput(): OutputPage {
+ return $this->skin->getOutput();
+ }
+
+ /**
+ * @return User
+ */
+ public function getUser() {
+ return $this->skin->getUser();
+ }
+
+ /**
+ * @return string|null $language
+ */
+ public function getLanguage() {
+ return $this->skin->getLanguage()->getCode();
+ }
+
+ /**
+ * @return MessageLocalizer
+ */
+ public function getMessageLocalizer(): MessageLocalizer {
+ return $this->localizer;
+ }
+}