--- /dev/null
+== MediaWiki 1.28 ==
+
+THIS IS NOT A RELEASE YET
+
+MediaWiki 1.28 is an alpha-quality branch and is not recommended for use in
+production.
+
+=== Configuration changes in 1.28 ===
+* The load.php entry point now enforces the existing policy of not allowing
+ access to session data, which includes the session user and the session
+ user's language. If such access is attempted, an exception will be thrown.
+
+=== New features in 1.28 ===
+
+
+=== External library changes in 1.28 ===
+
+==== Upgraded external libraries ====
+
+
+==== New external libraries ====
+
+
+==== Removed and replaced external libraries ====
+
+
+=== Bug fixes in 1.28 ===
+
+
+=== Action API changes in 1.28 ===
+
+
+=== Action API internal changes in 1.28 ===
+
+
+=== Languages updated in 1.28 ===
+
+MediaWiki supports over 350 languages. Many localisations are updated
+regularly. Below only new and removed languages are listed, as well as
+changes to languages because of Phabricator reports.
+
+=== Other changes in 1.27 ===
+
+
+== Compatibility ==
+
+MediaWiki 1.28 requires PHP 5.5.9 or later. There is experimental support for
+HHVM 3.6.5 or later.
+
+MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
+support for them is somewhat less mature. There is experimental support for
+Oracle and Microsoft SQL Server.
+
+The supported versions are:
+
+* MySQL 5.0.3 or later
+* PostgreSQL 8.3 or later
+* SQLite 3.3.7 or later
+* Oracle 9.0.1 or later
+* Microsoft SQL Server 2005 (9.00.1399)
+
+== Upgrading ==
+
+1.28 has several database changes since 1.27, and will not work without schema
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
+
+If upgrading from before 1.11, and you are using a wiki as a commons
+repository, make sure that it is updated as well. Otherwise, errors may arise
+due to database schema changes.
+
+If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
+new database fields are filled with data.
+
+If you are upgrading from MediaWiki 1.4.x or earlier, you should upgrade to
+1.5 first. The upgrade script maintenance/upgrade1_5.php has been removed
+with MediaWiki 1.21.
+
+Don't forget to always back up your database before upgrading!
+
+See the file UPGRADE for more detailed upgrade instructions.
+
+For notes on 1.27.x and older releases, see HISTORY.
+
+== Online documentation ==
+
+Documentation for both end-users and site administrators is available on
+MediaWiki.org, and is covered under the GNU Free Documentation License (except
+for pages that explicitly state that their contents are in the public domain):
+
+ https://www.mediawiki.org/wiki/Documentation
+
+== Mailing list ==
+
+A mailing list is available for MediaWiki user support and discussion:
+
+ https://lists.wikimedia.org/mailman/listinfo/mediawiki-l
+
+A low-traffic announcements-only list is also available:
+
+ https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce
+
+It's highly recommended that you sign up for one of these lists if you're
+going to run a public MediaWiki, so you can be notified of security fixes.
+
+== IRC help ==
+
+There's usually someone online in #mediawiki on irc.freenode.net.
'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php',
+ 'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/Services/CannotReplaceActiveServiceException.php',
+ 'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/Services/ContainerDisabledException.php',
+ 'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/Services/DestructibleService.php',
+ 'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/Services/NoSuchServiceException.php',
+ 'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/Services/ServiceAlreadyDefinedException.php',
'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/Services/ServiceContainer.php',
+ 'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/Services/ServiceDisabledException.php',
'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ . '/includes/session/BotPasswordSessionProvider.php',
'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ . '/includes/session/CookieSessionProvider.php',
'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ . '/includes/session/ImmutableSessionProviderWithCookie.php',
"psr/log": "1.0.0",
"wikimedia/assert": "0.2.2",
"wikimedia/base-convert": "1.0.1",
- "wikimedia/cdb": "1.3.0",
+ "wikimedia/cdb": "1.4.0",
"wikimedia/cldr-plural-rule-parser": "1.0.0",
"wikimedia/composer-merge-plugin": "1.3.1",
"wikimedia/html-formatter": "1.0.1",
- "wikimedia/ip-set": "1.0.1",
+ "wikimedia/ip-set": "1.1.0",
"wikimedia/php-session-serializer": "1.0.3",
"wikimedia/relpath": "1.0.3",
"wikimedia/running-stat": "1.1.0",
$request: $wgRequest
$mediaWiki: The $mediawiki object
-'MediaWikiServices': Override services in the default MediaWikiServices instance.
-Extensions may use this to define, replace, or wrap existing services.
-However, the preferred way to define a new service is the $wgServiceWiringFiles array.
+'MediaWikiServices': Called when a global MediaWikiServices instance is
+initialized. Extensions may use this to define, replace, or wrap services.
+However, the preferred way to define a new service is
+the $wgServiceWiringFiles array.
$services: MediaWikiServices
'MessageCache::get': When fetching a message. Can be used to override the key
entry points" such as hook handler functions. See "Migration" below.
+== Service Reset ==
+
+Services get their configuration injected, and changes to global
+configuration variables will not have any effect on services that were already
+instantiated. This would typically be the case for low level services like
+the ConfigFactory or the ObjectCacheManager, which are used during extension
+registration. To address this issue, Setup.php resets the global service
+locator instance by calling MediaWikiServices::resetGlobalInstance() once
+configuration and extension registration is complete.
+
+Note that "unmanaged" legacy services services that manage their own singleton
+must not keep references to services managed by MediaWikiServices, to allow a
+clean reset. After the global MediaWikiServices instance got reset, any such
+references would be stale, and using a stale service will result in an error.
+
+Services should either have all dependencies injected and be themselves managed
+by MediaWikiServices, or they should use the Service Locator pattern, accessing
+service instances via the global MediaWikiServices instance state when needed.
+This ensures that no stale service references remain after a reset.
+
+
== Configuration ==
When the default MediaWikiServices instance is created, a Config object is
mediawiki/extensions$ ln -s ../../extensions-trunk/FooBar
Most extensions are available through Git:
- https://gerrit.wikimedia.org/r/#/admin/projects/?filter=mediawiki%252Fextensions%252F
- https://git.wikimedia.org/project/mediawiki
+ https://phabricator.wikimedia.org/diffusion/MEXT/
Please note that under POSIX systems (Linux...), parent of a symbolic path
* MediaWiki version number
* @since 1.2
*/
-$wgVersion = '1.27.0-alpha';
+$wgVersion = '1.28.0-alpha';
/**
* Name of the site. It must be changed in LocalSettings.php
*/
function getPreviewText() {
global $wgOut, $wgUser, $wgRawHtml, $wgLang;
- global $wgAllowUserCss, $wgAllowUserJs;
+ global $wgAllowUserCss, $wgAllowUserJs, $wgAjaxEditStash;
$stats = $wgOut->getContext()->getStats();
# Try to stash the edit for the final submission step
# @todo: different date format preferences cause cache misses
- ApiStashEdit::stashEditFromPreview(
- $this->getArticle(), $content, $pstContent,
- $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
- );
+ if ( $wgAjaxEditStash ) {
+ ApiStashEdit::stashEditFromPreview(
+ $this->getArticle(), $content, $pstContent,
+ $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
+ );
+ }
$parserOutput->setEditSectionTokens( false ); // no section edit links
$previewHTML = $parserOutput->getText();
* Note 2: use $this->getDB() in maintenance scripts that may be invoked by
* updater to ensure that a proper database is being updated.
*
+ * @todo Replace calls to wfGetDB with calls to LoadBalancer::getConnection()
+ * on an injected instance of LoadBalancer.
+ *
* @return DatabaseBase
*/
function wfGetDB( $db, $groups = [], $wiki = false ) {
/**
* Get a load balancer object.
*
+ * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancer()
+ * or MediaWikiServices::getDBLoadBalancerFactory() instead.
+ *
* @param string|bool $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
function wfGetLB( $wiki = false ) {
- return wfGetLBFactory()->getMainLB( $wiki );
+ if ( $wiki === false ) {
+ return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancer();
+ } else {
+ $factory = \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ return $factory->getMainLB( $wiki );
+ }
}
/**
* Get the load balancer factory object
*
+ * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
+ *
* @return LBFactory
*/
function wfGetLBFactory() {
- return LBFactory::singleton();
+ return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
}
/**
/**
* Get the appropriate HTML attributes to add to the "a" element of an interwiki link.
*
+ * @since 1.16.3
* @deprecated since 1.25
*
* @param string $title The title text for the link, URL-encoded (???) but
/**
* Get the appropriate HTML attributes to add to the "a" element of an internal link.
*
+ * @since 1.16.3
* @deprecated since 1.25
*
* @param string $title The title text for the link, URL-encoded (???) but
* Get the appropriate HTML attributes to add to the "a" element of an internal
* link, given the Title object for the page we want to link to.
*
+ * @since 1.16.3
* @deprecated since 1.25
*
* @param Title $nt
/**
* Common code for getLinkAttributesX functions
*
+ * @since 1.16.3
* @deprecated since 1.25
*
* @param string $title
/**
* Return the CSS colour of a known link
*
+ * @since 1.16.3
* @param Title $t
* @param int $threshold User defined threshold
* @return string CSS class
/**
* Identical to link(), except $options defaults to 'known'.
+ * @since 1.16.3
* @see Linker::link
* @return string
*/
* same as the other make*LinkObj static functions, despite $query not
* being used.
*
+ * @since 1.16.3
* @param Title $nt
* @param string $html [optional]
* @param string $query [optional]
}
/**
+ * @since 1.16.3
* @param LinkTarget $target
* @return LinkTarget|Title You will get back the same type you passed in, or a Title object
*/
- static function normaliseSpecialPage( LinkTarget $target ) {
+ public static function normaliseSpecialPage( LinkTarget $target ) {
if ( $target->getNamespace() == NS_SPECIAL ) {
list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $target->getDBkey() );
if ( !$name ) {
* Return the code for images which were added via external links,
* via Parser::maybeMakeExternalImage().
*
+ * @since 1.16.3
* @param string $url
* @param string $alt
*
/**
* Make a "broken" link to an image
*
+ * @since 1.16.3
* @param Title $title
* @param string $label Link label (plain text)
* @param string $query Query string
/**
* Get the URL to upload a certain file
*
+ * @since 1.16.3
* @param Title $destFile Title object of the file to upload
* @param string $query Urlencoded query string to prepend
* @return string Urlencoded URL
/**
* Create a direct link to a given uploaded file.
*
+ * @since 1.16.3
* @param Title $title
* @param string $html Pre-sanitized HTML
* @param string $time MW timestamp of file creation time
* Create a direct link to a given uploaded file.
* This will make a broken link if $file is false.
*
+ * @since 1.16.3
* @param Title $title
* @param File|bool $file File object or false
* @param string $html Pre-sanitized HTML
* a message key from the link text.
* Usage example: Linker::specialLink( 'Recentchanges' )
*
+ * @since 1.16.3
* @param string $name
* @param string $key
* @return string
/**
* Make an external link
+ * @since 1.16.3. $title added in 1.21
* @param string $url URL to link to
* @param string $text Text of link
* @param bool $escape Do we escape the link text?
* @param string $userName User name in database.
* @param string $altUserName Text to display instead of the user name (optional)
* @return string HTML fragment
- * @since 1.19 Method exists for a long time. $altUserName was added in 1.19.
+ * @since 1.16.3. $altUserName was added in 1.19.
*/
public static function userLink( $userId, $userName, $altUserName = false ) {
$classes = 'mw-userlink';
/**
* Generate standard user tool links (talk, contributions, block link, etc.)
*
+ * @since 1.16.3
* @param int $userId User identifier
* @param string $userText User name or IP address
* @param bool $redContribsWhenNoEdits Should the contributions link be
/**
* Alias for userToolLinks( $userId, $userText, true );
+ * @since 1.16.3
* @param int $userId User identifier
* @param string $userText User name or IP address
* @param int $edits User edit count (optional, for performance)
}
/**
+ * @since 1.16.3
* @param int $userId User id in database.
* @param string $userText User name in database.
* @return string HTML fragment with user talk link
}
/**
+ * @since 1.16.3
* @param int $userId Userid
* @param string $userText User name in database.
* @return string HTML fragment with block link
/**
* Generate a user link if the current user is allowed to view it
+ * @since 1.16.3
* @param Revision $rev
* @param bool $isPublic Show only if all users can see it
* @return string HTML fragment
/**
* Generate a user tool link cluster if the current user is allowed to view it
+ * @since 1.16.3
* @param Revision $rev
* @param bool $isPublic Show only if all users can see it
* @return string HTML
* auto-generated comments (from section editing) and formats [[wikilinks]].
*
* @author Erik Moeller <moeller@scireview.de>
+ * @since 1.16.3. $wikiId added in 1.26
*
* Note: there's not always a title to pass to this function.
* Since you can't set a default parameter for a reference, I've turned it
* Formats wiki links and media links in text; all other wiki formatting
* is ignored
*
+ * @since 1.16.3. $wikiId added in 1.26
* @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
+ *
* @param string $comment Text to format links in. WARNING! Since the output of this
* function is html, $comment must be sanitized for use as html. You probably want
* to pass $comment through Sanitizer::escapeHtmlAllowEntities() before calling
* Wrap a comment in standard punctuation and formatting if
* it's non-empty, otherwise return empty string.
*
+ * @since 1.16.3. $wikiId added in 1.26
* @param string $comment
* @param Title|null $title Title object (to generate link to section in autocomment) or null
* @param bool $local Whether section links should refer to local page
* Wrap and format the given revision's comment block, if the current
* user is allowed to view it.
*
+ * @since 1.16.3
* @param Revision $rev
* @param bool $local Whether section links should refer to local page
* @param bool $isPublic Show only if all users can see it
}
/**
+ * @since 1.16.3
* @param int $size
* @return string
*/
/**
* Add another level to the Table of Contents
*
+ * @since 1.16.3
* @return string
*/
public static function tocIndent() {
/**
* Finish one or more sublevels on the Table of Contents
*
+ * @since 1.16.3
* @param int $level
* @return string
*/
/**
* parameter level defines if we are on an indentation level
*
+ * @since 1.16.3
* @param string $anchor
* @param string $tocline
* @param string $tocnumber
* End a Table Of Contents line.
* tocUnindent() will be used instead if we're ending a line below
* the new level.
+ * @since 1.16.3
* @return string
*/
public static function tocLineEnd() {
/**
* Wraps the TOC in a table and provides the hide/collapse javascript.
*
+ * @since 1.16.3
* @param string $toc Html of the Table Of Contents
* @param string|Language|bool $lang Language for the toc title, defaults to user language
* @return string Full html of the TOC
/**
* Generate a table of contents from a section tree.
*
+ * @since 1.16.3. $lang added in 1.17
* @param array $tree Return value of ParserOutput::getSections()
* @param string|Language|bool $lang Language for the toc title, defaults to user language
* @return string HTML fragment
/**
* Create a headline for content
*
+ * @since 1.16.3
* @param int $level The level of the headline (1-6)
* @param string $attribs Any attributes for the headline, starting with
* a space and ending with '>'
*
* If the option noBrackets is set the rollback link wont be enclosed in []
*
+ * @since 1.16.3. $context added in 1.20. $options added in 1.21
+ *
* @param Revision $rev
* @param IContextSource $context Context to use or null for the main context.
* @param array $options
/**
* Build a raw rollback link, useful for collections of "tool" links
*
+ * @since 1.16.3. $context added in 1.20. $editCount added in 1.21
* @param Revision $rev
* @param IContextSource|null $context Context to use or null for the main context.
* @param int $editCount Number of edits that would be reverted
* directly paste it in as the link (escaping needs to be done manually).
* Finally, if $more is a Message, call toString().
*
+ * @since 1.16.3. $more added in 1.21
* @param Title[] $templates Array of templates
* @param bool $preview Whether this is for a preview
* @param bool $section Whether this is for a section edit
/**
* Returns HTML for the "hidden categories on this page" list.
*
+ * @since 1.16.3
* @param array $hiddencats Array of hidden categories from Article::getHiddenCategories
* or similar
* @return string HTML output
* Format a size in bytes for output, using an appropriate
* unit (B, KB, MB or GB) according to the magnitude in question
*
+ * @since 1.16.3
* @param int $size Size to format
* @return string
*/
* isn't always, because sometimes the accesskey needs to go on a different
* element than the id, for reverse-compatibility, etc.)
*
+ * @since 1.16.3 $msgParams added in 1.27
* @param string $name Id of the element, minus prefixes.
* @param string|null $options Null or the string 'withaccess' to add an access-
* key hint
* the id but isn't always, because sometimes the accesskey needs to go on
* a different element than the id, for reverse-compatibility, etc.)
*
+ * @since 1.16.3
* @param string $name Id of the element, minus prefixes.
* @return string Contents of the accesskey attribute (which you must HTML-
* escape), or false for no accesskey attribute
/**
* Creates a dead (show/hide) link for deleting revisions/log entries
*
+ * @since 1.16.3
* @param bool $delete Set to true to use (show/hide) rather than (show)
*
* @return string HTML text wrapped in a span to allow for customization
/**
* Returns the attributes for the tooltip and access key.
*
+ * @since 1.16.3. $msgParams introduced in 1.27
* @param string $name
* @param array $msgParams Params for constructing the message
*
/**
* Returns raw bits of HTML, use titleAttrib()
+ * @since 1.16.3
* @param string $name
* @param array|null $options
* @return null|string
<?php
namespace MediaWiki;
+use Config;
use ConfigFactory;
use EventRelayerGroup;
use GlobalVarConfig;
-use Config;
use Hooks;
+use LBFactory;
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+use LoadBalancer;
use MediaWiki\Services\ServiceContainer;
+use MWException;
+use ResourceLoader;
use SearchEngine;
use SearchEngineConfig;
use SearchEngineFactory;
*/
class MediaWikiServices extends ServiceContainer {
+ /**
+ * @var MediaWikiServices|null
+ */
+ private static $instance = null;
+
/**
* Returns the global default instance of the top level service locator.
*
* @return MediaWikiServices
*/
public static function getInstance() {
- static $instance = null;
-
- if ( $instance === null ) {
+ if ( self::$instance === null ) {
// NOTE: constructing GlobalVarConfig here is not particularly pretty,
// but some information from the global scope has to be injected here,
// even if it's just a file name or database credentials to load
// configuration from.
- $config = new GlobalVarConfig();
- $instance = new self( $config );
+ $bootstrapConfig = new GlobalVarConfig();
+ self::$instance = self::newInstance( $bootstrapConfig );
+ }
- // Load the default wiring from the specified files.
- $wiringFiles = $config->get( 'ServiceWiringFiles' );
- $instance->loadWiringFiles( $wiringFiles );
+ return self::$instance;
+ }
- // Provide a traditional hook point to allow extensions to configure services.
- Hooks::run( 'MediaWikiServices', [ $instance ] );
+ /**
+ * Replaces the global MediaWikiServices instance.
+ *
+ * @note This is for use in PHPUnit tests only!
+ *
+ * @throws MWException if called outside of PHPUnit tests.
+ *
+ * @param MediaWikiServices $services The new MediaWikiServices object.
+ *
+ * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later.
+ */
+ public static function forceGlobalInstance( MediaWikiServices $services ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( __METHOD__ . ' must not be used outside unit tests.' );
}
+ $old = self::getInstance();
+ self::$instance = $services;
+
+ return $old;
+ }
+
+ /**
+ * Creates a new instance of MediaWikiServices and sets it as the global default
+ * instance. getInstance() will return a different MediaWikiServices object
+ * after every call to resetGlobalServiceLocator().
+ *
+ * @warning This should not be used during normal operation. It is intended for use
+ * when the configuration has changed significantly since bootstrap time, e.g.
+ * during the installation process or during testing.
+ *
+ * @warning Calling resetGlobalServiceLocator() may leave the application in an inconsistent
+ * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
+ * any of the services managed by MediaWikiServices exist. If any service objects
+ * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
+ * with the operation of the services managed by the new MediaWikiServices.
+ * Operating with a mix of services created by the old and the new
+ * MediaWikiServices instance may lead to INCONSISTENCIES and even DATA LOSS!
+ * Any class implementing LAZY LOADING is especially prone to this problem,
+ * since instances would typically retain a reference to a storage layer service.
+ *
+ * @see forceGlobalInstance()
+ * @see resetGlobalInstance()
+ * @see resetBetweenTest()
+ *
+ * @param Config|null $bootstrapConfig The Config object to be registered as the
+ * 'BootstrapConfig' service. This has to contain at least the information
+ * needed to set up the 'ConfigFactory' service. If not given, the bootstrap
+ * config of the old instance of MediaWikiServices will be re-used. If there
+ * was no previous instance, a new GlobalVarConfig object will be used to
+ * bootstrap the services.
+ *
+ * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
+ * Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
+ * is defined).
+ */
+ public static function resetGlobalInstance( Config $bootstrapConfig = null ) {
+ if ( self::$instance === null ) {
+ // no global instance yet, nothing to reset
+ return;
+ }
+
+ self::failIfResetNotAllowed( __METHOD__ );
+
+ if ( $bootstrapConfig === null ) {
+ $bootstrapConfig = self::$instance->getBootstrapConfig();
+ }
+
+ self::$instance->destroy();
+
+ self::$instance = self::newInstance( $bootstrapConfig );
+ }
+
+ /**
+ * Creates a new MediaWikiServices instance and initializes it according to the
+ * given $bootstrapConfig. In particular, all wiring files defined in the
+ * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
+ *
+ * @param Config|null $bootstrapConfig The Config object to be registered as the
+ * 'BootstrapConfig' service. This has to contain at least the information
+ * needed to set up the 'ConfigFactory' service. If not provided, any call
+ * to getBootstrapConfig(), getConfigFactory, or getMainConfig will fail.
+ * A MediaWikiServices instance without access to configuration is called
+ * "primordial".
+ *
+ * @return MediaWikiServices
+ * @throws MWException
+ */
+ private static function newInstance( Config $bootstrapConfig ) {
+ $instance = new self( $bootstrapConfig );
+
+ // Load the default wiring from the specified files.
+ $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
+ $instance->loadWiringFiles( $wiringFiles );
+
+ // Provide a traditional hook point to allow extensions to configure services.
+ Hooks::run( 'MediaWikiServices', [ $instance ] );
+
return $instance;
}
+ /**
+ * Disables all storage layer services. After calling this, any attempt to access the
+ * storage layer will result in an error. Use resetGlobalInstance() to restore normal
+ * operation.
+ *
+ * @warning This is intended for extreme situations only and should never be used
+ * while serving normal web requests. Legitimate use cases for this method include
+ * the installation process. Test fixtures may also use this, if the fixture relies
+ * on globalState.
+ *
+ * @see resetGlobalInstance()
+ * @see resetChildProcessServices()
+ */
+ public static function disableStorageBackend() {
+ // TODO: also disable some Caches, JobQueues, etc
+ $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ];
+ $services = self::getInstance();
+
+ foreach ( $destroy as $name ) {
+ $services->disableService( $name );
+ }
+ }
+
+ /**
+ * Resets any services that may have become stale after a child process
+ * returns from after pcntl_fork(). It's also safe, but generally unnecessary,
+ * to call this method from the parent process.
+ *
+ * @note This is intended for use in the context of process forking only!
+ *
+ * @see resetGlobalInstance()
+ * @see disableStorageBackend()
+ */
+ public static function resetChildProcessServices() {
+ // NOTE: for now, just reset everything. Since we don't know the interdependencies
+ // between services, we can't do this more selectively at this time.
+ self::resetGlobalInstance();
+
+ // Child, reseed because there is no bug in PHP:
+ // http://bugs.php.net/bug.php?id=42465
+ mt_srand( getmypid() );
+ }
+
+ /**
+ * Resets the given service for testing purposes.
+ *
+ * @warning This is generally unsafe! Other services may still retain references
+ * to the stale service instance, leading to failures and inconsistencies. Subclasses
+ * may use this method to reset specific services under specific instances, but
+ * it should not be exposed to application logic.
+ *
+ * @note With proper dependency injection used throughout the codebase, this method
+ * should not be needed. It is provided to allow tests that pollute global service
+ * instances to clean up.
+ *
+ * @param string $name
+ * @param string $destroy Whether the service instance should be destroyed if it exists.
+ * When set to false, any existing service instance will effectively be detached
+ * from the container.
+ *
+ * @throws MWException if called outside of PHPUnit tests.
+ */
+ public function resetServiceForTesting( $name, $destroy = true ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
+ throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' );
+ }
+
+ $this->resetService( $name, $destroy );
+ }
+
+ /**
+ * Convenience method that throws an exception unless it is called during a phase in which
+ * resetting of global services is allowed. In general, services should not be reset
+ * individually, since that may introduce inconsistencies.
+ *
+ * This method will throw an exception if:
+ *
+ * - self::$resetInProgress is false (to allow all services to be reset together
+ * via resetGlobalInstance)
+ * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation)
+ * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing)
+ *
+ * This method is intended to be used to safeguard against accidentally resetting
+ * global service instances that are not yet managed by MediaWikiServices. It is
+ * defined here in the MediaWikiServices services class to have a central place
+ * for managing service bootstrapping and resetting.
+ *
+ * @param string $method the name of the caller method, as given by __METHOD__.
+ *
+ * @throws MWException if called outside bootstrap mode.
+ *
+ * @see resetGlobalInstance()
+ * @see forceGlobalInstance()
+ * @see disableStorageBackend()
+ */
+ public static function failIfResetNotAllowed( $method ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' )
+ && !defined( 'MW_PARSER_TEST' )
+ && !defined( 'MEDIAWIKI_INSTALL' )
+ && !defined( 'RUN_MAINTENANCE_IF_MAIN' )
+ && defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' )
+ ) {
+ throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' );
+ }
+ }
+
/**
* @param Config $config The Config object to be registered as the 'BootstrapConfig' service.
* This has to contain at least the information needed to set up the 'ConfigFactory'
public function __construct( Config $config ) {
parent::__construct();
- // register the given Config object as the bootstrap config service.
+ // Register the given Config object as the bootstrap config service.
$this->defineService( 'BootstrapConfig', function() use ( $config ) {
return $config;
} );
}
+ // CONVENIENCE GETTERS ////////////////////////////////////////////////////
+
/**
* Returns the Config object containing the bootstrap configuration.
* Bootstrap configuration would typically include database credentials
return $this->getService( 'SkinFactory' );
}
+ /**
+ * @return LBFactory
+ */
+ public function getDBLoadBalancerFactory() {
+ return $this->getService( 'DBLoadBalancerFactory' );
+ }
+
+ /**
+ * @return LoadBalancer The main DB load balancer for the local wiki.
+ */
+ public function getDBLoadBalancer() {
+ return $this->getService( 'DBLoadBalancer' );
+ }
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service getter here, don't forget to add a test
// case for it in MediaWikiServicesTest::provideGetters() and in
* @param string|array|MessageSpecifier $value
* @return Message
* @throws InvalidArgumentException
+ * @since 1.27
*/
public static function newFromSpecifier( $value ) {
if ( $value instanceof RawMessage ) {
use MediaWiki\MediaWikiServices;
return [
+ 'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
+ $config = $services->getMainConfig()->get( 'LBFactoryConf' );
+
+ $class = LBFactory::getLBFactoryClass( $config );
+ if ( !isset( $config['readOnlyReason'] ) ) {
+ // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
+ $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
+ }
+
+ return new $class( $config );
+ },
+
+ 'DBLoadBalancer' => function( MediaWikiServices $services ) {
+ // just return the default LB from the DBLoadBalancerFactory service
+ return $services->getDBLoadBalancerFactory()->getMainLB();
+ },
+
'SiteStore' => function( MediaWikiServices $services ) {
- $loadBalancer = wfGetLB(); // TODO: use LB from MediaWikiServices
- $rawSiteStore = new DBSiteStore( $loadBalancer );
+ $rawSiteStore = new DBSiteStore( $services->getDBLoadBalancer() );
// TODO: replace wfGetCache with a CacheFactory service.
// TODO: replace wfIsHHVM with a capabilities service.
},
'SkinFactory' => function( MediaWikiServices $services ) {
- return new SkinFactory();
+ $factory = new SkinFactory();
+
+ $names = $services->getMainConfig()->get( 'ValidSkinNames' );
+
+ foreach ( $names as $name => $skin ) {
+ $factory->register( $name, $skin, function () use ( $name, $skin ) {
+ $class = "Skin$skin";
+ return new $class( $name );
+ } );
+ }
+ // Register a hidden "fallback" skin
+ $factory->register( 'fallback', 'Fallback', function () {
+ return new SkinFallback;
+ } );
+ // Register a hidden skin for api output
+ $factory->register( 'apioutput', 'ApiOutput', function () {
+ return new SkinApi;
+ } );
+
+ return $factory;
},
///////////////////////////////////////////////////////////////////////////
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ */
+class CannotReplaceActiveServiceException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ */
+class ContainerDisabledException extends RuntimeException {
+
+ /**
+ * @param Exception|null $previous
+ */
+ public function __construct( Exception $previous = null ) {
+ parent::__construct( 'Container disabled!', 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for destructible services.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * DestructibleService defines a standard interface for shutting down a service instance.
+ * The intended use is for a service container to be able to shut down services that should
+ * no longer be used, and allow such services to release any system resources.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface DestructibleService {
+
+ /**
+ * Notifies the service object that it should expect to no longer be used, and should release
+ * any system resources it may own. The behavior of all service methods becomes undefined after
+ * destroy() has been called. It is recommended that implementing classes should throw an
+ * exception when service methods are accessed after destroy() has been called.
+ */
+ public function destroy();
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when the requested service is not known.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when the requested service is not known.
+ */
+class NoSuchServiceException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "No such service: $serviceName", 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ */
+class ServiceAlreadyDefinedException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Service already defined: $serviceName", 0, $previous );
+ }
+
+}
* @see docs/injection.txt for an overview of using dependency injection in the
* MediaWiki code base.
*/
-class ServiceContainer {
+class ServiceContainer implements DestructibleService {
/**
* @var object[]
*/
private $extraInstantiationParams;
+ /**
+ * @var boolean
+ */
+ private $destroyed = false;
+
/**
* @param array $extraInstantiationParams Any additional parameters to be passed to the
* instantiator function when creating a service. This is typically used to provide
$this->extraInstantiationParams = $extraInstantiationParams;
}
+ /**
+ * Destroys all contained service instances that implement the DestructibleService
+ * interface. This will render all services obtained from this MediaWikiServices
+ * instance unusable. In particular, this will disable access to the storage backend
+ * via any of these services. Any future call to getService() will throw an exception.
+ *
+ * @see resetGlobalInstance()
+ */
+ public function destroy() {
+ foreach ( $this->getServiceNames() as $name ) {
+ $service = $this->peekService( $name );
+ if ( $service !== null && $service instanceof DestructibleService ) {
+ $service->destroy();
+ }
+ }
+
+ $this->destroyed = true;
+ }
+
/**
* @param array $wiringFiles A list of PHP files to load wiring information from.
* Each file is loaded using PHP's include mechanism. Each file is expected to
return isset( $this->serviceInstantiators[$name] );
}
+ /**
+ * Returns the service instance for $name only if that service has already been instantiated.
+ * This is intended for situations where services get destroyed/cleaned up, so we can
+ * avoid creating a service just to destroy it again.
+ *
+ * @note This is intended for internal use and for test fixtures.
+ * Application logic should use getService() instead.
+ *
+ * @see getService().
+ *
+ * @param string $name
+ *
+ * @return object|null The service instance, or null if the service has not yet been instantiated.
+ * @throws RuntimeException if $name does not refer to a known service.
+ */
+ public function peekService( $name ) {
+ if ( !$this->hasService( $name ) ) {
+ throw new NoSuchServiceException( $name );
+ }
+
+ return isset( $this->services[$name] ) ? $this->services[$name] : null;
+ }
+
/**
* @return string[]
*/
Assert::parameterType( 'string', $name, '$name' );
if ( $this->hasService( $name ) ) {
- throw new RuntimeException( 'Service already defined: ' . $name );
+ throw new ServiceAlreadyDefinedException( $name );
}
$this->serviceInstantiators[$name] = $instantiator;
Assert::parameterType( 'string', $name, '$name' );
if ( !$this->hasService( $name ) ) {
- throw new RuntimeException( 'Service not defined: ' . $name );
+ throw new NoSuchServiceException( $name );
}
if ( isset( $this->services[$name] ) ) {
- throw new RuntimeException( 'Cannot redefine a service that is already in use: ' . $name );
+ throw new CannotReplaceActiveServiceException( $name );
}
$this->serviceInstantiators[$name] = $instantiator;
}
+ /**
+ * Disables a service.
+ *
+ * @note Attempts to call getService() for a disabled service will result
+ * in a DisabledServiceException. Calling peekService for a disabled service will
+ * return null. Disabled services are listed by getServiceNames(). A disabled service
+ * can be enabled again using redefineService().
+ *
+ * @note If the service was already active (that is, instantiated) when getting disabled,
+ * and the service instance implements DestructibleService, destroy() is called on the
+ * service instance.
+ *
+ * @see redefineService()
+ * @see resetService()
+ *
+ * @param string $name The name of the service to disable.
+ *
+ * @throws RuntimeException if $name is not a known service.
+ */
+ public function disableService( $name ) {
+ $this->resetService( $name );
+
+ $this->redefineService( $name, function() use ( $name ) {
+ throw new ServiceDisabledException( $name );
+ } );
+ }
+
+ /**
+ * Resets a service by dropping the service instance.
+ * If the service instances implements DestructibleService, destroy()
+ * is called on the service instance.
+ *
+ * @warning This is generally unsafe! Other services may still retain references
+ * to the stale service instance, leading to failures and inconsistencies. Subclasses
+ * may use this method to reset specific services under specific instances, but
+ * it should not be exposed to application logic.
+ *
+ * @note This is declared final so subclasses can not interfere with the expectations
+ * disableService() has when calling resetService().
+ *
+ * @see redefineService()
+ * @see disableService().
+ *
+ * @param string $name The name of the service to reset.
+ * @param bool $destroy Whether the service instance should be destroyed if it exists.
+ * When set to false, any existing service instance will effectively be detached
+ * from the container.
+ *
+ * @throws RuntimeException if $name is not a known service.
+ */
+ final protected function resetService( $name, $destroy = true ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ $instance = $this->peekService( $name );
+
+ if ( $destroy && $instance instanceof DestructibleService ) {
+ $instance->destroy();
+ }
+
+ unset( $this->services[$name] );
+ }
+
/**
* Returns a service object of the kind associated with $name.
* Services instances are instantiated lazily, on demand.
*
* @param string $name The service name
*
- * @throws InvalidArgumentException if $name is not a known service.
+ * @throws NoSuchServiceException if $name is not a known service.
+ * @throws ServiceDisabledException if this container has already been destroyed.
+ *
* @return object The service instance
*/
public function getService( $name ) {
+ if ( $this->destroyed ) {
+ throw new ContainerDisabledException();
+ }
+
if ( !isset( $this->services[$name] ) ) {
$this->services[$name] = $this->createService( $name );
}
array_merge( [ $this ], $this->extraInstantiationParams )
);
} else {
- throw new InvalidArgumentException( 'Unknown service: ' . $name );
+ throw new NoSuchServiceException( $name );
}
return $service;
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ *
+ * 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
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ */
+class ServiceDisabledException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Service disabled: $serviceName", 0, $previous );
+ }
+
+}
*
* @file
*/
+use MediaWiki\MediaWikiServices;
/**
* This file is not a valid entry point, perform no further processing unless
$wgSkipSkins[] = $wgSkipSkin;
}
-// Register skins
-// Use a closure to avoid leaking into global state
-call_user_func( function () use ( $wgValidSkinNames ) {
- $factory = SkinFactory::getDefaultInstance();
- foreach ( $wgValidSkinNames as $name => $skin ) {
- $factory->register( $name, $skin, function () use ( $name, $skin ) {
- $class = "Skin$skin";
- return new $class( $name );
- } );
- }
- // Register a hidden "fallback" skin
- $factory->register( 'fallback', 'Fallback', function () {
- return new SkinFallback;
- } );
- // Register a hidden skin for api output
- $factory->register( 'apioutput', 'ApiOutput', function () {
- return new SkinApi;
- } );
-} );
$wgSkipSkins[] = 'fallback';
$wgSkipSkins[] = 'apioutput';
require_once "$IP/includes/AutoLoader.php";
}
+// Reset the global service locator, so any services that have already been created will be
+// re-created while taking into account any custom settings and extensions.
+MediaWikiServices::resetGlobalInstance( new GlobalVarConfig() );
+
+// Define a constant that indicates that the bootstrapping of the service locator
+// is complete.
+define( 'MW_SERVICE_BOOTSTRAP_COMPLETE', 1 );
+
// Install a header callback to prevent caching of responses with cookies (T127993)
if ( !$wgCommandLineMode ) {
header_register_callback( function () {
// We need a dummy HTMLForm for the validate callback...
$htmlForm = new HTMLForm( [], $this );
}
- $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
- $field->mParent = $htmlForm;
+ $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
$validation = $field->validate( $value, $user->getOptions() );
break;
case 'registered-multiselect':
"Kurousagi",
"Revi",
"Yearning",
- "Priviet"
+ "Priviet",
+ "Ykhwong"
]
},
"apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|설명문서]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 메일링 리스트]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 알림 사항]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 버그 및 요청]\n</div>\n<strong>상태:</strong> 이 페이지에 보여지는 모든 기능은 정상적으로 작동하지만, API는 여전히 활발하게 개발되고 있으며, 언제든지 변경될 수 있습니다. 업데이트 공지를 받아보려면 [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 메일링 리스트]를 구독하십시오.\n\n<strong>잘못된 요청:</strong> API에 잘못된 요청이 전송되면 HTTP 헤더에서 \"MediaWiki-API-Error\" 키를 보내고, 헤더 값과 오류 코드가 같게 설정됩니다. 자세한 정보에 대해서는 [[mw:API:Errors_and_warnings|API:오류와 경고]]를 참조하십시오.\n\n<strong>테스트하기:</strong> API 요청을 테스트의 편의를 위해, [[Special:ApiSandbox]]를 보세요.",
"apihelp-createaccount-example-pass": "사용자 <kbd>testuser</kbd>를 만들고 비밀번호를 <kbd>test123</kbd>으로 설정합니다.",
"apihelp-createaccount-example-mail": "사용자 <kbd>testmailuser</kbd>를 만들고 자동 생성된 비밀번호를 이메일로 보냅니다.",
"apihelp-delete-description": "문서 삭제",
+ "apihelp-delete-param-pageid": "삭제할 문서의 ID. <var>$1title</var>과 함께 사용할 수 없습니다.",
+ "apihelp-delete-param-reason": "삭제의 이유. 설정하지 않으면 자동 생성되는 이유를 사용합니다.",
"apihelp-delete-param-unwatch": "문서를 현재 사용자의 주시문서 목록에서 제거합니다.",
"apihelp-delete-example-simple": "<kbd>Main Page</kbd>를 삭제합니다.",
"apihelp-disabled-description": "이 모듈은 해제되었습니다.",
"apihelp-feedcontributions-param-deletedonly": "삭제된 기여만 봅니다.",
"apihelp-feedcontributions-param-toponly": "최신 판인 편집만 봅니다.",
"apihelp-feedrecentchanges-param-feedformat": "피드 포맷.",
+ "apihelp-feedrecentchanges-param-invert": "선택한 항목을 제외한 모든 이름공간.",
"apihelp-feedrecentchanges-param-hideminor": "사소한 편집을 숨깁니다.",
"apihelp-feedrecentchanges-param-hidebots": "봇의 편집을 숨깁니다.",
"apihelp-feedrecentchanges-param-hideanons": "익명 사용자의 편집을 숨깁니다.",
"apihelp-feedrecentchanges-example-30days": "30일간의 최근 바뀜을 봅니다.",
"apihelp-filerevert-description": "파일을 이전 판으로 되돌립니다.",
"apihelp-filerevert-example-revert": "<kbd>Wiki.png</kbd>를 <kbd>2011-03-05T15:27:40Z</kbd> 판으로 되돌립니다.",
+ "apihelp-help-param-helpformat": "도움말 출력 포맷.",
"apihelp-import-param-xml": "업로드한 XML 파일.",
"apihelp-login-param-name": "계정 이름.",
"apihelp-login-param-password": "비밀번호.",
"apihelp-login-param-domain": "도메인 (선택).",
"apihelp-login-example-login": "로그인.",
+ "apihelp-mergehistory-param-reason": "문서 병합 이유.",
"apihelp-move-description": "문서 이동하기.",
"apihelp-move-param-reason": "제목을 변경하는 이유",
"apihelp-move-param-movetalk": "토론 문서가 존재한다면, 토론 문서도 이름을 변경해주세요.",
"apihelp-opensearch-description": "OpenSearch 프로토콜을 이용하여 위키 검색하기",
"apihelp-opensearch-param-search": "문자열 검색",
"apihelp-opensearch-param-limit": "반환할 결과의 최대 수",
+ "apihelp-opensearch-param-format": "출력 포맷.",
"apihelp-options-param-reset": "사이트 기본으로 설정 초기화",
"apihelp-options-example-reset": "모든 설정 초기화",
+ "apihelp-paraminfo-param-helpformat": "도움말 문자열 포맷.",
+ "apihelp-protect-param-reason": "보호 또는 보호 해제의 이유.",
"apihelp-protect-example-protect": "문서 보호",
"apihelp-query+allmessages-example-ipb": "<kbd>ipb-</kbd>로 시작하는 메시지를 보입니다.",
"apihelp-query+allrevisions-description": "모든 판 표시.",
"apihelp-mergehistory-param-fromid": "将被合并历史的页面的页面ID。不能与<var>$1from</var>一起使用。",
"apihelp-mergehistory-param-to": "将要合并历史的页面的标题。不能与<var>$1toid</var>一起使用。",
"apihelp-mergehistory-param-toid": "将要合并历史的页面的页面ID。不能与<var>$1to</var>一起使用。",
+ "apihelp-mergehistory-param-timestamp": "指定时间戳,决定源页面的哪些修订历史被移动到目标页面的历史中。如果省略,源页面的所有历史记录都将被合并到目标页面。",
"apihelp-mergehistory-param-reason": "历史合并的原因。",
"apihelp-mergehistory-example-merge": "将<kbd>Oldpage</kbd>的完整历史合并至<kbd>Newpage</kbd>。",
"apihelp-mergehistory-example-merge-timestamp": "将<kbd>Oldpage</kbd>直到<kbd>2015-12-31T04:37:41Z</kbd>的页面修订版本合并至<kbd>Newpage</kbd>。",
"apihelp-query+pageswithprop-param-dir": "排序的方向。",
"apihelp-query+pageswithprop-example-simple": "列出前10个使用<code>{{DISPLAYTITLE:}}</code>的页面。",
"apihelp-query+pageswithprop-example-generator": "获取有关前10个使用<code>__NOTOC__</code>的页面的额外信息。",
- "apihelp-query+prefixsearch-description": "为页面标题执行前缀搜索。\n\nDespite the similarity in names, this module is not intended to be equivalent to [[Special:PrefixIndex]]; for that, see <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> with the <kbd>apprefix</kbd> parameter. The purpose of this module is similar to <kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>: to take user input and provide the best-matching titles. Depending on the search engine backend, this might include typo correction, redirect avoidance, or other heuristics.",
+ "apihelp-query+prefixsearch-description": "执行页面标题的带前缀搜索。\n\n尽管名称类似,但此模块不等于[[Special:PrefixIndex]];详见<kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>中的<kbd>apprefix</kbd>参数。此模块的目的类似<kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>:基于用户的输入提供最佳匹配的标题。取决于搜索引擎后端,这可能包括错拼纠正、避免重定向和其他启发性行为。",
"apihelp-query+prefixsearch-param-search": "搜索字符串。",
"apihelp-query+prefixsearch-param-namespace": "搜索的名字空间。",
"apihelp-query+prefixsearch-param-limit": "要返回的结果最大数。",
if ( !$line ) {
// completely ignore this RC entry if we don't want to render it
unset( $block[$i] );
+ continue;
}
// Roll up flags
$lines[] = $line;
}
+
// Further down are some assumptions that $block is a 0-indexed array
// with (count-1) as last key. Let's make sure it is.
$block = array_values( $block );
}
/**
- * Register a new config factory function
- * Will override if it's already registered
+ * @return string[]
+ */
+ public function getConfigNames() {
+ return array_keys( $this->factoryFunctions );
+ }
+
+ /**
+ * Register a new config factory function.
+ * Will override if it's already registered.
+ * Use "*" for $name to provide a fallback config for all unknown names.
* @param string $name
- * @param callable $callback That takes this ConfigFactory as an argument
+ * @param callable|Config $callback A factory callabck that takes this ConfigFactory
+ * as an argument and returns a Config instance, or an existing Config instance.
* @throws InvalidArgumentException If an invalid callback is provided
*/
public function register( $name, $callback ) {
+ if ( $callback instanceof Config ) {
+ $instance = $callback;
+
+ // Register a callback anyway, for consistency. Note that getConfigNames()
+ // relies on $factoryFunctions to have all config names.
+ $callback = function() use ( $instance ) {
+ return $instance;
+ };
+ } else {
+ $instance = null;
+ }
+
if ( !is_callable( $callback ) ) {
throw new InvalidArgumentException( 'Invalid callback provided' );
}
+
+ $this->configs[$name] = $instance;
$this->factoryFunctions[$name] = $callback;
}
*/
public function makeConfig( $name ) {
if ( !isset( $this->configs[$name] ) ) {
- if ( !isset( $this->factoryFunctions[$name] ) ) {
+ $key = $name;
+ if ( !isset( $this->factoryFunctions[$key] ) ) {
+ $key = '*';
+ }
+ if ( !isset( $this->factoryFunctions[$key] ) ) {
throw new ConfigException( "No registered builder available for $name." );
}
- $conf = call_user_func( $this->factoryFunctions[$name], $this );
+ $conf = call_user_func( $this->factoryFunctions[$key], $this );
if ( $conf instanceof Config ) {
$this->configs[$name] = $conf;
} else {
return $this->configs[$name];
}
+
}
* @ingroup Database
*/
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\DestructibleService;
use Psr\Log\LoggerInterface;
use MediaWiki\Logger\LoggerFactory;
* An interface for generating database load balancers
* @ingroup Database
*/
-abstract class LBFactory {
+abstract class LBFactory implements DestructibleService {
+
/** @var ChronologyProtector */
protected $chronProt;
/** @var LoggerInterface */
protected $logger;
- /** @var LBFactory */
- private static $instance;
-
/** @var string|bool Reason all LBs are read-only or false if not */
protected $readOnlyReason = false;
$this->logger = LoggerFactory::getInstance( 'DBTransaction' );
}
+ /**
+ * Disables all load balancers. All connections are closed, and any attempt to
+ * open a new connection will result in a DBAccessError.
+ * @see LoadBalancer::disable()
+ */
+ public function destroy() {
+ $this->shutdown();
+ $this->forEachLBCallMethod( 'disable' );
+ }
+
/**
* Disables all access to the load balancer, will cause all database access
* to throw a DBAccessError
*/
public static function disableBackend() {
- global $wgLBFactoryConf;
- self::$instance = new LBFactoryFake( $wgLBFactoryConf );
+ MediaWikiServices::disableStorageBackend();
}
/**
* Get an LBFactory instance
*
+ * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
+ *
* @return LBFactory
*/
public static function singleton() {
- global $wgLBFactoryConf;
-
- if ( is_null( self::$instance ) ) {
- $class = self::getLBFactoryClass( $wgLBFactoryConf );
- $config = $wgLBFactoryConf;
- if ( !isset( $config['readOnlyReason'] ) ) {
- $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
- }
- self::$instance = new $class( $config );
- }
-
- return self::$instance;
+ return MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
}
/**
* Returns the LBFactory class to use and the load balancer configuration.
*
+ * @todo instead of this, use a ServiceContainer for managing the different implementations.
+ *
* @param array $config (e.g. $wgLBFactoryConf)
* @return string Class name
*/
/**
* Shut down, close connections and destroy the cached instance.
- */
- public static function destroyInstance() {
- if ( self::$instance ) {
- self::$instance->shutdown();
- self::$instance->forEachLBCallMethod( 'closeAll' );
- self::$instance = null;
- }
- }
-
- /**
- * Set the instance to be the given object
*
- * @param LBFactory $instance
+ * @deprecated since 1.27, use LBFactory::destroy()
*/
- public static function setInstance( $instance ) {
- self::destroyInstance();
- self::$instance = $instance;
+ public static function destroyInstance() {
+ self::singleton()->destroy();
}
/**
class DBAccessError extends MWException {
public function __construct() {
parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
- "This is not allowed." );
+ "This is not allowed, because database access has been disabled." );
}
}
/** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */
const POS_WAIT_TIMEOUT = 10;
+ /**
+ * @var boolean
+ */
+ private $disabled = false;
+
/**
* @param array $params Array with keys:
* - servers : Required. Array of server info structures.
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
+ * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+ *
* @param int $i Server index
* @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DatabaseBase|bool Returns false on errors
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
+ * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+ *
* @param int $i Server index
* @param string $wiki Wiki ID to open
* @return DatabaseBase
* @return DatabaseBase
*/
protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ if ( $this->disabled ) {
+ throw new DBAccessError();
+ }
+
if ( !is_array( $server ) ) {
throw new MWException( 'You must update your load-balancing configuration. ' .
'See DefaultSettings.php entry for $wgDBservers.' );
return false;
}
+ /**
+ * Disable this load balancer. All connections are closed, and any attempt to
+ * open a new connection will result in a DBAccessError.
+ *
+ * @since 1.27
+ */
+ public function disable() {
+ $this->closeAll();
+ $this->disabled = true;
+ }
+
/**
* Close all open connections
*/
if ( !$status->isOK() ) {
throw new MWException( __METHOD__ . ': unexpected DB connection error' );
}
- LBFactory::setInstance( new LBFactorySingle( [
- 'connection' => $status->value ] ) );
+
+ \MediaWiki\MediaWikiServices::resetGlobalInstance();
+ $services = \MediaWiki\MediaWikiServices::getInstance();
+
+ $connection = $status->value;
+ $services->redefineService( 'DBLoadBalancerFactory', function() use ( $connection ) {
+ return new LBFactorySingle( [
+ 'connection' => $connection ] );
+ } );
+
}
/**
*/
abstract public function showStatusMessage( Status $status );
+ /**
+ * Constructs a Config object that contains configuration settings that should be
+ * overwritten for the installation process.
+ *
+ * @since 1.27
+ *
+ * @param Config $baseConfig
+ *
+ * @return Config The config to use during installation.
+ */
+ public static function getInstallerConfig( Config $baseConfig ) {
+ $configOverrides = new HashConfig();
+
+ // disable (problematic) object cache types explicitly, preserving all other (working) ones
+ // bug T113843
+ $emptyCache = [ 'class' => 'EmptyBagOStuff' ];
+
+ $objectCaches = [
+ CACHE_NONE => $emptyCache,
+ CACHE_DB => $emptyCache,
+ CACHE_ANYTHING => $emptyCache,
+ CACHE_MEMCACHED => $emptyCache,
+ ] + $baseConfig->get( 'ObjectCaches' );
+
+ $configOverrides->set( 'ObjectCaches', $objectCaches );
+
+ // Load the installer's i18n.
+ $messageDirs = $baseConfig->get( 'MessagesDirs' );
+ $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
+
+ $configOverrides->set( 'MessagesDirs', $messageDirs );
+
+ return new MultiConfig( [ $configOverrides, $baseConfig ] );
+ }
+
/**
* Constructor, always call this from child classes.
*/
public function __construct() {
- global $wgMessagesDirs, $wgUser;
+ global $wgMemc, $wgUser;
+
+ $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
+ $installerConfig = self::getInstallerConfig( $defaultConfig );
+
+ // Reset all services and inject config overrides
+ MediaWiki\MediaWikiServices::resetGlobalInstance( $installerConfig );
// Don't attempt to load user language options (T126177)
// This will be overridden in the web installer with the user-specified language
RequestContext::getMain()->setLanguage( 'en' );
// Disable the i18n cache
+ // TODO: manage LocalisationCache singleton in MediaWikiServices
Language::getLocalisationCache()->disableBackend();
- // Disable LoadBalancer and wfGetDB etc.
- LBFactory::disableBackend();
+
+ // Disable all global services, since we don't have any configuration yet!
+ MediaWiki\MediaWikiServices::disableStorageBackend();
// Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
// SqlBagOStuff will then throw since we just disabled wfGetDB)
- $GLOBALS['wgMemc'] = new EmptyBagOStuff;
- ObjectCache::clear();
- $emptyCache = [ 'class' => 'EmptyBagOStuff' ];
- // disable (problematic) object cache types explicitly, preserving all other (working) ones
- // bug T113843
- $GLOBALS['wgObjectCaches'] = [
- CACHE_NONE => $emptyCache,
- CACHE_DB => $emptyCache,
- CACHE_ANYTHING => $emptyCache,
- CACHE_MEMCACHED => $emptyCache,
- ] + $GLOBALS['wgObjectCaches'];
-
- // Load the installer's i18n.
- $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
+ $wgMemc = ObjectCache::getInstance( CACHE_NONE );
// Having a user with id = 0 safeguards us from DB access via User::loadOptions().
$wgUser = User::newFromId( 0 );
"config-ctype": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/ctype.installation.php phần mở rộng Ctype].",
"config-iconv": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/iconv.installation.php phần mở rộng iconv].",
"config-json": "<strong>Lỗi chí tử:</strong> PHP được biên dịch mà không có hỗ trợ cho JSON.\nBạn phải cài đặt hoặc phần mở rộng JSON PHP hoặc phần mở rộng [http://pecl.php.net/package/jsonc PECL jsonc] trước khi cài đặt MediaWiki.\n* Phần mở rộng PHP có sẵn trong Red Hat Enterprise Linux (CentOS) 5 và 6 nhưng phải được kích hoạt trong <code>/etc/php.ini</code> hoặc <code>/etc/php.d/json.ini</code>.\n* Một số phiên bản Linux được phát hành sau tháng 5 năm 2013 bỏ qua phần mở rộng PHP và gói lại phần mở rộng PECL là <code>php5-json</code> hoặc <code>php-pecl-jsonc</code> thay thế.",
+ "config-mbstring-absent": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/mbstring.setup.php phần mở rộng mbstring].",
"config-xcache": "[http://xcache.lighttpd.net/ XCache] đã được cài đặt",
"config-apc": "[http://www.php.net/apc APC] đã được cài đặt",
"config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] đã được cài đặt",
chemical/x-mdl-rxnfile rxn
chemical/x-mdl-rdfile rd
chemical/x-mdl-rgfile rg
+application/x-amf amf
+application/sla stl
/**
* Wrapper around wfMessage that sets the current context.
*
+ * @since 1.16
* @return Message
* @see wfMessage
*/
* @ingroup SpecialPage
*/
class SpecialBlock extends FormSpecialPage {
- /** @var User User to be blocked, as passed either by parameter (url?wpTarget=Foo)
+ /** @var User|string|null User to be blocked, as passed either by parameter (url?wpTarget=Foo)
* or as subpage (Special:Block/Foo) */
protected $target;
$otherBlockMessages = [];
if ( $this->target !== null ) {
+ $targetName = $this->target;
+ if ( $this->target instanceof User ) {
+ $targetName = $this->target->getName();
+ }
# Get other blocks, i.e. from GlobalBlocking or TorBlock extension
- Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $this->target ] );
+ Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $targetName ] );
if ( count( $otherBlockMessages ) ) {
$s = Html::rawElement(
'不采声' => '不采聲',
'不采聲' => '不采聲',
'不锈钢' => '不鏽鋼',
+'不食乾腊' => '不食乾腊',
'不食干腊' => '不食乾腊',
'不斗' => '不鬥',
'丑三' => '丑三',
'丰容' => '丰容',
'丰情' => '丰情',
'丰标' => '丰標',
+'丰標' => '丰標',
'丰标不凡' => '丰標不凡',
'丰標不凡' => '丰標不凡',
'丰神' => '丰神',
'么爷' => '么爺',
'么雞' => '么雞',
'么么小丑' => '么麼小丑',
+'么麼小丑' => '么麼小丑',
'之一只' => '之一只',
'之二只' => '之二只',
'之八九只' => '之八九只',
'于仁泰' => '于仁泰',
'于仲文' => '于仲文',
'于佳卉' => '于佳卉',
+'于來山' => '于來山',
'于来山' => '于來山',
'于伟国' => '于偉國',
'于偉國' => '于偉國',
'于再清' => '于再清',
'于冕' => '于冕',
'于冠华' => '于冠華',
+'于冠華' => '于冠華',
'于凌奎' => '于凌奎',
'于凌辰' => '于凌辰',
'于勒' => '于勒',
'于化虎' => '于化虎',
'于占元' => '于占元',
'于友泽' => '于友澤',
+'于友澤' => '于友澤',
'于台烟' => '于台煙',
'于台煙' => '于台煙',
'于右任' => '于右任',
'于吉' => '于吉',
'于和伟' => '于和偉',
+'于和偉' => '于和偉',
'于品海' => '于品海',
'于国桢' => '于國楨',
'于國楨' => '于國楨',
'于大宝' => '于大寶',
'于大寶' => '于大寶',
'于天仁' => '于天仁',
+'于天龍' => '于天龍',
'于天龙' => '于天龍',
'于奇库杜克' => '于奇庫杜克',
'于奇庫杜克' => '于奇庫杜克',
'于家堡' => '于家堡',
'于寘' => '于寘',
'于宝轩' => '于寶軒',
+'于寶軒' => '于寶軒',
'于小伟' => '于小偉',
'于小偉' => '于小偉',
'于小彤' => '于小彤',
'于志宁' => '于志寧',
'于志寧' => '于志寧',
'于忠肃集' => '于忠肅集',
+'于忠肅集' => '于忠肅集',
'于思' => '于思',
'于慎行' => '于慎行',
'于慧' => '于慧',
'于格' => '于格',
'于枫' => '于楓',
'于楓' => '于楓',
+'于榮光' => '于榮光',
'于荣光' => '于榮光',
'于樂' => '于樂',
'于树洁' => '于樹潔',
'于西翰' => '于西翰',
'于謙' => '于謙',
'于谦' => '于謙',
+'于謹' => '于謹',
'于谨' => '于謹',
'于貝爾' => '于貝爾',
'于贝尔' => '于貝爾',
'于双戈' => '于雙戈',
'于雙戈' => '于雙戈',
'于云鹤' => '于雲鶴',
+'于雲鶴' => '于雲鶴',
'于震' => '于震',
'于震寰' => '于震寰',
'于震环' => '于震環',
'伊斯兰历' => '伊斯蘭曆',
'伊斯兰历史' => '伊斯蘭歷史',
'伊东怜' => '伊東怜',
+'伊東怜' => '伊東怜',
'伊尔汗历表' => '伊爾汗曆表',
'伊达里子' => '伊達里子',
'伊适杰' => '伊適杰',
+'伊適杰' => '伊適杰',
'伊里布' => '伊里布',
'伊郁' => '伊鬱',
'伏几' => '伏几',
'余光中' => '余光中',
'余光生' => '余光生',
'余力为' => '余力為',
+'余力為' => '余力為',
'余威德' => '余威德',
'余子明' => '余子明',
'余思敏' => '余思敏',
'并吞' => '併吞',
'并拢' => '併攏',
'并案' => '併案',
-'并流' => '併流',
'并火' => '併火',
'并为一家' => '併為一家',
'并为一体' => '併為一體',
'俭仆' => '儉僕',
'俭朴' => '儉樸',
'俭确之教' => '儉确之教',
+'儉确之教' => '儉确之教',
'儒略改革历' => '儒略改革曆',
'儒略改革历史' => '儒略改革歷史',
'儒略历' => '儒略曆',
'党姓' => '党姓',
'党家' => '党家',
'党怀英' => '党懷英',
+'党懷英' => '党懷英',
'党进' => '党進',
+'党進' => '党進',
'党項' => '党項',
'党项' => '党項',
'内脏' => '內臟',
'公仔面' => '公仔麵',
'公仆' => '公僕',
'公孙丑' => '公孫丑',
+'公寓里' => '公寓裡',
+'公寓里弄' => '公寓里弄',
'公干' => '公幹',
'公历' => '公曆',
'公历史' => '公歷史',
'六天后' => '六天後',
'六年' => '六年',
'六楼后座' => '六樓后座',
+'六樓后座' => '六樓后座',
'六谷' => '六穀',
'六扎' => '六紮',
'六冲' => '六衝',
'几椅' => '几椅',
'几榻' => '几榻',
'几净窗明' => '几淨窗明',
+'几淨窗明' => '几淨窗明',
'几筵' => '几筵',
'几面上' => '几面上',
'凶征' => '凶徵',
'分钟里' => '分鐘裡',
'刑余' => '刑餘',
'划一桨' => '划一槳',
+'划一槳' => '划一槳',
'划上' => '划上',
'划下' => '划下',
'划不來' => '划不來',
'划不来' => '划不來',
'划了一会' => '划了一會',
+'划了一會' => '划了一會',
'划來划去' => '划來划去',
'划来划去' => '划來划去',
'划具' => '划具',
'划槳' => '划槳',
'划水' => '划水',
'划着独木舟' => '划着獨木舟',
+'划着獨木舟' => '划着獨木舟',
'划着竹筏' => '划着竹筏',
'划着船' => '划着船',
'划算' => '划算',
'刘佳怜' => '劉佳怜',
'劉佳怜' => '劉佳怜',
'刘芸后' => '劉芸后',
+'劉芸后' => '劉芸后',
'力拼' => '力拚',
'力拼众敌' => '力拼眾敵',
'力争上游' => '力爭上遊',
'叶恭弘' => '叶恭弘',
'叶音' => '叶音',
'叶韵' => '叶韻',
+'叶韻' => '叶韻',
'吃板刀面' => '吃板刀麵',
'吃碗面' => '吃碗麵',
'吃姜' => '吃薑',
'启发式' => '啟發式',
'啷当' => '啷噹',
'喂了一声' => '喂了一聲',
+'喂了一聲' => '喂了一聲',
'喂喂' => '喂喂',
'喂哟' => '喂喲',
+'喂喲' => '喂喲',
'喂!' => '喂!',
'喂,' => '喂,',
'善于' => '善於',
'向慕' => '嚮慕',
'向迩' => '嚮邇',
'严云农' => '嚴云農',
+'嚴云農' => '嚴云農',
'严于' => '嚴於',
'嚼谷' => '嚼穀',
'啰啰苏苏' => '囉囉囌囌',
'地志' => '地誌',
'地丑德齐' => '地醜德齊',
'坏于' => '坏於',
+'坏於' => '坏於',
'坐如钟' => '坐如鐘',
'坐台' => '坐檯',
'坐钟' => '坐鐘',
'夏游' => '夏遊',
'外强中干' => '外強中乾',
'外制' => '外製',
+'外面包' => '外面包',
'多半只' => '多半只',
'多只包括' => '多只包括',
'多只可' => '多只可',
'寺钟' => '寺鐘',
'封后' => '封后',
'封为后' => '封為后',
+'封為后' => '封為后',
'封面里' => '封面裡',
'射雕' => '射鵰',
'专向往' => '專向往',
'对准钟' => '對準鐘',
'对准钟表' => '對準鐘錶',
'对着干' => '對着幹',
+'對着幹' => '對着幹',
'对华发' => '對華發',
'对表中' => '對表中',
'对表扬' => '對表揚',
'尸子' => '尸子',
'尸居余气' => '尸居餘氣',
'尸弃佛' => '尸棄佛',
+'尸棄佛' => '尸棄佛',
'尸祝' => '尸祝',
+'尸祿' => '尸祿',
'尸禄' => '尸祿',
'尸罗精舍' => '尸羅精舍',
'尸羅精舍' => '尸羅精舍',
'尸臣' => '尸臣',
'尸谏' => '尸諫',
'尸魂界' => '尸魂界',
+'尸鳩' => '尸鳩',
'尸鸠' => '尸鳩',
'局促不安' => '局促不安',
'局里' => '局裡',
'帘子' => '帘子',
'帘布' => '帘布',
'帝后台' => '帝后臺',
+'帝后臺' => '帝后臺',
'师范' => '師範',
'席卷' => '席捲',
'带征' => '帶徵',
'張杰' => '張杰',
'张柏芝' => '張栢芝',
'张乐于张徐' => '張樂于張徐',
+'張樂于張徐' => '張樂于張徐',
'强制' => '強制',
'强制作用' => '強制作用',
'强奸' => '強姦',
'心系世' => '心繫世',
'心系中' => '心繫中',
'心系乔' => '心繫乔',
+'心繫乔' => '心繫乔',
'心系五' => '心繫五',
'心系京' => '心繫京',
'心系人' => '心繫人',
'心系全' => '心繫全',
'心系两' => '心繫兩',
'心系农' => '心繫农',
+'心繫农' => '心繫农',
'心系功' => '心繫功',
'心系动' => '心繫動',
'心系募' => '心繫募',
'欲障' => '慾障',
'忧郁' => '憂鬱',
'凭几' => '憑几',
+'憑几' => '憑几',
'凭吊' => '憑弔',
'凭折' => '憑摺',
'凭准' => '憑準',
'所占卜' => '所占卜',
'所占星' => '所占星',
'所占算' => '所占算',
+'所有只' => '所有只',
'所托' => '所託',
'扁拟谷盗虫' => '扁擬穀盜蟲',
'手塚治虫' => '手塚治虫',
'扑作教刑' => '扑作教刑',
'扑打' => '扑打',
'扑挞' => '扑撻',
+'扑撻' => '扑撻',
'打干哕' => '打乾噦',
'打出吊入' => '打出弔入',
'打卡钟' => '打卡鐘',
'搏斗' => '搏鬥',
'捣鬼吊白' => '搗鬼弔白',
'扼肮' => '搤肮',
+'搤肮' => '搤肮',
'扼肮拊背' => '搤肮拊背',
'搬斗' => '搬鬥',
'搭干铺' => '搭乾鋪',
'扑咚咚' => '撲鼕鼕',
'擀面' => '擀麵',
'击扑' => '擊扑',
+'擊扑' => '擊扑',
'击钟' => '擊鐘',
'操作钟' => '操作鐘',
'担仔面' => '擔仔麵',
'数字钟' => '數字鐘',
'数字钟表' => '數字鐘錶',
'数罪并罚' => '數罪併罰',
-'数与虏确' => '數與虜确',
'数只' => '數隻',
'文丑' => '文丑',
'文学志' => '文學誌',
'旁注' => '旁註',
'旅游' => '旅遊',
'旋回' => '旋迴',
+'旋松' => '旋鬆',
'族里' => '族裡',
'日心历表' => '日心曆表',
'日历' => '日曆',
'武后' => '武后',
'武斗' => '武鬥',
'岁聿云暮' => '歲聿云暮',
+'歲聿云暮' => '歲聿云暮',
'历史里' => '歷史裡',
'归并' => '歸併',
'归于' => '歸於',
'气冲斗牛' => '氣沖斗牛',
'气郁' => '氣鬱',
'氤郁' => '氤鬱',
+'水并流' => '水併流',
'水来汤里去' => '水來湯裡去',
'水准' => '水準',
'水无怜奈' => '水無怜奈',
+'水無怜奈' => '水無怜奈',
'水表示' => '水表示',
'水表面' => '水表面',
'水里' => '水裡',
'永志不忘' => '永誌不忘',
'求知欲' => '求知慾',
'求签' => '求籤',
+'江并流' => '江併流',
'池里' => '池裡',
'污蔑' => '污衊',
'汤卤' => '汤滷',
+'汤滷' => '汤滷',
'汲于' => '汲於',
'决斗' => '決鬥',
'沈淀' => '沈澱',
'洒洒' => '洒洒',
'洒淅' => '洒淅',
'洒涤' => '洒滌',
+'洒滌' => '洒滌',
'洒濯' => '洒濯',
'洒然' => '洒然',
'洒脱' => '洒脫',
'涂敏恒' => '涂敏恆',
'涂泽民' => '涂澤民',
'涂澤民' => '涂澤民',
+'涂紹煃' => '涂紹煃',
'涂绍煃' => '涂紹煃',
'涂羽卿' => '涂羽卿',
'涂謹申' => '涂謹申',
'漓湘' => '灕湘',
'漓然' => '灕然',
'滩涂' => '灘涂',
+'灘涂' => '灘涂',
'滩席' => '灘蓆',
'火并非' => '火並非',
'火并' => '火併',
'照入签' => '照入籤',
'照相干片' => '照相乾片',
'煨干' => '煨乾',
+'煮制' => '煮製',
'煮面' => '煮麵',
'熊杰' => '熊杰',
'荧郁' => '熒鬱',
+'炖制' => '燉製',
'燎发' => '燎髮',
'烧干' => '燒乾',
'燕几' => '燕几',
'特制' => '特製',
'牵一发' => '牽一髮',
'牵系' => '牽繫',
+'犖确' => '犖确',
'荦确' => '犖确',
'狂并潮' => '狂併潮',
'狃于' => '狃於',
'狱里' => '獄裡',
'奖杯' => '獎盃',
'独裁制' => '獨裁制',
+'獨裁制' => '獨裁制',
'独辟蹊径' => '獨闢蹊徑',
'获匪其丑' => '獲匪其醜',
'兽欲' => '獸慾',
'眼睛里' => '眼睛裡',
'眼里' => '眼裡',
'着眼于' => '着眼於',
+'着眼於' => '着眼於',
'困乏' => '睏乏',
'困了' => '睏了',
'困倦' => '睏倦',
'磨蝎' => '磨蝎',
'磨制' => '磨製',
'磨炼' => '磨鍊',
+'磨面' => '磨麵',
'磬钟' => '磬鐘',
'硗确' => '磽确',
+'磽确' => '磽确',
'砻谷' => '礱穀',
'示范' => '示範',
'社里' => '社裡',
'秋游' => '秋遊',
'种丹妮' => '种丹妮',
'种师中' => '种師中',
+'种師中' => '种師中',
'种师道' => '种師道',
+'种師道' => '种師道',
'种放' => '种放',
'科尼亚克期' => '科尼亞克期',
'科斗' => '科斗',
'精准' => '精準',
'精致' => '精緻',
'精制' => '精製',
-'精炼' => '精鍊',
'精辟' => '精闢',
'精松' => '精鬆',
'糊里糊涂' => '糊裡糊塗',
'系里' => '系裡',
'纪历' => '紀曆',
'纪历史' => '紀歷史',
+'紅后假說' => '紅后假說',
'红后假说' => '紅后假說',
'红绳系足' => '紅繩繫足',
'红钟' => '紅鐘',
'经有云' => '經有云',
'综合征' => '綜合徵',
'绿发' => '綠髮',
+'维系统' => '維系統',
'维系' => '維繫',
'绾发' => '綰髮',
'纲鉴' => '綱鑑',
'总数只' => '總數只',
'总数里' => '總數裡',
'总裁制' => '總裁制',
+'總裁制' => '總裁制',
'繁复' => '繁複',
'繁钟' => '繁鐘',
'绷扒吊拷' => '繃扒弔拷',
'系于' => '繫於',
'系于一发' => '繫於一髮',
'系着' => '繫着',
+'繫着' => '繫着',
'系结' => '繫結',
'系紧' => '繫緊',
'系绳' => '繫繩',
'聊斋志异' => '聊齋志異',
'圣人历' => '聖人曆',
'圣后' => '聖后',
+'聖后' => '聖后',
'圣马尔谷日' => '聖馬爾谷日',
'聖馬爾谷日' => '聖馬爾谷日',
'聘雇' => '聘僱',
'背地里' => '背地裡',
'胎发' => '胎髮',
'胜肽' => '胜肽',
+'胜鍵' => '胜鍵',
'胜键' => '胜鍵',
'胡云' => '胡云',
'胡子婴' => '胡子嬰',
'脱发' => '脫髮',
'脺脏' => '脺臟',
'脾脏' => '脾臟',
-'腊之以为饵' => '腊之以為餌',
'腊味' => '腊味',
'腊毒' => '腊毒',
'腊笔' => '腊筆',
'腌臜' => '腌臢',
+'腌臢' => '腌臢',
'肾脏' => '腎臟',
'腐干' => '腐乾',
'腐余' => '腐餘',
'脑干' => '腦幹',
'腰里' => '腰裡',
'脚注' => '腳註',
-'脚炼' => '腳鍊',
'肠脏' => '腸臟',
'胶卷' => '膠捲',
'膨松' => '膨鬆',
'草席' => '草蓆',
'荐居' => '荐居',
'荐臻' => '荐臻',
+'荐饑' => '荐饑',
'荐饥' => '荐饑',
'荷花淀' => '荷花澱',
'庄里' => '莊裡',
'落腮胡' => '落腮鬍',
'落发' => '落髮',
'叶叶琴' => '葉叶琴',
+'葉叶琴' => '葉叶琴',
'叶叶琹' => '葉叶琹',
+'葉叶琹' => '葉叶琹',
'叶阳后' => '葉陽后',
'葉陽后' => '葉陽后',
'葡萄干' => '葡萄乾',
'冲然' => '衝然',
'冲盹' => '衝盹',
'冲着' => '衝着',
+'衝着' => '衝着',
'冲破' => '衝破',
'冲程' => '衝程',
'冲突' => '衝突',
'言云' => '言云',
'言大而夸' => '言大而夸',
'言里' => '言裡',
-'言辩而确' => '言辯而确',
'订制' => '訂製',
'计划' => '計劃',
'计时表' => '計時錶',
'托了' => '託了',
'托事' => '託事',
'托交' => '託交',
-'托人' => '託人',
'托付' => '託付',
'托克逊' => '託克遜',
'托儿' => '託兒',
'托过' => '託過',
'托里县' => '託里縣',
'托附' => '託附',
+'許愿起經' => '許愿起經',
'许愿起经' => '許愿起經',
'許聖杰' => '許聖杰',
'注上' => '註上',
'谋干' => '謀幹',
'謝杰' => '謝杰',
'谢杰' => '謝杰',
+'謝華后' => '謝華后',
'谢华后' => '謝華后',
'谬采虚声' => '謬採虛聲',
'谬赞' => '謬讚',
'豔后' => '豔后',
'象征' => '象徵',
'贪欲' => '貪慾',
+'貴价' => '貴价',
'贵价' => '貴价',
'貴子里' => '貴子里',
'贵干' => '貴幹',
'赋范' => '賦范',
'质数里' => '質數裡',
'质朴' => '質樸',
+'賭后' => '賭后',
'赌后' => '賭后',
'赌台' => '賭檯',
'赌斗' => '賭鬥',
'赶制' => '趕製',
'赶面棍' => '趕麵棍',
'赵威后' => '趙威后',
+'趙威后' => '趙威后',
'赵惠后' => '趙惠后',
+'趙惠后' => '趙惠后',
'赵治勋' => '趙治勳',
'趱干' => '趲幹',
'足于' => '足於',
'鉴识' => '鑑識',
'鉴赏' => '鑑賞',
'鉴于' => '鑒於',
+'長几' => '長几',
'长几' => '長几',
'长于' => '長於',
'长历' => '長曆',
'闯荡' => '闖蕩',
'闯炼' => '闖鍊',
'关系' => '關係',
-'关弓与我确' => '關弓與我确',
'关于' => '關於',
'辟佛' => '闢佛',
'辟作' => '闢作',
'随于' => '隨於',
'隐占' => '隱佔',
'隐几' => '隱几',
+'隱几' => '隱几',
'隐于' => '隱於',
'只字' => '隻字',
'只影' => '隻影',
'双折射' => '雙折射',
'双折' => '雙摺',
'双胜类' => '雙胜類',
+'雙胜類' => '雙胜類',
'双雕' => '雙鵰',
'杂合面儿' => '雜合麵兒',
'杂志' => '雜誌',
'难于' => '難於',
'雨蒙蒙' => '雨濛濛',
'雪窗萤几' => '雪窗螢几',
+'雪窗螢几' => '雪窗螢几',
'雪里' => '雪裡',
'雪里红' => '雪裡紅',
'雪里蕻' => '雪裡蕻',
'显示钟' => '顯示鐘',
'显示钟表' => '顯示鐘錶',
'风干' => '風乾',
+'風后' => '風后',
'风后' => '風后',
'风土志' => '風土誌',
'风后,' => '風後,',
'发胶' => '髮膠',
'发菜' => '髮菜',
'发蜡' => '髮蠟',
+'髮踊沖冠' => '髮踊沖冠',
'发踊冲冠' => '髮踴沖冠',
'发辫' => '髮辮',
'发钗' => '髮釵',
'斗劲' => '鬥勁',
'斗勇' => '鬥勇',
'斗胜' => '鬥勝',
-'斗口' => '鬥口',
'斗合' => '鬥合',
'斗嘴' => '鬥嘴',
'斗地主' => '鬥地主',
'鲜于' => '鮮于',
'鲸须' => '鯨鬚',
'鳥栖' => '鳥栖',
+'鳥栖市' => '鳥栖市',
'鸟栖市' => '鳥栖市',
'凤梨干' => '鳳梨乾',
'鸣钟' => '鳴鐘',
'鹤发' => '鶴髮',
'鸾鉴' => '鸞鑑',
'鹰雕' => '鹰鵰',
+'鹰鵰' => '鹰鵰',
'咸、甜' => '鹹、甜',
'咸味' => '鹹味',
'咸嘴淡舌' => '鹹嘴淡舌',
'麹霉' => '麴黴',
'面人儿' => '麵人兒',
'面包' => '麵包',
+'面团' => '麵團',
'面坊' => '麵坊',
'面坯儿' => '麵坯兒',
'面塑' => '麵塑',
'面筋' => '麵筋',
'面粉' => '麵粉',
'面糊' => '麵糊',
-'面团' => '麵糰',
'面缸' => '麵缸',
'面茶' => '麵茶',
'面制品' => '麵製品',
'乾上乾下' => '乾上乾下',
'乾东' => '乾东',
'乾東' => '乾东',
+'乾为天' => '乾为天',
'乾為天' => '乾为天',
+'乾为阳' => '乾为阳',
'乾為陽' => '乾为阳',
'乾九' => '乾九',
'乾乾' => '乾乾',
'藉詞' => '借词',
'傒倖' => '傒倖',
'先名後姓' => '先名后姓',
+'兒宽' => '兒宽',
'兒寬' => '兒宽',
'六么' => '六幺',
'蘭質薰心' => '兰质薰心',
'同陞和' => '同升和',
'名著' => '名著',
'吳克羣' => '吴克羣',
+'吴克羣' => '吴克羣',
'周易乾' => '周易乾',
'諠譁' => '喧哗',
'回覆' => '回复',
'跼促' => '局促',
'侷限' => '局限',
'跼限' => '局限',
+'山崎闇斋' => '山崎闇斋',
'山崎闇齋' => '山崎闇斋',
'岳託' => '岳讬',
'巨著' => '巨著',
'么謙' => '幺谦',
'么麼' => '幺麽',
'么麽' => '幺麽',
+'幺麽' => '幺麽',
'么麽小丑' => '幺麽小丑',
'慶餘' => '庆馀',
'康乾' => '康乾',
'於崇文' => '於崇文',
'於志賀' => '於志贺',
'於志贺' => '於志贺',
+'於戏' => '於戏',
'於戲' => '於戏',
'於梨华' => '於梨华',
'於梨華' => '於梨华',
'於氏' => '於氏',
'於潜' => '於潜',
'於潛縣' => '於潜县',
+'於潜县' => '於潜县',
'於祥玉' => '於祥玉',
'於菟' => '於菟',
'於賢德' => '於贤德',
'論著' => '论著',
'譯著' => '译著',
'謝肇淛' => '谢肇淛',
+'谢肇淛' => '谢肇淛',
'象乾' => '象乾',
'躊躇滿志' => '踌躇滿志',
'較著' => '较著',
'电线杆' => '電線桿',
'电脑程序' => '電腦程式',
'计算机程序' => '電腦程式',
+'电脑网络' => '電腦網路',
+'電腦網絡' => '電腦網路',
'荷尔斯泰因' => '霍爾斯坦',
'荷爾斯泰因' => '霍爾斯坦',
'面包着' => '面包著',
'克羅埃西亞' => '克羅地亞',
'奈洛比' => '內羅畢',
'公布' => '公佈',
+'公寓里' => '公寓裏',
'冒著' => '冒着',
'冒著作' => '冒著作',
'冒著名' => '冒著名',
'都卜勒' => '多普勒',
'多明尼加' => '多米尼加',
'大姊' => '大姐',
+'大姊姊' => '大姐姐',
'天份' => '天分',
'夾著' => '夹着',
'夾著書' => '夹著书',
'著處' => '着处',
'著她' => '着她',
'著妳' => '着妳',
-'著姓' => '着姓',
'著它' => '着它',
'著定' => '着定',
'著實' => '着实',
"listgrouprights-namespaceprotection-restrictedto": "Правы, якія дазваляюць удзельніку рэдагаваць",
"listgrants": "Дазволы",
"listgrants-grant": "Дазвол",
+ "listgrants-rights": "Правы",
"trackingcategories": "Катэгорыі, якія патрабуюць увагі",
"trackingcategories-summary": "На гэтай старонцы пералічаныя катэгорыя, які патрабуюць увагі і якія аўтаматычна запаўняюцца праграмным забесьяпчэньнем MediaWiki. Іх назвы могуць быць зьмененыя рэдагаваньнем сыстэмных паведамленьняў у прасторы назваў {{ns:8}}.",
"trackingcategories-msg": "Катэгорыя, якая патрабуе ўвагі",
"tags-edit-chosen-no-results": "Не знойдзена бірак, якія б адпавядалі запыту",
"tags-edit-reason": "Прычына:",
"tags-edit-nooldid-title": "Недапушчальная мэтавая версія",
+ "tags-edit-nooldid-text": "Вы або не пазначылі мэтавую версію для выканання гэтай функцыі, або пазначаная версія не існуе.",
+ "tags-edit-none-selected": "Калі ласка, выберыце прынамсі адну бірку для дадання ці выдалення.",
"comparepages": "Параўнанне старонак",
"compare-page1": "Старонка 1",
"compare-page2": "Старонка 2",
"compare-revision-not-exists": "Паказанай вамі версіі не існуе.",
"dberr-problems": "Прабачце, на пляцоўцы здарыліся тэхнічныя цяжкасці.",
"dberr-again": "Паспрабуйце перачытаць праз некалькі хвілін.",
- "dberr-info": "(Немагчыма звязацца з серверам баз даных: $1)",
- "dberr-info-hidden": "(Немагчыма звязацца з серверам базы звестак)",
+ "dberr-info": "(Немагчыма звязацца з базай даных: $1)",
+ "dberr-info-hidden": "(Немагчыма звязацца з базай звестак)",
"dberr-usegoogle": "Тымчасам можна паспрабаваць пошук праз Гугл.",
"dberr-outofdate": "Заўважце, што тамтэйшыя індэксы тутэйшага зместу могуць быць састарэлымі.",
"dberr-cachederror": "Гэта копія старонкі, узятая з кэшу, і, магчыма, састарэлая.",
"htmlform-cloner-create": "Дадаць яшчэ",
"htmlform-cloner-delete": "Сцерці",
"htmlform-cloner-required": "Неабходна хаця б адно значэнне.",
+ "htmlform-title-badnamespace": "[[:$1]] не ў прасторы назваў \"{{ns:$2}}\".",
"htmlform-title-not-exists": "$1 не існуе.",
"htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
"sqlite-has-fts": "$1 з падтрымкай поўна-тэкставага пошуку",
"currentrev-asof": "Revisió de $1",
"revisionasof": "Revisió del $1",
"revision-info": "La revisió el $1 per {{GENDER:$6|$2}}$7",
- "previousrevision": "←Versió més antiga",
- "nextrevision": "Versió més nova→",
+ "previousrevision": "← Versió més antiga",
+ "nextrevision": "Versió més nova →",
"currentrevisionlink": "Versió actual",
"cur": "act",
"next": "seg",
"feedback-useragent": "Uživatelský agent:",
"searchsuggest-search": "Hledat",
"searchsuggest-containing": "obsahující…",
+ "api-error-autoblocked": "Vaše IP adresa byla automaticky zablokována, protože ji používal zablokovaný uživatel.",
"api-error-badaccess-groups": "Nemáte povoleno nahrávat soubory na tuto wiki.",
"api-error-badtoken": "Vnitřní chyba: špatný token.",
+ "api-error-blocked": "Byla vám zablokována možnost editace.",
"api-error-copyuploaddisabled": "Načítání z URL je na tomto severu zakázáno.",
"api-error-duplicate": "Na této wiki již {{PLURAL:$1|existuje jiný soubor|existují jiné soubory}} se shodným obsahem.",
"api-error-duplicate-archive": "{{PLURAL:$1|Soubor|Soubory}} se stejným obsahem již zde dříve {{PLURAL:$1|byl|byly}}, ale {{PLURAL:$1|byl smazán|byly smazány}}.",
"feedback-useragent": "User Agent:",
"searchsuggest-search": "Suchen",
"searchsuggest-containing": "enthält …",
+ "api-error-autoblocked": "Deine IP-Adresse wurde automatisch gesperrt, da sie von einem gesperrten Benutzer verwendet wurde.",
"api-error-badaccess-groups": "Du hast nicht die Berechtigung Dateien in dieses Wiki hochzuladen.",
"api-error-badtoken": "Interner Fehler: Der Token ist fehlerhaft.",
+ "api-error-blocked": "Du wurdest für das Bearbeiten gesperrt.",
"api-error-copyuploaddisabled": "Das Hochladen via URL wurde auf diesem Server deaktiviert.",
"api-error-duplicate": "Es gibt im Wiki bereits {{PLURAL:$1|eine andere Datei|mehrere andere Dateien}} gleichen Inhalts.",
"api-error-duplicate-archive": "Es {{PLURAL:$1|war bereits eine andere Datei|waren bereits andere Dateien}} gleichen Inhalts vorhanden. Sie {{PLURAL:$1|wurde|wurden}} allerdings gelöscht.",
"tog-ccmeonemails": "Send me copies of emails I send to other users",
"tog-diffonly": "Do not show page content below diffs",
"tog-showhiddencats": "Show hidden categories",
- "tog-norollbackdiff": "Omit diff after performing a rollback",
+ "tog-norollbackdiff": "Don't show diff after performing a rollback",
"tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
"tog-prefershttps": "Always use a secure connection when logged in",
"underline-always": "Always",
"feedback-useragent": "Agent utilisateur :",
"searchsuggest-search": "Rechercher",
"searchsuggest-containing": "contenant...",
+ "api-error-autoblocked": "Votre adresse IP a été bloquée automatiquement, parce qu’elle a été utilisée par un utilisateur bloqué.",
"api-error-badaccess-groups": "Vous n'êtes pas autorisé à verser des fichiers sur ce wiki.",
"api-error-badtoken": "Erreur interne : mauvais « jeton ».",
+ "api-error-blocked": "Vous avez été bloqué en édition.",
"api-error-copyuploaddisabled": "Les versements via URL sont désactivés sur ce serveur.",
"api-error-duplicate": "Il y a déjà {{PLURAL:$1|un autre fichier présent|d'autres fichiers présents}} sur le site avec le même contenu.",
"api-error-duplicate-archive": "Il y avait déjà {{PLURAL:$1|un autre fichier présent|d'autres fichiers présents}} sur le site avec le même contenu, mais {{PLURAL:$1|il a été supprimé|ils ont été supprimés}}.",
"feedback-useragent": "Axente de usuario:",
"searchsuggest-search": "Procurar",
"searchsuggest-containing": "que conteña...",
+ "api-error-autoblocked": "A súa dirección IP foi bloqueada automaticamente porque foi usada por un usuario bloqueado.",
"api-error-badaccess-groups": "Non ten os permisos necesarios para cargar ficheiros neste wiki.",
"api-error-badtoken": "Erro interno: Pase incorrecto.",
+ "api-error-blocked": "Foi bloqueado fronte á edición.",
"api-error-copyuploaddisabled": "As cargas mediante URL están desactivadas neste servidor.",
"api-error-duplicate": "Xa hai {{PLURAL:$1|outro ficheiro| outros ficheiros}} no wiki co mesmo contido.",
"api-error-duplicate-archive": "Había {{PLURAL:$1|outro ficheiro|outros ficheiros}} no sitio co mesmo contido, pero {{PLURAL:$1|foi borrado|foron borrados}}.",
"redirectedfrom": "(הופנה מהדף $1)",
"redirectpagesub": "דף הפניה",
"redirectto": "הפניה ל:",
- "lastmodifiedat": "שׁוּנה לאחרונה ב־$1, בשעה $2.",
+ "lastmodifiedat": "×\93×£ ×\96×\94 ש×\81×\95Ö¼× ×\94 ×\9c×\90×\97ר×\95× ×\94 ×\91Ö¾$1, ×\91שע×\94 $2.",
"viewcount": "דף זה נצפה {{PLURAL:$1|פעם אחת|פעמיים|$1 פעמים}}.",
"protectedpage": "דף מוגן",
"jumpto": "קפיצה אל:",
"userinvalidcssjstitle": "'''אזהרה:''' העיצוב \"$1\" אינו קיים.\nדפי .css ו־.js מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
"updated": "(מעודכן)",
"note": "'''הערה:'''",
- "previewnote": "<strong>×\96Ö´×\9bר×\95 ש×\96×\95 רק תצ×\95×\92×\94 ×\9eק×\93×\99×\9e×\94.</strong>\n×\94ש×\99× ×\95×\99×\99×\9d ש×\9c×\9b×\9d ×\98ר×\9d נשמרו!",
+ "previewnote": "<strong>×\96Ö´×\9bר×\95 ש×\96×\95 רק תצ×\95×\92×\94 ×\9eק×\93×\99×\9e×\94.</strong>\n×\94ש×\99× ×\95×\99×\99×\9d ש×\9c×\9b×\9d ×¢×\93×\99×\99×\9f ×\9c×\90 נשמרו!",
"continue-editing": "מעבר לאזור העריכה",
"previewconflict": "תצוגה מקדימה זו מציגה כיצד ייראה הטקסט בחלון העריכה העליון, אם תבחרו לשמור אותו.",
"session_fail_preview": "מצטערים! לא ניתן לבצע את עריכתכם עקב אובדן מידע הכניסה.\n\nייתכן שנותקתם מהחשבון. <strong>אנא ודאו שאתם עדיין מחוברים לחשבון ונסו שוב.</strong>\nאם זה עדיין לא עובד, נסו [[Special:UserLogout|לצאת מהחשבון]] ולהיכנס אליו שנית, וודאו שהדפדפן שלכם מאפשר קבלת עוגיות מאתר זה.",
"uploadvirus": "הקובץ מכיל וירוס!\nפרטים:\n<div dir=\"ltr\">$1</div>",
"uploadjava": "קובץ זה הוא קובץ ZIP שמכיל קובץ ‎.class של Java.\nהעלאת קובצי Java אסורה, כיוון שהם יכולים לגרום לעקיפת מגבלות האבטחה.",
"upload-source": "קובץ המקור",
- "sourcefilename": "ש×\9d ×\94ק×\95×\91×¥:",
+ "sourcefilename": "ש×\9d ק×\95×\91×¥ ×\94×\9eק×\95ר:",
"sourceurl": "כתובת URL של המקור:",
"destfilename": "שמירת הקובץ בשם:",
"upload-maxfilesize": "גודל הקובץ המרבי: $1",
"filedelete-old-unregistered": "גרסת הקובץ \"$1\" אינה רשומה בבסיס הנתונים.",
"filedelete-current-unregistered": "הקובץ \"$1\" אינו רשום בבסיס הנתונים.",
"filedelete-archive-read-only": "השרת אינו יכול לכתוב לתיקיית הארכיון \"$1\".",
- "previousdiff": "â\86\92 ×\9e×¢×\91ר ×\9c×\94ש×\95×\95×\90ת ×\94×\92רס×\90×\95ת הקודמת",
- "nextdiff": "×\9e×¢×\91ר ×\9c×\94ש×\95×\95×\90ת ×\94×\92רס×\90×\95ת הבאה ←",
+ "previousdiff": "â\86\92 ×\94ער×\99×\9b×\94 הקודמת",
+ "nextdiff": "×\94ער×\99×\9b×\94 הבאה ←",
"mediawarning": "<strong>אזהרה:</strong> סוג קובץ זה עלול להכיל קוד זדוני.\nהרצת הקוד עלולה לסכן את המחשב שלך.",
"imagemaxsize": "גודל תמונה מרבי:<br /><em>(בדפי תיאור של קבצים)</em>",
"thumbsize": "גודל של תמונות ממוזערות:",
"feedback-useragent": "User agent:",
"searchsuggest-search": "חיפוש",
"searchsuggest-containing": "כולל...",
+ "api-error-autoblocked": "כתובת ה־IP שלך נחסמה אוטומטית, כי היא הייתה בשימוש על־ידי משתמש חסום.",
"api-error-badaccess-groups": "אינך מורשה להעלות קבצים לאתר הוויקי הזה.",
"api-error-badtoken": "שגיאה פנימית: אסימון שבור.",
+ "api-error-blocked": "נחסמת מעריכה.",
"api-error-copyuploaddisabled": "העלאה לפי כתובת כובתה בשרת זה.",
"api-error-duplicate": "כבר יש באתר הזה {{PLURAL:$1|קובץ אחר|קבצים אחרים}} עם אותו תוכן.",
"api-error-duplicate-archive": "כבר {{PLURAL:$1|היה|היו}} באתר הזה {{PLURAL:$1|קובץ|קבצים}} עם אותו תוכן, אבל {{PLURAL:$1|הוא נמחק|הם נמחקו}}.",
"preview": "Хьалхе бӀаргтассар",
"showpreview": "Хьалххе хьажар",
"showdiff": "Даь хувцамаш",
- "anoneditwarning": "Зем хила! Шо кхы чудаьннадац. Шун IP-моттиг укх хийца оагӀув искаречу дӀаяздаь хургья.",
+ "anoneditwarning": "<strong>Теркам бе!</strong> Хьо автор хинна система чуваьннавац. Нагахьа санна Iа моллагIа хувцам бой, Хьа IP-адрес дийла массанен бIаргагуш хургда. Нагахьа санна Хьо <strong>[$1 хьачувоале]</strong> е <strong>[$2 учёта яздар хьакхолле]</strong>, нийсдараш (хувцамаш) бувзам болаш хургда Хьа доакъашхой цIерца, иштта кхыдола толажагIи гIойленагIи дола дикаьш хургда Хьона.",
"summary-preview": "Лоацам ба:",
"subject-preview": "Кортале хургья:",
"blockedtitle": "Дакъалаьцархо чӀега бела ва/я",
"editconflict": "ГӀалатнийсдара къовсам: $1",
"yourtext": "Хьа яздам",
"copyrightwarning": "Теркам бе, $2 ($1 хьажа) бокъонаца лорадеш, тӀахьежама кӀала уллаш, оаш мел чуяккхаш дола хоамаш, яздамаш долга.\nНаггахь санна шоай яздамаш пурам доацаш мала волашву саго хувца е кхы дола моттиге яздердолаш, безам беци, укхаз Ӏочуцаяздеча, дикаьгӀа да.<br />\nОаш дош лу, даь дола хувцама да волга/йолга, е оаш пурам долаш Ӏочуяздеш да кхычера меттигара шоай яздамаш/хоамаш.\n'''Яздархой бокъоца лорадеш дола хӀамаш, цара пурам доацаш, Ӏочумаязаде!'''",
- "templatesused": "УкÑ\85 бÓ\80аÑ\80гоагÓ\80Ñ\83вни оагÓ\80Ñ\83в Ñ\82Ó\80а лелаÑ\8fÑ\8c {{PLURAL:$1|1=Ð\9aÑ\83Ñ\86кеп|Ð\9aÑ\83Ñ\86кепаш}}:",
+ "templatesused": "УкÑ\85 оагIон Ñ\82Iа {{PLURAL:$1|1=пайда Ñ\8dÑ\86а Ð\9bо|пайда Ñ\8dÑ\86а Ð\9bонаш}}:",
"templatesusedpreview": "Хьалхе бӀаргтассама оагӀув тӀа леладеш дола {{PLURAL:$1|1=Куцкеп|Куцкепаш}}:",
"template-protected": "(лорадаь да)",
"template-semiprotected": "(цхьа долча даькъе гIо оттадаь да)",
- "hiddencategories": "Ер оагӀув укх {{PLURAL:$1|1=къайла цатегаца|къайла цатегашца}} дакъа лоаца:",
- "permissionserrorstext-withaction": "$2 де бокъо яц {{PLURAL:$1|1=из бахьан долаш|из бахьанаш долаш}}:",
+ "hiddencategories": "Ер оагIув {{PLURAL:$1|$1 къайла категориех|1=цаI къайла категорех}} я:",
+ "permissionserrorstext-withaction": "Ер $2 де Хьа бокъо яц {{PLURAL:$1|1=из бахьан долаш|из бахьанаш долаш}}:",
"recreate-moveddeleted-warn": "'''Зем бе! Шо хьалххе дIайоаккхаш хинна оагӀув хьае гӀерта.'''\n\nХьажа, бокъонцахь езаш йолга.\nКӀалхагIа укх оагӀуви дӀадаккхами цӀи хувцами тептараш хьекха да.",
"moveddeleted-notice": "Ер оагӀув дӀаяккха хиннай.\nНовкъостала, кӀалха хьахьекха да дӀадаккхама а хувцама а тептарашкара дIаяздараш.",
"log-fulllog": "Деррига таптара бӀаргтасса",
"viewpagelogs": "Укх оагӀон тептараш хьокха",
"currentrev-asof": "тӀеххьара верси $1",
"revisionasof": "Верси $1",
- "revision-info": "$1; $2 хувцам",
+ "revision-info": "Верси $1; {{GENDER:$6|$2}}$7",
"previousrevision": "← Xьалхарча",
"nextrevision": "ТIехьайоагIараш →",
"currentrevisionlink": "ХIанзара верси",
"thumbnail_error": "ЗIамигасуртанчий кхеллама гIалат: $1",
"import-upload-filename": "ПаьлацIи:",
"tooltip-pt-userpage": "{{GENDER:|Хьа}} доакъашхочунна оагIув",
- "tooltip-pt-mytalk": "Шун дувцамий оагIув",
+ "tooltip-pt-mytalk": "{{GENDER:|Хьа}} дувца оттадара оагIув",
"tooltip-pt-preferences": "{{GENDER:|Хьа оттамаш}}",
"tooltip-pt-watchlist": "ОоагIувна дагарле, шо бIаргалокхаш йола",
- "tooltip-pt-mycontris": "Шун хувцамаш",
+ "tooltip-pt-mycontris": "{{GENDER:|хьа}} хувцамаш",
"tooltip-pt-login": "Укхаза хьай цIи аьле чувала/яла йиша я, амма из параз дац",
"tooltip-pt-logout": "Аравала/яла",
"tooltip-pt-createaccount": "Хьа бокъо я учёта яздар кхелла система чу вала, амма параз долаш дац из.",
"feedback-useragent": "Agente utente:",
"searchsuggest-search": "Ricerca",
"searchsuggest-containing": "contenente...",
+ "api-error-autoblocked": "Il tuo indirizzo IP è stato bloccato automaticamente, perché è stato utilizzato da un utente bloccato.",
"api-error-badaccess-groups": "Non sei autorizzato a caricare documenti su questa wiki.",
"api-error-badtoken": "Errore interno: token errato.",
+ "api-error-blocked": "Sei stato bloccato, non puoi fare modifiche.",
"api-error-copyuploaddisabled": "Il caricamento tramite URL è disabilitato su questo server.",
"api-error-duplicate": "Sul sito {{PLURAL:$1|c'è già un altro documento|ci sono già altri documenti}} con lo stesso contenuto.",
"api-error-duplicate-archive": "{{PLURAL:$1|C'era un altro file|C'erano altri file}} già nel sito con lo stesso contenuto, ma {{PLURAL:$1|è stato cancellato|sono stati cancellati}}.",
"december-date": "12월 $1일",
"period-am": "오전",
"period-pm": "오후",
- "pagecategories": "{{PLURAL:$1|분류}}",
+ "pagecategories": "{{PLURAL:$1|분류|분류}}",
"category_header": "\"$1\" 분류에 속하는 문서",
"subcategories": "하위 분류",
"category-media-header": "\"$1\" 분류에 속하는 미디어",
"category-empty": "이 분류에 속하는 문서나 자료가 없습니다.",
- "hidden-categories": "{{PLURAL:$1|숨은 분류}}",
+ "hidden-categories": "{{PLURAL:$1|숨은 분류|숨은 분류}}",
"hidden-category-category": "숨은 분류",
"category-subcat-count": "{{PLURAL:$2|이 분류에는 하위 분류 1개만이 속해 있습니다.|다음은 이 분류에 속하는 {{PLURAL:$1|하위 분류}} $2개 가운데 $1개입니다.}}",
"category-subcat-count-limited": "이 분류에 {{PLURAL:$1|하위 분류가|하위 분류 $1개가}} 있습니다.",
"badaccess-group0": "요청한 명령을 실행할 권한이 없습니다.",
"badaccess-groups": "요청한 명령은 {{PLURAL:$2|다음|다음 중 하나의}} 권한을 가진 사용자에게 제한됩니다: $1.",
"versionrequired": "미디어위키 $1 버전 필요",
- "versionrequiredtext": "이 문서를 사용하려면 $1 버전 미디어위키가 필요합니다.\n[[Special:Version|설치된 미디어위키 버전]]을 참조하세요.",
+ "versionrequiredtext": "이 문서를 사용하려면 $1 버전의 미디어위키가 필요합니다.\n[[Special:Version|설치된 미디어위키 버전]]을 참조하세요.",
"ok": "확인",
"retrievedfrom": "원본 주소 \"$1\"",
"youhavenewmessages": "다른 사용자로부터의 $1가 {{PLURAL:$3|있습니다}}. ($2)",
"laggedslavemode": "<strong>경고:</strong> 문서가 최근에 바뀐 내용을 포함하지 않을 수도 있습니다.",
"readonly": "데이터베이스 잠김",
"enterlockreason": "데이터베이스를 잠그는 이유와 예상되는 기간을 적어 주세요.",
- "readonlytext": "데이터베이스가 잠겨 있어서 문서를 편집할 수 없습니다. 데이터베이스 관리가 끝난 후에는 정상으로 돌아올 것입니다.\n\n관리자가 데이터베이스를 잠글 때 남긴 메시지는 다음과 같습니다: $1",
+ "readonlytext": "데이터베이스가 잠겨 있어서 문서를 편집할 수 없습니다. 데이터베이스 관리가 끝난 후에는 정상으로 돌아올 것입니다.\n\n시스템 관리자가 데이터베이스를 잠글 때 남긴 메시지는 다음과 같습니다: $1",
"missing-article": "데이터베이스에서 \"$1\" 문서의 $2 텍스트를 찾지 못했습니다.\n\n삭제된 문서의 오래된 차이나 역사 링크를 보려고 시도할 때 이러한 문제가 발생할 수 있습니다.\n\n그렇지 않다면, 소프트웨어에 버그가 발생했을 수도 있습니다.\n[[Special:ListUsers/sysop|관리자]]에게 URL을 참조하여 알려주세요.",
"missingarticle-rev": "(판번호: $1)",
"missingarticle-diff": "(차이: $1, $2)",
- "readonly_lag": "슬레이브 데이터베이스가 마스터 서버의 자료를 새로 고치는 중입니다. 데이터베이스가 자동으로 잠겨져 있습니다",
+ "readonly_lag": "슬레이브 데이터베이스 서버들이 마스터 서버와 동기화되고 있습니다. 그 동안 데이터베이스가 자동으로 잠겨져 있습니다.",
"nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' HTTP 헤더가 붙어있지만 API 쓰기 모듈에 대한 요청을 했습니다.",
"internalerror": "내부 오류",
"internalerror_info": "내부 오류: $1",
"feedback-useragent": "사용자 에이전트:",
"searchsuggest-search": "검색",
"searchsuggest-containing": "다음 문자열 포함...",
+ "api-error-autoblocked": "사용자의 IP 주소는 차단된 사용자에 의해 사용되었으므로 자동으로 차단된 상태입니다.",
"api-error-badaccess-groups": "이 위키에 파일을 올릴 권한이 없습니다.",
"api-error-badtoken": "내부 오류: 토큰이 잘못되었습니다.",
+ "api-error-blocked": "편집에서 차단되어 있습니다.",
"api-error-copyuploaddisabled": "이 서버에서 URL을 통해 파일 올리기가 비활성화되어 있습니다.",
"api-error-duplicate": "이 위키에 내용이 똑같은 {{PLURAL:$1|다른 파일}}이 있습니다.",
"api-error-duplicate-archive": "같은 내용을 담고 있던 {{PLURAL:$1|다른 파일}}이 있었지만 이 {{PLURAL:$1|파일}}은 삭제되었습니다.",
"log-action-filter-patrol": "점검 종류:",
"log-action-filter-protect": "보호 종류:",
"log-action-filter-rights": "권한 변경 종류",
+ "log-action-filter-suppress": "숨기기 종류",
"log-action-filter-upload": "업로드 종류:",
"log-action-filter-all": "모두",
"log-action-filter-block-block": "차단",
"log-action-filter-newusers-create": "익명의 사용자에 의한 생성",
"log-action-filter-newusers-create2": "등록된 사용자에 의한 생성",
"log-action-filter-newusers-autocreate": "자동 생성",
+ "log-action-filter-newusers-byemail": "이메일로 보낸 비밀번호로 생성",
"log-action-filter-patrol-patrol": "수동 점검",
"log-action-filter-patrol-autopatrol": "자동 점검",
"log-action-filter-protect-protect": "보호",
"log-action-filter-protect-move_prot": "이동 보호",
"log-action-filter-rights-rights": "수동 변경",
"log-action-filter-rights-autopromote": "자동 변경",
+ "log-action-filter-suppress-event": "로그 숨기기",
+ "log-action-filter-suppress-revision": "판 숨기기",
+ "log-action-filter-suppress-delete": "문서 숨기기",
+ "log-action-filter-suppress-block": "차단을 통한 사용자 숨기기",
+ "log-action-filter-suppress-reblock": "재차단을 통한 사용자 숨기기",
"log-action-filter-upload-upload": "새로 업로드",
"log-action-filter-upload-overwrite": "다시 업로드"
}
"rcshowhidemine-show": "nîşan bide",
"rcshowhidemine-hide": "veşêre",
"rcshowhidecategorization": "Kategorîzekirina rûpelan $1",
- "rcshowhidecategorization-show": "Nîşan bide",
- "rcshowhidecategorization-hide": "Veşêre",
+ "rcshowhidecategorization-show": "nîşan bide",
+ "rcshowhidecategorization-hide": "veşêre",
"rclinks": "$1 guherandinên di $2 rojên dawî de nîşan bide<br />$3",
"diff": "cudahî",
"hist": "dîrok",
"feedback-useragent": "User Agent:",
"searchsuggest-search": "Sichen",
"searchsuggest-containing": "mat ...",
+ "api-error-autoblocked": "Är IP-Adress gouf automatesch gespaart wëll se vun engem gespaarte Benotzer benotzt gouf",
"api-error-badaccess-groups": "Et ass Iech net erlaabt fir Fichieren op dës Wiki eropzelueden.",
"api-error-badtoken": "Interne Feeler: falschen Token.",
+ "api-error-blocked": "Dir gouft gespaart a kënnt dofir keng Ännerunge maachen.",
"api-error-copyuploaddisabled": "D'Eroplueden iwwer eng URL ass op dësem Server desaktivéiert.",
"api-error-duplicate": "Et gëtt schonn {{PLURAL:$1|en anere Fichier|e puer aner Fichiere}} mat dem selwechten Inhalt op dem Site",
"api-error-duplicate-archive": "Et gouf schonn {{PLURAL:$1| een anere Fichier|e puer aner Fichieren}} op dem Site mat deemselwechten Inhalt, {{PLURAL:$1|e gouf|se goufen}} awer geläscht.",
"minoredit": "Tai smulkus pataisymas",
"watchthis": "Stebėti šį puslapį",
"savearticle": "Išsaugoti puslapį",
+ "publishpage": "Skelbti puslapį",
"preview": "Peržiūra",
"showpreview": "Rodyti peržiūrą",
"showdiff": "Rodyti skirtumus",
"ipb-unblock": "Atblokuoti naudotojo vardą arba IP adresą",
"ipb-blocklist": "Rodyti egzistuojančius blokavimus",
"ipb-blocklist-contribs": "{{GENDER:$1|$1}} indėlis",
+ "ipb-blocklist-duration-left": "$1 kairėje",
"unblockip": "Atblokuoti naudotoją",
"unblockiptext": "Naudokite šią formą, kad atkurtumėte redagavimo galimybę\nankščiau užblokuotam IP adresui ar naudotojui.",
"ipusubmit": "Atblokuoti šį adresą",
"tooltip-ca-nstab-category": "Rodyti kategorijos puslapį",
"tooltip-minoredit": "Pažymėti keitimą kaip smulkų",
"tooltip-save": "Išsaugoti pakeitimus",
+ "tooltip-publish": "Skelbti jūsų pakeitimus",
"tooltip-preview": "Pakeitimų peržiūra, prašome pažiūrėti prieš išsaugant!",
"tooltip-diff": "Rodo, kokius pakeitimus padarėte tekste",
"tooltip-compareselectedversions": "Žiūrėti dviejų pasirinktų puslapio versijų skirtumus.",
"sessionprovider-generic": "$1 sesijos",
"sessionprovider-mediawiki-session-cookiesessionprovider": "sesijos su slapukais",
"sessionprovider-nocookies": "Slapukai gali būti neaktyvuoti. Įsitikinkite, kad slapukai yra aktyvuoti ir pradėkite vėl.",
- "randomrootpage": "Atsitiktinis šakninis puslapis"
+ "randomrootpage": "Atsitiktinis šakninis puslapis",
+ "log-action-filter-all": "Visi",
+ "log-action-filter-newusers-autocreate": "Automatinis kūrimas",
+ "log-action-filter-protect-protect": "Apsauga"
}
"creditspage": "Panghargaan laman",
"spam_blanking": "Sado revisi nan ado pautan ka $1, kosong",
"spam_deleting": "Sado revisi nan ado pautan ka $1, dihapuih",
- "simpleantispam-label": "Pamarisoan anti-spam.\nMasukan ko '''DILARANG'''!",
+ "simpleantispam-label": "Pamarisoan anti-spam.\n<strong>Jan</strong> diisi!",
"pageinfo-title": "Informasi untuak \"$1\"",
"pageinfo-not-current": "Maaf, indak dapek mangagiahan informasi ko ka revisi lamo.",
"pageinfo-header-basic": "Informasi dasar",
"feedback-useragent": "Aplikacja klienta:",
"searchsuggest-search": "Szukaj",
"searchsuggest-containing": "zawierające...",
+ "api-error-autoblocked": "Twój adres IP został automatycznie zablokowany, ponieważ był używany przez zablokowanego użytkownika.",
"api-error-badaccess-groups": "Nie masz uprawnień aby przesyłać pliki do tej wiki.",
"api-error-badtoken": "Błąd wewnętrzny – nieprawidłowy kod weryfikacyjny (token).",
+ "api-error-blocked": "Została ci zablokowana możliwość edycji.",
"api-error-copyuploaddisabled": "Przesyłanie poprzez podanie adresu URL zostało na tym serwerze wyłączone.",
"api-error-duplicate": "{{PLURAL:$1|Jest już inny plik|Są już inne pliki}} o tej samej zawartości",
"api-error-duplicate-archive": "{{PLURAL:$1|Był już inny plik|Były już inne pliki}} o takiej samej zawartości, ale {{PLURAL:$1|został usunięty|zostały usunięte}}.",
"variantname-gan": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
"variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
"variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
- "variantname-sr": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
+ "variantname-sr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-kk-kz": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
"variantname-kk-tr": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
"variantname-kk-cn": "{{optional}}\nVarient Option for wikis with variants conversion enabled.",
"feedback-useragent": "A label denoting the user agent in the feedback that is posted to the feedback page.\n{{Identical|User agent}}",
"searchsuggest-search": "Greyed out default text in the simple search box in the Vector skin. (It disappears and lets the user enter the requested search terms when the search box receives focus.)\n\n{{Identical|Search}}",
"searchsuggest-containing": "Label used in the special item of the search suggestions list which gives the user an option to perform a full text search for the term.",
- "api-error-autoblocked": "API error message that can be used for client side localisation of API errors.",
+ "api-error-autoblocked": "API error message that can be used for client side localisation of API errors.\n\nCf. {{msg-mw|Autoblockedtext}}.",
"api-error-badaccess-groups": "API error message that can be used for client side localisation of API errors.",
"api-error-badtoken": "API error message that can be used for client side localisation of API errors.",
"api-error-blocked": "API error message that can be used for client side localisation of API errors.",
"feedback-useragent": "Uporabniški agent:",
"searchsuggest-search": "Iskanje",
"searchsuggest-containing": "vsebujoč ...",
+ "api-error-autoblocked": "Vaš IP-naslov smo samodejno blokirali, saj ga je uporabljal blokiran uporabnik.",
"api-error-badaccess-groups": "Nalaganje datotek na ta wiki vam ni dovoljeno.",
"api-error-badtoken": "Notranja napaka: slab žeton.",
+ "api-error-blocked": "Urejanje vam je preprečeno.",
"api-error-copyuploaddisabled": "Nalaganje preko URL je na tem strežniku onemogočeno.",
"api-error-duplicate": "Na strani že {{PLURAL:$1|obstaja druga datoteka|obstajata drugi datoteki|obstajajo druge datoteke}} z enako vsebino.",
"api-error-duplicate-archive": "Na strani {{PLURAL:$1|je že bila druga datoteka|sta že bili drugi datoteki|so že bile nekatere druge datoteke}} z enako vsebino, vendar {{PLURAL:$1|je bila izbrisana|sta bili izbrisani|so bile izbrisane}}.",
"tog-watchdefault": "Tự động theo dõi các trang và tập tin tôi sửa",
"tog-watchmoves": "Tự động theo dõi các trang và tập tin tôi di chuyển",
"tog-watchdeletion": "Tự động theo dõi các trang và tập tin tôi xóa",
+ "tog-watchuploads": "Thêm các tập tin tải lên của tôi vào danh sách theo dõi của tôi",
"tog-watchrollback": "Tự động theo dõi các trang tôi lùi sửa",
"tog-minordefault": "Mặc định đánh dấu tất cả sửa đổi của tôi là sửa đổi nhỏ",
"tog-previewontop": "Hiển thị phần xem trước nằm trên hộp sửa đổi",
"minoredit": "Sửa đổi nhỏ",
"watchthis": "Theo dõi trang này",
"savearticle": "Lưu trang",
+ "publishpage": "Xuất bản trang",
"preview": "Xem trước",
"showpreview": "Xem trước",
"showdiff": "Xem thay đổi",
"userpage-userdoesnotexist": "Đây chưa có tài khoản với tên “<nowiki>$1</nowiki>”. Xin hãy kiểm tra lại nếu bạn muốn tạo hay sửa trang này.",
"userpage-userdoesnotexist-view": "Chưa có tài khoản với tên “$1”.",
"blocked-notice-logextract": "Người dùng này hiện đang bị cấm sửa đổi. Nhật trình cấm gần nhất được ghi ở dưới để tiện theo dõi:",
- "clearyourcache": "'''Chú ý:''' Sau khi lưu trang, có thể bạn sẽ phải xóa bộ nhớ đệm của trình duyệt để xem các thay đổi.\n* '''Firefox / Safari:''' Nhấn giữ phím ''Shift'' trong khi nhấn ''Tải lại'' (''Reload''), hoặc nhấn tổ hợp ''Ctrl-F5'' hay ''Ctrl-R'' (⌘R trên Mac)\n* '''Google Chrome:''' Nhấn tổ hợp ''Ctrl-Shift-R'' (⇧⌘R trên Mac)\n* '''Internet Explorer:''' Nhấn giữ phím ''Ctrl'' trong khi nhấn ''Làm tươi'' (''Refresh''), hoặc nhấn tổ hợp ''Ctrl-F5''\n* '''Opera:''' Xóa bộ nhớ đệm trong ''Công cụ → Sở thích'' (''Tools → Preferences'')",
+ "clearyourcache": "<strong>Chú ý:</strong> Sau khi lưu trang, có thể bạn sẽ phải xóa bộ nhớ đệm của trình duyệt để xem các thay đổi.\n* <strong>Firefox / Safari:</strong> Nhấn giữ phím <em>Shift</em> trong khi nhấn <em>Tải lại</em> (<em>Reload</em>), hoặc nhấn tổ hợp <em>Ctrl-F5</em> hay <em>Ctrl-R</em> (⌘R trên Mac)\n* <strong>Google Chrome:</strong> Nhấn tổ hợp <em>Ctrl-Shift-R</em> (⇧⌘R trên Mac)\n* <strong>Internet Explorer:</strong> Nhấn giữ phím <em>Ctrl</em> trong khi nhấn <em>Làm tươi</em>, hoặc nhấn tổ hợp <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Mở <em>Menu → Cài đặt</em> (<em>Opera → Tùy chỉnh</em> trên Mac), mở tab <em>Quyền riêng tư & bảo mật</em>, bấm <em>Xóa dữ liệu duyệt web</em> và đánh hộp kiểm <em>Hình ảnh và tệp trong cache</em>.",
"usercssyoucanpreview": "'''Mẹo:''' Sử dụng nút “{{int:showpreview}}” để kiểm thử bản CSS của bạn trước khi lưu trang.",
"userjsyoucanpreview": "'''Mẹo:''' Sử dụng nút “{{int:showpreview}}” để kiểm thử bản JS của bạn trước khi lưu trang.",
"usercsspreview": "'''Hãy nhớ rằng bạn chỉ đang xem trước trang CSS cá nhân của bạn.\nNó chưa được lưu!'''",
"recentchangeslinked-page": "Tên trang:",
"recentchangeslinked-to": "Hiện thay đổi tại những trang có liên kết đến trang này thay thế",
"recentchanges-page-added-to-category": "[[:$1]] được xếp vào thể loại",
- "recentchanges-page-added-to-category-bundled": "[[:$1]] và [[Special:WhatLinksHere/$1|{{PLURAL:$2|một trang|$2 trang}} nữa]] được xếp vào thể loại",
+ "recentchanges-page-added-to-category-bundled": "[[:$1]] được xếp vào thể loại; [[Special:WhatLinksHere/$1|trang này được nhúng vào các trang khác]]",
"recentchanges-page-removed-from-category": "[[:$1]] được gỡ khỏi thể loại",
- "recentchanges-page-removed-from-category-bundled": "[[:$1]] và [[Special:WhatLinksHere/$1|{{PLURAL:$2|một trang|$2 trang}} nữa]] được gỡ khỏi thể loại",
+ "recentchanges-page-removed-from-category-bundled": "[[:$1]] được xóa gỡ thể loại; [[Special:WhatLinksHere/$1|trang này được nhúng vào các trang khác]]",
"autochange-username": "MediaWiki thay đổi tự động",
"upload": "Tải tập tin lên",
"uploadbtn": "Tải tập tin lên",
"changecontentmodel-nodirectediting": "Kiểu nội dung $1 không hỗ trợ sửa đổi trực tiếp",
"log-name-contentmodel": "Nhật trình thay đổi kiểu nội dung",
"log-description-contentmodel": "Sự kiện có liên quan đến kiểu nội dung của trang.",
- "logentry-contentmodel-new": "$1 {{GENDER:$2}}đã tạo trang $3 với mô hình nội dung không mặc định “$5”",
+ "logentry-contentmodel-new": "$1 {{GENDER:$2}}đã tạo trang $3 với kiểu nội dung không mặc định “$5”",
"logentry-contentmodel-change": "$1 {{GENDER:$2}}đã thay đổi kiểu nội dung của trang $3 từ “$4” thành “$5”",
"logentry-contentmodel-change-revertlink": "lùi lại",
"logentry-contentmodel-change-revert": "lùi lại",
"ipb-unblock": "Bỏ cấm thành viên hay địa chỉ IP",
"ipb-blocklist": "Xem danh sách đang bị cấm",
"ipb-blocklist-contribs": "Đóng góp của $1",
+ "ipb-blocklist-duration-left": "còn $1 nữa",
"unblockip": "Bỏ cấm thành viên",
"unblockiptext": "Sử dụng mẫu sau để phục hồi lại quyền sửa đổi đối với một địa chỉ IP hoặc tên thành viên đã bị cấm trước đó.",
"ipusubmit": "Bỏ cấm",
"tooltip-ca-nstab-category": "Xem trang thể loại",
"tooltip-minoredit": "Đánh dấu đây là sửa đổi nhỏ",
"tooltip-save": "Lưu lại những thay đổi của bạn",
+ "tooltip-publish": "Xuất bản các thay đổi của bạn",
"tooltip-preview": "Xem trước những thay đổi, hãy dùng nó trước khi lưu!",
"tooltip-diff": "Xem thay đổi bạn đã thực hiện.",
"tooltip-compareselectedversions": "Xem khác biệt giữa hai phiên bản đã chọn của trang này.",
"confirmemail_body_set": "Ai đó, có thể là bạn, từ địa chỉ IP $1, đã đặt địa chỉ này là địa\nchỉ thư điện tử của tài khoản “$2” tại {{SITENAME}}.\n\nĐể xác nhận rằng tài khoản này thực sự là của bạn và để kích hoạt các tính năng\nthư điện tử tại {{SITENAME}}, xin mở liên kết này trong trình duyệt:\n\n$3\n\nNếu tài khoản *không* phải là của bạn, hãy nhấn vào liên kết này để hủy thủ tục\nxác nhận địa chỉ thư điện tử:\n\n$5\n\nMã xác nhận này sẽ hết hạn vào $4.",
"confirmemail_invalidated": "Đã hủy xác nhận địa chỉ thư điện tử",
"invalidateemail": "Hủy xác nhận thư điện tử",
+ "notificationemail_subject_changed": "Địa chỉ thư điện tử đăng ký tại {{SITENAME}} đã được thay đổi",
+ "notificationemail_subject_removed": "Địa chỉ thư điện tử đăng ký tại {{SITENAME}} đã được loại bỏ",
+ "notificationemail_body_changed": "Ai đó, có thể là bạn, từ địa chỉ IP $1,\nđã thay đổi địa chỉ thư điện tử của tài khoản “$2” thành “$3” tại {{SITENAME}}.\n\nNếu bạn không phải là người thay đổi địa chỉ này, xin hãy liên lạc với một bảo quản viên của trang Web ngay lập tức.",
+ "notificationemail_body_removed": "Ai đó, có thể là bạn, từ địa chỉ IP $1,\nđã loại bỏ địa chỉ thư điện tử của tài khoản “$2” tại {{SITENAME}}.\n\nNếu bạn không phải là người loại bỏ địa chỉ này, xin hãy liên lạc với một bảo quản viên của trang Web ngay lập tức.",
"scarytranscludedisabled": "[Nhúng giữa các wiki bị tắt]",
"scarytranscludefailed": "[Truy xuất bản mẫu $1 bị thất bại]",
"scarytranscludefailed-httpstatus": "[Truy xuất bản mẫu $1 bị thất bại: HTTP $2]",
"watchlistedit-raw-done": "Danh sách các trang bạn theo dõi đã được cập nhật.",
"watchlistedit-raw-added": "$1 tựa đề đã được thêm vào:",
"watchlistedit-raw-removed": "$1 tựa đề đã được xóa khỏi danh sách:",
- "watchlistedit-clear-title": "Đã xóa sạch danh sách theo dõi",
+ "watchlistedit-clear-title": "Xóa sạch danh sách theo dõi",
"watchlistedit-clear-legend": "Xóa sạch danh sách theo dõi",
"watchlistedit-clear-explain": "Tất cả các tiêu đề sẽ được xóa khỏi danh sách theo dõi của bạn.",
"watchlistedit-clear-titles": "Các tiêu đề:",
"logentry-protect-protect-cascade": "$1 {{GENDER:$2}}đã khóa $3 $4 [theo tầng]",
"logentry-protect-modify": "$1 {{GENDER:$2}}đã đổi mức khóa $3 $4",
"logentry-protect-modify-cascade": "$1 {{GENDER:$2}}đã đổi mức khóa $3 $4 [theo tầng]",
- "logentry-rights-rights": "$1 {{GENDER:$2}}đã đổi các nhóm bao gồm $3 từ $4 đến $5",
+ "logentry-rights-rights": "$1 {{GENDER:$2}}đã đổi các nhóm bao gồm {{GENDER:$6}}$3 từ $4 đến $5",
"logentry-rights-rights-legacy": "{{GENDER:$2}}$1 đã đổi các nhóm bao gồm $3",
"logentry-rights-autopromote": "$1 {{GENDER:$2}}đã được tự động phong cấp từ $4 đến $5",
"logentry-upload-upload": "$1 {{GENDER:$2}}đã tải lên $3",
"feedback-useragent": "Tác nhân người dùng:",
"searchsuggest-search": "Tìm kiếm",
"searchsuggest-containing": "có chứa…",
+ "api-error-autoblocked": "Địa chỉ IP của bạn bị cấm tự động vì nó đã được sử dụng bởi một người dùng bị cấm.",
"api-error-badaccess-groups": "Bạn không được phép tải tập tin lên wiki này.",
"api-error-badtoken": "Lỗi nội bộ: Dấu hiệu bị hỏng.",
+ "api-error-blocked": "Bạn đã bị cấm không được sửa đổi.",
"api-error-copyuploaddisabled": "Chức năng tải lên từ URL đã bị tắt trên máy chủ này.",
"api-error-duplicate": "Wiki này đã có {{PLURAL:$1|tập tin|$1 tập tin}} cùng nội dung có tên khác.",
"api-error-duplicate-archive": "{{PLURAL:$1|Một|Các}} tập tin khác cùng nội dung đã tồn tại trên website, nhưng {{PLURAL:$1|nó|chúng}} đã bị xóa.",
"api-error-nomodule": "Lỗi nội bộ: Mô đun tải lên không được định rõ.",
"api-error-ok-but-empty": "Lỗi nội bộ: Máy chủ không phản hồi.",
"api-error-overwrite": "Không được ghi đè một tập tin đã tồn tại.",
+ "api-error-ratelimited": "Bạn cố tải lên nhiều tập tin trong một thời gian ngắn vượt quá hạn chế của wiki này.",
"api-error-stashfailed": "Lỗi nội bộ: Máy chủ bị thất bại trong việc lưu giữ tập tin tạm.",
"api-error-publishfailed": "Lỗi nội bộ: Máy chủ bị thất bại trong việc xuất bản tập tin tạm.",
"api-error-stasherror": "Đã xuất hiện lỗi khi tải tập tin lên hàng đợi.",
"api-error-unknownerror": "Lỗi không rõ: “$1”.",
"api-error-uploaddisabled": "Chức năng tải lên đã bị tắt trên wiki này.",
"api-error-verification-error": "Tập tin này có thể bị hỏng hoặc có phần mở rộng sai.",
+ "api-error-was-deleted": "Một tập tin cùng tên này đã được tải lên và bị xóa về sau.",
"duration-seconds": "$1 giây",
"duration-minutes": "$1 phút",
"duration-hours": "$1 giờ",
"special-characters-group-ipa": "Phiên âm quốc tế",
"special-characters-group-symbols": "Ký hiệu",
"special-characters-group-greek": "Hy Lạp",
+ "special-characters-group-greekextended": "Hy Lạp mở rộng",
"special-characters-group-cyrillic": "Kirin",
"special-characters-group-arabic": "Ả Rập",
"special-characters-group-arabicextended": "Ả Rập mở rộng",
"sessionprovider-mediawiki-session-cookiesessionprovider": "phiên dựa trên cookie",
"sessionprovider-nocookies": "Cookie có thể bị vô hiệu hóa. Đảm bảo bạn đã bật cookie và bắt đầu một lần nữa.",
"randomrootpage": "Trang gốc ngẫu nhiên",
+ "log-action-filter-block": "Kiểu cấm:",
+ "log-action-filter-contentmodel": "Kiểu thay đổi kiểu nội dung:",
+ "log-action-filter-delete": "Kiểu xóa:",
+ "log-action-filter-import": "Kiểu nhập:",
+ "log-action-filter-managetags": "Kiểu tác vụ quản lý thẻ:",
+ "log-action-filter-move": "Kiểu di chuyển:",
+ "log-action-filter-newusers": "Kiểu tạo tài khoản:",
+ "log-action-filter-patrol": "Kiểu tuần tra:",
"log-action-filter-protect": "Loại bảo vệ:",
+ "log-action-filter-rights": "Kiểu thay đổi quyền:",
+ "log-action-filter-suppress": "Kiểu ẩn giấu",
"log-action-filter-upload": "Loại tải lên:",
"log-action-filter-all": "Tất cả",
"log-action-filter-block-block": "Khối",
+ "log-action-filter-block-reblock": "Thay đổi tác vụ cấm",
+ "log-action-filter-block-unblock": "Bỏ cấm",
+ "log-action-filter-contentmodel-change": "Thay đổi kiểu nội dung",
+ "log-action-filter-contentmodel-new": "Tạo trang có kiểu nội dung không chuẩn",
+ "log-action-filter-delete-delete": "Xóa trang",
+ "log-action-filter-delete-restore": "Phục hồi trang",
+ "log-action-filter-delete-event": "Xóa nhật trình",
+ "log-action-filter-delete-revision": "Xóa phiên bản",
+ "log-action-filter-import-interwiki": "Nhập liên wiki",
"log-action-filter-import-upload": "Nhập bằng cách tải lên XML",
+ "log-action-filter-managetags-create": "Tạo thẻ",
+ "log-action-filter-managetags-delete": "Xóa thẻ",
+ "log-action-filter-managetags-activate": "Kích hoạt thẻ",
+ "log-action-filter-managetags-deactivate": "Vô hiệu thẻ",
+ "log-action-filter-move-move": "Di chuyển mà không ghi đè trang đổi hướng",
+ "log-action-filter-move-move_redir": "Di chuyển mà ghi đè trang đổi hướng",
"log-action-filter-newusers-create": "Tạo bởi người dùng vô danh",
"log-action-filter-newusers-create2": "Tạo bởi người dùng đã đăng ký",
+ "log-action-filter-newusers-autocreate": "Tạo tự động",
"log-action-filter-newusers-byemail": "Tạo với mật khẩu được gửi qua thư điện tử",
+ "log-action-filter-patrol-patrol": "Tuần tra thủ công",
+ "log-action-filter-patrol-autopatrol": "Tuần tra tự động",
"log-action-filter-protect-protect": "Bảo vệ",
+ "log-action-filter-protect-modify": "Thay đổi mức khóa",
+ "log-action-filter-protect-unprotect": "Mở khóa",
+ "log-action-filter-protect-move_prot": "Di chuyển khóa",
+ "log-action-filter-rights-rights": "Thay đổi thủ công",
"log-action-filter-rights-autopromote": "Tự động thay đổi",
+ "log-action-filter-suppress-event": "Ẩn giấu nhật trình",
+ "log-action-filter-suppress-revision": "Ẩn giấy phiên bản",
+ "log-action-filter-suppress-delete": "Ẩn giấu trang",
+ "log-action-filter-suppress-block": "Ẩn giấu người dùng bằng cách cấm",
+ "log-action-filter-suppress-reblock": "Ẩn giấu người dùng bằng cách cấm lại",
"log-action-filter-upload-upload": "Tải lên mới",
"log-action-filter-upload-overwrite": "Tải lên lại"
}
"feedback-useragent": "用户代理:",
"searchsuggest-search": "搜索",
"searchsuggest-containing": "含有...",
+ "api-error-autoblocked": "您的IP地址已被自动封禁,因为它曾被一位已封禁用户使用。",
"api-error-badaccess-groups": "您没有将文件上传到此 wiki 的权限。",
"api-error-badtoken": "内部错误:会话无效。",
+ "api-error-blocked": "您已被封禁,不能编辑。",
"api-error-copyuploaddisabled": "通过URL上传的功能已被此服务器禁用。",
"api-error-duplicate": "在网站上已经具有相同内容的{{PLURAL:$1|另一个文件|另一些文件}}。",
"api-error-duplicate-archive": "在网站上曾经具有相同内容的{{PLURAL:$1|另一个文件|另一些文件}},但已被删除。",
use MediaWiki\Logger\LoggerFactory;
// This endpoint is supposed to be independent of request cookies and other
-// details of the session. Log warnings for violations of the no-session
-// constraint.
-define( 'MW_NO_SESSION', 'warn' );
+// details of the session. Enforce this constraint with respect to session use.
+define( 'MW_NO_SESSION', 1 );
require __DIR__ . '/includes/WebStart.php';
姊弟 姐弟
姊夫 姐夫
大姊 大姐
+大姊姊 大姐姐
御姊 御姐
表姊 表姐
堂姊 堂姐
著處 着处
著她 着她
著妳 着妳
-著姓 着姓
著它 着它
著定 着定
著實 着实
电影里 電影裏
广播里 廣播裏
电视里 電視裏
+公寓里 公寓裏
窝里斗 窩裏鬥
苑裡 苑裡
霄裡 霄裡
流動網絡 行動網路
网络游戏 網路遊戲
網絡遊戲 網路遊戲
+电脑网络 電腦網路
+電腦網絡 電腦網路
咪高峰 麥克風
電單車 機車
搜索引擎 搜尋引擎
騰格里 騰格里
村里長 村里長
進制 進制
+總裁制 總裁制
+獨裁制 獨裁制
模范三軍 模范三軍
陳冲 陳冲
劉佳怜 劉佳怜
併骨
併網
併線
-併流
+江併流
+水併流
逼併
併名
併火
意大利麵
湯下麵
茶麵
-麵糰
+麵團
北山索麵
土索麵
米麵
、麵點
麵製品
乾脆麵
+磨麵
冷面相
糞穢衊面
僕僕
犖确
磽确
确瘠
-言辯而确
-數與虜确
-關弓與我确
拚捨
廣捨
齊王捨牛
翻鬆
浮鬆
弄鬆
+旋鬆
精鬆
懈鬆
鬆蛋
跌扑
轉向往
酒帘
-裡面包
金表態
金表情
金表揚
長几
隆准許
雄斗斗
+裡面包
+表面包
+外面包
面包住
面包辦
面包廂
面粉碎
面粉紅
面食飯
+水表面
+費米面
顛顛仆仆
高干擾
高干預
折子戲
搤肮拊背
文采郁郁
-腊之以為餌
腊毒
蜡月
蜡祭
范文照
機械系
體系
+維系統
心理
鹰鵰
天地志狼
電影裡
廣播裡
電視裡
+公寓裡
+公寓里弄
裏白 #植物常用名
烏蘇里 #分詞用
首發
有只允
有只採
有只用
+所有只
葉叶琹
胡子昂
胡子嬰
製得
製取
譯製
+燉製
+煮製
遏制 #以下分詞用
管制
抑制
啊喂
呵喂
呦喂
-水表面
-表面包
-費米面
松口鎮
沙瑯
琺瑯
并吞
併吞下
髮網
+精鍊
+腳鍊
+託人
+鬥口
* @file
*/
+// This endpoint is supposed to be independent of request cookies and other
+// details of the session. Log warnings for violations of the no-session
+// constraint.
+define( 'MW_NO_SESSION', 'warn' );
+
require_once __DIR__ . '/includes/WebStart.php';
if ( $wgRequest->getVal( 'ctype' ) == 'application/xml' ) {
* @file
*/
+// This endpoint is supposed to be independent of request cookies and other
+// details of the session. Log warnings for violations of the no-session
+// constraint.
+define( 'MW_NO_SESSION', 'warn' );
+
ini_set( 'zlib.output_compression', 'off' );
$wgEnableProfileInfo = false;
'scripts' => [
'resources/lib/moment/moment.js',
'resources/src/moment-global.js',
- 'resources/src/moment-local-dmy.js',
],
'languageScripts' => [
'af' => 'resources/lib/moment/locale/af.js',
'zh-hans' => 'resources/lib/moment/locale/zh-cn.js',
'zh-hant' => 'resources/lib/moment/locale/zh-tw.js',
],
+ // HACK: skinScripts come after languageScripts, and we need locale overrides to come
+ // after locale definitions
+ 'skinScripts' => [
+ 'default' => [
+ 'resources/src/moment-locale-overrides.js',
+ ],
+ ],
'targets' => [ 'desktop', 'mobile' ],
],
+++ /dev/null
-// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
-// This affects English only (and languages without localisations, that fall back to English).
-// http://momentjs.com/docs/#/customization/long-date-formats/
-/*global moment */
-moment.locale( 'en', {
- longDateFormat: {
- // Unchanged, but have to be repeated here:
- LT: 'h:mm A',
- LTS: 'h:mm:ss A',
- // Customized:
- L: 'DD/MM/YYYY',
- LL: 'D MMMM YYYY',
- LLL: 'D MMMM YYYY LT',
- LLLL: 'dddd, D MMMM YYYY LT'
- }
-} );
--- /dev/null
+// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
+// This affects English only (and languages without localisations, that fall back to English).
+// http://momentjs.com/docs/#/customization/long-date-formats/
+/*global moment, mw */
+moment.locale( 'en', {
+ longDateFormat: {
+ // Unchanged, but have to be repeated here:
+ LT: 'h:mm A',
+ LTS: 'h:mm:ss A',
+ // Customized:
+ L: 'DD/MM/YYYY',
+ LL: 'D MMMM YYYY',
+ LLL: 'D MMMM YYYY LT',
+ LLLL: 'dddd, D MMMM YYYY LT'
+ }
+} );
+
+// HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
+// wgTranslateNumerals is respected.
+moment.locale( moment.locale(), {
+ preparse: function ( s ) {
+ var i,
+ table = mw.language.getDigitTransformTable();
+ if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+ for ( i = 0; i < 10; i++ ) {
+ if ( table[ i ] !== undefined ) {
+ s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
+ }
+ }
+ }
+ // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
+ // aside from digit transformation. We can only override preparse, not extend it, so we
+ // have to replicate the comma replacement functionality here.
+ if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+ s = s.replace( /،/g, ',' );
+ }
+ return s;
+ },
+ postformat: function ( s ) {
+ var i,
+ table = mw.language.getDigitTransformTable();
+ if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+ for ( i = 0; i < 10; i++ ) {
+ if ( table[ i ] !== undefined ) {
+ s = s.replace( new RegExp( mw.RegExp.escape( i ), 'g' ), table[ i ] );
+ }
+ }
+ }
+ // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
+ // aside from digit transformation. We can only override postformat, not extend it, so we
+ // have to replicate the comma replacement functionality here.
+ if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+ s = s.replace( /,/g, '،' );
+ }
+ return s;
+ }
+} );
mediawiki/skins$ ln -s ../../skins-trunk/FooBar
The default skin Vector can be installed by cloning from Git:
- git clone https://git.wikimedia.org/git/mediawiki/skins/Vector.git
+ git clone https://phabricator.wikimedia.org/diffusion/SVEC/Vector
Other skins are also available:
- https://gerrit.wikimedia.org/r/#/admin/projects/?filter=mediawiki%252Fskins%252F
- https://git.wikimedia.org/project/mediawiki
+ https://phabricator.wikimedia.org/diffusion/SKIN/
Please note that under POSIX systems (Linux...), parent of a symbolic path
use MediaWiki\Logger\LegacySpi;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\Logger\MonologSpi;
+use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerInterface;
/**
* @since 1.18
*/
abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
+
+ /**
+ * The service locator created by prepareServices(). This service locator will
+ * be restored after each test. Tests that pollute the global service locator
+ * instance should use overrideMwServices() to isolate the test.
+ *
+ * @var MediaWikiServices|null
+ */
+ private static $serviceLocator = null;
+
/**
* $called tracks whether the setUp and tearDown method has been called.
* class extending MediaWikiTestCase usually override setUp and tearDown
}
}
- public function run( PHPUnit_Framework_TestResult $result = null ) {
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
+ // but let's make doubly sure.
+ self::prepareServices( new GlobalVarConfig() );
+ }
+
+ /**
+ * Prepare service configuration for unit testing.
+ *
+ * This calls MediaWikiServices::resetGlobalInstance() to allow some critical services
+ * to be overridden for testing.
+ *
+ * prepareServices() only needs to be called once, but should be called as early as possible,
+ * before any class has a chance to grab a reference to any of the global services
+ * instances that get discarded by prepareServices(). Only the first call has any effect,
+ * later calls are ignored.
+ *
+ * @note This is called by PHPUnitMaintClass::finalSetup.
+ *
+ * @see MediaWikiServices::resetGlobalInstance()
+ *
+ * @param Config $bootstrapConfig The bootstrap config to use with the new
+ * MediaWikiServices. Only used for the first call to this method.
+ */
+ public static function prepareServices( Config $bootstrapConfig ) {
+ static $servicesPrepared = false;
+
+ if ( $servicesPrepared ) {
+ return;
+ } else {
+ $servicesPrepared = true;
+ }
+
+ self::resetGlobalServices( $bootstrapConfig );
+ }
+
+ /**
+ * Reset global services, and install testing environment.
+ * This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
+ * This should only be used to set up the testing environment, not when
+ * running unit tests. Use overrideMwServices() for that.
+ *
+ * @see MediaWikiServices::resetGlobalInstance()
+ * @see prepareServices()
+ * @see overrideMwServices()
+ *
+ * @param Config|null $bootstrapConfig The bootstrap config to use with the new
+ * MediaWikiServices.
+ */
+ protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
+ $oldServices = MediaWikiServices::getInstance();
+ $oldConfigFactory = $oldServices->getConfigFactory();
+
+ $testConfig = self::makeTestConfig( $bootstrapConfig );
+
+ MediaWikiServices::resetGlobalInstance( $testConfig );
+
+ self::$serviceLocator = MediaWikiServices::getInstance();
+ self::installTestServices(
+ $oldConfigFactory,
+ self::$serviceLocator
+ );
+ }
+
+ /**
+ * Create a config suitable for testing, based on a base config, default overrides,
+ * and custom overrides.
+ *
+ * @param Config|null $baseConfig
+ * @param Config|null $customOverrides
+ *
+ * @return Config
+ */
+ private static function makeTestConfig(
+ Config $baseConfig = null,
+ Config $customOverrides = null
+ ) {
+ $defaultOverrides = new HashConfig();
+
+ if ( !$baseConfig ) {
+ $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
+ }
+
/* Some functions require some kind of caching, and will end up using the db,
* which we can't allow, as that would open a new connection for mysql.
* Replace with a HashBag. They would not be going to persist anyway.
*/
- ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+ $hashCache = [ 'class' => 'HashBagOStuff' ];
+ $objectCaches = [
+ CACHE_DB => $hashCache,
+ CACHE_ACCEL => $hashCache,
+ CACHE_MEMCACHED => $hashCache,
+ 'apc' => $hashCache,
+ 'xcache' => $hashCache,
+ 'wincache' => $hashCache,
+ ] + $baseConfig->get( 'ObjectCaches' );
+
+ $defaultOverrides->set( 'ObjectCaches', $objectCaches );
+ $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
+
+ $testConfig = $customOverrides
+ ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
+ : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
+
+ return $testConfig;
+ }
+
+ /**
+ * @param ConfigFactory $oldConfigFactory
+ * @param MediaWikiServices $newServices
+ *
+ * @throws MWException
+ */
+ private static function installTestServices(
+ ConfigFactory $oldConfigFactory,
+ MediaWikiServices $newServices
+ ) {
+ // Use bootstrap config for all configuration.
+ // This allows config overrides via global variables to take effect.
+ $bootstrapConfig = $newServices->getBootstrapConfig();
+ $newServices->resetServiceForTesting( 'ConfigFactory' );
+ $newServices->redefineService(
+ 'ConfigFactory',
+ self::makeTestConfigFactoryInstantiator(
+ $oldConfigFactory,
+ [ 'main' => $bootstrapConfig ]
+ )
+ );
+ }
+
+ /**
+ * @param ConfigFactory $oldFactory
+ * @param Config[] $configurations
+ *
+ * @return Closure
+ */
+ private static function makeTestConfigFactoryInstantiator(
+ ConfigFactory $oldFactory,
+ array $configurations
+ ) {
+ return function( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
+ $factory = new ConfigFactory();
+
+ // clone configurations from $oldFactory that are not overwritten by $configurations
+ $namesToClone = array_diff(
+ $oldFactory->getConfigNames(),
+ array_keys( $configurations )
+ );
+
+ foreach ( $namesToClone as $name ) {
+ $factory->register( $name, $oldFactory->makeConfig( $name ) );
+ }
+
+ foreach ( $configurations as $name => $config ) {
+ $factory->register( $name, $config );
+ }
+
+ return $factory;
+ };
+ }
+
+ /**
+ * Resets some well known services that typically have state that may interfere with unit tests.
+ * This is a lightweight alternative to resetGlobalServices().
+ *
+ * @note There is no guarantee that no references remain to stale service instances destroyed
+ * by a call to doLightweightServiceReset().
+ *
+ * @throws MWException if called outside of PHPUnit tests.
+ *
+ * @see resetGlobalServices()
+ */
+ private function doLightweightServiceReset() {
+ global $wgRequest;
- // Sandbox APC by replacing with in-process hash instead.
- // Ensures values are removed between tests.
- ObjectCache::$instances['apc'] =
- ObjectCache::$instances['xcache'] =
- ObjectCache::$instances['wincache'] = new HashBagOStuff;
+ JobQueueGroup::destroySingletons();
+ ObjectCache::clear();
+ FileBackendGroup::destroySingleton();
+
+ // TODO: move global state into MediaWikiServices
+ RequestContext::resetMain();
+ MediaHandler::resetCache();
+ if ( session_id() !== '' ) {
+ session_write_close();
+ session_id( '' );
+ }
+
+ $wgRequest = new FauxRequest();
+ MediaWiki\Session\SessionManager::resetCache();
+ }
+
+ public function run( PHPUnit_Framework_TestResult $result = null ) {
+ // Reset all caches between tests.
+ $this->doLightweightServiceReset();
$needsResetDB = false;
}
$this->mwGlobals = [];
$this->restoreLoggers();
+
+ if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
+ MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
+ }
+
+ // TODO: move global state into MediaWikiServices
RequestContext::resetMain();
MediaHandler::resetCache();
if ( session_id() !== '' ) {
);
}
+ /**
+ * Sets a service, maintaining a stashed version of the previous service to be
+ * restored in tearDown
+ *
+ * @since 1.27
+ *
+ * @param string $name
+ * @param object $object
+ */
+ protected function setService( $name, $object ) {
+ // If we did not yet override the service locator, so so now.
+ if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
+ $this->overrideMwServices();
+ }
+
+ MediaWikiServices::getInstance()->disableService( $name );
+ MediaWikiServices::getInstance()->redefineService(
+ $name,
+ function () use ( $object ) {
+ return $object;
+ }
+ );
+ }
+
/**
* Sets a global, maintaining a stashed version of the previous global to be
* restored in tearDown
* @param mixed $value Value to set the global to (ignored
* if an array is given as first argument).
*
+ * @note To allow changes to global variables to take effect on global service instances,
+ * call overrideMwServices().
+ *
* @since 1.21
*/
protected function setMwGlobals( $pairs, $value = null ) {
* @param array|string $globalKeys Key to the global variable, or an array of keys.
*
* @throws Exception When trying to stash an unset global
+ *
+ * @note To allow changes to global variables to take effect on global service instances,
+ * call overrideMwServices().
+ *
* @since 1.23
*/
protected function stashMwGlobals( $globalKeys ) {
*
* @throws MWException If the designated global is not an array.
*
+ * @note To allow changes to global variables to take effect on global service instances,
+ * call overrideMwServices().
+ *
* @since 1.21
*/
protected function mergeMwGlobalArrayValue( $name, $values ) {
$this->setMwGlobals( $name, $merged );
}
+ /**
+ * Stashes the global instance of MediaWikiServices, and installs a new one,
+ * allowing test cases to override settings and services.
+ * The previous instance of MediaWikiServices will be restored on tearDown.
+ *
+ * @since 1.27
+ *
+ * @param Config $configOverrides Configuration overrides for the new MediaWikiServices instance.
+ * @param callable[] $services An associative array of services to re-define. Keys are service
+ * names, values are callables.
+ *
+ * @return MediaWikiServices
+ * @throws MWException
+ */
+ protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
+ if ( !$configOverrides ) {
+ $configOverrides = new HashConfig();
+ }
+
+ $oldInstance = MediaWikiServices::getInstance();
+ $oldConfigFactory = $oldInstance->getConfigFactory();
+
+ $testConfig = self::makeTestConfig( null, $configOverrides );
+ $newInstance = new MediaWikiServices( $testConfig );
+
+ // Load the default wiring from the specified files.
+ // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
+ $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
+ $newInstance->loadWiringFiles( $wiringFiles );
+
+ // Provide a traditional hook point to allow extensions to configure services.
+ Hooks::run( 'MediaWikiServices', [ $newInstance ] );
+
+ foreach ( $services as $name => $callback ) {
+ $newInstance->redefineService( $name, $callback );
+ }
+
+ self::installTestServices(
+ $oldConfigFactory,
+ $newInstance
+ );
+ MediaWikiServices::forceGlobalInstance( $newInstance );
+
+ return $newInstance;
+ }
+
/**
* @since 1.27
* @param string|Language $lang
* @param LoggerInterface $logger
*/
protected function setLogger( $channel, LoggerInterface $logger ) {
+ // TODO: Once loggers are managed by MediaWikiServices, use
+ // overrideMwServices() to set loggers.
+
$provider = LoggerFactory::getProvider();
$wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
$singletons = $wrappedProvider->singletons;
$user = User::newFromName( 'UTSysop' );
$comment = __METHOD__ . ': Sample page for unit test.';
+ // Avoid memory leak...?
+ // LinkCache::singleton()->clear();
+ // Maybe. But doing this absolutely breaks $title->isRedirect() when called during unit tests....
+
$page = WikiPage::factory( $title );
$page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
return;
}
+ // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
+ // and DatabaseBase no longer use global state.
+
self::$dbSetup = true;
if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
<?php
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\ServiceDisabledException;
/**
* @covers MediaWiki\MediaWikiServices
*/
class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
+ /**
+ * @return Config
+ */
+ private function newTestConfig() {
+ $globalConfig = new GlobalVarConfig();
+
+ $testConfig = new HashConfig();
+ $testConfig->set( 'ServiceWiringFiles', $globalConfig->get( 'ServiceWiringFiles' ) );
+ $testConfig->set( 'ConfigRegistry', $globalConfig->get( 'ConfigRegistry' ) );
+
+ return $testConfig;
+ }
+
+ /**
+ * @return MediaWikiServices
+ */
+ private function newMediaWikiServices( Config $config = null ) {
+ if ( $config === null ) {
+ $config = $this->newTestConfig();
+ }
+
+ $instance = new MediaWikiServices( $config );
+
+ // Load the default wiring from the specified files.
+ $wiringFiles = $config->get( 'ServiceWiringFiles' );
+ $instance->loadWiringFiles( $wiringFiles );
+
+ return $instance;
+ }
+
public function testGetInstance() {
$services = MediaWikiServices::getInstance();
$this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $services );
}
+ public function testForceGlobalInstance() {
+ $newServices = $this->newMediaWikiServices();
+ $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+ $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $oldServices );
+ $this->assertNotSame( $oldServices, $newServices );
+
+ $theServices = MediaWikiServices::getInstance();
+ $this->assertSame( $theServices, $newServices );
+
+ MediaWikiServices::forceGlobalInstance( $oldServices );
+
+ $theServices = MediaWikiServices::getInstance();
+ $this->assertSame( $theServices, $oldServices );
+ }
+
+ public function testResetGlobalInstance() {
+ $newServices = $this->newMediaWikiServices();
+ $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+ MediaWikiServices::resetGlobalInstance( $this->newTestConfig() );
+ $theServices = MediaWikiServices::getInstance();
+
+ $this->assertNotSame( $theServices, $newServices );
+ $this->assertNotSame( $theServices, $oldServices );
+
+ MediaWikiServices::forceGlobalInstance( $oldServices );
+ }
+
+ public function testDisableStorageBackend() {
+ $newServices = $this->newMediaWikiServices();
+ $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+ $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $lbFactory->expects( $this->once() )
+ ->method( 'destroy' );
+
+ $newServices->redefineService(
+ 'DBLoadBalancerFactory',
+ function() use ( $lbFactory ) {
+ return $lbFactory;
+ }
+ );
+
+ // force the service to become active, so we can check that it does get destroyed
+ $newServices->getService( 'DBLoadBalancerFactory' );
+
+ MediaWikiServices::disableStorageBackend(); // should destroy DBLoadBalancerFactory
+
+ try {
+ MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' );
+ $this->fail( 'DBLoadBalancerFactory shoudl have been disabled' );
+ }
+ catch ( ServiceDisabledException $ex ) {
+ // ok, as expected
+ }
+ catch ( Throwable $ex ) {
+ $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
+ }
+
+ MediaWikiServices::forceGlobalInstance( $oldServices );
+ }
+
+ public function testResetChildProcessServices() {
+ $newServices = $this->newMediaWikiServices();
+ $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+ $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $lbFactory->expects( $this->once() )
+ ->method( 'destroy' );
+
+ $newServices->redefineService(
+ 'DBLoadBalancerFactory',
+ function() use ( $lbFactory ) {
+ return $lbFactory;
+ }
+ );
+
+ // force the service to become active, so we can check that it does get destroyed
+ $oldLBFactory = $newServices->getService( 'DBLoadBalancerFactory' );
+
+ MediaWikiServices::resetChildProcessServices();
+ $finalServices = MediaWikiServices::getInstance();
+
+ $newLBFactory = $finalServices->getService( 'DBLoadBalancerFactory' );
+
+ $this->assertNotSame( $oldLBFactory, $newLBFactory );
+
+ MediaWikiServices::forceGlobalInstance( $oldServices );
+ }
+
+ public function testResetServiceForTesting() {
+ $services = $this->newMediaWikiServices();
+ $serviceCounter = 0;
+
+ $services->defineService(
+ 'Test',
+ function() use ( &$serviceCounter ) {
+ $serviceCounter++;
+ $service = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+ $service->expects( $this->once() )->method( 'destroy' );
+ return $service;
+ }
+ );
+
+ // This should do nothing. In particular, it should not create a service instance.
+ $services->resetServiceForTesting( 'Test' );
+ $this->assertEquals( 0, $serviceCounter, 'No service instance should be created yet.' );
+
+ $oldInstance = $services->getService( 'Test' );
+ $this->assertEquals( 1, $serviceCounter, 'A service instance should exit now.' );
+
+ // The old instance should be detached, and destroy() called.
+ $services->resetServiceForTesting( 'Test' );
+ $newInstance = $services->getService( 'Test' );
+
+ $this->assertNotSame( $oldInstance, $newInstance );
+
+ // Satisfy the expectation that destroy() is called also for the second service instance.
+ $newInstance->destroy();
+ }
+
+ public function testResetServiceForTesting_noDestroy() {
+ $services = $this->newMediaWikiServices();
+
+ $services->defineService(
+ 'Test',
+ function() {
+ $service = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+ $service->expects( $this->never() )->method( 'destroy' );
+ return $service;
+ }
+ );
+
+ $oldInstance = $services->getService( 'Test' );
+
+ // The old instance should be detached, but destroy() not called.
+ $services->resetServiceForTesting( 'Test', false );
+ $newInstance = $services->getService( 'Test' );
+
+ $this->assertNotSame( $oldInstance, $newInstance );
+ }
+
public function provideGetters() {
- // NOTE: This should list all service getters defined in MediaWikiServices.
- // NOTE: For every test case defined here there should be a corresponding
- // test case defined in provideGetService().
- return [
- 'BootstrapConfig' => [ 'getBootstrapConfig', Config::class ],
- 'ConfigFactory' => [ 'getConfigFactory', ConfigFactory::class ],
- 'MainConfig' => [ 'getMainConfig', Config::class ],
- 'SiteStore' => [ 'getSiteStore', SiteStore::class ],
- 'SiteLookup' => [ 'getSiteLookup', SiteLookup::class ],
- 'StatsdDataFactory' => [ 'getStatsdDataFactory', StatsdDataFactory::class ],
- 'EventRelayerGroup' => [ 'getEventRelayerGroup', EventRelayerGroup::class ],
- 'SearchEngine' => [ 'newSearchEngine', SearchEngine::class ],
- 'SearchEngineFactory' => [ 'getSearchEngineFactory', SearchEngineFactory::class ],
- 'SearchEngineConfig' => [ 'getSearchEngineConfig', SearchEngineConfig::class ],
- 'SkinFactory' => [ 'getSkinFactory', SkinFactory::class ],
- ];
+ $getServiceCases = $this->provideGetService();
+ $getterCases = [];
+
+ // All getters should be named just like the service, with "get" added.
+ foreach ( $getServiceCases as $name => $case ) {
+ list( $service, $class ) = $case;
+ $getterCases[$name] = [
+ 'get' . $service,
+ $class,
+ ];
+ }
+
+ return $getterCases;
}
/**
'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory::class ],
'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
'SkinFactory' => [ 'SkinFactory', SkinFactory::class ],
+ 'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', 'LBFactory' ],
+ 'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
];
}
$name = 'TestService92834576';
- $this->setExpectedException( 'InvalidArgumentException' );
+ $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
$services->getService( $name );
}
+ public function testPeekService() {
+ $services = $this->newServiceContainer();
+
+ $services->defineService(
+ 'Foo',
+ function() {
+ return new stdClass();
+ }
+ );
+
+ $services->defineService(
+ 'Bar',
+ function() {
+ return new stdClass();
+ }
+ );
+
+ // trigger instantiation of Foo
+ $services->getService( 'Foo' );
+
+ $this->assertInternalType(
+ 'object',
+ $services->peekService( 'Foo' ),
+ 'Peek should return the service object if it had been accessed before.'
+ );
+
+ $this->assertNull(
+ $services->peekService( 'Bar' ),
+ 'Peek should return null if the service was never accessed.'
+ );
+ }
+
+ public function testPeekService_fail_unknown() {
+ $services = $this->newServiceContainer();
+
+ $name = 'TestService92834576';
+
+ $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
+
+ $services->peekService( $name );
+ }
+
public function testDefineService() {
$services = $this->newServiceContainer();
return $theService;
} );
- $this->setExpectedException( 'RuntimeException' );
+ $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
$services->defineService( $name, function() use ( $theService ) {
return $theService;
];
// loading the same file twice should fail, because
- $this->setExpectedException( 'RuntimeException' );
+ $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
$services->loadWiringFiles( $wiringFiles );
}
$theService = new stdClass();
$name = 'TestService92834576';
- $this->setExpectedException( 'RuntimeException' );
+ $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
$services->redefineService( $name, function() use ( $theService ) {
return $theService;
// create the service, so it can no longer be redefined
$services->getService( $name );
- $this->setExpectedException( 'RuntimeException' );
+ $this->setExpectedException( 'MediaWiki\Services\CannotReplaceActiveServiceException' );
+
+ $services->redefineService( $name, function() use ( $theService ) {
+ return $theService;
+ } );
+ }
+
+ public function testDisableService() {
+ $services = $this->newServiceContainer( [ 'Foo' ] );
+
+ $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+ $destructible->expects( $this->once() )
+ ->method( 'destroy' );
+
+ $services->defineService( 'Foo', function() use ( $destructible ) {
+ return $destructible;
+ } );
+ $services->defineService( 'Bar', function() {
+ return new stdClass();
+ } );
+ $services->defineService( 'Qux', function() {
+ return new stdClass();
+ } );
+
+ // instantiate Foo and Bar services
+ $services->getService( 'Foo' );
+ $services->getService( 'Bar' );
+
+ // disable service, should call destroy() once.
+ $services->disableService( 'Foo' );
+
+ // disabled service should still be listed
+ $this->assertContains( 'Foo', $services->getServiceNames() );
+
+ // getting other services should still work
+ $services->getService( 'Bar' );
+
+ // disable non-destructible service, and not-yet-instantiated service
+ $services->disableService( 'Bar' );
+ $services->disableService( 'Qux' );
+
+ $this->assertNull( $services->peekService( 'Bar' ) );
+ $this->assertNull( $services->peekService( 'Qux' ) );
+
+ // disabled service should still be listed
+ $this->assertContains( 'Bar', $services->getServiceNames() );
+ $this->assertContains( 'Qux', $services->getServiceNames() );
+
+ // re-enable Bar service
+ $services->redefineService( 'Bar', function() {
+ return new stdClass();
+ } );
+
+ $services->getService( 'Bar' );
+
+ $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
+ $services->getService( 'Qux' );
+ }
+
+ public function testDisableService_fail_undefined() {
+ $services = $this->newServiceContainer();
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+
+ $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
$services->redefineService( $name, function() use ( $theService ) {
return $theService;
} );
}
+ public function testDestroy() {
+ $services = $this->newServiceContainer();
+
+ $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
+ $destructible->expects( $this->once() )
+ ->method( 'destroy' );
+
+ $services->defineService( 'Foo', function() use ( $destructible ) {
+ return $destructible;
+ } );
+
+ $services->defineService( 'Bar', function() {
+ return new stdClass();
+ } );
+
+ // create the service
+ $services->getService( 'Foo' );
+
+ // destroy the container
+ $services->destroy();
+
+ $this->setExpectedException( 'MediaWiki\Services\ContainerDisabledException' );
+ $services->getService( 'Bar' );
+ }
+
}
public function testRegister() {
$factory = new ConfigFactory();
$factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
- $this->assertTrue( true ); // No exception thrown
+ $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
+ }
+
+ /**
+ * @covers ConfigFactory::register
+ */
+ public function testRegisterInvalid() {
+ $factory = new ConfigFactory();
$this->setExpectedException( 'InvalidArgumentException' );
$factory->register( 'invalid', 'Invalid callback' );
}
+ /**
+ * @covers ConfigFactory::register
+ */
+ public function testRegisterInstance() {
+ $config = GlobalVarConfig::newInstance();
+ $factory = new ConfigFactory();
+ $factory->register( 'unittest', $config );
+ $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
+ }
+
+ /**
+ * @covers ConfigFactory::register
+ */
+ public function testRegisterAgain() {
+ $factory = new ConfigFactory();
+ $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+ $config1 = $factory->makeConfig( 'unittest' );
+
+ $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+ $config2 = $factory->makeConfig( 'unittest' );
+
+ $this->assertNotSame( $config1, $config2 );
+ }
+
+ /**
+ * @covers ConfigFactory::register
+ */
+ public function testGetConfigNames() {
+ $factory = new ConfigFactory();
+ $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
+ $factory->register( 'bar', new HashConfig() );
+
+ $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
+ }
+
/**
* @covers ConfigFactory::makeConfig
*/
public function testMakeConfig() {
$factory = new ConfigFactory();
$factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+
+ $conf = $factory->makeConfig( 'unittest' );
+ $this->assertInstanceOf( 'Config', $conf );
+ $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
+ }
+
+ /**
+ * @covers ConfigFactory::makeConfig
+ */
+ public function testMakeConfigFallback() {
+ $factory = new ConfigFactory();
+ $factory->register( '*', 'GlobalVarConfig::newInstance' );
$conf = $factory->makeConfig( 'unittest' );
$this->assertInstanceOf( 'Config', $conf );
}
* @covers ConfigFactory::getDefaultInstance
*/
public function testGetDefaultInstance() {
+ // NOTE: the global config factory returned here has been overwritten
+ // for operation in test mode. It may not reflect LocalSettings.
$factory = ConfigFactory::getDefaultInstance();
$this->assertInstanceOf( 'Config', $factory->makeConfig( 'main' ) );
-
- $this->setExpectedException( 'ConfigException' );
- $factory->makeConfig( 'xyzzy' );
}
+
}
// may break testing against floating point values
// treated with PHP's serialize()
ini_set( 'serialize_precision', 17 );
+
+ // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
+ // But PHPUnit may not be loaded yet, so we have to wait until just
+ // before PHPUnit_TextUI_Command::main() is executed at the end of this file.
}
public function execute() {
'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
'Using PHP ' . PHP_VERSION . "\n";
+// Prepare global services for unit tests.
+// FIXME: this should be done in the finalSetup() method,
+// but PHPUnit may not have been loaded at that point.
+MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
+
$wgPhpUnitClass::main();
<?php
use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LoggerInterface;
/**
* @covers MediaWikiTestCase
public function testLoggersAreRestoredOnTearDown() {
// replacing an existing logger
$logger1 = LoggerFactory::getInstance( 'foo' );
- $this->setLogger( 'foo', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+ $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
$logger2 = LoggerFactory::getInstance( 'foo' );
$this->tearDown();
$logger3 = LoggerFactory::getInstance( 'foo' );
$this->assertNotSame( $logger1, $logger2 );
// replacing a non-existing logger
- $this->setLogger( 'bar', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+ $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
$logger1 = LoggerFactory::getInstance( 'bar' );
$this->tearDown();
$logger2 = LoggerFactory::getInstance( 'bar' );
// replacing same logger twice
$logger1 = LoggerFactory::getInstance( 'baz' );
- $this->setLogger( 'baz', $this->getMock( '\Psr\Log\LoggerInterface' ) );
- $this->setLogger( 'baz', $this->getMock( '\Psr\Log\LoggerInterface' ) );
+ $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
+ $this->setLogger( 'foo', $this->getMock( LoggerInterface::class ) );
$this->tearDown();
$logger2 = LoggerFactory::getInstance( 'baz' );