From: jenkins-bot Date: Thu, 29 Oct 2015 20:41:00 +0000 (+0000) Subject: Merge "[search] Fix method call on null value" X-Git-Tag: 1.31.0-rc.0~9177 X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dcompta/comptes/journal.php?a=commitdiff_plain;h=281f185ede65c09ccb273bfc4f37302106f531a4;hp=9250e72a6c4d187f1f3a35f486d35bb8b6317488;p=lhc%2Fweb%2Fwiklou.git Merge "[search] Fix method call on null value" --- diff --git a/RELEASE-NOTES-1.27 b/RELEASE-NOTES-1.27 index b3add69744..d0a5c4e5fe 100644 --- a/RELEASE-NOTES-1.27 +++ b/RELEASE-NOTES-1.27 @@ -92,6 +92,8 @@ changes to languages because of Bugzilla reports. * ProfilerOutputUdp was removed. Note that there is a ProfilerOutputStats class. * WikiPage::doDeleteArticleReal() and WikiPage::doDeleteArticle() now ignore the 2nd and 3rd arguments (formerly $id and $commit). +* Removed "loaderScripts" option from ResourceLoaderFileModule class. +* Removed ORM-like wrapper added in 1.20. == Compatibility == diff --git a/autoload.php b/autoload.php index 731bdaaf5f..65a84bf4aa 100644 --- a/autoload.php +++ b/autoload.php @@ -541,9 +541,8 @@ $wgAutoloadLocalClasses = array( 'IDatabase' => __DIR__ . '/includes/db/IDatabase.php', 'IEContentAnalyzer' => __DIR__ . '/includes/libs/IEContentAnalyzer.php', 'IEUrlExtension' => __DIR__ . '/includes/libs/IEUrlExtension.php', + 'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php', 'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php', - 'IORMRow' => __DIR__ . '/includes/db/IORMRow.php', - 'IORMTable' => __DIR__ . '/includes/db/IORMTable.php', 'IP' => __DIR__ . '/includes/utils/IP.php', 'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php', 'IPTC' => __DIR__ . '/includes/media/IPTC.php', @@ -847,10 +846,6 @@ $wgAutoloadLocalClasses = array( 'OOUIHTMLForm' => __DIR__ . '/includes/htmlform/OOUIHTMLForm.php', 'ORAField' => __DIR__ . '/includes/db/DatabaseOracle.php', 'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php', - 'ORMIterator' => __DIR__ . '/includes/db/ORMIterator.php', - 'ORMResult' => __DIR__ . '/includes/db/ORMResult.php', - 'ORMRow' => __DIR__ . '/includes/db/ORMRow.php', - 'ORMTable' => __DIR__ . '/includes/db/ORMTable.php', 'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php', 'ObjectCacheSessionHandler' => __DIR__ . '/includes/objectcache/ObjectCacheSessionHandler.php', 'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php', @@ -1076,7 +1071,7 @@ $wgAutoloadLocalClasses = array( 'RollbackEdits' => __DIR__ . '/maintenance/rollbackEdits.php', 'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php', 'RunJobs' => __DIR__ . '/maintenance/runJobs.php', - 'RunningStat' => __DIR__ . '/includes/libs/RunningStat.php', + 'RunningStat' => __DIR__ . '/includes/compat/RunningStatCompat.php', 'SQLiteField' => __DIR__ . '/includes/db/DatabaseSqlite.php', 'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php', 'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php', diff --git a/composer.json b/composer.json index 62090173ac..4aa9b92c5c 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "ext-iconv": "*", "liuggio/statsd-php-client": "1.0.16", "mediawiki/at-ease": "1.1.0", - "oojs/oojs-ui": "0.12.12", + "oojs/oojs-ui": "0.13.0", "oyejorge/less.php": "1.7.0.9", "php": ">=5.3.3", "psr/log": "1.0.0", @@ -31,6 +31,7 @@ "wikimedia/composer-merge-plugin": "1.2.1", "wikimedia/ip-set": "1.0.1", "wikimedia/relpath": "1.0.3", + "wikimedia/running-stat": "1.1.0", "wikimedia/utfnormal": "1.0.3", "wikimedia/wrappedstring": "2.0.0", "zordius/lightncandy": "0.21" @@ -39,7 +40,7 @@ "jakub-onderka/php-parallel-lint": "0.9", "justinrainbow/json-schema": "~1.3", "mediawiki/mediawiki-codesniffer": "0.4.0", - "monolog/monolog": "1.14.0", + "monolog/monolog": "~1.17.2", "nmred/kafka-php": "0.1.4", "phpunit/phpunit": "3.7.37", "wikimedia/avro": "1.7.7" diff --git a/docs/extension.schema.json b/docs/extension.schema.json index 218a19c3b9..dde4fa189e 100644 --- a/docs/extension.schema.json +++ b/docs/extension.schema.json @@ -302,128 +302,185 @@ "ResourceModules": { "type": "object", "description": "ResourceLoader modules to register", - "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9-\\.]+$": { "type": "object", - "description": "A single ResourceLoader module descriptor", - "properties": { - "localBasePath": { - "type": "string", - "description": "Base path to prepend to all local paths in $options. Defaults to $IP" - }, - "remoteBasePath": { - "type": "string", - "description": "Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath" - }, - "remoteExtPath": { - "type": "string", - "description": "Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath" - }, - "scripts": { - "type": ["string", "array"], - "description": "Scripts to always include (array of file paths)", - "items": { - "type": "string" - } - }, - "languageScripts": { - "type": "object", - "description": "Scripts to include in specific language contexts (mapping of language code to file path(s))", - "patternProperties": { - "^[a-zA-Z0-9-]{2,}$": { - "type": [ - "string", - "array" - ], + "anyOf": [ + { + "description": "A ResourceLoaderFileModule definition", + "additionalProperties": false, + "properties": { + "localBasePath": { + "type": "string", + "description": "Base path to prepend to all local paths in $options. Defaults to $IP" + }, + "remoteBasePath": { + "type": "string", + "description": "Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath" + }, + "remoteExtPath": { + "type": "string", + "description": "Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath" + }, + "scripts": { + "type": ["string", "array"], + "description": "Scripts to always include (array of file paths)", "items": { "type": "string" } - } - } - }, - "skinScripts": { - "type": "object", - "description": "Scripts to include in specific skin contexts (mapping of skin name to script(s)", - "patternProperties": { - ".+": { - "type": [ - "string", - "array" - ], + }, + "languageScripts": { + "type": "object", + "description": "Scripts to include in specific language contexts (mapping of language code to file path(s))", + "patternProperties": { + "^[a-zA-Z0-9-]{2,}$": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + } + } + }, + "skinScripts": { + "type": "object", + "description": "Scripts to include in specific skin contexts (mapping of skin name to script(s)", + "patternProperties": { + ".+": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + } + } + }, + "debugScripts": { + "type": ["string", "array"], + "description": "Scripts to include in debug contexts", "items": { "type": "string" } - } - } - }, - "debugScripts": { - "type": ["string", "array"], - "description": "Scripts to include in debug contexts", - "items": { - "type": "string" - } - }, - "loaderScripts": { - "type": ["string", "array"], - "description": "Scripts to include in the startup module", - "items": { - "type": "string" - } - }, - "dependencies": { - "type": ["string", "array"], - "description": "Modules which must be loaded before this module", - "items": { - "type": "string" - } - }, - "styles": { - "type": ["string", "array", "object"], - "description": "Styles to always load", - "items": { - "type": "string" - } - }, - "skinStyles": { - "type": "object", - "description": "Styles to include in specific skin contexts (mapping of skin name to style(s))", - "patternProperties": { - ".+": { - "type": [ - "string", - "array" - ], + }, + "loaderScripts": { + "type": ["string", "array"], + "description": "Scripts to include in the startup module", + "items": { + "type": "string" + } + }, + "dependencies": { + "type": ["string", "array"], + "description": "Modules which must be loaded before this module", + "items": { + "type": "string" + } + }, + "styles": { + "type": ["string", "array", "object"], + "description": "Styles to always load", + "items": { + "type": "string" + } + }, + "skinStyles": { + "type": "object", + "description": "Styles to include in specific skin contexts (mapping of skin name to style(s))", + "patternProperties": { + ".+": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + } + } + }, + "messages": { + "type": ["string", "array"], + "description": "Messages to always load", + "items": { + "type": "string" + } + }, + "group": { + "type": "string", + "description": "Group which this module should be loaded together with" + }, + "position": { + "type": "string", + "description": "Position on the page to load this module at", + "enum": [ + "bottom", + "top" + ] + }, + "templates": { + "type": "object", + "description": "Templates to be loaded for client-side usage" + }, + "targets": { + "type": ["string", "array"], + "description": "ResourceLoader target the module can run on", "items": { "type": "string" } } } }, - "messages": { - "type": ["string", "array"], - "description": "Messages to always load", - "items": { - "type": "string" + { + "description": "A ResourceLoaderImageModule definition", + "additionalProperties": false, + "properties": { + "class": { + "enum": ["ResourceLoaderImageModule"] + }, + "data": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "selector": { + "type": "string" + }, + "selectorWithoutVariant": { + "type": "string" + }, + "selectorWithVariant": { + "type": "string" + }, + "variants": { + "type": "object" + }, + "images": { + "type": "object" + }, + "position": { + "enum": [ + "top", + "bottom" + ] + } } }, - "group": { - "type": "string", - "description": "Group which this module should be loaded together with" - }, - "position": { - "type": "string", - "description": "Position on the page to load this module at", - "enum": [ - "bottom", - "top" - ] - }, - "templates": { - "type": "object", - "description": "Templates to be loaded for client-side usage" + { + "description": "An arbitrary ResourceLoaderModule definition", + "properties": { + "class": { + "type": "string", + "pattern": "^((?!ResourceLoader(File|Image)Module).)*$" + } + }, + "required": ["class"] } - } + ] } } }, diff --git a/includes/Block.php b/includes/Block.php index 5dee23ea4a..b57b3e8b92 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -681,10 +681,10 @@ class Block { public static function isWhitelistedFromAutoblocks( $ip ) { // Try to get the autoblock_whitelist from the cache, as it's faster // than getting the msg raw and explode()'ing it. - - $lines = ObjectCache::getMainWANInstance()->getWithSetCallback( + $cache = ObjectCache::getMainWANInstance(); + $lines = $cache->getWithSetCallback( wfMemcKey( 'ipb', 'autoblock', 'whitelist' ), - 86400, + $cache::TTL_DAY, function () { return explode( "\n", wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() ); diff --git a/includes/Collation.php b/includes/Collation.php index 40e8627187..7fa5c6e477 100644 --- a/includes/Collation.php +++ b/includes/Collation.php @@ -508,7 +508,7 @@ class IcuCollation extends Collation { // Save to cache $this->firstLetterData = $data; - $cache->set( $cacheKey, $data, 86400 * 7 /* 1 week */ ); + $cache->set( $cacheKey, $data, $cache::TTL_WEEK ); return $data; } diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 910c121a21..e9169384c4 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2263,7 +2263,8 @@ $wgMainWANCache = false; * the value is an associative array of parameters. The "cacheId" parameter is * a cache identifier from $wgObjectCaches. The "relayerConfig" parameter is an * array used to construct an EventRelayer object. The "pool" parameter is a - * string that is used as a PubSub channel prefix. + * string that is used as a PubSub channel prefix. The "loggroup" parameter + * controls where log events are sent. * * @since 1.26 */ @@ -3396,14 +3397,14 @@ $wgMangleFlashPolicy = true; /** @} */ # End of output format settings } /*************************************************************************//** - * @name Resource loader settings + * @name ResourceLoader settings * @{ */ /** * Client-side resource modules. * - * Extensions should add their resource loader module definitions + * Extensions should add their ResourceLoader module definitions * to the $wgResourceModules variable. * * @par Example: @@ -3528,7 +3529,7 @@ $wgResourceLoaderSources = array(); $wgResourceBasePath = null; /** - * Maximum time in seconds to cache resources served by the resource loader. + * Maximum time in seconds to cache resources served by ResourceLoader. * Used to set last modified headers (max-age/s-maxage). * * Following options to distinguish: @@ -3735,7 +3736,7 @@ $wgResourceLoaderStorageVersion = 1; */ $wgAllowSiteCSSOnRestrictedPages = false; -/** @} */ # End of resource loader settings } +/** @} */ # End of ResourceLoader settings } /*************************************************************************//** * @name Page title and interwiki link settings @@ -6741,6 +6742,7 @@ $wgJobClasses = array( 'ThumbnailRender' => 'ThumbnailRenderJob', 'recentChangesUpdate' => 'RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'RefreshLinksJob', // for cascading protection + 'refreshLinksDynamic' => 'RefreshLinksJob', // for pages with dynamic content 'activityUpdateJob' => 'ActivityUpdateJob', 'enqueue' => 'EnqueueJob', // local queue for multi-DC setups 'null' => 'NullJob' diff --git a/includes/EditPage.php b/includes/EditPage.php index 81f35f9ea2..2bddc3ee73 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -2041,7 +2041,7 @@ class EditPage { } /** - * @param Title $title + * @param User $user * @param string $oldModel * @param string $newModel * @param string $reason @@ -2059,26 +2059,26 @@ class EditPage { $log->publish( $logid ); } - /** * Register the change of watch status */ protected function updateWatchlist() { global $wgUser; - if ( $wgUser->isLoggedIn() - && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS ) - ) { - $fname = __METHOD__; - $title = $this->mTitle; - $watch = $this->watchthis; - - // Do this in its own transaction to reduce contention... - $dbw = wfGetDB( DB_MASTER ); - $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) { - WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); - } ); + if ( !$wgUser->isLoggedIn() ) { + return; } + + $user = $wgUser; + $title = $this->mTitle; + $watch = $this->watchthis; + // Do this in its own transaction to reduce contention... + DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) { + if ( $watch == $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) ) { + return; // nothing to change + } + WatchAction::doWatchOrUnwatch( $watch, $title, $user ); + } ); } /** diff --git a/includes/MovePage.php b/includes/MovePage.php index 0f9374a7e3..736cd8d650 100644 --- a/includes/MovePage.php +++ b/includes/MovePage.php @@ -371,7 +371,7 @@ class MovePage { $dbw->endAtomic( __METHOD__ ); - $params = array( $this->oldTitle, $this->newTitle, $user, $pageid, $redirid, $reason ); + $params = array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ); $dbw->onTransactionIdle( function () use ( $params ) { Hooks::run( 'TitleMoveComplete', $params ); } ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index d29ec54d4b..726fec1b84 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -50,7 +50,7 @@ class OutputPage extends ContextSource { /** * @var array Additional stylesheets. Looks like this is for extensions. - * Might be replaced by resource loader. + * Might be replaced by ResourceLoader. */ protected $mExtStyles = array(); @@ -132,7 +132,7 @@ class OutputPage extends ContextSource { private $mLanguageLinks = array(); /** - * Used for JavaScript (pre resource loader) + * Used for JavaScript (predates ResourceLoader) * @todo We should split JS / CSS. * mScripts content is inserted as is in "" by Skin. This might * contain either a link to a stylesheet or inline CSS. @@ -151,8 +151,6 @@ class OutputPage extends ContextSource { /** @var array Array of elements in "". Parser might add its own headers! */ protected $mHeadItems = array(); - // @todo FIXME: Next 5 variables probably come from the resource loader - /** @var array */ protected $mModules = array(); @@ -224,7 +222,7 @@ class OutputPage extends ContextSource { /** * @var bool Comes from the parser. This was probably made to load CSS/JS * only if we had "". Used directly in CategoryPage.php. - * Looks like resource loader can replace this. + * Looks like ResourceLoader can replace this. */ public $mNoGallery = false; @@ -568,8 +566,8 @@ class OutputPage extends ContextSource { } /** - * Add one or more modules recognized by the resource loader. Modules added - * through this function will be loaded by the resource loader when the + * Add one or more modules recognized by ResourceLoader. Modules added + * through this function will be loaded by ResourceLoader when the * page loads. * * @param string|array $modules Module name (string) or array of module names @@ -591,8 +589,8 @@ class OutputPage extends ContextSource { } /** - * Add only JS of one or more modules recognized by the resource loader. Module - * scripts added through this function will be loaded by the resource loader when + * Add only JS of one or more modules recognized by ResourceLoader. Module + * scripts added through this function will be loaded by ResourceLoader when * the page loads. * * @param string|array $modules Module name (string) or array of module names @@ -610,25 +608,11 @@ class OutputPage extends ContextSource { * @return array Array of module names */ public function getModuleStyles( $filter = false, $position = null ) { - // T97420 - $resourceLoader = $this->getResourceLoader(); - - foreach ( $this->mModuleStyles as $val ) { - $module = $resourceLoader->getModule( $val ); - - if ( $module instanceof ResourceLoaderModule && $module->isPositionDefault() ) { - $warning = __METHOD__ . ': style module should define its position explicitly: ' . - $val . ' ' . get_class( $module ); - wfDebugLog( 'resourceloader', $warning ); - wfLogWarning( $warning ); - } - } - return $this->getModules( $filter, $position, 'mModuleStyles' ); } /** - * Add only CSS of one or more modules recognized by the resource loader. + * Add only CSS of one or more modules recognized by ResourceLoader. * * Module styles added through this function will be added using standard link CSS * tags, rather than as a combined Javascript and CSS package. Thus, they will @@ -3074,10 +3058,6 @@ class OutputPage extends ContextSource { ResourceLoaderModule::TYPE_SCRIPTS ); - $links[] = $this->makeResourceLoaderLink( $this->getModuleStyles( true, 'bottom' ), - ResourceLoaderModule::TYPE_STYLES - ); - // Modules requests - let the client calculate dependencies and batch requests as it likes // Only load modules that have marked themselves for loading at the bottom $modules = $this->getModules( true, 'bottom' ); @@ -3143,9 +3123,6 @@ class OutputPage extends ContextSource { * @return string */ function getBottomScripts() { - // In case the skin wants to add bottom CSS - $this->getSkin()->setupSkinUserCss( $this ); - return $this->getScriptsForBottomQueue(); } @@ -3684,7 +3661,7 @@ class OutputPage extends ContextSource { $otherTags = array(); // Tags to append after the normal tags $resourceLoader = $this->getResourceLoader(); - $moduleStyles = $this->getModuleStyles( true, 'top' ); + $moduleStyles = $this->getModuleStyles(); // Per-site custom styles $moduleStyles[] = 'site'; diff --git a/includes/SiteStats.php b/includes/SiteStats.php index 5b361b9be7..33bab6573f 100644 --- a/includes/SiteStats.php +++ b/includes/SiteStats.php @@ -180,9 +180,10 @@ class SiteStats { * @return int */ static function numberingroup( $group ) { - return ObjectCache::getMainWANInstance()->getWithSetCallback( + $cache = ObjectCache::getMainWANInstance(); + return $cache->getWithSetCallback( wfMemcKey( 'SiteStats', 'groupcounts', $group ), - 3600, + $cache::TTL_HOUR, function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) { $dbr = wfGetDB( DB_SLAVE ); diff --git a/includes/User.php b/includes/User.php index e1c9e358f1..a6b897dd92 100644 --- a/includes/User.php +++ b/includes/User.php @@ -438,10 +438,11 @@ class User implements IDBAccessObject { $data[$name] = $this->$name; } $data['mVersion'] = self::VERSION; - $key = wfMemcKey( 'user', 'id', $this->mId ); - $opts = Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) ); - ObjectCache::getMainWANInstance()->set( $key, $data, 3600, $opts ); + + $cache = ObjectCache::getMainWANInstance(); + $key = wfMemcKey( 'user', 'id', $this->mId ); + $cache->set( $key, $data, $cache::TTL_HOUR, $opts ); } /** @name newFrom*() static factory methods */ @@ -1739,8 +1740,6 @@ class User implements IDBAccessObject { return false; } - global $wgMemc; - $limits = $wgRateLimits[$action]; $keys = array(); $id = $this->getId(); @@ -1795,11 +1794,13 @@ class User implements IDBAccessObject { $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit; } + $cache = ObjectCache::getLocalClusterInstance(); + $triggered = false; foreach ( $keys as $key => $limit ) { list( $max, $period ) = $limit; $summary = "(limit $max in {$period}s)"; - $count = $wgMemc->get( $key ); + $count = $cache->get( $key ); // Already pinged? if ( $count ) { if ( $count >= $max ) { @@ -1812,11 +1813,11 @@ class User implements IDBAccessObject { } else { wfDebug( __METHOD__ . ": adding record for $key $summary\n" ); if ( $incrBy > 0 ) { - $wgMemc->add( $key, 0, intval( $period ) ); // first ping + $cache->add( $key, 0, intval( $period ) ); // first ping } } if ( $incrBy > 0 ) { - $wgMemc->incr( $key, $incrBy ); + $cache->incr( $key, $incrBy ); } } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index c13b30b47f..3d56128163 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -800,7 +800,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $subscribers ), ); - ApiResult::setArrayType( $arr['subscribers'], 'BCarray' ); + ApiResult::setArrayType( $arr['subscribers'], 'array' ); ApiResult::setIndexedTagName( $arr['subscribers'], 's' ); $data[] = $arr; } diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php index e87fc97a19..ebddd51734 100644 --- a/includes/api/ApiStashEdit.php +++ b/includes/api/ApiStashEdit.php @@ -39,7 +39,7 @@ class ApiStashEdit extends ApiBase { const ERROR_UNCACHEABLE = 'uncacheable'; public function execute() { - global $wgMemc; + $cache = ObjectCache::getLocalClusterInstance(); $user = $this->getUser(); $params = $this->extractRequestParams(); @@ -111,11 +111,10 @@ class ApiStashEdit extends ApiBase { // De-duplicate requests on the same key if ( $user->pingLimiter( 'stashedit' ) ) { $status = 'ratelimited'; - } elseif ( $wgMemc->lock( $key, 0, 30 ) ) { + } elseif ( $cache->lock( $key, 0, 30 ) ) { /** @noinspection PhpUnusedLocalVariableInspection */ - $unlocker = new ScopedCallback( function() use ( $key ) { - global $wgMemc; - $wgMemc->unlock( $key ); + $unlocker = new ScopedCallback( function() use ( $cache, $key ) { + $cache->unlock( $key ); } ); $status = self::parseAndStash( $page, $content, $user ); } else { @@ -133,7 +132,7 @@ class ApiStashEdit extends ApiBase { * @since 1.25 */ public static function parseAndStash( WikiPage $page, Content $content, User $user ) { - global $wgMemc; + $cache = ObjectCache::getLocalClusterInstance(); $format = $content->getDefaultFormat(); $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false ); @@ -146,7 +145,7 @@ class ApiStashEdit extends ApiBase { ); if ( $stashInfo ) { - $ok = $wgMemc->set( $key, $stashInfo, $ttl ); + $ok = $cache->set( $key, $stashInfo, $ttl ); if ( $ok ) { wfDebugLog( 'StashEdit', "Cached parser output for key '$key'." ); return self::ERROR_NONE; @@ -173,7 +172,7 @@ class ApiStashEdit extends ApiBase { * will do nothing. Provided the values are cacheable, they will be stored * in memcached so that final edit submission might make use of them. * - * @param Article|WikiPage $page Page title + * @param Page|Article|WikiPage $page Page title * @param Content $content Proposed page content * @param Content $pstContent The result of preSaveTransform() on $content * @param ParserOutput $pOut The result of getParserOutput() on $pstContent @@ -186,7 +185,7 @@ class ApiStashEdit extends ApiBase { Page $page, Content $content, Content $pstContent, ParserOutput $pOut, ParserOptions $pstOpts, ParserOptions $pOpts, $timestamp ) { - global $wgMemc; + $cache = ObjectCache::getLocalClusterInstance(); // getIsPreview() controls parser function behavior that references things // like user/revision that don't exists yet. The user/text should already @@ -219,7 +218,7 @@ class ApiStashEdit extends ApiBase { return false; } - $ok = $wgMemc->set( $key, $stashInfo, $ttl ); + $ok = $cache->set( $key, $stashInfo, $ttl ); if ( !$ok ) { wfDebugLog( 'StashEdit', "Failed to cache preview parser output for key '$key'." ); } else { @@ -247,17 +246,17 @@ class ApiStashEdit extends ApiBase { * @return stdClass|bool Returns false on cache miss */ public static function checkCache( Title $title, Content $content, User $user ) { - global $wgMemc; + $cache = ObjectCache::getLocalClusterInstance(); $key = self::getStashKey( $title, $content, $user ); - $editInfo = $wgMemc->get( $key ); + $editInfo = $cache->get( $key ); if ( !is_object( $editInfo ) ) { $start = microtime( true ); // We ignore user aborts and keep parsing. Block on any prior parsing // so as to use it's results and make use of the time spent parsing. - if ( $wgMemc->lock( $key, 30, 30 ) ) { - $editInfo = $wgMemc->get( $key ); - $wgMemc->unlock( $key ); + if ( $cache->lock( $key, 30, 30 ) ) { + $editInfo = $cache->get( $key ); + $cache->unlock( $key ); } $sec = microtime( true ) - $start; if ( $sec > .01 ) { diff --git a/includes/api/i18n/ksh.json b/includes/api/i18n/ksh.json index a13ad0ae03..d307fa79f9 100644 --- a/includes/api/i18n/ksh.json +++ b/includes/api/i18n/ksh.json @@ -60,6 +60,7 @@ "apihelp-delete-param-title": "De Övverschreff vun dä Sigg zom fottschmiiße. Kam_mer nit zersamme met „$1pageid“ bruche.", "apihelp-delete-param-pageid": "De Kännong vun dä Sigg zom fottschmiiße. Kam_mer nit zersamme met „$1title“ bruche.", "apihelp-delete-param-reason": "Der Jrond för et Fottschmiiße. Wann dä nit aanjejovve es, weed ene automattesch usjräschnete Jrond jenumme.", + "apihelp-delete-param-tags": "Donn de Makehronge änndere, di för dä Enndraach em Logbohch jesaz wähde sulle.", "apihelp-delete-param-watch": "Donn di Sigg en däm aktoälle Metmaacher sing Oppaßleß opnämme.", "apihelp-delete-param-watchlist": "Donn di Sigg op däm aktoälle Metmaacher sing Oppaßleß udder nemm se druß fott, donn de Enschtällonge nämme, udder donn de Oppaßleß jaa nit verändere.", "apihelp-delete-param-unwatch": "Schmihß di Sigg us däm aktoälle Metmaacher singe Oppaßless erus.", @@ -388,6 +389,15 @@ "apihelp-query+allredirects-example-unique": "Ongerscheidlijje Sigge opleste.", "apihelp-query+allredirects-example-unique-generator": "Hollt alle Zihlsigge un makkehr di (noch) nit doh sin.", "apihelp-query+allredirects-example-generator": "Holl de Sigge met de Ömleidonge.", + "apihelp-query+allrevisions-description": "Donn alle Väsjohne opleßte.", + "apihelp-query+allrevisions-param-start": "Et Dattom un de Zigg vun woh aff opjezallt wähde sull.", + "apihelp-query+allrevisions-param-end": "Et Dattom un de Zigg bes woh hen opjezallt wähde sull.", + "apihelp-query+allrevisions-param-user": "Donn blohß Väsjohne vun heh däm Metmaacher opleßte.", + "apihelp-query+allrevisions-param-excludeuser": "Donn kein Väsjohne vun heh däm Metmaacher opleßte.", + "apihelp-query+allrevisions-param-namespace": "Donn blohß Sigge en heh däm Appachtemang opleßte.", + "apihelp-query+allrevisions-param-generatetitles": "Wann als ene Jenerahtor enjesaz, brängk dat Övverschreffte un kein Kännonge vun Väsjohne.", + "apihelp-query+allrevisions-example-user": "Donn de läzde fuffzisch Beijdrähsch vum Metmaacher „Example“ opleßte.", + "apihelp-query+allrevisions-example-ns-main": "Donn de eezde fuffzisch Väsjohne em Houp-Appachemang opleßte.", "apihelp-query+alltransclusions-param-from": "De Övverschreff vun dä ennjeföhschte Sigg, woh de Leß medd aanfange sull.", "apihelp-query+alltransclusions-param-to": "De Övverschreff vun dä ennjeföhschte Sigg, woh et Zälle ophühre sull.", "apihelp-query+alltransclusions-param-prefix": "Söhk noh alle dä ennjeföhschte Sigge ier Övverschreffte, di met heh däm Täx aanfange.", @@ -693,6 +703,7 @@ "apihelp-query+recentchanges-param-limit": "Wi vill Änderonge ensjesammp zem aanzeije?", "apihelp-query+recentchanges-param-type": "Wat för en Zoot Änneronge aanzeije?", "apihelp-query+recentchanges-param-toponly": "Bloß Änderonge aanzeije, woh de neußte Väsjohn beij eruß kohm.", + "apihelp-query+recentchanges-param-generaterevisions": "Wann als ene Jenerahtor enjesaz, brängk dat Kännonge vun Väsjohne un kein Övverschreffte. Enndrähsch en de neußte Änderonge der ohne en Väsjohnskännong, alsu de miehste Logbohchenndrähsch, bränge jaa nix.", "apihelp-query+recentchanges-example-simple": "Zeijsch de {{LCFIRST:{{int:recentchanges}}}}", "apihelp-query+redirects-description": "Jiff alle Ömleijdonge noh dä aanjejovve Sigge uß.", "apihelp-query+redirects-param-prop": "Wat för en Eijeschaffte holle:", diff --git a/includes/api/i18n/tcy.json b/includes/api/i18n/tcy.json new file mode 100644 index 0000000000..bdee17e199 --- /dev/null +++ b/includes/api/i18n/tcy.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Bharathesha Alasandemajalu" + ] + }, + "apihelp-query+watchlist-param-type": "ವಾ ನಮೂನೆದ ಬದಲಾವಣೆ ತೊಜವೋಡು", + "apihelp-query+watchlist-paramvalue-type-external": "ಪಿದಯೀದ ಬದಲಾವಣೇ", + "apihelp-query+watchlist-paramvalue-type-new": "ಪಾಲೆ ಉಂಡುಮಾನ್ಪುನಾ" +} diff --git a/includes/api/i18n/zh-hans.json b/includes/api/i18n/zh-hans.json index 93617f9e58..b4275b5a21 100644 --- a/includes/api/i18n/zh-hans.json +++ b/includes/api/i18n/zh-hans.json @@ -880,6 +880,7 @@ "apihelp-query+recentchanges-param-token": "请改用[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。", "apihelp-query+recentchanges-param-limit": "返回总计更新数。", "apihelp-query+recentchanges-param-type": "显示的更改类型。", + "apihelp-query+recentchanges-param-generaterevisions": "当作为生成器使用时,生成修订ID而不是标题。不带关联修订ID的最近更改记录(例如大多数日志记录)将不会生成任何东西。", "apihelp-query+recentchanges-example-simple": "最近更改列表。", "apihelp-query+recentchanges-example-generator": "获取有关最近未巡查更改的页面信息。", "apihelp-query+redirects-description": "返回至指定页面的所有重定向。", diff --git a/includes/cache/FileCacheBase.php b/includes/cache/FileCacheBase.php index 5632596a43..360420b615 100644 --- a/includes/cache/FileCacheBase.php +++ b/includes/cache/FileCacheBase.php @@ -235,8 +235,8 @@ abstract class FileCacheBase { * @return void */ public function incrMissesRecent( WebRequest $request ) { - global $wgMemc; if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) { + $cache = ObjectCache::getLocalClusterInstance(); # Get a large IP range that should include the user even if that # person's IP address changes $ip = $request->getIP(); @@ -249,17 +249,17 @@ abstract class FileCacheBase { # Bail out if a request already came from this range... $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip ); - if ( $wgMemc->get( $key ) ) { + if ( $cache->get( $key ) ) { return; // possibly the same user } - $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); + $cache->set( $key, 1, self::MISS_TTL_SEC ); # Increment the number of cache misses... $key = $this->cacheMissKey(); - if ( $wgMemc->get( $key ) === false ) { - $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); + if ( $cache->get( $key ) === false ) { + $cache->set( $key, 1, self::MISS_TTL_SEC ); } else { - $wgMemc->incr( $key ); + $cache->incr( $key ); } } } @@ -269,9 +269,9 @@ abstract class FileCacheBase { * @return int */ public function getMissesRecent() { - global $wgMemc; + $cache = ObjectCache::getLocalClusterInstance(); - return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() ); + return self::MISS_FACTOR * $cache->get( $this->cacheMissKey() ); } /** diff --git a/includes/cache/MessageBlobStore.php b/includes/cache/MessageBlobStore.php index b7c70c1e3f..6290eaea01 100644 --- a/includes/cache/MessageBlobStore.php +++ b/includes/cache/MessageBlobStore.php @@ -1,6 +1,6 @@ wanCache->delete( $sidebarKey, 5 ); + $this->wanCache->delete( $sidebarKey ); } // Update the message in the message blob store diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php index b59437e094..088398278a 100644 --- a/includes/changes/EnhancedChangesList.php +++ b/includes/changes/EnhancedChangesList.php @@ -608,7 +608,7 @@ class EnhancedChangesList extends ChangesList { } # Diff and hist links - if ( $type == RC_LOG && $type != RC_CATEGORIZE ) { + if ( $type != RC_LOG && $type != RC_CATEGORIZE ) { $query['action'] = 'history'; $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query ); } diff --git a/includes/compat/RunningStatCompat.php b/includes/compat/RunningStatCompat.php new file mode 100644 index 0000000000..ac82f44d0e --- /dev/null +++ b/includes/compat/RunningStatCompat.php @@ -0,0 +1,28 @@ +__call( __FUNCTION__, func_get_args() ); } + public function writesPending() { + return $this->__call( __FUNCTION__, func_get_args() ); + } + public function writesOrCallbacksPending() { return $this->__call( __FUNCTION__, func_get_args() ); } diff --git a/includes/db/Database.php b/includes/db/Database.php index 57c28bbef0..cf774fa053 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -441,6 +441,14 @@ abstract class DatabaseBase implements IDatabase { return $this->mDoneWrites ?: false; } + /** + * @return bool Whether there is a transaction open with possible write queries + * @since 1.27 + */ + public function writesPending() { + return $this->mTrxLevel && $this->mTrxDoneWrites; + } + /** * Returns true if there is a transaction open with possible write * queries or transaction pre-commit/idle callbacks waiting on it to finish. @@ -3819,16 +3827,20 @@ abstract class DatabaseBase implements IDatabase { * * @param IDatabase $db1 * @param IDatabase ... - * @return array ('lag': highest lag, 'since': lowest estimate UNIX timestamp) + * @return array Map of values: + * - lag: highest lag of any of the DBs + * - since: oldest UNIX timestamp of any of the DB lag estimates + * - pending: whether any of the DBs have uncommitted changes * @since 1.27 */ public static function getCacheSetOptions( IDatabase $db1 ) { - $res = array( 'lag' => 0, 'since' => INF ); + $res = array( 'lag' => 0, 'since' => INF, 'pending' => false ); foreach ( func_get_args() as $db ) { /** @var IDatabase $db */ $status = $db->getSessionLagStatus(); $res['lag'] = max( $res['lag'], $status['lag'] ); $res['since'] = min( $res['since'], $status['since'] ); + $res['pending'] = $res['pending'] ?: $db->writesPending(); } return $res; diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php index e6c285e7a8..78d26ae94b 100644 --- a/includes/db/DatabaseError.php +++ b/includes/db/DatabaseError.php @@ -78,6 +78,10 @@ class DBExpectedError extends DBError { return $s; } + function getPageTitle() { + return $this->msg( 'databaseerror', 'Database error' ); + } + /** * @return string */ @@ -456,4 +460,7 @@ class DBUnexpectedError extends DBError { * @ingroup Database */ class DBReadOnlyError extends DBExpectedError { + function getPageTitle() { + return $this->msg( 'readonly', 'Database is locked' ); + } } diff --git a/includes/db/IDatabase.php b/includes/db/IDatabase.php index d11058307d..19eb12628b 100644 --- a/includes/db/IDatabase.php +++ b/includes/db/IDatabase.php @@ -159,6 +159,12 @@ interface IDatabase { */ public function lastDoneWrites(); + /** + * @return bool Whether there is a transaction open with possible write queries + * @since 1.27 + */ + public function writesPending(); + /** * Returns true if there is a transaction open with possible write * queries or transaction pre-commit/idle callbacks waiting on it to finish. diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php deleted file mode 100644 index c66cddfd5d..0000000000 --- a/includes/db/IORMRow.php +++ /dev/null @@ -1,270 +0,0 @@ - - */ - -interface IORMRow { - /** - * Load the specified fields from the database. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param array|null $fields - * @param bool $override - * @param bool $skipLoaded - * - * @return bool Success indicator - */ - public function loadFields( $fields = null, $override = true, $skipLoaded = false ); - - /** - * Gets the value of a field. - * - * @since 1.20 - * - * @param string $name - * @param mixed $default - * - * @throws MWException - * @return mixed - */ - public function getField( $name, $default = null ); - - /** - * Gets the value of a field but first loads it if not done so already. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param string $name - * - * @return mixed - */ - public function loadAndGetField( $name ); - - /** - * Remove a field. - * - * @since 1.20 - * - * @param string $name - */ - public function removeField( $name ); - - /** - * Returns the objects database id. - * - * @since 1.20 - * - * @return int|null - */ - public function getId(); - - /** - * Sets the objects database id. - * - * @since 1.20 - * - * @param int|null $id - */ - public function setId( $id ); - - /** - * Gets if a certain field is set. - * - * @since 1.20 - * - * @param string $name - * - * @return bool - */ - public function hasField( $name ); - - /** - * Gets if the id field is set. - * - * @since 1.20 - * - * @return bool - */ - public function hasIdField(); - - /** - * Sets multiple fields. - * - * @since 1.20 - * - * @param array $fields The fields to set - * @param bool $override Override already set fields with the provided values? - */ - public function setFields( array $fields, $override = true ); - - /** - * Serializes the object to an associative array which - * can then easily be converted into JSON or similar. - * - * @since 1.20 - * - * @param null|array $fields - * @param bool $incNullId - * - * @return array - */ - public function toArray( $fields = null, $incNullId = false ); - - /** - * Load the default values, via getDefaults. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $override - */ - public function loadDefaults( $override = true ); - - /** - * Writes the answer to the database, either updating it - * when it already exists, or inserting it when it doesn't. - * - * @since 1.20 - * - * @param string|null $functionName - * @deprecated since 1.22 - * - * @return bool Success indicator - */ - public function save( $functionName = null ); - - /** - * Removes the object from the database. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @return bool Success indicator - */ - public function remove(); - - /** - * Return the names and values of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getFields(); - - /** - * Return the names of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getSetFieldNames(); - - /** - * Sets the value of a field. - * Strings can be provided for other types, - * so this method can be called from unserialization handlers. - * - * @since 1.20 - * - * @param string $name - * @param mixed $value - * - * @throws MWException - */ - public function setField( $name, $value ); - - /** - * Add an amount (can be negative) to the specified field (needs to be numeric). - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param string $field - * @param int $amount - * - * @return bool Success indicator - */ - public function addToField( $field, $amount ); - - /** - * Return the names of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getFieldNames(); - - /** - * Computes and updates the values of the summary fields. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param array|string|null $summaryFields - */ - public function loadSummaryFields( $summaryFields = null ); - - /** - * Sets the value for the @see $updateSummaries field. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $update - */ - public function setUpdateSummaries( $update ); - - /** - * Sets the value for the @see $inSummaryMode field. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $summaryMode - */ - public function setSummaryMode( $summaryMode ); - - /** - * Returns the table this IORMRow is a row in. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @return IORMTable - */ - public function getTable(); -} diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php deleted file mode 100644 index b2527f95b5..0000000000 --- a/includes/db/IORMTable.php +++ /dev/null @@ -1,520 +0,0 @@ - - */ - -interface IORMTable { - /** - * Returns the name of the database table objects of this type are stored in. - * - * @since 1.20 - * - * @return string - */ - public function getName(); - - /** - * Returns the name of a IORMRow implementing class that - * represents single rows in this table. - * - * @since 1.20 - * - * @return string - */ - public function getRowClass(); - - /** - * Returns an array with the fields and their types this object contains. - * This corresponds directly to the fields in the database, without prefix. - * - * field name => type - * - * Allowed types: - * * id - * * str - * * int - * * float - * * bool - * * array - * * blob - * - * @todo Get rid of the id field. Every row instance needs to have one so - * this is just causing hassle at various locations by requiring an extra - * check for field name. - * - * @since 1.20 - * - * @return array - */ - public function getFields(); - - /** - * Returns a list of default field values. - * field name => field value - * - * @since 1.20 - * - * @return array - */ - public function getDefaults(); - - /** - * Returns a list of the summary fields. - * These are fields that cache computed values, such as the amount of linked objects of $type. - * This is relevant as one might not want to do actions such as log changes when these get updated. - * - * @since 1.20 - * - * @return array - */ - public function getSummaryFields(); - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as DBDataObject. Field names get prefixed. - * - * @see DatabaseBase::select() - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return ORMResult The result set - * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode) - */ - public function select( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ); - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as DBDataObject. Field names get prefixed. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return array Array of self - */ - public function selectObjects( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ); - - /** - * Do the actual select. - * - * @since 1.20 - * - * @param null|string|array $fields - * @param array $conditions - * @param array $options - * @param null|string $functionName - * - * @return ResultWrapper - * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode) - */ - public function rawSelect( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ); - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as associative arrays. - * Provided field names get prefixed. - * Returned field names will not have a prefix. - * - * When $collapse is true: - * If one field is selected, each item in the result array will be this field. - * If two fields are selected, each item in the result array will have as key - * the first field and as value the second field. - * If more then two fields are selected, each item will be an associative array. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param bool $collapse Set to false to always return each result row as associative array. - * @param string|null $functionName - * - * @return array Array of array - */ - public function selectFields( $fields = null, array $conditions = array(), - array $options = array(), $collapse = true, $functionName = null ); - - /** - * Selects the specified fields of the first matching record. - * Field names get prefixed. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return IORMRow|bool False on failure - */ - public function selectRow( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null ); - - /** - * Selects the specified fields of the records matching the provided - * conditions. Field names do NOT get prefixed. - * - * @since 1.20 - * - * @param array $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return ResultWrapper - */ - public function rawSelectRow( array $fields, array $conditions = array(), - array $options = array(), $functionName = null ); - - /** - * Selects the specified fields of the first record matching the provided - * conditions and returns it as an associative array, or false when nothing matches. - * This method makes use of selectFields and expects the same parameters and - * returns the same results (if there are any, if there are none, this method returns false). - * @see IORMTable::selectFields - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param bool $collapse Set to false to always return each result row as associative array. - * @param string|null $functionName - * - * @return mixed|array|bool False on failure - */ - public function selectFieldsRow( $fields = null, array $conditions = array(), - array $options = array(), $collapse = true, $functionName = null ); - - /** - * Returns if there is at least one record matching the provided conditions. - * Condition field names get prefixed. - * - * @since 1.20 - * - * @param array $conditions - * - * @return bool - */ - public function has( array $conditions = array() ); - - /** - * Checks if the table exists - * - * @since 1.21 - * - * @return bool - */ - public function exists(); - - /** - * Returns the amount of matching records. - * Condition field names get prefixed. - * - * Note that this can be expensive on large tables. - * In such cases you might want to use DatabaseBase::estimateRowCount instead. - * - * @since 1.20 - * - * @param array $conditions - * @param array $options - * - * @return int - */ - public function count( array $conditions = array(), array $options = array() ); - - /** - * Removes the object from the database. - * - * @since 1.20 - * - * @param array $conditions - * @param string|null $functionName - * - * @return bool Success indicator - */ - public function delete( array $conditions, $functionName = null ); - - /** - * Get API parameters for the fields supported by this object. - * - * @since 1.20 - * - * @param bool $requireParams - * @param bool $setDefaults - * - * @return array - */ - public function getAPIParams( $requireParams = false, $setDefaults = false ); - - /** - * Returns an array with the fields and their descriptions. - * - * field name => field description - * - * @since 1.20 - * - * @return array - */ - public function getFieldDescriptions(); - - /** - * Get the database type used for read operations. - * - * @since 1.20 - * - * @return int DB_ enum - */ - public function getReadDb(); - - /** - * Set the database type to use for read operations. - * - * @param int $db - * - * @since 1.20 - */ - public function setReadDb( $db ); - - /** - * Get the ID of the any foreign wiki to use as a target for database operations - * - * @since 1.20 - * - * @return string|bool The target wiki, in a form that LBFactory - * understands (or false if the local wiki is used) - */ - public function getTargetWiki(); - - /** - * Set the ID of the any foreign wiki to use as a target for database operations - * - * @param string|bool $wiki The target wiki, in a form that LBFactory - * understands (or false if the local wiki shall be used) - * - * @since 1.20 - */ - public function setTargetWiki( $wiki ); - - /** - * Get the database type used for read operations. - * This is to be used instead of wfGetDB. - * - * @see LoadBalancer::getConnection - * - * @since 1.20 - * - * @return DatabaseBase The database object - */ - public function getReadDbConnection(); - - /** - * Get the database type used for read operations. - * This is to be used instead of wfGetDB. - * - * @see LoadBalancer::getConnection - * - * @since 1.20 - * - * @return DatabaseBase The database object - */ - public function getWriteDbConnection(); - - /** - * Get the database type used for read operations. - * - * @see wfGetLB - * - * @since 1.20 - * - * @return LoadBalancer The database load balancer object - */ - public function getLoadBalancer(); - - /** - * Releases the lease on the given database connection. This is useful mainly - * for connections to a foreign wiki. It does nothing for connections to the local wiki. - * - * @see LoadBalancer::reuseConnection - * - * @param DatabaseBase $db The database - * - * @since 1.20 - */ - public function releaseConnection( DatabaseBase $db ); - - /** - * Update the records matching the provided conditions by - * setting the fields that are keys in the $values param to - * their corresponding values. - * - * @since 1.20 - * - * @param array $values - * @param array $conditions - * - * @return bool Success indicator - */ - public function update( array $values, array $conditions = array() ); - - /** - * Computes the values of the summary fields of the objects matching the provided conditions. - * - * @since 1.20 - * - * @param array|string|null $summaryFields - * @param array $conditions - */ - public function updateSummaryFields( $summaryFields = null, array $conditions = array() ); - - /** - * Takes in an associative array with field names as keys and - * their values as value. The field names are prefixed with the - * db field prefix. - * - * @since 1.20 - * - * @param array $values - * - * @return array - */ - public function getPrefixedValues( array $values ); - - /** - * Takes in a field or array of fields and returns an - * array with their prefixed versions, ready for db usage. - * - * @since 1.20 - * - * @param array $fields - * - * @return array - */ - public function getPrefixedFields( array $fields ); - - /** - * Takes in a field and returns an it's prefixed version, ready for db usage. - * - * @since 1.20 - * - * @param string|array $field - * - * @return string - */ - public function getPrefixedField( $field ); - - /** - * Takes an array of field names with prefix and returns the unprefixed equivalent. - * - * @since 1.20 - * @deprecated since 1.25, will be removed - * - * @param string[] $fieldNames - * - * @return string[] - */ - public function unprefixFieldNames( array $fieldNames ); - - /** - * Takes a field name with prefix and returns the unprefixed equivalent. - * - * @since 1.20 - * @deprecated since 1.25, will be removed - * - * @param string $fieldName - * - * @return string - */ - public function unprefixFieldName( $fieldName ); - - /** - * Get an array with fields from a database result, - * that can be fed directly to the constructor or - * to setFields. - * - * @since 1.20 - * - * @param stdClass $result - * - * @return array - */ - public function getFieldsFromDBResult( stdClass $result ); - - /** - * Get a new instance of the class from a database result. - * - * @since 1.20 - * - * @param stdClass $result - * - * @return IORMRow - */ - public function newRowFromDBResult( stdClass $result ); - - /** - * Get a new instance of the class from an array. - * - * @since 1.20 - * - * @param array $data - * @param bool $loadDefaults - * - * @return IORMRow - */ - public function newRow( array $data, $loadDefaults = false ); - - /** - * Return the names of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getFieldNames(); - - /** - * Gets if the object can take a certain field. - * - * @since 1.20 - * - * @param string $name - * - * @return bool - */ - public function canHaveField( $name ); -} diff --git a/includes/db/ORMIterator.php b/includes/db/ORMIterator.php deleted file mode 100644 index e8104b6ffe..0000000000 --- a/includes/db/ORMIterator.php +++ /dev/null @@ -1,30 +0,0 @@ - - */ -interface ORMIterator extends Iterator { -} diff --git a/includes/db/ORMResult.php b/includes/db/ORMResult.php deleted file mode 100644 index 327d20d9e4..0000000000 --- a/includes/db/ORMResult.php +++ /dev/null @@ -1,121 +0,0 @@ - - */ - -class ORMResult implements ORMIterator { - /** - * @var ResultWrapper - */ - protected $res; - - /** - * @var int - */ - protected $key; - - /** - * @var IORMRow - */ - protected $current; - - /** - * @var IORMTable - */ - protected $table; - - /** - * @param IORMTable $table - * @param ResultWrapper $res - */ - public function __construct( IORMTable $table, ResultWrapper $res ) { - $this->table = $table; - $this->res = $res; - $this->key = 0; - $this->setCurrent( $this->res->current() ); - } - - /** - * @param bool|object $row - */ - protected function setCurrent( $row ) { - if ( $row === false ) { - $this->current = false; - } else { - $this->current = $this->table->newRowFromDBResult( $row ); - } - } - - /** - * @return int - */ - public function count() { - return $this->res->numRows(); - } - - /** - * @return bool - */ - public function isEmpty() { - return $this->res->numRows() === 0; - } - - /** - * @return IORMRow - */ - public function current() { - return $this->current; - } - - /** - * @return int - */ - public function key() { - return $this->key; - } - - public function next() { - $row = $this->res->next(); - $this->setCurrent( $row ); - $this->key++; - } - - public function rewind() { - $this->res->rewind(); - $this->key = 0; - $this->setCurrent( $this->res->current() ); - } - - /** - * @return bool - */ - public function valid() { - return $this->current !== false; - } -} diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php deleted file mode 100644 index b0bade3328..0000000000 --- a/includes/db/ORMRow.php +++ /dev/null @@ -1,593 +0,0 @@ - - */ - -class ORMRow implements IORMRow { - /** - * The fields of the object. - * field name (w/o prefix) => value - * - * @since 1.20 - * @var array - */ - protected $fields = array( 'id' => null ); - - /** - * If the object should update summaries of linked items when changed. - * For example, update the course_count field in universities when a course in courses is deleted. - * Settings this to false can prevent needless updating work in situations - * such as deleting a university, which will then delete all it's courses. - * - * @deprecated since 1.22 - * @since 1.20 - * @var bool - */ - protected $updateSummaries = true; - - /** - * Indicates if the object is in summary mode. - * This mode indicates that only summary fields got updated, - * which allows for optimizations. - * - * @deprecated since 1.22 - * @since 1.20 - * @var bool - */ - protected $inSummaryMode = false; - - /** - * @deprecated since 1.22 - * @since 1.20 - * @var ORMTable|null - */ - protected $table; - - /** - * Constructor. - * - * @since 1.20 - * - * @param IORMTable|null $table Deprecated since 1.22 - * @param array|null $fields - * @param bool $loadDefaults Deprecated since 1.22 - */ - public function __construct( IORMTable $table = null, $fields = null, $loadDefaults = false ) { - $this->table = $table; - - if ( !is_array( $fields ) ) { - $fields = array(); - } - - if ( $loadDefaults ) { - $fields = array_merge( $this->table->getDefaults(), $fields ); - } - - $this->setFields( $fields ); - } - - /** - * Load the specified fields from the database. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param array|null $fields - * @param bool $override - * @param bool $skipLoaded - * - * @return bool Success indicator - */ - public function loadFields( $fields = null, $override = true, $skipLoaded = false ) { - if ( is_null( $this->getId() ) ) { - return false; - } - - if ( is_null( $fields ) ) { - $fields = array_keys( $this->table->getFields() ); - } - - if ( $skipLoaded ) { - $fields = array_diff( $fields, array_keys( $this->fields ) ); - } - - if ( !empty( $fields ) ) { - $result = $this->table->rawSelectRow( - $this->table->getPrefixedFields( $fields ), - array( $this->table->getPrefixedField( 'id' ) => $this->getId() ), - array( 'LIMIT' => 1 ), - __METHOD__ - ); - - if ( $result !== false ) { - $this->setFields( $this->table->getFieldsFromDBResult( $result ), $override ); - - return true; - } - - return false; - } - - return true; - } - - /** - * Gets the value of a field. - * - * @since 1.20 - * - * @param string $name Field name - * @param mixed $default Default value to return when none is found - * (default: null) - * - * @throws MWException - * @return mixed - */ - public function getField( $name, $default = null ) { - if ( $this->hasField( $name ) ) { - return $this->fields[$name]; - } elseif ( !is_null( $default ) ) { - return $default; - } else { - throw new MWException( 'Attempted to get not-set field ' . $name ); - } - } - - /** - * Gets the value of a field but first loads it if not done so already. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param string $name - * - * @return mixed - */ - public function loadAndGetField( $name ) { - if ( !$this->hasField( $name ) ) { - $this->loadFields( array( $name ) ); - } - - return $this->getField( $name ); - } - - /** - * Remove a field. - * - * @since 1.20 - * - * @param string $name - */ - public function removeField( $name ) { - unset( $this->fields[$name] ); - } - - /** - * Returns the objects database id. - * - * @since 1.20 - * - * @return int|null - */ - public function getId() { - return $this->getField( 'id' ); - } - - /** - * Sets the objects database id. - * - * @since 1.20 - * - * @param int|null $id - */ - public function setId( $id ) { - $this->setField( 'id', $id ); - } - - /** - * Gets if a certain field is set. - * - * @since 1.20 - * - * @param string $name - * - * @return bool - */ - public function hasField( $name ) { - return array_key_exists( $name, $this->fields ); - } - - /** - * Gets if the id field is set. - * - * @since 1.20 - * - * @return bool - */ - public function hasIdField() { - return $this->hasField( 'id' ) && !is_null( $this->getField( 'id' ) ); - } - - /** - * Gets the fields => values to write to the table. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @return array - */ - protected function getWriteValues() { - $values = array(); - - foreach ( $this->table->getFields() as $name => $type ) { - if ( array_key_exists( $name, $this->fields ) ) { - $value = $this->fields[$name]; - - // Skip null id fields so that the DBMS can set the default. - if ( $name === 'id' && is_null( $value ) ) { - continue; - } - - switch ( $type ) { - case 'array': - $value = (array)$value; - // fall-through! - case 'blob': - $value = serialize( $value ); - // fall-through! - } - - $values[$this->table->getPrefixedField( $name )] = $value; - } - } - - return $values; - } - - /** - * Sets multiple fields. - * - * @since 1.20 - * - * @param array $fields The fields to set - * @param bool $override Override already set fields with the provided values? - */ - public function setFields( array $fields, $override = true ) { - foreach ( $fields as $name => $value ) { - if ( $override || !$this->hasField( $name ) ) { - $this->setField( $name, $value ); - } - } - } - - /** - * Serializes the object to an associative array which - * can then easily be converted into JSON or similar. - * - * @since 1.20 - * - * @param null|array $fields - * @param bool $incNullId - * - * @return array - */ - public function toArray( $fields = null, $incNullId = false ) { - $data = array(); - $setFields = array(); - - if ( !is_array( $fields ) ) { - $setFields = $this->getSetFieldNames(); - } else { - foreach ( $fields as $field ) { - if ( $this->hasField( $field ) ) { - $setFields[] = $field; - } - } - } - - foreach ( $setFields as $field ) { - if ( $incNullId || $field != 'id' || $this->hasIdField() ) { - $data[$field] = $this->getField( $field ); - } - } - - return $data; - } - - /** - * Load the default values, via getDefaults. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $override - */ - public function loadDefaults( $override = true ) { - $this->setFields( $this->table->getDefaults(), $override ); - } - - /** - * Writes the answer to the database, either updating it - * when it already exists, or inserting it when it doesn't. - * - * @since 1.20 - * @deprecated since 1.22 Use IORMTable->updateRow or ->insertRow - * - * @param string|null $functionName - * - * @return bool Success indicator - */ - public function save( $functionName = null ) { - if ( $this->hasIdField() ) { - return $this->table->updateRow( $this, $functionName ); - } else { - return $this->table->insertRow( $this, $functionName ); - } - } - - /** - * Updates the object in the database. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param string|null $functionName - * - * @return bool Success indicator - */ - protected function saveExisting( $functionName = null ) { - $dbw = $this->table->getWriteDbConnection(); - - $success = $dbw->update( - $this->table->getName(), - $this->getWriteValues(), - $this->table->getPrefixedValues( $this->getUpdateConditions() ), - is_null( $functionName ) ? __METHOD__ : $functionName - ); - - $this->table->releaseConnection( $dbw ); - - // DatabaseBase::update does not always return true for success as documented... - return $success !== false; - } - - /** - * Returns the WHERE considtions needed to identify this object so - * it can be updated. - * - * @since 1.20 - * - * @return array - */ - protected function getUpdateConditions() { - return array( 'id' => $this->getId() ); - } - - /** - * Inserts the object into the database. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param string|null $functionName - * @param array|null $options - * - * @return bool Success indicator - */ - protected function insert( $functionName = null, array $options = null ) { - $dbw = $this->table->getWriteDbConnection(); - - $success = $dbw->insert( - $this->table->getName(), - $this->getWriteValues(), - is_null( $functionName ) ? __METHOD__ : $functionName, - $options - ); - - // DatabaseBase::insert does not always return true for success as documented... - $success = $success !== false; - - if ( $success ) { - $this->setField( 'id', $dbw->insertId() ); - } - - $this->table->releaseConnection( $dbw ); - - return $success; - } - - /** - * Removes the object from the database. - * - * @since 1.20 - * @deprecated since 1.22, use IORMTable->removeRow - * - * @return bool Success indicator - */ - public function remove() { - $this->beforeRemove(); - - $success = $this->table->removeRow( $this, __METHOD__ ); - - if ( $success ) { - $this->onRemoved(); - } - - return $success; - } - - /** - * Gets called before an object is removed from the database. - * - * @since 1.20 - * @deprecated since 1.22 - */ - protected function beforeRemove() { - $this->loadFields( $this->getBeforeRemoveFields(), false, true ); - } - - /** - * Before removal of an object happens, @see beforeRemove gets called. - * This method loads the fields of which the names have been returned by - * this one (or all fields if null is returned). This allows for loading - * info needed after removal to get rid of linked data and the like. - * - * @since 1.20 - * - * @return array|null - */ - protected function getBeforeRemoveFields() { - return array(); - } - - /** - * Gets called after successful removal. - * Can be overridden to get rid of linked data. - * - * @since 1.20 - * @deprecated since 1.22 - */ - protected function onRemoved() { - $this->setField( 'id', null ); - } - - /** - * Return the names and values of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getFields() { - return $this->fields; - } - - /** - * Return the names of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getSetFieldNames() { - return array_keys( $this->fields ); - } - - /** - * Sets the value of a field. - * Strings can be provided for other types, - * so this method can be called from unserialization handlers. - * - * @since 1.20 - * - * @param string $name - * @param mixed $value - * - * @throws MWException - */ - public function setField( $name, $value ) { - $this->fields[$name] = $value; - } - - /** - * Add an amount (can be negative) to the specified field (needs to be numeric). - * - * @since 1.20 - * @deprecated since 1.22, use IORMTable->addToField - * - * @param string $field - * @param int $amount - * - * @return bool Success indicator - */ - public function addToField( $field, $amount ) { - return $this->table->addToField( $this->getUpdateConditions(), $field, $amount ); - } - - /** - * Return the names of the fields. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @return array - */ - public function getFieldNames() { - return array_keys( $this->table->getFields() ); - } - - /** - * Computes and updates the values of the summary fields. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param array|string|null $summaryFields - */ - public function loadSummaryFields( $summaryFields = null ) { - } - - /** - * Sets the value for the @see $updateSummaries field. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $update - */ - public function setUpdateSummaries( $update ) { - $this->updateSummaries = $update; - } - - /** - * Sets the value for the @see $inSummaryMode field. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @param bool $summaryMode - */ - public function setSummaryMode( $summaryMode ) { - $this->inSummaryMode = $summaryMode; - } - - /** - * Returns the table this IORMRow is a row in. - * - * @since 1.20 - * @deprecated since 1.22 - * - * @return IORMTable - */ - public function getTable() { - return $this->table; - } -} diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php deleted file mode 100644 index 562a8106ff..0000000000 --- a/includes/db/ORMTable.php +++ /dev/null @@ -1,1144 +0,0 @@ - - */ - -class ORMTable extends DBAccessBase implements IORMTable { - /** - * Cache for instances, used by the singleton method. - * - * @since 1.20 - * @deprecated since 1.21 - * - * @var ORMTable[] - */ - protected static $instanceCache = array(); - - /** - * @since 1.21 - * - * @var string - */ - protected $tableName; - - /** - * @since 1.21 - * - * @var string[] - */ - protected $fields = array(); - - /** - * @since 1.21 - * - * @var string - */ - protected $fieldPrefix = ''; - - /** - * @since 1.21 - * - * @var string - */ - protected $rowClass = 'ORMRow'; - - /** - * @since 1.21 - * - * @var array - */ - protected $defaults = array(); - - /** - * ID of the database connection to use for read operations. - * Can be changed via @see setReadDb. - * - * @since 1.20 - * - * @var int DB_ enum - */ - protected $readDb = DB_SLAVE; - - /** - * Constructor. - * - * @since 1.21 - * - * @param string $tableName - * @param string[] $fields - * @param array $defaults - * @param string|null $rowClass - * @param string $fieldPrefix - */ - public function __construct( $tableName = '', array $fields = array(), - array $defaults = array(), $rowClass = null, $fieldPrefix = '' - ) { - $this->tableName = $tableName; - $this->fields = $fields; - $this->defaults = $defaults; - - if ( is_string( $rowClass ) ) { - $this->rowClass = $rowClass; - } - - $this->fieldPrefix = $fieldPrefix; - } - - /** - * @see IORMTable::getName - * - * @since 1.21 - * - * @return string - * @throws MWException - */ - public function getName() { - if ( $this->tableName === '' ) { - throw new MWException( 'The table name needs to be set' ); - } - - return $this->tableName; - } - - /** - * Gets the db field prefix. - * - * @since 1.20 - * @deprecated since 1.25, use the $this->fieldPrefix property instead - * - * @return string - */ - protected function getFieldPrefix() { - return $this->fieldPrefix; - } - - /** - * @see IORMTable::getRowClass - * - * @since 1.21 - * - * @return string - */ - public function getRowClass() { - return $this->rowClass; - } - - /** - * @see ORMTable::getFields - * - * @since 1.21 - * - * @return array - * @throws MWException - */ - public function getFields() { - if ( $this->fields === array() ) { - throw new MWException( 'The table needs to have one or more fields' ); - } - - return $this->fields; - } - - /** - * Returns a list of default field values. - * field name => field value - * - * @since 1.20 - * - * @return array - */ - public function getDefaults() { - return $this->defaults; - } - - /** - * Returns a list of the summary fields. - * These are fields that cache computed values, such as the amount of linked objects of $type. - * This is relevant as one might not want to do actions such as log changes when these get updated. - * - * @since 1.20 - * - * @return array - */ - public function getSummaryFields() { - return array(); - } - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as DBDataObject. Field names get prefixed. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return ORMResult - */ - public function select( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null - ) { - $res = $this->rawSelect( $fields, $conditions, $options, $functionName ); - - return new ORMResult( $this, $res ); - } - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as DBDataObject. Field names get prefixed. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return array Array of row objects - * @throws DBQueryError If the query failed (even if the database was in ignoreErrors mode). - */ - public function selectObjects( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null - ) { - $result = $this->selectFields( $fields, $conditions, $options, false, $functionName ); - - $objects = array(); - - foreach ( $result as $record ) { - $objects[] = $this->newRow( $record ); - } - - return $objects; - } - - /** - * Do the actual select. - * - * @since 1.20 - * - * @param null|string|array $fields - * @param array $conditions - * @param array $options - * @param null|string $functionName - * @return ResultWrapper - * @throws Exception - * @throws MWException - */ - public function rawSelect( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null - ) { - if ( is_null( $fields ) ) { - $fields = array_keys( $this->getFields() ); - } else { - $fields = (array)$fields; - } - - $dbr = $this->getReadDbConnection(); - $result = $dbr->select( - $this->getName(), - $this->getPrefixedFields( $fields ), - $this->getPrefixedValues( $conditions ), - is_null( $functionName ) ? __METHOD__ : $functionName, - $options - ); - - /* @var Exception $error */ - $error = null; - - if ( $result === false ) { - // Database connection was in "ignoreErrors" mode. We don't like that. - // So, we emulate the DBQueryError that should have been thrown. - $error = new DBQueryError( - $dbr, - $dbr->lastError(), - $dbr->lastErrno(), - $dbr->lastQuery(), - is_null( $functionName ) ? __METHOD__ : $functionName - ); - } - - $this->releaseConnection( $dbr ); - - if ( $error ) { - // Note: construct the error before releasing the connection, - // but throw it after. - throw $error; - } - - return $result; - } - - /** - * Selects the specified fields of the records matching the provided - * conditions and returns them as associative arrays. - * Provided field names get prefixed. - * Returned field names will not have a prefix. - * - * When $collapse is true: - * If one field is selected, each item in the result array will be this field. - * If two fields are selected, each item in the result array will have as key - * the first field and as value the second field. - * If more then two fields are selected, each item will be an associative array. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param bool $collapse Set to false to always return each result row as associative array. - * @param string|null $functionName - * - * @return array Array of array - */ - public function selectFields( $fields = null, array $conditions = array(), - array $options = array(), $collapse = true, $functionName = null - ) { - $objects = array(); - - $result = $this->rawSelect( $fields, $conditions, $options, $functionName ); - - foreach ( $result as $record ) { - $objects[] = $this->getFieldsFromDBResult( $record ); - } - - if ( $collapse ) { - if ( count( $fields ) === 1 ) { - $objects = array_map( 'array_shift', $objects ); - } elseif ( count( $fields ) === 2 ) { - $o = array(); - - foreach ( $objects as $object ) { - $o[array_shift( $object )] = array_shift( $object ); - } - - $objects = $o; - } - } - - return $objects; - } - - /** - * Selects the specified fields of the first matching record. - * Field names get prefixed. - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return IORMRow|bool False on failure - */ - public function selectRow( $fields = null, array $conditions = array(), - array $options = array(), $functionName = null - ) { - $options['LIMIT'] = 1; - - $objects = $this->select( $fields, $conditions, $options, $functionName ); - - return ( !$objects || $objects->isEmpty() ) ? false : $objects->current(); - } - - /** - * Selects the specified fields of the records matching the provided - * conditions. Field names do NOT get prefixed. - * - * @since 1.20 - * - * @param array $fields - * @param array $conditions - * @param array $options - * @param string|null $functionName - * - * @return stdClass - */ - public function rawSelectRow( array $fields, array $conditions = array(), - array $options = array(), $functionName = null - ) { - $dbr = $this->getReadDbConnection(); - - $result = $dbr->selectRow( - $this->getName(), - $fields, - $conditions, - is_null( $functionName ) ? __METHOD__ : $functionName, - $options - ); - - $this->releaseConnection( $dbr ); - - return $result; - } - - /** - * Selects the specified fields of the first record matching the provided - * conditions and returns it as an associative array, or false when nothing matches. - * This method makes use of selectFields and expects the same parameters and - * returns the same results (if there are any, if there are none, this method returns false). - * @see ORMTable::selectFields - * - * @since 1.20 - * - * @param array|string|null $fields - * @param array $conditions - * @param array $options - * @param bool $collapse Set to false to always return each result row as associative array. - * @param string|null $functionName - * - * @return mixed|array|bool False on failure - */ - public function selectFieldsRow( $fields = null, array $conditions = array(), - array $options = array(), $collapse = true, $functionName = null - ) { - $options['LIMIT'] = 1; - - $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName ); - - return empty( $objects ) ? false : $objects[0]; - } - - /** - * Returns if there is at least one record matching the provided conditions. - * Condition field names get prefixed. - * - * @since 1.20 - * - * @param array $conditions - * - * @return bool - */ - public function has( array $conditions = array() ) { - return $this->selectRow( array( 'id' ), $conditions ) !== false; - } - - /** - * Checks if the table exists - * - * @since 1.21 - * - * @return bool - */ - public function exists() { - $dbr = $this->getReadDbConnection(); - $exists = $dbr->tableExists( $this->getName() ); - $this->releaseConnection( $dbr ); - - return $exists; - } - - /** - * Returns the amount of matching records. - * Condition field names get prefixed. - * - * Note that this can be expensive on large tables. - * In such cases you might want to use DatabaseBase::estimateRowCount instead. - * - * @since 1.20 - * - * @param array $conditions - * @param array $options - * - * @return int - */ - public function count( array $conditions = array(), array $options = array() ) { - $res = $this->rawSelectRow( - array( 'rowcount' => 'COUNT(*)' ), - $this->getPrefixedValues( $conditions ), - $options, - __METHOD__ - ); - - return $res->rowcount; - } - - /** - * Removes the object from the database. - * - * @since 1.20 - * - * @param array $conditions - * @param string|null $functionName - * - * @return bool Success indicator - */ - public function delete( array $conditions, $functionName = null ) { - $dbw = $this->getWriteDbConnection(); - - $result = $dbw->delete( - $this->getName(), - $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ), - is_null( $functionName ) ? __METHOD__ : $functionName - ) !== false; // DatabaseBase::delete does not always return true for success as documented... - - $this->releaseConnection( $dbw ); - - return $result; - } - - /** - * Get API parameters for the fields supported by this object. - * - * @since 1.20 - * - * @param bool $requireParams - * @param bool $setDefaults - * - * @return array - */ - public function getAPIParams( $requireParams = false, $setDefaults = false ) { - $typeMap = array( - 'id' => 'integer', - 'int' => 'integer', - 'float' => 'NULL', - 'str' => 'string', - 'bool' => 'integer', - 'array' => 'string', - 'blob' => 'string', - ); - - $params = array(); - $defaults = $this->getDefaults(); - - foreach ( $this->getFields() as $field => $type ) { - if ( $field == 'id' ) { - continue; - } - - $hasDefault = array_key_exists( $field, $defaults ); - - $params[$field] = array( - ApiBase::PARAM_TYPE => $typeMap[$type], - ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault - ); - - if ( $type == 'array' ) { - $params[$field][ApiBase::PARAM_ISMULTI] = true; - } - - if ( $setDefaults && $hasDefault ) { - $default = is_array( $defaults[$field] ) - ? implode( '|', $defaults[$field] ) - : $defaults[$field]; - $params[$field][ApiBase::PARAM_DFLT] = $default; - } - } - - return $params; - } - - /** - * Returns an array with the fields and their descriptions. - * - * field name => field description - * - * @since 1.20 - * - * @return array - */ - public function getFieldDescriptions() { - return array(); - } - - /** - * Get the database ID used for read operations. - * - * @since 1.20 - * - * @return int DB_ enum - */ - public function getReadDb() { - return $this->readDb; - } - - /** - * Set the database ID to use for read operations, use DB_XXX constants or - * an index to the load balancer setup. - * - * @param int $db - * - * @since 1.20 - */ - public function setReadDb( $db ) { - $this->readDb = $db; - } - - /** - * Get the ID of the any foreign wiki to use as a target for database operations - * - * @since 1.20 - * - * @return string|bool The target wiki, in a form that LBFactory understands - * (or false if the local wiki is used) - */ - public function getTargetWiki() { - return $this->wiki; - } - - /** - * Set the ID of the any foreign wiki to use as a target for database operations - * - * @param string|bool $wiki The target wiki, in a form that LBFactory - * understands (or false if the local wiki shall be used) - * - * @since 1.20 - */ - public function setTargetWiki( $wiki ) { - $this->wiki = $wiki; - } - - /** - * Get the database type used for read operations. - * This is to be used instead of wfGetDB. - * - * @see LoadBalancer::getConnection - * - * @since 1.20 - * - * @return DatabaseBase The database object - */ - public function getReadDbConnection() { - return $this->getConnection( $this->getReadDb(), array() ); - } - - /** - * Get the database type used for read operations. - * This is to be used instead of wfGetDB. - * - * @see LoadBalancer::getConnection - * - * @since 1.20 - * - * @return DatabaseBase The database object - */ - public function getWriteDbConnection() { - return $this->getConnection( DB_MASTER, array() ); - } - - /** - * Releases the lease on the given database connection. This is useful mainly - * for connections to a foreign wiki. It does nothing for connections to the local wiki. - * - * @see LoadBalancer::reuseConnection - * - * @param DatabaseBase $db - * - * @since 1.20 - */ - // @codingStandardsIgnoreStart Suppress "useless method overriding" sniffer warning - public function releaseConnection( DatabaseBase $db ) { - parent::releaseConnection( $db ); // just make it public - } - // @codingStandardsIgnoreEnd - - /** - * Update the records matching the provided conditions by - * setting the fields that are keys in the $values param to - * their corresponding values. - * - * @since 1.20 - * - * @param array $values - * @param array $conditions - * - * @return bool Success indicator - */ - public function update( array $values, array $conditions = array() ) { - $dbw = $this->getWriteDbConnection(); - - $result = $dbw->update( - $this->getName(), - $this->getPrefixedValues( $values ), - $this->getPrefixedValues( $conditions ), - __METHOD__ - ) !== false; // DatabaseBase::update does not always return true for success as documented... - - $this->releaseConnection( $dbw ); - - return $result; - } - - /** - * Computes the values of the summary fields of the objects matching the provided conditions. - * - * @since 1.20 - * - * @param array|string|null $summaryFields - * @param array $conditions - */ - public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) { - $slave = $this->getReadDb(); - $this->setReadDb( DB_MASTER ); - - /** - * @var IORMRow $item - */ - foreach ( $this->select( null, $conditions ) as $item ) { - $item->loadSummaryFields( $summaryFields ); - $item->setSummaryMode( true ); - $item->save(); - } - - $this->setReadDb( $slave ); - } - - /** - * Takes in an associative array with field names as keys and - * their values as value. The field names are prefixed with the - * db field prefix. - * - * @since 1.20 - * - * @param array $values - * - * @return array - */ - public function getPrefixedValues( array $values ) { - $prefixedValues = array(); - - foreach ( $values as $field => $value ) { - if ( is_integer( $field ) ) { - if ( is_array( $value ) ) { - $field = $value[0]; - $value = $value[1]; - } else { - $value = explode( ' ', $value, 2 ); - $value[0] = $this->getPrefixedField( $value[0] ); - $prefixedValues[] = implode( ' ', $value ); - continue; - } - } - - $prefixedValues[$this->getPrefixedField( $field )] = $value; - } - - return $prefixedValues; - } - - /** - * Takes in a field or array of fields and returns an - * array with their prefixed versions, ready for db usage. - * - * @since 1.20 - * - * @param array $fields - * - * @return array - */ - public function getPrefixedFields( array $fields ) { - foreach ( $fields as &$field ) { - $field = $this->getPrefixedField( $field ); - } - - return $fields; - } - - /** - * Takes in a field and returns an it's prefixed version, ready for db usage. - * - * @since 1.20 - * - * @param string|array $field - * - * @return string - */ - public function getPrefixedField( $field ) { - return $this->fieldPrefix . $field; - } - - /** - * Takes an array of field names with prefix and returns the unprefixed equivalent. - * - * @since 1.20 - * @deprecated since 1.25, will be removed - * - * @param string[] $fieldNames - * - * @return string[] - */ - public function unprefixFieldNames( array $fieldNames ) { - wfDeprecated( __METHOD__, '1.25' ); - - return $this->stripFieldPrefix( $fieldNames ); - } - - /** - * Takes an array of field names with prefix and returns the unprefixed equivalent. - * - * @param string[] $fieldNames - * - * @return string[] - */ - private function stripFieldPrefix( array $fieldNames ) { - $start = strlen( $this->fieldPrefix ); - - return array_map( function ( $fieldName ) use ( $start ) { - return substr( $fieldName, $start ); - }, $fieldNames ); - } - - /** - * Takes a field name with prefix and returns the unprefixed equivalent. - * - * @since 1.20 - * @deprecated since 1.25, will be removed - * - * @param string $fieldName - * - * @return string - */ - public function unprefixFieldName( $fieldName ) { - wfDeprecated( __METHOD__, '1.25' ); - - return substr( $fieldName, strlen( $this->fieldPrefix ) ); - } - - /** - * Get an instance of this class. - * - * @since 1.20 - * @deprecated since 1.21 - * - * @return IORMTable - */ - public static function singleton() { - $class = get_called_class(); - - if ( !array_key_exists( $class, self::$instanceCache ) ) { - self::$instanceCache[$class] = new $class; - } - - return self::$instanceCache[$class]; - } - - /** - * Get an array with fields from a database result, - * that can be fed directly to the constructor or - * to setFields. - * - * @since 1.20 - * - * @param stdClass $result - * @throws MWException - * @return array - */ - public function getFieldsFromDBResult( stdClass $result ) { - $result = (array)$result; - - $rawFields = array_combine( - $this->stripFieldPrefix( array_keys( $result ) ), - array_values( $result ) - ); - - $fieldDefinitions = $this->getFields(); - $fields = array(); - - foreach ( $rawFields as $name => $value ) { - if ( array_key_exists( $name, $fieldDefinitions ) ) { - switch ( $fieldDefinitions[$name] ) { - case 'int': - $value = (int)$value; - break; - case 'float': - $value = (float)$value; - break; - case 'bool': - if ( is_string( $value ) ) { - $value = $value !== '0'; - } elseif ( is_int( $value ) ) { - $value = $value !== 0; - } - break; - case 'array': - if ( is_string( $value ) ) { - $value = unserialize( $value ); - } - - if ( !is_array( $value ) ) { - $value = array(); - } - break; - case 'blob': - if ( is_string( $value ) ) { - $value = unserialize( $value ); - } - break; - case 'id': - if ( is_string( $value ) ) { - $value = (int)$value; - } - break; - } - - $fields[$name] = $value; - } else { - throw new MWException( 'Attempted to set unknown field ' . $name ); - } - } - - return $fields; - } - - /** - * @see ORMTable::newRowFromFromDBResult - * - * @deprecated since 1.20 use newRowFromDBResult instead - * @since 1.20 - * - * @param stdClass $result - * - * @return IORMRow - */ - public function newFromDBResult( stdClass $result ) { - return self::newRowFromDBResult( $result ); - } - - /** - * Get a new instance of the class from a database result. - * - * @since 1.20 - * - * @param stdClass $result - * - * @return IORMRow - */ - public function newRowFromDBResult( stdClass $result ) { - return $this->newRow( $this->getFieldsFromDBResult( $result ) ); - } - - /** - * @see ORMTable::newRow - * - * @deprecated since 1.20 use newRow instead - * @since 1.20 - * - * @param array $data - * @param bool $loadDefaults - * - * @return IORMRow - */ - public function newFromArray( array $data, $loadDefaults = false ) { - return static::newRow( $data, $loadDefaults ); - } - - /** - * Get a new instance of the class from an array. - * - * @since 1.20 - * - * @param array $fields - * @param bool $loadDefaults - * - * @return IORMRow - */ - public function newRow( array $fields, $loadDefaults = false ) { - $class = $this->getRowClass(); - - return new $class( $this, $fields, $loadDefaults ); - } - - /** - * Return the names of the fields. - * - * @since 1.20 - * - * @return array - */ - public function getFieldNames() { - return array_keys( $this->getFields() ); - } - - /** - * Gets if the object can take a certain field. - * - * @since 1.20 - * - * @param string $name - * - * @return bool - */ - public function canHaveField( $name ) { - return array_key_exists( $name, $this->getFields() ); - } - - /** - * Updates the provided row in the database. - * - * @since 1.22 - * - * @param IORMRow $row The row to save - * @param string|null $functionName - * - * @return bool Success indicator - */ - public function updateRow( IORMRow $row, $functionName = null ) { - $dbw = $this->getWriteDbConnection(); - - $success = $dbw->update( - $this->getName(), - $this->getWriteValues( $row ), - $this->getPrefixedValues( array( 'id' => $row->getId() ) ), - is_null( $functionName ) ? __METHOD__ : $functionName - ); - - $this->releaseConnection( $dbw ); - - // DatabaseBase::update does not always return true for success as documented... - return $success !== false; - } - - /** - * Inserts the provided row into the database. - * - * @since 1.22 - * - * @param IORMRow $row - * @param string|null $functionName - * @param array|null $options - * - * @return bool Success indicator - */ - public function insertRow( IORMRow $row, $functionName = null, array $options = null ) { - $dbw = $this->getWriteDbConnection(); - - $success = $dbw->insert( - $this->getName(), - $this->getWriteValues( $row ), - is_null( $functionName ) ? __METHOD__ : $functionName, - $options - ); - - // DatabaseBase::insert does not always return true for success as documented... - $success = $success !== false; - - if ( $success ) { - $row->setField( 'id', $dbw->insertId() ); - } - - $this->releaseConnection( $dbw ); - - return $success; - } - - /** - * Gets the fields => values to write to the table. - * - * @since 1.22 - * - * @param IORMRow $row - * - * @return array - */ - protected function getWriteValues( IORMRow $row ) { - $values = array(); - - $rowFields = $row->getFields(); - - foreach ( $this->getFields() as $name => $type ) { - if ( array_key_exists( $name, $rowFields ) ) { - $value = $rowFields[$name]; - - switch ( $type ) { - case 'array': - $value = (array)$value; - // fall-through! - case 'blob': - $value = serialize( $value ); - // fall-through! - } - - $values[$this->getPrefixedField( $name )] = $value; - } - } - - return $values; - } - - /** - * Removes the provided row from the database. - * - * @since 1.22 - * - * @param IORMRow $row - * @param string|null $functionName - * - * @return bool Success indicator - */ - public function removeRow( IORMRow $row, $functionName = null ) { - $success = $this->delete( - array( 'id' => $row->getId() ), - is_null( $functionName ) ? __METHOD__ : $functionName - ); - - // DatabaseBase::delete does not always return true for success as documented... - return $success !== false; - } - - /** - * Add an amount (can be negative) to the specified field (needs to be numeric). - * - * @since 1.22 - * - * @param array $conditions - * @param string $field - * @param int $amount - * - * @return bool Success indicator - * @throws MWException - */ - public function addToField( array $conditions, $field, $amount ) { - if ( !array_key_exists( $field, $this->fields ) ) { - throw new MWException( 'Unknown field "' . $field . '" provided' ); - } - - if ( $amount == 0 ) { - return true; - } - - $absoluteAmount = abs( $amount ); - $isNegative = $amount < 0; - - $fullField = $this->getPrefixedField( $field ); - - $dbw = $this->getWriteDbConnection(); - - $success = $dbw->update( - $this->getName(), - array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ), - $this->getPrefixedValues( $conditions ), - __METHOD__ - ) !== false; // DatabaseBase::update does not always return true for success as documented... - - $this->releaseConnection( $dbw ); - - return $success; - } -} diff --git a/includes/db/loadbalancer/LBFactory.php b/includes/db/loadbalancer/LBFactory.php index e7b762740a..86f0110256 100644 --- a/includes/db/loadbalancer/LBFactory.php +++ b/includes/db/loadbalancer/LBFactory.php @@ -200,7 +200,10 @@ abstract class LBFactory { * Commit changes on all master connections */ public function commitMasterChanges() { + $start = microtime( true ); $this->forEachLBCallMethod( 'commitMasterChanges' ); + $timeMs = 1000 * ( microtime( true ) - $start ); + RequestContext::getMain()->getStats()->timing( "db.commit-masters", $timeMs ); } /** diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php index 242a1a5f22..9a24b96366 100644 --- a/includes/deferred/LinksUpdate.php +++ b/includes/deferred/LinksUpdate.php @@ -77,6 +77,11 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate { */ private $linkDeletions = null; + /** + * @var User|null + */ + private $user; + /** * Constructor * @@ -267,7 +272,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate { // Which ever runs first generally no-ops the other one. $jobs = array(); foreach ( $bc->getCascadeProtectedLinks() as $title ) { - $jobs[] = new RefreshLinksJob( $title, array( 'prioritize' => true ) ); + $jobs[] = RefreshLinksJob::newPrioritized( $title, array() ); } JobQueueGroup::singleton()->push( $jobs ); } @@ -907,6 +912,24 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate { $this->mRevision = $revision; } + /** + * Set the User who triggered this LinksUpdate + * + * @since 1.27 + * @param User $user + */ + public function setTriggeringUser( User $user ) { + $this->user = $user; + } + + /** + * @since 1.27 + * @return null|User + */ + public function getTriggeringUser() { + return $this->user; + } + /** * Invalidate any necessary link lists related to page property changes * @param array $changed @@ -980,15 +1003,23 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate { } public function getAsJobSpecification() { + if ( $this->user ) { + $userInfo = array( + 'userId' => $this->user->getId(), + 'userName' => $this->user->getName(), + ); + } else { + $userInfo = false; + } return array( 'wiki' => $this->mDb->getWikiID(), 'job' => new JobSpecification( 'refreshLinksPrioritized', array( - 'prioritize' => true, // Reuse the parser cache if it was saved 'rootJobTimestamp' => $this->mParserOutput->getCacheTime(), - 'useRecursiveLinksUpdate' => $this->mRecursive + 'useRecursiveLinksUpdate' => $this->mRecursive, + 'triggeringUser' => $userInfo, ), array( 'removeDuplicates' => true ), $this->getTitle() diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php index cd82ab1034..8d76e96b7a 100644 --- a/includes/filebackend/FileBackend.php +++ b/includes/filebackend/FileBackend.php @@ -375,11 +375,6 @@ abstract class FileBackend { if ( empty( $opts['force'] ) ) { // sanity unset( $opts['nonLocking'] ); } - foreach ( $ops as &$op ) { - if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20) - $op['headers']['Content-Disposition'] = $op['disposition']; - } - } /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doOperationsInternal( $ops, $opts ); @@ -611,9 +606,6 @@ abstract class FileBackend { } foreach ( $ops as &$op ) { $op['overwrite'] = true; // avoids RTTs in key/value stores - if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20) - $op['headers']['Content-Disposition'] = $op['disposition']; - } } /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php index e72d026205..83c1da190d 100644 --- a/includes/filebackend/SwiftFileBackend.php +++ b/includes/filebackend/SwiftFileBackend.php @@ -262,7 +262,9 @@ class SwiftFileBackend extends FileBackendStore { } $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 ); - $contentType = $this->getContentType( $params['dst'], $params['content'], null ); + $contentType = isset( $params['headers']['content-type'] ) + ? $params['headers']['content-type'] + : $this->getContentType( $params['dst'], $params['content'], null ); $reqs = array( array( 'method' => 'PUT', @@ -318,7 +320,9 @@ class SwiftFileBackend extends FileBackendStore { return $status; } $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 ); - $contentType = $this->getContentType( $params['dst'], null, $params['src'] ); + $contentType = isset( $params['headers']['content-type'] ) + ? $params['headers']['content-type'] + : $this->getContentType( $params['dst'], null, $params['src'] ); $handle = fopen( $params['src'], 'rb' ); if ( $handle === false ) { // source doesn't exist? diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php index 4ffbf4add2..f898bb6aff 100644 --- a/includes/filerepo/ForeignAPIRepo.php +++ b/includes/filerepo/ForeignAPIRepo.php @@ -55,11 +55,11 @@ class ForeignAPIRepo extends FileRepo { ); protected $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' ); - /** @var int Check back with Commons after a day (24*60*60) */ - protected $apiThumbCacheExpiry = 86400; + /** @var int Check back with Commons after this expiry */ + protected $apiThumbCacheExpiry = 86400; // 1 day (24*3600) - /** @var int Redownload thumbnail files after a month (86400*30) */ - protected $fileCacheExpiry = 2592000; + /** @var int Redownload thumbnail files after this expiry */ + protected $fileCacheExpiry = 2592000; // 1 month (30*24*3600) /** @var array */ protected $mFileExists = array(); @@ -329,7 +329,7 @@ class ForeignAPIRepo extends FileRepo { * @return bool|string */ function getThumbUrlFromCache( $name, $width, $height, $params = "" ) { - global $wgMemc; + $cache = ObjectCache::getMainWANInstance(); // We can't check the local cache using FileRepo functions because // we override fileExistsBatch(). We have to use the FileBackend directly. $backend = $this->getBackend(); // convenience @@ -342,7 +342,7 @@ class ForeignAPIRepo extends FileRepo { $sizekey = "$width:$height:$params"; /* Get the array of urls that we already know */ - $knownThumbUrls = $wgMemc->get( $key ); + $knownThumbUrls = $cache->get( $key ); if ( !$knownThumbUrls ) { /* No knownThumbUrls for this file */ $knownThumbUrls = array(); @@ -388,7 +388,7 @@ class ForeignAPIRepo extends FileRepo { if ( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) { /* Use our current and already downloaded thumbnail */ $knownThumbUrls[$sizekey] = $localUrl; - $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); + $cache->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); return $localUrl; } @@ -410,7 +410,7 @@ class ForeignAPIRepo extends FileRepo { return $foreignUrl; } $knownThumbUrls[$sizekey] = $localUrl; - $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); + $cache->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" ); return $localUrl; @@ -552,19 +552,16 @@ class ForeignAPIRepo extends FileRepo { } if ( !isset( $this->mQueryCache[$url] ) ) { - global $wgMemc; - - $key = $this->getLocalCacheKey( get_class( $this ), $target, md5( $url ) ); - $data = $wgMemc->get( $key ); - - if ( !$data ) { - $data = self::httpGet( $url ); - - if ( !$data ) { - return null; + $data = ObjectCache::getMainWANInstance()->getWithSetCallback( + $this->getLocalCacheKey( get_class( $this ), $target, md5( $url ) ), + $cacheTTL, + function () use ( $url ) { + return ForeignAPIRepo::httpGet( $url ); } + ); - $wgMemc->set( $key, $data, $cacheTTL ); + if ( !$data ) { + return null; } if ( count( $this->mQueryCache ) > 100 ) { diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php index 5eda550f9f..ee11df953e 100644 --- a/includes/filerepo/file/File.php +++ b/includes/filerepo/file/File.php @@ -1137,6 +1137,7 @@ abstract class File implements IDBAccessObject { if ( !$thumb ) { // bad params? $thumb = false; } elseif ( $thumb->isError() ) { // transform error + /** @var $thumb MediaTransformError */ $this->lastError = $thumb->toText(); // Ignore errors if requested if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { @@ -2019,15 +2020,19 @@ abstract class File implements IDBAccessObject { * @return string */ function getDescriptionText( $lang = false ) { - global $wgMemc, $wgLang; + global $wgLang; + if ( !$this->repo || !$this->repo->fetchDescription ) { return false; } - if ( !$lang ) { - $lang = $wgLang; - } + + $lang = $lang ?: $wgLang; + $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() ); if ( $renderUrl ) { + $cache = ObjectCache::getMainWANInstance(); + + $key = null; if ( $this->repo->descriptionCacheExpiry > 0 ) { wfDebug( "Attempting to get the description from cache..." ); $key = $this->repo->getLocalCacheKey( @@ -2036,7 +2041,7 @@ abstract class File implements IDBAccessObject { $lang->getCode(), $this->getName() ); - $obj = $wgMemc->get( $key ); + $obj = $cache->get( $key ); if ( $obj ) { wfDebug( "success!\n" ); @@ -2046,8 +2051,8 @@ abstract class File implements IDBAccessObject { } wfDebug( "Fetching shared description from $renderUrl\n" ); $res = Http::get( $renderUrl, array(), __METHOD__ ); - if ( $res && $this->repo->descriptionCacheExpiry > 0 ) { - $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry ); + if ( $res && $key ) { + $cache->set( $key, $res, $this->repo->descriptionCacheExpiry ); } return $res; diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index 3c78290c27..cad806d6a3 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -334,22 +334,20 @@ class ForeignAPIFile extends File { } function purgeDescriptionPage() { - global $wgMemc, $wgContLang; + global $wgContLang; $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() ); $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) ); - $wgMemc->delete( $key ); + ObjectCache::getMainWANInstance()->delete( $key ); } /** * @param array $options */ function purgeThumbnails( $options = array() ) { - global $wgMemc; - $key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() ); - $wgMemc->delete( $key ); + ObjectCache::getMainWANInstance()->delete( $key ); $files = $this->getThumbnails(); // Give media handler a chance to filter the purge list diff --git a/includes/htmlform/HTMLRadioField.php b/includes/htmlform/HTMLRadioField.php index 19b45bea17..2d057042ef 100644 --- a/includes/htmlform/HTMLRadioField.php +++ b/includes/htmlform/HTMLRadioField.php @@ -40,9 +40,9 @@ class HTMLRadioField extends HTMLFormField { function getInputOOUI( $value ) { $options = array(); - foreach ( $this->getOptions() as $label => $value ) { + foreach ( $this->getOptions() as $label => $data ) { $options[] = array( - 'data' => $value, + 'data' => $data, 'label' => $this->mOptionsLabelsNotFromMessage ? new OOUI\HtmlSnippet( $label ) : $label, ); } diff --git a/includes/installer/i18n/he.json b/includes/installer/i18n/he.json index 9b5a3221bb..09cfe6a2ad 100644 --- a/includes/installer/i18n/he.json +++ b/includes/installer/i18n/he.json @@ -6,7 +6,8 @@ "ערן", "아라", "Inkbug", - "Yona b" + "Yona b", + "Rotemliss" ] }, "config-desc": "תכנית ההתקנה של מדיה־ויקי", @@ -104,8 +105,6 @@ "config-db-install-account": "חשבון משתמש להתקנה", "config-db-username": "שם המשתמש במסד הנתונים:", "config-db-password": "הססמה במסד הנתונים:", - "config-db-password-empty": "נא להזין ססמה למשתמש מסד הנתונים החדש: $1.\nאף־על־פי שאפשר ליצור חשבונות ללא ססמה, זה לא מאובטח.", - "config-db-username-empty": "יש להזין ערך עבור \"{{int:config-db-username}}\".", "config-db-install-username": "יש להכניס שם משתמש שישמש לחיבור למסד נתונים במהלך ההתקנה.\nזהו לא שם משתמש לחשבון במדיה־ויקי; זהו שם משתמש בשרת מסד נתונים.", "config-db-install-password": "יש להקליד ססמה שתשמש אותך לצורך חיבור למסד נתונים במהלך ההתקנה.\nזוהי לא ססמה של חשבון במדיה־ויקי; זוהי ססמה לשרת מסד נתונים.", "config-db-install-help": "יש להקליד את שם המשתמש ואת הססמה להתחברות למסד הנתונים במהלך ההתקנה.", @@ -325,5 +324,5 @@ "config-nofile": "הקובץ \"$1\" לא נמצא. האם הוא נמחק?", "config-extension-link": "הידעת שמדיה־ויקי תומכת ב־[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions הרחבות]?\n\nבאפשרותך לעיין ב־[//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category הרחבות לפי קטגוריה].", "mainpagetext": "'''תוכנת מדיה־ויקי הותקנה בהצלחה.'''", - "mainpagedocfooter": "היעזרו ב[//meta.wikimedia.org/wiki/Help:Contents מדריך למשתמש] למידע על שימוש בתוכנת הוויקי.\n\n== קישורים שימושיים ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימת ההגדרות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ שאלות ותשובות על מדיה־ויקי]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources תרגום מדיה־ויקי לשפה שלך]" + "mainpagedocfooter": "היעזרו ב[//meta.wikimedia.org/wiki/Help:Contents מדריך למשתמש] למידע על שימוש בתוכנת הוויקי.\n\n== קישורים שימושיים ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימת ההגדרות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ שאלות ותשובות על מדיה־ויקי]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources תרגום מדיה־ויקי לשפה שלך]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam איך להיאבק נגד ספאם באתר הוויקי שלך]" } diff --git a/includes/installer/i18n/pl.json b/includes/installer/i18n/pl.json index 6a1d4a4f7a..051fe92042 100644 --- a/includes/installer/i18n/pl.json +++ b/includes/installer/i18n/pl.json @@ -336,5 +336,5 @@ "config-nofile": "Nie udało się odnaleźć pliku \"$1\". Czy nie został usunięty?", "config-extension-link": "Czy wiesz, że twoja wiki obsługuje [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions/pl rozszerzenia]?\n\nMożesz przejrzeć [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category rozszerzenia według kategorii] lub [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] aby zobaczyć pełną listę rozszerzeń.", "mainpagetext": "'''Instalacja MediaWiki powiodła się.'''", - "mainpagedocfooter": "Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] w celu uzyskania informacji o działaniu oprogramowania wiki.\n\n== Na początek ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/pl MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]" + "mainpagedocfooter": "Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika], aby uzyskać informacje o działaniu oprogramowania wiki.\n\n== Na początek ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/pl MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam \nDowiedz się, jak walczyć ze spamem na swojej wiki]" } diff --git a/includes/installer/i18n/roa-tara.json b/includes/installer/i18n/roa-tara.json index 8b82db01cc..bf8da763b4 100644 --- a/includes/installer/i18n/roa-tara.json +++ b/includes/installer/i18n/roa-tara.json @@ -65,5 +65,5 @@ "config-help": "ajute", "config-help-tooltip": "cazze pe spannere", "mainpagetext": "'''MediaUicchi ha state 'nstallete.'''", - "mainpagedocfooter": "Vè vide [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer wiki.\n\n== Pe accumenzà ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste pe le configuraziune]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenghe d'a poste de MediaUicchi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizzazzione de MediaUicchi pa lènga toje]" + "mainpagedocfooter": "Vè 'ndruche [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer uicchi.\n\n== Pe accumenzà ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Elenghe de le 'mbostaziune pa configurazione]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ de MediaUicchi]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenghe d'a poste de MediaUicchi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizzazzione de MediaUicchi pa lènga toje]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 'Mbare accume combattere condre a 'u rummate sus 'a uicchi toje]" } diff --git a/includes/installer/i18n/yi.json b/includes/installer/i18n/yi.json index e877280347..f3cc63ece9 100644 --- a/includes/installer/i18n/yi.json +++ b/includes/installer/i18n/yi.json @@ -70,5 +70,5 @@ "config-help": "הילף", "config-nofile": "מ'האט נישט געקענט טרעפן די טעקע \"$1\". צי האט מען זי אויסגעמעקט?", "mainpagetext": "'''מעדיעוויקי אינסטאלירט מיט דערפאלג.'''", - "mainpagedocfooter": "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.\n\n== נוצליכע וועבלינקען פֿאַר אנהייבערס ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ אפֿט געפֿרעגטע שאלות]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources איבערזעצן מעדיעוויקי אין אײַער שפראך]" + "mainpagedocfooter": "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.\n\n== נוצליכע וועבלינקען פֿאַר אנהייבערס ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ אפֿט געפֿרעגטע שאלות]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources איבערזעצן מעדיעוויקי אין אײַער שפראך]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam לערנט וויאזוי צו באקעמפן בפעם אויף אייער וויקי]" } diff --git a/includes/jobqueue/Job.php b/includes/jobqueue/Job.php index 3e23391cdf..2d13c7e5e5 100644 --- a/includes/jobqueue/Job.php +++ b/includes/jobqueue/Job.php @@ -64,12 +64,17 @@ abstract class Job implements IJobSpecification { */ public static function factory( $command, Title $title, $params = array() ) { global $wgJobClasses; + if ( isset( $wgJobClasses[$command] ) ) { $class = $wgJobClasses[$command]; - return new $class( $title, $params ); + $job = new $class( $title, $params ); + $job->command = $command; + + return $job; } - throw new MWException( "Invalid job command `{$command}`" ); + + throw new InvalidArgumentException( "Invalid job command '{$command}'" ); } /** diff --git a/includes/jobqueue/JobQueueDB.php b/includes/jobqueue/JobQueueDB.php index 7907614b89..6ecfaf4001 100644 --- a/includes/jobqueue/JobQueueDB.php +++ b/includes/jobqueue/JobQueueDB.php @@ -144,15 +144,13 @@ class JobQueueDB extends JobQueue { * @throws MWException */ protected function doGetAbandonedCount() { - global $wgMemc; - if ( $this->claimTTL <= 0 ) { return 0; // no acknowledgements } $key = $this->getCacheKey( 'abandonedcount' ); - $count = $wgMemc->get( $key ); + $count = $this->cache->get( $key ); if ( is_int( $count ) ) { return $count; } @@ -170,7 +168,8 @@ class JobQueueDB extends JobQueue { } catch ( DBError $e ) { $this->throwDBException( $e ); } - $wgMemc->set( $key, $count, self::CACHE_TTL_SHORT ); + + $this->cache->set( $key, $count, self::CACHE_TTL_SHORT ); return $count; } diff --git a/includes/jobqueue/JobQueueGroup.php b/includes/jobqueue/JobQueueGroup.php index 5bd1cc94a8..4609d2d37e 100644 --- a/includes/jobqueue/JobQueueGroup.php +++ b/includes/jobqueue/JobQueueGroup.php @@ -286,18 +286,17 @@ class JobQueueGroup { * @since 1.23 */ public function queuesHaveJobs( $type = self::TYPE_ANY ) { - global $wgMemc; - $key = wfMemcKey( 'jobqueue', 'queueshavejobs', $type ); + $cache = ObjectCache::getLocalClusterInstance(); - $value = $wgMemc->get( $key ); + $value = $cache->get( $key ); if ( $value === false ) { $queues = $this->getQueuesWithJobs(); if ( $type == self::TYPE_DEFAULT ) { $queues = array_intersect( $queues, $this->getDefaultQueueTypes() ); } $value = count( $queues ) ? 'true' : 'false'; - $wgMemc->add( $key, $value, 15 ); + $cache->add( $key, $value, 15 ); } return ( $value === 'true' ); @@ -382,19 +381,24 @@ class JobQueueGroup { * @return mixed */ private function getCachedConfigVar( $name ) { - global $wgConf, $wgMemc; + global $wgConf; if ( $this->wiki === wfWikiID() ) { return $GLOBALS[$name]; // common case } else { + $cache = ObjectCache::getLocalClusterInstance(); list( $db, $prefix ) = wfSplitWikiID( $this->wiki ); $key = wfForeignMemcKey( $db, $prefix, 'configvalue', $name ); - $value = $wgMemc->get( $key ); // ('v' => ...) or false + $value = $cache->get( $key ); // ('v' => ...) or false if ( is_array( $value ) ) { return $value['v']; } else { $value = $wgConf->getConfig( $this->wiki, $name ); - $wgMemc->set( $key, array( 'v' => $value ), 86400 + mt_rand( 0, 86400 ) ); + $cache->set( + $key, + array( 'v' => $value ), + $cache::TTL_DAY + mt_rand( 0, $cache::TTL_DAY ) + ); return $value; } diff --git a/includes/jobqueue/jobs/RefreshLinksJob.php b/includes/jobqueue/jobs/RefreshLinksJob.php index 3a83cb8253..26f452067b 100644 --- a/includes/jobqueue/jobs/RefreshLinksJob.php +++ b/includes/jobqueue/jobs/RefreshLinksJob.php @@ -41,10 +41,6 @@ class RefreshLinksJob extends Job { function __construct( Title $title, array $params ) { parent::__construct( 'refreshLinks', $title, $params ); - // A separate type is used just for cascade-protected backlinks - if ( !empty( $this->params['prioritize'] ) ) { - $this->command .= 'Prioritized'; - } // Base backlink update jobs and per-title update jobs can be de-duplicated. // If template A changes twice before any jobs run, a clean queue will have: // (A base, A base) @@ -64,6 +60,30 @@ class RefreshLinksJob extends Job { && ( !isset( $params['pages'] ) || count( $params['pages'] ) == 1 ); } + /** + * @param Title $title + * @param array $params + * @return RefreshLinksJob + */ + public static function newPrioritized( Title $title, array $params ) { + $job = new self( $title, $params ); + $job->command = 'refreshLinksPrioritized'; + + return $job; + } + + /** + * @param Title $title + * @param array $params + * @return RefreshLinksJob + */ + public static function newDynamic( Title $title, array $params ) { + $job = new self( $title, $params ); + $job->command = 'refreshLinksDynamic'; + + return $job; + } + function run() { global $wgUpdateRowsPerJob; @@ -195,8 +215,20 @@ class RefreshLinksJob extends Job { $updates = $content->getSecondaryDataUpdates( $title, null, !empty( $this->params['useRecursiveLinksUpdate'] ), $parserOutput ); foreach ( $updates as $key => $update ) { - if ( $update instanceof LinksUpdate && isset( $this->params['triggeredRecursive'] ) ) { - $update->setTriggeredRecursive(); + if ( $update instanceof LinksUpdate ) { + if ( isset( $this->params['triggeredRecursive'] ) ) { + $update->setTriggeredRecursive(); + } + if ( isset( $this->params['triggeringUser'] ) && $this->params['triggeringUser'] ) { + $userInfo = $this->params['triggeringUser']; + if ( $userInfo['userId'] ) { + $user = User::newFromId( $userInfo['userId'] ); + } else { + // Anonymous, use the username + $user = User::newFromName( $userInfo['userName'], false ); + } + $update->setTriggeringUser( $user ); + } } } diff --git a/includes/libs/RunningStat.php b/includes/libs/RunningStat.php deleted file mode 100644 index 8bd4656cde..0000000000 --- a/includes/libs/RunningStat.php +++ /dev/null @@ -1,176 +0,0 @@ -. -define( 'NEGATIVE_INF', -INF ); - -/** - * Represents a running summary of a stream of numbers. - * - * RunningStat instances are accumulator-like objects that provide a set of - * continuously-updated summary statistics for a stream of numbers, without - * requiring that each value be stored. The measures it provides are the - * arithmetic mean, variance, standard deviation, and extrema (min and max); - * together they describe the central tendency and statistical dispersion of a - * set of values. - * - * One RunningStat instance can be merged into another; the resultant - * RunningStat has the state it would have had if it had accumulated each - * individual point. This allows data to be summarized in parallel and in - * stages without loss of fidelity. - * - * Based on a C++ implementation by John D. Cook: - * - * - * - * The in-line documentation for this class incorporates content from the - * English Wikipedia articles "Variance", "Algorithms for calculating - * variance", and "Standard deviation". - * - * @since 1.23 - */ -class RunningStat implements Countable { - - /** @var int Number of samples. **/ - public $n = 0; - - /** @var float The first moment (or mean, or expected value). **/ - public $m1 = 0.0; - - /** @var float The second central moment (or variance). **/ - public $m2 = 0.0; - - /** @var float The least value in the set. **/ - public $min = INF; - - /** @var float The greatest value in the set. **/ - public $max = NEGATIVE_INF; - - /** - * Count the number of accumulated values. - * @return int Number of values - */ - public function count() { - return $this->n; - } - - /** - * Add a number to the data set. - * @param int|float $x Value to add - */ - public function push( $x ) { - $x = (float) $x; - - $this->min = min( $this->min, $x ); - $this->max = max( $this->max, $x ); - - $n1 = $this->n; - $this->n += 1; - $delta = $x - $this->m1; - $delta_n = $delta / $this->n; - $this->m1 += $delta_n; - $this->m2 += $delta * $delta_n * $n1; - } - - /** - * Get the mean, or expected value. - * - * The arithmetic mean is the sum of all measurements divided by the number - * of observations in the data set. - * - * @return float Mean - */ - public function getMean() { - return $this->m1; - } - - /** - * Get the estimated variance. - * - * Variance measures how far a set of numbers is spread out. A small - * variance indicates that the data points tend to be very close to the - * mean (and hence to each other), while a high variance indicates that the - * data points are very spread out from the mean and from each other. - * - * @return float Estimated variance - */ - public function getVariance() { - if ( $this->n === 0 ) { - // The variance of the empty set is undefined. - return NAN; - } elseif ( $this->n === 1 ) { - return 0.0; - } else { - return $this->m2 / ( $this->n - 1.0 ); - } - } - - /** - * Get the estimated standard deviation. - * - * The standard deviation of a statistical population is the square root of - * its variance. It shows how much variation from the mean exists. In - * addition to expressing the variability of a population, the standard - * deviation is commonly used to measure confidence in statistical conclusions. - * - * @return float Estimated standard deviation - */ - public function getStdDev() { - return sqrt( $this->getVariance() ); - } - - /** - * Merge another RunningStat instance into this instance. - * - * This instance then has the state it would have had if all the data had - * been accumulated by it alone. - * - * @param RunningStat RunningStat instance to merge into this one - */ - public function merge( RunningStat $other ) { - // If the other RunningStat is empty, there's nothing to do. - if ( $other->n === 0 ) { - return; - } - - // If this RunningStat is empty, copy values from other RunningStat. - if ( $this->n === 0 ) { - $this->n = $other->n; - $this->m1 = $other->m1; - $this->m2 = $other->m2; - $this->min = $other->min; - $this->max = $other->max; - return; - } - - $n = $this->n + $other->n; - $delta = $other->m1 - $this->m1; - $delta2 = $delta * $delta; - - $this->m1 = ( ( $this->n * $this->m1 ) + ( $other->n * $other->m1 ) ) / $n; - $this->m2 = $this->m2 + $other->m2 + ( $delta2 * $this->n * $other->n / $n ); - $this->min = min( $this->min, $other->min ); - $this->max = max( $this->max, $other->max ); - $this->n = $n; - } -} diff --git a/includes/libs/Xhprof.php b/includes/libs/Xhprof.php index 5ed67c7323..d2cd0e4ea8 100644 --- a/includes/libs/Xhprof.php +++ b/includes/libs/Xhprof.php @@ -18,6 +18,8 @@ * @file */ +use RunningStat\RunningStat; + /** * Convenience class for working with XHProf * . XHProf can be installed as a PECL @@ -254,7 +256,7 @@ class Xhprof { } for ( $i = 0; $i < $stats['ct']; $i++ ) { - $this->inclusive[$child][$stat]->push( + $this->inclusive[$child][$stat]->addObservation( $value / $stats['ct'] ); } diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index ecc5e372f3..703c195a2b 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -42,7 +42,7 @@ use Psr\Log\NullLogger; * * @ingroup Cache */ -abstract class BagOStuff implements LoggerAwareInterface { +abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { /** @var array[] Lock tracking */ protected $locks = array(); @@ -220,7 +220,7 @@ abstract class BagOStuff implements LoggerAwareInterface { do { $this->clearLastError(); $casToken = null; // passed by reference - $currentValue = $this->getWithToken( $key, $casToken, BagOStuff::READ_LATEST ); + $currentValue = $this->getWithToken( $key, $casToken, self::READ_LATEST ); if ( $this->getLastError() ) { return false; // don't spam retries (retry only on races) } @@ -276,7 +276,7 @@ abstract class BagOStuff implements LoggerAwareInterface { } $this->clearLastError(); - $currentValue = $this->get( $key, BagOStuff::READ_LATEST ); + $currentValue = $this->get( $key, self::READ_LATEST ); if ( $this->getLastError() ) { $success = false; } else { @@ -319,7 +319,7 @@ abstract class BagOStuff implements LoggerAwareInterface { } } - $expiry = min( $expiry ?: INF, 86400 ); + $expiry = min( $expiry ?: INF, self::TTL_DAY ); $this->clearLastError(); $timestamp = microtime( true ); // starting UNIX timestamp @@ -389,7 +389,7 @@ abstract class BagOStuff implements LoggerAwareInterface { * @since 1.26 */ final public function getScopedLock( $key, $timeout = 6, $expiry = 30, $rclass = '' ) { - $expiry = min( $expiry ?: INF, 86400 ); + $expiry = min( $expiry ?: INF, self::TTL_DAY ); if ( !$this->lock( $key, $timeout, $expiry, $rclass ) ) { return null; @@ -582,7 +582,7 @@ abstract class BagOStuff implements LoggerAwareInterface { * @return int */ protected function convertExpiry( $exptime ) { - if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) { + if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) { return time() + $exptime; } else { return $exptime; @@ -597,7 +597,7 @@ abstract class BagOStuff implements LoggerAwareInterface { * @return int */ protected function convertToRelative( $exptime ) { - if ( $exptime >= 86400 * 3650 /* 10 years */ ) { + if ( $exptime >= ( 10 * self::TTL_YEAR ) ) { $exptime -= time(); if ( $exptime <= 0 ) { $exptime = 1; diff --git a/includes/libs/objectcache/IExpiringStore.php b/includes/libs/objectcache/IExpiringStore.php new file mode 100644 index 0000000000..b5ad702a53 --- /dev/null +++ b/includes/libs/objectcache/IExpiringStore.php @@ -0,0 +1,40 @@ += 0 due to clock skew */ + const TINY_NEGATIVE = -0.000001; + /** Cache format version number */ const VERSION = 1; @@ -107,6 +114,10 @@ class WANObjectCache { const FLD_VALUE = 1; const FLD_TTL = 2; const FLD_TIME = 3; + const FLD_FLAGS = 4; + + /** @var integer Treat this value as expired-on-arrival */ + const FLG_STALE = 1; const ERR_NONE = 0; // no error const ERR_NO_RESPONSE = 1; // no response @@ -125,12 +136,18 @@ class WANObjectCache { * - cache : BagOStuff object * - pool : pool name * - relayer : EventRelayer object + * - logger : LoggerInterface object */ public function __construct( array $params ) { $this->cache = $params['cache']; $this->pool = $params['pool']; $this->relayer = $params['relayer']; $this->procCache = new HashBagOStuff(); + $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() ); + } + + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; } /** @@ -149,11 +166,11 @@ class WANObjectCache { /** * Fetch the value of a key from cache * - * If passed in, $curTTL is set to the remaining TTL (current time left): - * - a) INF; if the key exists, has no TTL, and is not expired by $checkKeys - * - b) float (>=0); if the key exists, has a TTL, and is not expired by $checkKeys - * - c) float (<0); if the key is tombstoned or existing but expired by $checkKeys - * - d) null; if the key does not exist and is not tombstoned + * If supplied, $curTTL is set to the remaining TTL (current time left): + * - a) INF; if $key exists, has no TTL, and is not expired by $checkKeys + * - b) float (>=0); if $key exists, has a TTL, and is not expired by $checkKeys + * - c) float (<0); if $key is tombstoned, stale, or existing but expired by $checkKeys + * - d) null; if $key does not exist and is not tombstoned * * If a key is tombstoned, $curTTL will reflect the time since delete(). * @@ -284,7 +301,7 @@ class WANObjectCache { * // Fetch the row from the DB * $row = $dbr->selectRow( ... ); * $key = $cache->makeKey( 'building', $buildingId ); - * $cache->set( $key, $row, 86400, $setOpts ); + * $cache->set( $key, $row, $cache::TTL_DAY, $setOpts ); * @endcode * * @param string $key Cache key @@ -300,10 +317,13 @@ class WANObjectCache { * the current time the data was read or (if applicable) the time when * the snapshot-isolated transaction the data was read from started. * Default: 0 seconds - * - lockTSE : if excessive possible snapshot lag is detected, - * then stash the value into a temporary location - * with this TTL. This is only useful if the reads - * use getWithSetCallback() with "lockTSE" set. + * - pending : Whether this data is possibly from an uncommitted write transaction. + * Generally, other threads should not see values from the future and + * they certainly should not see ones that ended up getting rolled back. + * Default: false + * - lockTSE : if excessive replication/snapshot lag is detected, then store the value + * with this TTL and flag it as stale. This is only useful if the reads for + * this key use getWithSetCallback() with "lockTSE" set. * Default: WANObjectCache::TSE_NONE * @return bool Success */ @@ -312,21 +332,39 @@ class WANObjectCache { $age = isset( $opts['since'] ) ? max( 0, microtime( true ) - $opts['since'] ) : 0; $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0; - if ( $lag > self::MAX_REPLICA_LAG ) { - // Too much lag detected; lower TTL so it converges faster - $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED; + // Do not cache potentially uncommitted data as it might get rolled back + if ( !empty( $opts['pending'] ) ) { + $this->logger->info( "Rejected set() for $key due to pending writes." ); + + return true; // no-op the write for being unsafe } - if ( $age > self::MAX_SNAPSHOT_LAG ) { + $wrapExtra = array(); // additional wrapped value fields + // Check if there's a risk of writing stale data after the purge tombstone expired + if ( ( $lag + $age ) > self::MAX_READ_LAG ) { + // Case A: read lag with "lockTSE"; save but record value as stale if ( $lockTSE >= 0 ) { - $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds - $this->cache->set( self::STASH_KEY_PREFIX . $key, $value, $tempTTL ); - } + $ttl = max( 1, (int)$lockTSE ); // set() expects seconds + $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale + // Case B: any long-running transaction; ignore this set() + } elseif ( $age > self::MAX_READ_LAG ) { + $this->logger->warning( "Rejected set() for $key due to snapshot lag." ); + + return true; // no-op the write for being unsafe + // Case C: high replication lag; lower TTL instead of ignoring all set()s + } elseif ( $lag > self::MAX_READ_LAG ) { + $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED; + $this->logger->warning( "Lowered set() TTL for $key due to replication lag." ); + // Case D: medium length request with medium replication lag; ignore this set() + } else { + $this->logger->warning( "Rejected set() for $key due to high read lag." ); - return true; // no-op the write for being unsafe + return true; // no-op the write for being unsafe + } } - $wrapped = $this->wrap( $value, $ttl ); + // Wrap that value with time/TTL/version metadata + $wrapped = $this->wrap( $value, $ttl ) + $wrapExtra; $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) { return ( is_string( $cWrapped ) ) @@ -382,13 +420,16 @@ class WANObjectCache { * $dbw->commit(); // end of request * @endcode * - * If called twice on the same key, then the last hold-off TTL takes - * precedence. For idempotence, the $ttl should not vary for different - * delete() calls on the same key. Also note that lowering $ttl reduces - * the effective range of the 'lockTSE' parameter to getWithSetCallback(). + * The $ttl parameter can be used when purging values that have not actually changed + * recently. For example, a cleanup script to purge cache entries does not really need + * a hold-off period, so it can use the value 1. Likewise for user-requested purge. + * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback(). + * + * If called twice on the same key, then the last hold-off TTL takes precedence. For + * idempotence, the $ttl should not vary for different delete() calls on the same key. * * @param string $key Cache key - * @param integer $ttl How long to block writes to the key [seconds] + * @param integer $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL * @return bool True if the item was purged or not found, false on failure */ final public function delete( $key, $ttl = self::HOLDOFF_TTL ) { @@ -538,8 +579,8 @@ class WANObjectCache { * $catInfo = $cache->getWithSetCallback( * // Key to store the cached value under * $cache->makeKey( 'cat-attributes', $catId ), - * // Time-to-live (seconds) - * 60, + * // Time-to-live (in seconds) + * $cache::TTL_MINUTE, * // Function that derives the new key value * function ( $oldValue, &$ttl, array &$setOpts ) { * $dbr = wfGetDB( DB_SLAVE ); @@ -556,8 +597,8 @@ class WANObjectCache { * $catConfig = $cache->getWithSetCallback( * // Key to store the cached value under * $cache->makeKey( 'site-cat-config' ), - * // Time-to-live (seconds) - * 86400, + * // Time-to-live (in seconds) + * $cache::TTL_DAY, * // Function that derives the new key value * function ( $oldValue, &$ttl, array &$setOpts ) { * $dbr = wfGetDB( DB_SLAVE ); @@ -581,7 +622,7 @@ class WANObjectCache { * // Key to store the cached value under * $cache->makeKey( 'cat-state', $cat->getId() ), * // Time-to-live (seconds) - * 900, + * $cache::TTL_HOUR, * // Function that derives the new key value * function ( $oldValue, &$ttl, array &$setOpts ) { * // Determine new value from the DB @@ -608,7 +649,7 @@ class WANObjectCache { * $lastCatActions = $cache->getWithSetCallback( * // Key to store the cached value under * $cache->makeKey( 'cat-last-actions', 100 ), - * // Time-to-live (seconds) + * // Time-to-live (in seconds) * 10, * // Function that derives the new key value * function ( $oldValue, &$ttl, array &$setOpts ) { @@ -890,7 +931,7 @@ class WANObjectCache { * * @param mixed $value * @param integer $ttl [0=forever] - * @return string + * @return array */ protected function wrap( $value, $ttl ) { return array( @@ -913,7 +954,7 @@ class WANObjectCache { $purgeTimestamp = self::parsePurgeValue( $wrapped ); if ( is_float( $purgeTimestamp ) ) { // Purged values should always have a negative current $ttl - $curTTL = min( -0.000001, $purgeTimestamp - $now ); + $curTTL = min( $purgeTimestamp - $now, self::TINY_NEGATIVE ); return array( false, $curTTL ); } @@ -924,7 +965,12 @@ class WANObjectCache { return array( false, null ); } - if ( $wrapped[self::FLD_TTL] > 0 ) { + $flags = isset( $wrapped[self::FLD_FLAGS] ) ? $wrapped[self::FLD_FLAGS] : 0; + if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) { + // Treat as expired, with the cache time as the expiration + $age = $now - $wrapped[self::FLD_TIME]; + $curTTL = min( -$age, self::TINY_NEGATIVE ); + } elseif ( $wrapped[self::FLD_TTL] > 0 ) { // Get the approximate time left on the key $age = $now - $wrapped[self::FLD_TIME]; $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 ); diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php index b422bfa2cf..662c330349 100644 --- a/includes/media/DjVu.php +++ b/includes/media/DjVu.php @@ -289,7 +289,7 @@ class DjVuHandler extends ImageHandler { * @param bool $gettext DOCUMENT (Default: false) * @return bool|SimpleXMLElement */ - function getMetaTree( $image, $gettext = false ) { + public function getMetaTree( $image, $gettext = false ) { if ( $gettext && isset( $image->djvuTextTree ) ) { return $image->djvuTextTree; } @@ -375,56 +375,52 @@ class DjVuHandler extends ImageHandler { return !empty( $metadata ) && $metadata != serialize( array() ); } - function pageCount( $image ) { - global $wgMemc; + function pageCount( File $image ) { + $info = $this->getDimensionInfo( $image ); - $key = wfMemcKey( 'file-djvu', 'pageCount', $image->getSha1() ); + return $info ? $info['pageCount'] : false; + } - $count = $wgMemc->get( $key ); - if ( $count === false ) { - $tree = $this->getMetaTree( $image ); - if ( !$tree ) { - return false; - } - $count = count( $tree->xpath( '//OBJECT' ) ); - $wgMemc->set( $key, $count ); + function getPageDimensions( File $image, $page ) { + $index = $page - 1; // MW starts pages at 1 + + $info = $this->getDimensionInfo( $image ); + if ( $info && isset( $info['dimensionsByPage'][$index] ) ) { + return $info['dimensionsByPage'][$index]; } - return $count; + return false; } - function getPageDimensions( $image, $page ) { - global $wgMemc; - - $key = wfMemcKey( 'file-djvu', 'dimensions', $image->getSha1() ); - - $dimsByPage = $wgMemc->get( $key ); - if ( !is_array( $dimsByPage ) ) { - $tree = $this->getMetaTree( $image ); - if ( !$tree ) { - return false; - } + protected function getDimensionInfo( File $file ) { + $that = $this; - $dimsByPage = array(); - $count = count( $tree->xpath( '//OBJECT' ) ); - for ( $i = 0; $i < $count; ++$i ) { - $o = $tree->BODY[0]->OBJECT[$i]; - if ( $o ) { - $dimsByPage[$i] = array( - 'width' => (int)$o['width'], - 'height' => (int)$o['height'] - ); - } else { - $dimsByPage[$i] = false; + return ObjectCache::getMainWANInstance()->getWithSetCallback( + wfMemcKey( 'file-djvu', 'dimensions', $file->getSha1() ), + WANObjectCache::TTL_INDEFINITE, + function () use ( $that, $file ) { + $tree = $that->getMetaTree( $file ); + if ( !$tree ) { + return false; } - } - - $wgMemc->set( $key, $dimsByPage ); - } - $index = $page - 1; // MW starts pages at 1 + $dimsByPage = array(); + $count = count( $tree->xpath( '//OBJECT' ) ); + for ( $i = 0; $i < $count; ++$i ) { + $o = $tree->BODY[0]->OBJECT[$i]; + if ( $o ) { + $dimsByPage[$i] = array( + 'width' => (int)$o['width'], + 'height' => (int)$o['height'] + ); + } else { + $dimsByPage[$i] = false; + } + } - return isset( $dimsByPage[$index] ) ? $dimsByPage[$index] : false; + return array( 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ); + } + ); } /** @@ -432,7 +428,7 @@ class DjVuHandler extends ImageHandler { * @param int $page Page number to get information for * @return bool|string Page text or false when no text found. */ - function getPageText( $image, $page ) { + function getPageText( File $image, $page ) { $tree = $this->getMetaTree( $image, true ); if ( !$tree ) { return false; diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php index 5b578213a8..25f4806194 100644 --- a/includes/media/FormatMetadata.php +++ b/includes/media/FormatMetadata.php @@ -1571,7 +1571,7 @@ class FormatMetadata extends ContextSource { * @since 1.23 */ public function fetchExtendedMetadata( File $file ) { - global $wgMemc; + $cache = ObjectCache::getMainWANInstance(); // If revision deleted, exit immediately if ( $file->isDeleted( File::DELETED_FILE ) ) { @@ -1585,7 +1585,7 @@ class FormatMetadata extends ContextSource { $file->getSha1() ); - $cachedValue = $wgMemc->get( $cacheKey ); + $cachedValue = $cache->get( $cacheKey ); if ( $cachedValue && Hooks::run( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) ) @@ -1605,7 +1605,7 @@ class FormatMetadata extends ContextSource { // computation on a cache hit. $this->sanitizeArrayForAPI( $extendedMetadata ); $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ); - $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime ); + $cache->set( $cacheKey, $valueToCache, $maxCacheTime ); } return $extendedMetadata; diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php index 015eb5a84b..bad1468fe7 100644 --- a/includes/media/MediaHandler.php +++ b/includes/media/MediaHandler.php @@ -373,7 +373,7 @@ abstract class MediaHandler { * @param File $file * @return bool */ - function pageCount( $file ) { + function pageCount( File $file ) { return false; } @@ -434,7 +434,7 @@ abstract class MediaHandler { * @param int $page What page to get dimensions of * @return array|bool */ - function getPageDimensions( $image, $page ) { + function getPageDimensions( File $image, $page ) { $gis = $this->getImageSize( $image, $image->getLocalRefPath() ); if ( $gis ) { return array( @@ -454,7 +454,7 @@ abstract class MediaHandler { * @return bool|string Page text or false when no text found or if * unsupported. */ - function getPageText( $image, $page ) { + function getPageText( File $image, $page ) { return false; } diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php index 35581493ee..30f9e2e971 100644 --- a/includes/media/TransformationalImageHandler.php +++ b/includes/media/TransformationalImageHandler.php @@ -508,9 +508,10 @@ abstract class TransformationalImageHandler extends ImageHandler { * @return string|bool Representing the IM version; false on error */ protected function getMagickVersion() { - return ObjectCache::newAccelerator( CACHE_NONE )->getWithSetCallback( - "imagemagick-version", - 3600, + $cache = ObjectCache::newAccelerator( CACHE_NONE ); + return $cache->getWithSetCallback( + 'imagemagick-version', + $cache::TTL_HOUR, function () { global $wgImageMagickConvertCommand; @@ -523,7 +524,6 @@ abstract class TransformationalImageHandler extends ImageHandler { ); if ( $x != 1 ) { wfDebug( __METHOD__ . ": ImageMagick version check failed\n" ); - return false; } diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php index cb783a7efd..59c3c1f319 100644 --- a/includes/objectcache/ObjectCache.php +++ b/includes/objectcache/ObjectCache.php @@ -167,8 +167,6 @@ class ObjectCache { if ( isset( $params['loggroup'] ) ) { $params['logger'] = LoggerFactory::getInstance( $params['loggroup'] ); } else { - // For backwards-compatability with custom parameters, lets not - // have all logging suddenly disappear $params['logger'] = LoggerFactory::getInstance( 'objectcache' ); } if ( !isset( $params['keyspace'] ) ) { @@ -294,6 +292,11 @@ class ObjectCache { $class = $params['relayerConfig']['class']; $params['relayer'] = new $class( $params['relayerConfig'] ); $params['cache'] = self::newFromId( $params['cacheId'] ); + if ( isset( $params['loggroup'] ) ) { + $params['logger'] = LoggerFactory::getInstance( $params['loggroup'] ); + } else { + $params['logger'] = LoggerFactory::getInstance( 'objectcache' ); + } $class = $params['class']; return new $class( $params ); diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index fe648215f6..e71c0ec238 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -2186,6 +2186,7 @@ class WikiPage implements Page, IDBAccessObject { foreach ( $updates as $update ) { if ( $update instanceof LinksUpdate ) { $update->setRevision( $revision ); + $update->setTriggeringUser( $user ); } DeferredUpdates::addUpdate( $update ); } @@ -2914,8 +2915,9 @@ class WikiPage implements Page, IDBAccessObject { $status->value = $logid; // Show log excerpt on 404 pages rather than just a link + $cache = ObjectCache::getMainStashInstance(); $key = wfMemcKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) ); - ObjectCache::getMainStashInstance()->set( $key, 1, 86400 ); + $cache->set( $key, 1, $cache::TTL_DAY ); return $status; } @@ -3477,22 +3479,34 @@ class WikiPage implements Page, IDBAccessObject { return; } + $params = array( + 'isOpportunistic' => true, + 'rootJobTimestamp' => $parserOutput->getCacheTime() + ); + if ( $this->mTitle->areRestrictionsCascading() ) { // If the page is cascade protecting, the links should really be up-to-date - $params = array( 'prioritize' => true ); + JobQueueGroup::singleton()->lazyPush( + RefreshLinksJob::newPrioritized( $this->mTitle, $params ) + ); } elseif ( $parserOutput->hasDynamicContent() ) { - // Assume the output contains time/random based magic words - $params = array(); - } else { - // If the inclusions are deterministic, the edit-triggered link jobs are enough - return; - } - - // Check if the last link refresh was before page_touched - if ( $this->getLinksTimestamp() < $this->getTouched() ) { - $params['isOpportunistic'] = true; - $params['rootJobTimestamp'] = $parserOutput->getCacheTime(); - JobQueueGroup::singleton()->lazyPush( new RefreshLinksJob( $this->mTitle, $params ) ); + // Assume the output contains "dynamic" time/random based magic words. + // Only update pages that expired due to dynamic content and NOT due to edits + // to referenced templates/files. When the cache expires due to dynamic content, + // page_touched is unchanged. We want to avoid triggering redundant jobs due to + // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the + // template/file edit already triggered recursive RefreshLinksJob jobs. + if ( $this->getLinksTimestamp() > $this->getTouched() ) { + // If a page is uncacheable, do not keep spamming a job for it. + // Although it would be de-duplicated, it would still waste I/O. + $cache = ObjectCache::getLocalClusterInstance(); + $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() ); + if ( $cache->add( $key, time(), 60 ) ) { + JobQueueGroup::singleton()->lazyPush( + RefreshLinksJob::newDynamic( $this->mTitle, $params ) + ); + } + } } } diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php index 78f7775f76..5ffca2354f 100644 --- a/includes/parser/DateFormatter.php +++ b/includes/parser/DateFormatter.php @@ -134,7 +134,7 @@ class DateFormatter { if ( !$dateFormatter ) { $dateFormatter = $cache->getWithSetCallback( $cache->makeKey( 'dateformatter', $lang->getCode() ), - 3600, + $cache::TTL_HOUR, function () use ( $lang ) { return new DateFormatter( $lang ); } diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php index 3a2bb177e6..746e15be3e 100644 --- a/includes/parser/MWTidy.php +++ b/includes/parser/MWTidy.php @@ -51,6 +51,24 @@ class MWTidy { return $driver->tidy( $text ); } + /** + * Get CSS modules needed if HTML from the current driver is to be displayed. + * + * This is just a migration tool to allow some changes expected as part of + * Tidy replacement (T89331) to be exposed on the client side via user + * scripts, without actually replacing tidy. See T49673. + * + * @return array + */ + public static function getModuleStyles() { + $driver = self::singleton(); + if ( $driver && $driver instanceof MediaWiki\Tidy\RaggettBase ) { + return array( 'mediawiki.raggett' ); + } else { + return array(); + } + } + /** * Check HTML for errors, used if $wgValidateAllHtml = true. * diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 9060756a82..cfbf0b46cd 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -1340,6 +1340,7 @@ class Parser { if ( MWTidy::isEnabled() && $this->mOptions->getTidy() ) { $text = MWTidy::tidy( $text ); + $this->mOutput->addModuleStyles( MWTidy::getModuleStyles() ); } else { # attempt to sanitize at least some nesting problems # (bug #2702 and quite a few others) diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php index 37b45a67a6..e4c287a389 100644 --- a/includes/parser/ParserOutput.php +++ b/includes/parser/ParserOutput.php @@ -108,19 +108,17 @@ class ParserOutput extends CacheTime { public $mHeadItems = array(); /** - * @var array $mModules Modules to be loaded by the resource loader + * @var array $mModules Modules to be loaded by ResourceLoader */ public $mModules = array(); /** - * @var array $mModuleScripts Modules of which only the JS will be loaded by - * the resource loader. + * @var array $mModuleScripts Modules of which only the JS will be loaded by ResourceLoader. */ public $mModuleScripts = array(); /** - * @var array $mModuleStyles Modules of which only the CSSS will be loaded by - * the resource loader. + * @var array $mModuleStyles Modules of which only the CSSS will be loaded by ResourceLoader. */ public $mModuleStyles = array(); diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php index 596753723d..1db9ce5a57 100644 --- a/includes/resourceloader/DerivativeResourceLoaderContext.php +++ b/includes/resourceloader/DerivativeResourceLoaderContext.php @@ -1,6 +1,6 @@ moduleInfos[$name] ) ) { // A module has already been registered by this name - if ( $this->moduleInfos[$name] === $info ) { - $this->logger->warning( - 'ResourceLoader duplicate registration warning. ' . - 'Another module has already been registered as ' . $name - ); - } + $this->logger->warning( + 'ResourceLoader duplicate registration warning. ' . + 'Another module has already been registered as ' . $name + ); } // Check $name for validity diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php index 2e1752a6b1..c797fd6b94 100644 --- a/includes/resourceloader/ResourceLoaderContext.php +++ b/includes/resourceloader/ResourceLoaderContext.php @@ -1,6 +1,6 @@ [file path string or array of file path strings], - * // Scripts to include in the startup module - * 'loaderScripts' => [file path string or array of file path strings], * // Modules which must be loaded before this module * 'dependencies' => [module name string or array of module name strings], * 'templates' => array( @@ -239,7 +228,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { // Lists of file paths case 'scripts': case 'debugScripts': - case 'loaderScripts': case 'styles': $this->{$member} = (array)$option; break; @@ -279,7 +267,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { break; // Single strings case 'position': - $this->isPositionDefined = true; case 'group': case 'skipFunction': $this->{$member} = (string)$option; @@ -391,18 +378,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { return $this->debugRaw; } - /** - * Get loader script. - * - * @return string|bool JavaScript code to be added to startup module - */ - public function getLoaderScript() { - if ( count( $this->loaderScripts ) === 0 ) { - return false; - } - return $this->readScriptFiles( $this->loaderScripts ); - } - /** * Get all styles for a given context. * @@ -552,8 +527,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $this->templates, $context->getDebug() ? $this->debugScripts : array(), $this->getLanguageScripts( $context->getLanguage() ), - self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ), - $this->loaderScripts + self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ) ); if ( $this->skipFunction ) { $files[] = $this->skipFunction; @@ -593,7 +567,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { // - position (only used by OutputPage) 'scripts', 'debugScripts', - 'loaderScripts', 'styles', 'languageScripts', 'skinScripts', diff --git a/includes/resourceloader/ResourceLoaderImageModule.php b/includes/resourceloader/ResourceLoaderImageModule.php index 8de87f2ef5..8fd94f7a2e 100644 --- a/includes/resourceloader/ResourceLoaderImageModule.php +++ b/includes/resourceloader/ResourceLoaderImageModule.php @@ -1,6 +1,6 @@ isPositionDefined = true; case 'prefix': case 'selectorWithoutVariant': case 'selectorWithVariant': @@ -456,9 +455,4 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { $this->loadFromDefinition(); return $this->position; } - - public function isPositionDefault() { - $this->loadFromDefinition(); - return parent::isPositionDefault(); - } } diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php index 27c74d7457..83db56724d 100644 --- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php +++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php @@ -1,6 +1,6 @@ isPositionDefined; - } - /** * Whether this module's JS expects to work without the client-side ResourceLoader module. * Returning true from this function will prevent mw.loader.state() call from being @@ -315,25 +298,12 @@ abstract class ResourceLoaderModule { return false; } - /** - * Get the loader JS for this module, if set. - * - * @return mixed JavaScript loader code as a string or boolean false if no custom loader set - */ - public function getLoaderScript() { - // Stub, override expected - return false; - } - /** * Get a list of modules this module depends on. * * Dependency information is taken into account when loading a module * on the client side. * - * To add dependencies dynamically on the client side, use a custom - * loader script, see getLoaderScript() - * * Note: It is expected that $context will be made non-optional in the near * future. * diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php index 380b7a53f3..eba6815cc5 100644 --- a/includes/resourceloader/ResourceLoaderSiteModule.php +++ b/includes/resourceloader/ResourceLoaderSiteModule.php @@ -1,6 +1,6 @@ &$data ) { - if ( $data['loader'] !== false ) { - continue; - } $dependencies = $data['dependencies']; foreach ( $data['dependencies'] as $dependency ) { $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency ); @@ -230,7 +226,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { 'dependencies' => $module->getDependencies( $context ), 'group' => $module->getGroup(), 'source' => $module->getSource(), - 'loader' => $module->getLoaderScript(), 'skip' => $skipFunction, ); } @@ -240,22 +235,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { // Register sources $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() ); - // Concatenate module loader scripts and figure out the different call - // signatures for mw.loader.register + // Figure out the different call signatures for mw.loader.register $registrations = array(); foreach ( $registryData as $name => $data ) { - if ( $data['loader'] !== false ) { - $out .= ResourceLoader::makeCustomLoaderScript( - $name, - $data['version'], - $data['dependencies'], - $data['group'], - $data['source'], - $data['loader'] - ); - continue; - } - // Call mw.loader.register(name, version, dependencies, group, source, skip) $registrations[] = array( $name, diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php index 65d770e23f..67053362cc 100644 --- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php +++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php @@ -1,6 +1,6 @@ $option ) { switch ( $member ) { case 'position': - $this->isPositionDefined = true; - // Don't break since we need the member set as well case 'styles': case 'scripts': case 'group': diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php index 12ebb5432f..08e4885b94 100644 --- a/includes/skins/Skin.php +++ b/includes/skins/Skin.php @@ -1225,29 +1225,31 @@ abstract class Skin extends ContextSource { function buildSidebar() { global $wgEnableSidebarCache, $wgSidebarCacheExpiry; - $cache = ObjectCache::getMainWANInstance(); - $key = wfMemcKey( 'sidebar', $this->getLanguage()->getCode() ); + $that = $this; + $callback = function () use ( $that ) { + $bar = array(); + $that->addToSidebar( $bar, 'sidebar' ); + Hooks::run( 'SkinBuildSidebar', array( $that, &$bar ) ); - if ( $wgEnableSidebarCache ) { - $cachedsidebar = $cache->get( $key ); - if ( $cachedsidebar ) { - Hooks::run( 'SidebarBeforeOutput', array( $this, &$cachedsidebar ) ); - - return $cachedsidebar; - } - } + return $bar; + }; - $bar = array(); - $this->addToSidebar( $bar, 'sidebar' ); - - Hooks::run( 'SkinBuildSidebar', array( $this, &$bar ) ); if ( $wgEnableSidebarCache ) { - $cache->set( $key, $bar, $wgSidebarCacheExpiry ); + $cache = ObjectCache::getMainWANInstance(); + $sidebar = $cache->getWithSetCallback( + $cache->makeKey( 'sidebar', $this->getLanguage()->getCode() ), + $wgSidebarCacheExpiry, + $callback, + array( 'lockTSE' => 30 ) + ); + } else { + $sidebar = $callback(); } - Hooks::run( 'SidebarBeforeOutput', array( $this, &$bar ) ); + // Apply post-processing to the cached value + Hooks::run( 'SidebarBeforeOutput', array( $this, &$sidebar ) ); - return $bar; + return $sidebar; } /** @@ -1259,7 +1261,7 @@ abstract class Skin extends ContextSource { * @param array $bar * @param string $message */ - function addToSidebar( &$bar, $message ) { + public function addToSidebar( &$bar, $message ) { $this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() ); } diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index fd24521bdb..b100e49c4a 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -99,6 +99,7 @@ class SpecialSearch extends SpecialPage { 'mediawiki.special', 'mediawiki.special.search', 'mediawiki.ui', 'mediawiki.ui.button', 'mediawiki.ui.input', ) ); + $this->addHelpLink( 'Help:Searching' ); // Strip underscores from title parameter; most of the time we'll want // text form here. But don't strip underscores from actual text params! diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 8facb35c41..6c6ba3b39a 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -485,7 +485,7 @@ class LoginForm extends SpecialPage { * @return Status */ public function addNewAccountInternal() { - global $wgAuth, $wgMemc, $wgAccountCreationThrottle, $wgEmailConfirmToEdit; + global $wgAuth, $wgAccountCreationThrottle, $wgEmailConfirmToEdit; // If the user passes an invalid domain, something is fishy if ( !$wgAuth->validDomain( $this->mDomain ) ) { @@ -565,8 +565,9 @@ class LoginForm extends SpecialPage { return Status::newFatal( 'noname' ); } + $cache = ObjectCache::getLocalClusterInstance(); # Make sure the user does not exist already - $lock = $wgMemc->getScopedLock( wfGlobalCacheKey( 'account', md5( $this->mUsername ) ) ); + $lock = $cache->getScopedLock( wfGlobalCacheKey( 'account', md5( $this->mUsername ) ) ); if ( !$lock ) { return Status::newFatal( 'usernameinprogress' ); } elseif ( $u->idForName( User::READ_LOCKING ) ) { @@ -633,14 +634,14 @@ class LoginForm extends SpecialPage { } else { if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) { $key = wfMemcKey( 'acctcreate', 'ip', $ip ); - $value = $wgMemc->get( $key ); + $value = $cache->get( $key ); if ( !$value ) { - $wgMemc->set( $key, 0, 86400 ); + $cache->set( $key, 0, $cache::TTL_DAY ); } if ( $value >= $wgAccountCreationThrottle ) { return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle ); } - $wgMemc->incr( $key ); + $cache->incr( $key ); } } @@ -869,7 +870,7 @@ class LoginForm extends SpecialPage { * @return bool|int The integer hit count or True if it is already at the limit */ public static function incLoginThrottle( $username ) { - global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest; + global $wgPasswordAttemptThrottle, $wgRequest; $username = trim( $username ); // sanity $throttleCount = 0; @@ -878,11 +879,12 @@ class LoginForm extends SpecialPage { $count = $wgPasswordAttemptThrottle['count']; $period = $wgPasswordAttemptThrottle['seconds']; - $throttleCount = $wgMemc->get( $throttleKey ); + $cache = ObjectCache::getLocalClusterInstance(); + $throttleCount = $cache->get( $throttleKey ); if ( !$throttleCount ) { - $wgMemc->add( $throttleKey, 1, $period ); // start counter + $cache->add( $throttleKey, 1, $period ); // start counter } elseif ( $throttleCount < $count ) { - $wgMemc->incr( $throttleKey ); + $cache->incr( $throttleKey ); } elseif ( $throttleCount >= $count ) { return true; } @@ -897,11 +899,11 @@ class LoginForm extends SpecialPage { * @return void */ public static function clearLoginThrottle( $username ) { - global $wgMemc, $wgRequest; + global $wgRequest; $username = trim( $username ); // sanity $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); - $wgMemc->delete( $throttleKey ); + ObjectCache::getLocalClusterInstance()->delete( $throttleKey ); } /** @@ -960,9 +962,9 @@ class LoginForm extends SpecialPage { } function processLogin() { - global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle, - $wgInvalidPasswordReset; + global $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle, $wgInvalidPasswordReset; + $cache = ObjectCache::getLocalClusterInstance(); $authRes = $this->authenticateUserData(); switch ( $authRes ) { case self::SUCCESS: @@ -984,7 +986,7 @@ class LoginForm extends SpecialPage { // Reset the throttle $request = $this->getRequest(); $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); - $wgMemc->delete( $key ); + $cache->delete( $key ); if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { /* Replace the language object to provide user interface in diff --git a/includes/tidy/RaggettWrapper.php b/includes/tidy/RaggettWrapper.php index 3f549d0750..4759023fbf 100644 --- a/includes/tidy/RaggettWrapper.php +++ b/includes/tidy/RaggettWrapper.php @@ -51,6 +51,11 @@ class RaggettWrapper { // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '([ \r\n\t\f]*)!", + '
  • \1
  • ', $wrappedtext ); + // Wrap the whole thing in a doctype and body for Tidy. $wrappedtext = '' . @@ -79,6 +84,9 @@ class RaggettWrapper { // Revert back to <{link,meta}> $text = preg_replace( '!]*?)(/{0,1}>)!', '<$1$2$3', $text ); + // Remove datafld + $text = str_replace( '
  • mTokens->replace( $text ); diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index f600e32135..17fcab8b09 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -1968,7 +1968,7 @@ abstract class UploadBase { if ( $value === false ) { $cache->delete( $key ); } else { - $cache->set( $key, $value, 86400 ); + $cache->set( $key, $value, $cache::TTL_DAY ); } } } diff --git a/languages/Language.php b/languages/Language.php index 50ed513734..c7871c1bd2 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -207,10 +207,7 @@ class Language { * @return Language */ protected static function newFromCode( $code ) { - // Protect against path traversal below - if ( !Language::isValidCode( $code ) - || strcspn( $code, ":/\\\000" ) !== strlen( $code ) - ) { + if ( !Language::isValidCode( $code ) ) { throw new MWException( "Invalid language code \"$code\"" ); } @@ -224,7 +221,6 @@ class Language { // Check if there is a language class for the code $class = self::classFromCode( $code ); - self::preloadLanguageClass( $class ); if ( class_exists( $class ) ) { $lang = new $class; return $lang; @@ -238,9 +234,8 @@ class Language { } $class = self::classFromCode( $fallbackCode ); - self::preloadLanguageClass( $class ); if ( class_exists( $class ) ) { - $lang = Language::newFromCode( $fallbackCode ); + $lang = new $class; $lang->setCode( $code ); return $lang; } @@ -341,17 +336,16 @@ class Language { */ public static function isValidCode( $code ) { static $cache = array(); - if ( isset( $cache[$code] ) ) { - return $cache[$code]; + if ( !isset( $cache[$code] ) ) { + // People think language codes are html safe, so enforce it. + // Ideally we should only allow a-zA-Z0-9- + // but, .+ and other chars are often used for {{int:}} hacks + // see bugs 37564, 37587, 36938 + $cache[$code] = + // Protect against path traversal + strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) + && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code ); } - // People think language codes are html safe, so enforce it. - // Ideally we should only allow a-zA-Z0-9- - // but, .+ and other chars are often used for {{int:}} hacks - // see bugs 37564, 37587, 36938 - $cache[$code] = - strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) - && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code ); - return $cache[$code]; } @@ -411,35 +405,6 @@ class Language { return false; } - /** - * @param string $code - * @return string Name of the language class - */ - public static function classFromCode( $code ) { - if ( $code == 'en' ) { - return 'Language'; - } else { - return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); - } - } - - /** - * Includes language class files - * - * @param string $class Name of the language class - */ - public static function preloadLanguageClass( $class ) { - global $IP; - - if ( $class === 'Language' ) { - return; - } - - if ( file_exists( "$IP/languages/classes/$class.php" ) ) { - include_once "$IP/languages/classes/$class.php"; - } - } - /** * Get the LocalisationCache instance * @@ -3691,8 +3656,9 @@ class Language { # We got the first byte only of a multibyte char; remove it. $string = substr( $string, 0, -1 ); } elseif ( $char >= 0x80 && + // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' . - '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) + '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m ) ) { # We chopped in the middle of a character; remove it $string = $m[1]; @@ -4406,22 +4372,6 @@ class Language { $this->mParentLanguage = false; } - /** - * Get the name of a file for a certain language code - * @param string $prefix Prepend this to the filename - * @param string $code Language code - * @param string $suffix Append this to the filename - * @throws MWException - * @return string $prefix . $mangledCode . $suffix - */ - public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { - if ( !self::isValidBuiltInCode( $code ) ) { - throw new MWException( "Invalid language code \"$code\"" ); - } - - return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix; - } - /** * Get the language code from a file name. Inverse of getFileName() * @param string $filename $prefix . $languageCode . $suffix @@ -4439,6 +4389,34 @@ class Language { return str_replace( '_', '-', strtolower( $m[1] ) ); } + /** + * @param string $code + * @return string Name of the language class + */ + public static function classFromCode( $code ) { + if ( $code == 'en' ) { + return 'Language'; + } else { + return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); + } + } + + /** + * Get the name of a file for a certain language code + * @param string $prefix Prepend this to the filename + * @param string $code Language code + * @param string $suffix Append this to the filename + * @throws MWException + * @return string $prefix . $mangledCode . $suffix + */ + public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { + if ( !self::isValidBuiltInCode( $code ) ) { + throw new MWException( "Invalid language code \"$code\"" ); + } + + return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix; + } + /** * @param string $code * @return string @@ -4465,15 +4443,6 @@ class Language { return "$IP/languages/i18n/$code.json"; } - /** - * @param string $code - * @return string - */ - public static function getClassFileName( $code ) { - global $IP; - return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); - } - /** * Get the first fallback for a given language. * diff --git a/languages/i18n/as.json b/languages/i18n/as.json index cb750de9f9..e66358af1d 100644 --- a/languages/i18n/as.json +++ b/languages/i18n/as.json @@ -26,6 +26,7 @@ "tog-hideminor": "সাম্প্ৰতিক সাল-সলনিত অগুৰুত্বপূৰ্ণ সম্পাদনা নেদেখুৱাব", "tog-hidepatrolled": "সাম্প্ৰতিক সাল-সলনিত তহলদাৰী সম্পাদনা নেদেখুৱাব", "tog-newpageshidepatrolled": "নতুন পৃষ্ঠা তালিকাত তহলদাৰী পৃষ্ঠাসমূহ নেদেখুৱাব", + "tog-hidecategorization": "পৃষ্ঠাবোৰৰ শ্ৰেণীকৰণ লুকুৱাওক", "tog-extendwatchlist": "কেৱল সাম্প্ৰতিকেই নহয, লক্ষ্য-তালিকাৰ সকলো সাল-সলনি বহলাই দেখুৱাওক", "tog-usenewrc": "পৃষ্ঠাৰ পৰিৱৰ্তনসমূহ শেহতীয়া সালসলনি আৰু লক্ষ্যতালিকাত ভাগ কৰক", "tog-numberheadings": "শীৰ্ষকত স্বয়ংক্ৰিয়ভাৱে ক্ৰমিক নং দিয়ক", @@ -55,6 +56,7 @@ "tog-watchlisthideliu": "প্ৰবেশ কৰা সদস্যৰ সম্পাদনাসমূহ আঁতৰাই অনুসৰণ-তালিকা দেখুৱাওক", "tog-watchlisthideanons": "বেনামী সদস্যৰ সম্পাদনাসমূহ আঁতৰাই অনুসৰণ-তালিকা দেখুৱাওক", "tog-watchlisthidepatrolled": "পৰীক্ষিত সম্পাদনাসমূহ লক্ষ্য-তালিকাৰ পৰা লুকুৱাই ৰাখক", + "tog-watchlisthidecategorization": "পৃষ্ঠাবোৰৰ শ্ৰেণীকৰণ লুকুৱাওক", "tog-ccmeonemails": "মই অন্য সদস্যলৈ পঠোৱা ই-মেইলৰ প্ৰতিলিপি এটা মোলৈও পঠাব", "tog-diffonly": "পার্থক্যৰ তলত পৃষ্ঠাৰ বিষয়বস্তু নেদেখুৱাব", "tog-showhiddencats": "নিহিত শ্ৰেণীসমূহ দেখুৱাওক", @@ -429,8 +431,6 @@ "createaccountreason": "কাৰণ:", "createacct-reason": "কাৰণ", "createacct-reason-ph": "আপুনি কিয় আন এটা একাউণ্ট সৃষ্টি কৰিছে", - "createacct-captcha": "সুৰক্ষা পৰীক্ষা", - "createacct-imgcaptcha-ph": "ওপৰত দেখা পোৱা পাঠ্য লিখক", "createacct-submit": "আপোনাৰ একাউণ্ট সৃষ্টি কৰক", "createacct-another-submit": "একাউণ্ট সৃষ্টি কৰক", "createacct-benefit-heading": "আপোনাৰ দৰে মানুহেই {{SITENAME}} তৈয়াৰ কৰিছে", @@ -866,6 +866,7 @@ "showingresultsinrange": "তলত #$2ৰ পৰা #$3 পৰিসৰৰ ভিতৰত {{PLURAL:$1|1টা ফলাফল|$1টা লৈকে ফলাফল}} দেখুওৱা হৈছে।", "search-showingresults": "{{PLURAL:$4|$3-ৰ $1টো ফলাফল|$3-ৰ $1 - $2টো ফলাফল}}", "search-nonefound": "এই অনুসন্ধানৰ কোনো ফলাফল নাই ।", + "search-nonefound-thiswiki": "এই ছাইটত এই সন্ধানৰ লগত মিলা কোনো ফলাফল নাই।", "powersearch-legend": "শক্তিশালী সন্ধান", "powersearch-ns": "নামস্থানবোৰত সন্ধান:", "powersearch-togglelabel": "পৰীক্ষা কৰক:", @@ -1174,6 +1175,8 @@ "rcshowhidemine": "মোৰ সম্পাদনা $1", "rcshowhidemine-show": "দেখুৱাওক", "rcshowhidemine-hide": "লুকুৱাওক", + "rcshowhidecategorization-show": "দেখুৱাওক", + "rcshowhidecategorization-hide": "লুকুৱাওক", "rclinks": "যোৱা $2 দিনত হোৱা $1 টা সাল-সলনি চাওক ।
    $3", "diff": "পাৰ্থক্য", "hist": "ইতিবৃত্ত", diff --git a/languages/i18n/ast.json b/languages/i18n/ast.json index 26b4ddd6ee..9374954774 100644 --- a/languages/i18n/ast.json +++ b/languages/i18n/ast.json @@ -884,6 +884,7 @@ "showingresultsinrange": "Más abaxo s'{{PLURAL:$1|amuesa|amuesen}} fasta {{PLURAL:$1|1 resultáu|$1 resultaos}} nel rangu ente #$2 y #$3.", "search-showingresults": "{{PLURAL:$4|Resultáu $1 de $3|Resultaos $1 - $2 de $3}}", "search-nonefound": "Nun hebo resultaos que casaren cola consulta.", + "search-nonefound-thiswiki": "Nun hebo resultaos que casaran cola consulta nesti sitiu.", "powersearch-legend": "Busca avanzada", "powersearch-ns": "Buscar nos espacios de nome:", "powersearch-togglelabel": "Comprobar:", diff --git a/languages/i18n/be-tarask.json b/languages/i18n/be-tarask.json index 20d601f7d9..2a9a2b8102 100644 --- a/languages/i18n/be-tarask.json +++ b/languages/i18n/be-tarask.json @@ -887,6 +887,7 @@ "showingresultsinrange": "Ніжэй паказаныя да {{PLURAL:$1|$1 выніку ў|$1 вынікаў у}} дыяпазоне ад $2 да $3.", "search-showingresults": "{{PLURAL:$4|1=Вынік $1 з $3|Вынікі $1—$2 з $3}}", "search-nonefound": "Супадзеньняў па запыце ня знойдзена.", + "search-nonefound-thiswiki": "Супадзеньняў па запыце ня знойдзена на гэтым сайце.", "powersearch-legend": "Удасканалены пошук", "powersearch-ns": "Шукаць у прасторах назваў:", "powersearch-togglelabel": "Пазначыць:", @@ -1368,6 +1369,8 @@ "foreign-structured-upload-form-label-not-own-work-message-default": "Калі вы ня можаце загрузіць гэты файл паводле правілаў агульнага сховішча, калі ласка, закрыйце гэты дыялёг і паспрабуйце іншы мэтад.", "foreign-structured-upload-form-label-not-own-work-local-default": "Вы можаце паспрабаваць скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі гэты файл можна туды загрузіць згодна з правіламі.", "foreign-structured-upload-form-label-own-work-message-shared": "Я пацьвярджаю, што зьяўляюся ўласьнікам аўтарскіх правоў на гэты файл, і згодны незваротна перадаць гэты файл ў Вікісховішча на ўмовах ліцэнзіі [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0], а таксама згодны з [https://wikimediafoundation.org/wiki/Terms_of_Use умовамі выкарыстаньня].", + "foreign-structured-upload-form-label-not-own-work-message-shared": "Калі вы не зьяўляецеся ўласьнікам аўтарскіх правоў на гэты файл, або вы жадаеце распаўсюджваць яго пад іншай ліцэнзіяй, можаце скарыстацца [https://commons.wikimedia.org/wiki/Special:UploadWizard Майстарам загрузкі ў Вікісховішча].", + "foreign-structured-upload-form-label-not-own-work-local-shared": "Вы таксама можаце скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі правілы сайту дазваляюць загрузку такога файлу.", "backend-fail-stream": "Немагчыма накіраваць файл $1.", "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.", "backend-fail-notexists": "Файл $1 не існуе.", diff --git a/languages/i18n/bn.json b/languages/i18n/bn.json index a19587b364..c1db7420a9 100644 --- a/languages/i18n/bn.json +++ b/languages/i18n/bn.json @@ -2987,7 +2987,7 @@ "version-poweredby-others": "অন্যান্য", "version-poweredby-translators": "translatewiki.net অনুবাদকগণ", "version-credits-summary": "[[Special:Version|মিডিয়াউইকি]] সফটওয়্যারে অবদানের জন্য আমরা এই ব্যক্তিকে স্বীকৃতি দিতে চাই।", - "version-license-info": "মিডিয়াউইকি একটি ফ্রি সফটওয়্যার, আপনি এটি বিতরণ করতে পারবেন এবং/অথবা সম্পদানা করতে পারবেন, এক্ষেত্রে ফ্রি সফটওয়্যার ফাউন্ডেশনের প্রকাশিত গনু জেনারেল পাবলিক লাইসেন্সের ২য় অথবা সাম্প্রতিকতম কোনো সংস্করণ মেনে চলতে হবে। \n\nসকলের উপকারের লক্ষ্যে এটি বিতরণ করা হয়ে থাকে, কিন্তু এক্ষেত্রে কোনো ওয়ারেন্টি দেয়া হয় না, এমনকি বিশেষ কোনো কার্যক্ষেত্রে ব্যবহারের জন্যও তথাকথিত ওয়ারেন্টি দেয়া হয় না। বিস্তারিত জানতে দেখুন গনু জেনারেল পাবলিক লাইসেন্স। \n\nএই সফটওয়্যারের সাথে [{{SERVER}}{{SCRIPTPATH}}/COPYING গনু জেনারেল পাবলিক লাইসেন্সের একটি কপি] থাকার কথা; যদি আপনি না পেয়ে থাকেন তাহলে অনুগ্রহ করে ফ্রি সফটওয়্যার ফাউন্ডেশনকে জানান এই ঠিকানায়, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA অথবা [//www.gnu.org/licenses/old-licenses/gpl-2.0.html অনলাইনে দেখুন]।", + "version-license-info": "মিডিয়াউইকি একটি ফ্রি সফটওয়্যার; আপনি এটি বিতরণ করতে পারবেন এবং/অথবা সম্পদানা করতে পারবেন, এক্ষেত্রে ফ্রি সফটওয়্যার ফাউন্ডেশনের প্রকাশিত গনু জেনারেল পাবলিক লাইসেন্সের ২য় অথবা সাম্প্রতিকতম কোনো সংস্করণ মেনে চলতে হবে। \n\nসকলের উপকারের লক্ষ্যে এটি বিতরণ করা হয়ে থাকে, কিন্তু এক্ষেত্রে কোনো ওয়ারেন্টি দেয়া হয় না, এমনকি বিশেষ কোনো কার্যক্ষেত্রে ব্যবহারের জন্যও তথাকথিত ওয়ারেন্টি দেয়া হয় না। বিস্তারিত জানতে দেখুন গনু জেনারেল পাবলিক লাইসেন্স। \n\nএই সফটওয়্যারের সাথে [{{SERVER}}{{SCRIPTPATH}}/COPYING গনু জেনারেল পাবলিক লাইসেন্সের একটি অনুলিপি] থাকার কথা; যদি আপনি না পেয়ে থাকেন তাহলে অনুগ্রহ করে ফ্রি সফটওয়্যার ফাউন্ডেশনকে জানান এই ঠিকানায়, Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA অথবা [//www.gnu.org/licenses/old-licenses/gpl-2.0.html অনলাইনে দেখুন]।", "version-software": "ইনস্টলকৃত সফটওয়্যার", "version-software-product": "পণ্য", "version-software-version": "সংস্করণ", diff --git a/languages/i18n/bs.json b/languages/i18n/bs.json index 1d7b3396be..8e24e0e6ea 100644 --- a/languages/i18n/bs.json +++ b/languages/i18n/bs.json @@ -3205,7 +3205,7 @@ "logentry-delete-delete": "$1 {{GENDER:$2|obrisao|obrisala}} je stranicu $3", "logentry-delete-restore": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3", "logentry-delete-event": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|događaja|$5 događaja}} u evidenciji na $3: $4", - "logentry-delete-revision": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|izmjene|$5 izmjene|$5 izmjena}} na stranici $3: $4", + "logentry-delete-revision": "$1 {{GENDER:$2|promijenio|promijenila}} je vidljivost {{PLURAL:$5|izmjene|$5 izmjene|$5 izmjena}} na stranici $3: $4", "logentry-delete-event-legacy": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost događaja u evidenciji na $3", "logentry-delete-revision-legacy": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost izmjena na stranici $3", "logentry-suppress-delete": "$1 {{GENDER:$2|potisnuo|potisnula}} je stranicu $3", @@ -3213,7 +3213,7 @@ "logentry-suppress-revision": "$1 je tajno {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|izmjene|$5 izmjene|$5 izmjena}} na stranici $3: $4", "logentry-suppress-event-legacy": "$1 je tajno {{GENDER:$2|promijenio|promijenila}} vidljivost događaja u evidenciji na $3", "logentry-suppress-revision-legacy": "$1 je tajno {{GENDER:$2|promijenio|promijenila}} vidljivost izmjena na stranici $3", - "revdelete-content-hid": "skriveni sadržaj", + "revdelete-content-hid": "sadržaj je sakriven", "revdelete-summary-hid": "sažetak izmjene je sakriven", "revdelete-uname-hid": "sažetak izmjene je sakriven", "revdelete-content-unhid": "sadržaj je otkriven", diff --git a/languages/i18n/ca.json b/languages/i18n/ca.json index a89bc56fb1..003e797f89 100644 --- a/languages/i18n/ca.json +++ b/languages/i18n/ca.json @@ -51,7 +51,8 @@ "Xavier Dengra", "Pginer", "Eduardo Martinez", - "Matma Rex" + "Matma Rex", + "KRLS" ] }, "tog-underline": "Subratlla els enllaços:", @@ -3239,11 +3240,11 @@ "revdelete-uname-unhid": "ha revelat un nom d'usuari que era ocult", "revdelete-restricted": "ha aplicat restriccions als administradors", "revdelete-unrestricted": "ha tret les restriccions als administradors", - "logentry-block-block": "$1 {{GENDER:$2|ha estat blocat|ha estat blocada}} {{GENDER:$4|$3}} per un període de temps de $5 $6", + "logentry-block-block": "$1 {{GENDER:$2|ha blocat}} {{GENDER:$4|$3}} per un temps de $5 $6", "logentry-block-unblock": "$1 {{GENDER:$2|va desblocar}} {{GENDER:$4|$3}}", - "logentry-block-reblock": "$1 {{GENDER:$2|ha canviat}} la configuració del blocatge de {{GENDER:$4|$3}} per un període de temps de $5 $6", - "logentry-suppress-block": "$1 {{GENDER:$2|ha blocat}} {{GENDER:$4|$3}} per un període de temps de $5 $6", - "logentry-suppress-reblock": "$1 {{GENDER:$2|ha canviat}} la configuració de blocatge de {{GENDER:$4|$3}} per un període de temps de $5 $6", + "logentry-block-reblock": "$1 {{GENDER:$2|ha canviat}} la configuració del blocatge de {{GENDER:$4|$3}} per un temps de $5 $6", + "logentry-suppress-block": "$1 {{GENDER:$2|ha blocat}} {{GENDER:$4|$3}} per un temps de $5 $6", + "logentry-suppress-reblock": "$1 {{GENDER:$2|ha canviat}} la configuració de blocatge de {{GENDER:$4|$3}} per un temps de $5 $6", "logentry-import-upload": "$1 {{GENDER:$2|va importar}} $3 a través de càrrega de fitxer", "logentry-import-interwiki": "$1 {{GENDER:$2|va importar}} $3 d'un altre wiki", "logentry-merge-merge": "$1 {{GENDER:$2|ha fusionat}} $3 en $4 (revisions fins a $5)", diff --git a/languages/i18n/ce.json b/languages/i18n/ce.json index d9212b9e22..4ee4f0c2cf 100644 --- a/languages/i18n/ce.json +++ b/languages/i18n/ce.json @@ -382,7 +382,7 @@ "createacct-yourpasswordagain": "Бакъе пароль", "createacct-yourpasswordagain-ph": "Кхин цкъа язъе пароль", "remembermypassword": "Даглаца сан дӀаяздар хӀокху компьютеран тӀехь (цхьан $1 {{PLURAL:$1|дийнахь}})", - "userlogin-remembermypassword": "Дагахь латт ве/е со", + "userlogin-remembermypassword": "Системин чохь Ӏойла", "userlogin-signwithsecure": "Ларийна цхьаьнакхетар", "yourdomainname": "Хьан машан меттиг:", "password-change-forbidden": "Хьан йиш яц хӀокху вики чохь пароль хийца.", diff --git a/languages/i18n/ckb.json b/languages/i18n/ckb.json index 3972e3943c..ccf161b2ab 100644 --- a/languages/i18n/ckb.json +++ b/languages/i18n/ckb.json @@ -409,8 +409,6 @@ "createaccountreason": "هۆکار:", "createacct-reason": "ھۆکار", "createacct-reason-ph": "بۆ ھەژمارێکی تر دروست دەکەی", - "createacct-captcha": "تاوتوێی ئاسایشی", - "createacct-imgcaptcha-ph": "دەقەکەی لە ژێرەوە دەیبینی بینووسە", "createacct-submit": "ھەژمارەکەت دروست بکە", "createacct-another-submit": "ھەژمارێکی تر دروست بکە", "createacct-benefit-heading": "{{SITENAME}} لە لایەن کەسانێک وەکوو خۆت دروست کراوە.", @@ -2550,6 +2548,7 @@ "revdelete-uname-unhid": "ناوی بەکارهێنەری نیشان درا", "revdelete-restricted": "ئەو سنووری بەرگریانەی خستراوەتە سەر بەڕێوبەران", "revdelete-unrestricted": "ئەو سنووری بەرگریانەی لابردراوە لە سەر بەڕێوبەران", + "logentry-block-block": "$1 {{GENDER:$4|$3}}ی بۆ ماوەی $5 {{GENDER:$2|بەربەست کرد}} $6", "logentry-move-move": "$1 پەڕەی $3ی {{GENDER:$2|گواستەوە}} بۆ $4", "logentry-move-move-noredirect": "$1 پەڕەی $3ی بەبێ بەجێھشتنی ڕەوانەکەرێک {{GENDER:$2|گواستەوە}} بۆ $4", "logentry-move-move_redir": "$1 پەڕەی $3 {{GENDER:$2|گواستەوە}} بۆ $4 کە پێشتر ڕەوانەکەر بوو", diff --git a/languages/i18n/crh-cyrl.json b/languages/i18n/crh-cyrl.json index 29d0707138..1dc61ea671 100644 --- a/languages/i18n/crh-cyrl.json +++ b/languages/i18n/crh-cyrl.json @@ -5,7 +5,8 @@ "Alessandro", "Don Alessandro", "Urhixidur", - "아라" + "아라", + "Исмаил Садуев" ] }, "tog-underline": "Багълантыларнынъ тюбюни сызув:", @@ -130,7 +131,7 @@ "mytalk": "Музакере", "anontalk": "Бу IP-нинъ музакереси", "navigation": "Сайтта ёл тапув", - "and": " ве", + "and": " а", "qbfind": "Тап", "qbbrowse": "Бакъып чыкъ", "qbedit": "Денъиштир", @@ -353,8 +354,6 @@ "createaccountreason": "Себеп:", "createacct-reason": "Себеп", "createacct-reason-ph": "Башкъа бир эсап язысы неден себеп яратасынъыз", - "createacct-captcha": "Телюкесизлик контроли", - "createacct-imgcaptcha-ph": "Юкъарыда корьген метнинъизни язынъыз", "createacct-submit": "Эсап язынъызны яратынъыз", "createacct-another-submit": "Башкъа бир эсап язысы яратынъыз", "createacct-benefit-heading": "{{SITENAME}} сизинъ киби адамлар тарафындан языла.", @@ -1194,7 +1193,6 @@ "move-page-legend": "Саифенинъ адыны денъиштирюв", "movepagetext": "Ашагъыдаки форма къулланылып саифенинъ ады денъиштирилир. Бунынънен берабер денъиштирмелер журналы да янъы адгъа авуштырылыр.\nЭски ады янъы адына ёнетме олур. Эски серлевагъа ёнетип тургъан саифелерни автоматик оларакъ янъартып оласынъыз. Бу арекетни автоматик япмагъа истемесенъиз, бутюн [[Special:DoubleRedirects|чифт]] ве [[Special:BrokenRedirects|йыртыкъ]] ёнетме саифелерини озюнъиз тешкермеге меджбур олурсынъыз. Багълантылар эндиден берли догъру чалышмасындан эмин олмалысынъыз.\n\nЯнъы адда бир саифе энди бар олса, ад денъиштирилюви япылмайджакъ, анджакъ бар олгъан саифе ёнетме я да бош олса ад денъиштирилюви мумкюн оладжакъ. Бу демек ки, саифенинъ адыны янълыштан денъиштирген олсанъыз деминки адыны кери къайтарып оласынъыз, амма бар олгъан саифени тесадюфен ёкъ этамайсынъыз.\n\nТЕНБИ!\nАд денъиштирилюви популяр саифелер ичюн буюк ве бекленмеген денъишмелерге себеп ола билир. Лютфен, денъиштирме япмаздан эвель ола биледжеклерни козь огюне алынъыз.", "movepagetalktext": "Къошулгъан музакере саифесининъ де (бар олса)\nады автоматик тарзда денъиштириледжек. '''Мустесналар:'''\n\n* Айны бу адда бош олмагъан бир музакере саифеси энди бар;\n* Ашагъыдаки бошлукъкъа ишарет къоймадынъыз.\n\nБойле алларда, керек олса, саифелерни къолнен ташымагъа я да бирлештирмеге меджбур олурсынъыз.", - "movearticle": "Эски ад", "movecategorypage-warning": "Ихтар: Бир категория саифесининъ адыны денъиштирмек узьресинъиз. Лютфен, ялынъыз категория саифесининъ кочюриледжегини ве эски категорияда ер алгъан саифелернинъ янъы категориягъа авотматик оларакъ авуштырылмайджагъыны унутманъыз.", "movenologintext": "Саифенинъ адыны денъиштирип олмакъ ичюн [[Special:UserLogin|отурым ачынъыз]].", "movenotallowed": "Саифелер адларыны денъиштирмеге изининъиз ёкъ.", diff --git a/languages/i18n/de.json b/languages/i18n/de.json index 4414b1bfc3..99fc15b5af 100644 --- a/languages/i18n/de.json +++ b/languages/i18n/de.json @@ -956,6 +956,7 @@ "showingresultsinrange": "Unten {{PLURAL:$1|wird ein Ergebnis|werden bis zu $1 Ergebnisse}} im Bereich $2 bis $3 angezeigt.", "search-showingresults": "{{PLURAL:$4|Ergebnis $1 von $3|Ergebnisse $1 bis $2 von $3}}", "search-nonefound": "Zu deiner Suchanfrage wurden keine Ergebnisse gefunden.", + "search-nonefound-thiswiki": "Es gibt auf dieser Website keine der Suchanfrage entsprechenden Ergebnisse.", "powersearch-legend": "Erweiterte Suche", "powersearch-ns": "Suche in Namensräumen:", "powersearch-togglelabel": "Wähle aus:", diff --git a/languages/i18n/es.json b/languages/i18n/es.json index 3b1f548040..4b8b4fa938 100644 --- a/languages/i18n/es.json +++ b/languages/i18n/es.json @@ -124,7 +124,8 @@ "JasterTDC", "Laurenslimb", "Tusca", - "Tadol" + "Tadol", + "Nelson6e65" ] }, "tog-underline": "Subrayar los enlaces:", @@ -154,7 +155,7 @@ "tog-oldsig": "Firma actual:", "tog-fancysig": "Tratar la firma como wikitexto (sin un enlace automático)", "tog-uselivepreview": "Usar previsualización dinámica", - "tog-forceeditsummary": "Avisarme cuando grabe la página sin escribir un resumen de edición", + "tog-forceeditsummary": "Avisarme cuando deje en blanco el resumen de la edición", "tog-watchlisthideown": "Ocultar mis ediciones de la lista de seguimiento", "tog-watchlisthidebots": "Ocultar las ediciones de bots de la lista de seguimiento", "tog-watchlisthideminor": "Ocultar las ediciones menores de la lista de seguimiento", diff --git a/languages/i18n/fa.json b/languages/i18n/fa.json index 473ae0218e..4313261329 100644 --- a/languages/i18n/fa.json +++ b/languages/i18n/fa.json @@ -1071,20 +1071,20 @@ "group-bot": "ربات‌ها", "group-sysop": "مدیران", "group-bureaucrat": "دیوان‌سالاران", - "group-suppress": "پنهانگران", + "group-suppress": "فرونشانندگان", "group-all": "(همه)", "group-user-member": "{{GENDER:$1|کاربر}}", "group-autoconfirmed-member": "{{GENDER:$1|کاربر تأییدشده}}", "group-bot-member": "ربات", "group-sysop-member": "{{GENDER:$1|مدیر}}", "group-bureaucrat-member": "{{GENDER:$1|دیوان‌سالار}}", - "group-suppress-member": "{{GENDER:$1|پنهانگر}}", + "group-suppress-member": "{{GENDER:$1|فرونشاننده}}", "grouppage-user": "{{ns:project}}:کاربران", "grouppage-autoconfirmed": "{{ns:project}}:کاربران تأییدشده", "grouppage-bot": "{{ns:project}}:ربات‌ها", "grouppage-sysop": "{{ns:project}}:مدیران", "grouppage-bureaucrat": "{{ns:project}}:دیوان‌سالاران", - "grouppage-suppress": "{{ns:project}}:پنهانگر", + "grouppage-suppress": "{{ns:project}}:فرونشانی", "right-read": "خواندن صفحه", "right-edit": "ویرایش صفحه", "right-createpage": "ایجاد صفحه (در مورد صفحه‌های غیر بحث)", diff --git a/languages/i18n/fr.json b/languages/i18n/fr.json index 1d9e8cea69..3890845dde 100644 --- a/languages/i18n/fr.json +++ b/languages/i18n/fr.json @@ -3147,24 +3147,28 @@ "hebrew-calendar-m4": "tévet", "hebrew-calendar-m5": "chevat", "hebrew-calendar-m6": "adar", - "hebrew-calendar-m7": "Nissane", - "hebrew-calendar-m8": "Iyar", - "hebrew-calendar-m9": "Sivane", - "hebrew-calendar-m10": "Tamouz", - "hebrew-calendar-m11": "Av", - "hebrew-calendar-m12": "Éloul", + "hebrew-calendar-m6a": "adar I", + "hebrew-calendar-m6b": "adar II", + "hebrew-calendar-m7": "nissan", + "hebrew-calendar-m8": "iyar", + "hebrew-calendar-m9": "sivan", + "hebrew-calendar-m10": "tamouz", + "hebrew-calendar-m11": "av", + "hebrew-calendar-m12": "eloul", "hebrew-calendar-m1-gen": "tichri", "hebrew-calendar-m2-gen": "hechvan", "hebrew-calendar-m3-gen": "kislev", "hebrew-calendar-m4-gen": "tévet", "hebrew-calendar-m5-gen": "chevat", "hebrew-calendar-m6-gen": "adar", - "hebrew-calendar-m7-gen": "Nissane", - "hebrew-calendar-m8-gen": "Iyar", - "hebrew-calendar-m9-gen": "Sivane", - "hebrew-calendar-m10-gen": "Tamouz", - "hebrew-calendar-m11-gen": "Av", - "hebrew-calendar-m12-gen": "Éloul", + "hebrew-calendar-m6a-gen": "adar I", + "hebrew-calendar-m6b-gen": "adar II", + "hebrew-calendar-m7-gen": "nissan", + "hebrew-calendar-m8-gen": "iyar", + "hebrew-calendar-m9-gen": "sivan", + "hebrew-calendar-m10-gen": "tamouz", + "hebrew-calendar-m11-gen": "av", + "hebrew-calendar-m12-gen": "eloul", "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discussion]])", "duplicate-defaultsort": "Attention : la clé de tri par défaut « $2 » écrase la précédente clé « $1 ».", "duplicate-displaytitle": "Attention : Le titre d'affichage «$2» remplace l'ancien titre d'affichage «$1».", diff --git a/languages/i18n/he.json b/languages/i18n/he.json index 98e762eabc..c6aa30b8ec 100644 --- a/languages/i18n/he.json +++ b/languages/i18n/he.json @@ -903,6 +903,7 @@ "showingresultsinrange": "{{PLURAL:$1|מוצגת תוצאה אחת|מוצגות עד $1 תוצאות}} בין המספרים $2 ו‏‏־$3:", "search-showingresults": "{{PLURAL:$4|תוצאה $1 מתוך $3|תוצאות $1 - $2 מתוך $3}}", "search-nonefound": "לא נמצאו תוצאות המתאימות לחיפוש.", + "search-nonefound-thiswiki": "לא נמצאו תוצאות המתאימות לחיפוש באתר זה.", "powersearch-legend": "חיפוש מתקדם", "powersearch-ns": "חיפוש על־פי מרחבי שם:", "powersearch-togglelabel": "בחירה:", diff --git a/languages/i18n/id.json b/languages/i18n/id.json index 4532adad17..67c616b3de 100644 --- a/languages/i18n/id.json +++ b/languages/i18n/id.json @@ -42,7 +42,8 @@ "Mirws", "Ilham", "Matma Rex", - "WongKentir" + "WongKentir", + "Rachmat.Wahidi" ] }, "tog-underline": "Garis bawahi pranala:", @@ -1978,7 +1979,7 @@ "sp-contributions-newbies": "Hanya dari para pengguna baru", "sp-contributions-newbies-sub": "Untuk pengguna baru", "sp-contributions-newbies-title": "Kontribusi pengguna baru", - "sp-contributions-blocklog": "Log pemblokiran", + "sp-contributions-blocklog": "log pemblokiran", "sp-contributions-suppresslog": "kontribusi pengguna yang disembunyikan", "sp-contributions-deleted": "kontribusi pengguna yang dihapus", "sp-contributions-uploads": "unggahan", diff --git a/languages/i18n/it.json b/languages/i18n/it.json index f0b98c9bf4..f3cab6b76f 100644 --- a/languages/i18n/it.json +++ b/languages/i18n/it.json @@ -964,6 +964,7 @@ "showingresultsinrange": "{{PLURAL:$1|Viene mostrato|Vengono mostrati}} sotto {{PLURAL:$1|1 risultato|$1 risultati}} dal $2 al $3.", "search-showingresults": "{{PLURAL:$4|Risultato $1 di $3|Risultati $1 - $2 di $3}}", "search-nonefound": "La ricerca non ha prodotto risultati.", + "search-nonefound-thiswiki": "La ricerca non ha prodotto risultati in questo sito.", "powersearch-legend": "Ricerca avanzata", "powersearch-ns": "Cerca nei namespace:", "powersearch-togglelabel": "Seleziona:", diff --git a/languages/i18n/ja.json b/languages/i18n/ja.json index 1ee5473eec..f209c00a11 100644 --- a/languages/i18n/ja.json +++ b/languages/i18n/ja.json @@ -943,6 +943,7 @@ "showingresultsinrange": "$2 件目から$3 件目までの範囲内で最大 {{PLURAL:$1|$1 件の結果}}を表示しています。", "search-showingresults": "{{PLURAL:$4|$3 件中の $1 件目|$3 件中の $1 件目から $2 件目}}", "search-nonefound": "問い合わせに合致する検索結果はありませんでした。", + "search-nonefound-thiswiki": "このサイトでの、そのクエリに一致する結果は、何もありませんでした。", "powersearch-legend": "高度な検索", "powersearch-ns": "名前空間を指定して検索:", "powersearch-togglelabel": "チェックを入れる:", diff --git a/languages/i18n/ko.json b/languages/i18n/ko.json index 7b783cc4d9..8859cab933 100644 --- a/languages/i18n/ko.json +++ b/languages/i18n/ko.json @@ -918,6 +918,7 @@ "showingresultsinrange": "#$2부터 #$3까지의 범위에서 $1개의 {{PLURAL:$1|결과}}가 아래에 보입니다.", "search-showingresults": "{{PLURAL:$4|결과 $3개 중 $1개|결과 $3개 중 $1 - $2개}}", "search-nonefound": "검색어와 일치하는 결과가 없습니다.", + "search-nonefound-thiswiki": "이 사이트에서 검색어와 일치하는 결과가 없습니다.", "powersearch-legend": "고급 검색", "powersearch-ns": "다음 이름공간에서 검색:", "powersearch-togglelabel": "확인:", diff --git a/languages/i18n/ksh.json b/languages/i18n/ksh.json index ba06d012ca..c836c07b06 100644 --- a/languages/i18n/ksh.json +++ b/languages/i18n/ksh.json @@ -531,7 +531,7 @@ "passwordreset-emailerror-capture": "En e-mail met Aanjahbe zom neue Paßwoot för der Zohjang heh sullt verschek wääde, ävver dat Verscheke aan {{GENDER:$2|dä|dat|dä Metmaacher|de|dat}} $2 hät nit jeflup: $1", "changeemail": "Donn en Adräß för de e-mail ändere udder fott schmiiße", "changeemail-header": "Donn heh dat Fommulaa ußfölle, öm Ding Adräß för de e-mail ze ändere. Wann De en Adräß loß wähde wells, maach dat Fäld läddesch, ih dat De dat Fommolaa loß scheks.", - "changeemail-passwordrequired": "Do moß Ding Paßwood enjävve, öm di änderong ze beschtähteje.", + "changeemail-passwordrequired": "Do moß Ding Paßwood enjävve, öm di Änderong ze beschtähteje.", "changeemail-no-info": "Do mööts ald enjelogg sin, öm tiräk op di Sigg jonn ze dörve", "changeemail-oldemail": "Ding Address för de e-mail es jäz:", "changeemail-newemail": "Ding neue Address för de e-mail sull wääde:", diff --git a/languages/i18n/ku-latn.json b/languages/i18n/ku-latn.json index ed1dac4e12..b496fbce8f 100644 --- a/languages/i18n/ku-latn.json +++ b/languages/i18n/ku-latn.json @@ -351,8 +351,8 @@ "userlogin-yourpassword-ph": "Şîfreya xwe binivîse", "createacct-yourpassword-ph": "Şîfreya xwe binivîse", "yourpasswordagain": "Şîfreyê dîsa binivîse:", - "createacct-yourpasswordagain": "Şîfreye bipejirîne", - "createacct-yourpasswordagain-ph": "Şîfreyê ji nû ve têkeve", + "createacct-yourpasswordagain": "Şîfreyê bipejirîne", + "createacct-yourpasswordagain-ph": "Şîfreyê ji nû ve binivîse", "remembermypassword": "Şifreya min di her têketina min de bîne bîra xwe (herî zêde $1 {{PLURAL:$1|rojekê|rojan}})", "userlogin-remembermypassword": "Min têketî bihêle", "userlogin-signwithsecure": "Girêdana parastî bikarbîne", @@ -373,33 +373,31 @@ "gotaccount": "Hesabê te heye? $1.", "gotaccountlink": "Têkeve", "userlogin-resetlink": "Te agahiyên hesabê xwe ji bîr kirin?", - "userlogin-resetpassword-link": "Te şîfreye xwe jibîrkir?", + "userlogin-resetpassword-link": "Te şîfreya xwe ji bîr kir?", "userlogin-helplink2": "Alîkariya têketinê", - "userlogin-createanother": "Hesabek din çeke", + "userlogin-createanother": "Hesabekî din çeke", "createacct-emailrequired": "E-name", "createacct-emailoptional": "E-name", "createacct-email-ph": "E-nameya xwe binivîse", - "createacct-another-email-ph": "E-nameya xwe têkeve", - "createaccountmail": "Şîfreyek ji bo ji bo demeke kin bikarbînin û ji navnîşana hatiye diyarkirin re e-nameyek bişînin.", + "createacct-another-email-ph": "E-nameya xwe binivîse", + "createaccountmail": "Şîfreyeke demkî bikar bîne û wê ji navnîşana hatiye diyarkirin re bişîne.", "createacct-realname": "Navê te ya rast (Ko tu bixwazi bikeve, pêdivî nîne)", "createaccountreason": "Sedem:", "createacct-reason": "Sedem", - "createacct-reason-ph": "Çima hesabek din çedikîy", - "createacct-captcha": "Kontrola asayîşê", - "createacct-imgcaptcha-ph": "Nivîsa ku tu li jor dibînî binivîse", + "createacct-reason-ph": "Çima hesabekî din çêdikî", "createacct-submit": "Hesabê xwe biafirîne", - "createacct-another-submit": "Hesabek çêke", - "createacct-benefit-heading": "{{SITENAME}} ji alî mirovên wek te tê çêkirin.", + "createacct-another-submit": "Hesabekî çêke", + "createacct-benefit-heading": "{{SITENAME}} ji aliyê mirovên wek te ve tê çêkirin.", "createacct-benefit-body1": "{{PLURAL:$1|guhertin}}", "createacct-benefit-body2": "{{PLURAL:$1|rûpel}}", "badretype": "Her du şîfreyên ku te nivîsîn li hevdu nayên.", "userexists": "Ev navî bikarhênerî berê tê bikaranîn. Xêra xwe navekî din dake.", "loginerror": "Çewtiya têketinê", - "createacct-error": "Çewtîya çêkirine hesabê", + "createacct-error": "Çewtiya çêkirina hesabî", "createaccounterror": "Hesab nikare were çêkirin: $1", "nocookiesnew": "Hesabê bikarhêner hatibû çêkirin, lê te xwe qeyd nekiriye. {{SITENAME}} ji bo qeydkirina bikarhêneran cookie'yan bikartîne. Te bikaranîna cookie'yan girtiye. Xêra xwe cookie'yan qebûl bike, piştre bi navê bikarhêner û şîfreya xwe têkeve.", "nocookieslogin": "Ji bo qeydkirina bikarhêneran {{SITENAME}} \"cookies\" bikartîne. Te fonksîyona \"cookies\" girtîye. Xêra xwe kerema xwe \"cookies\" gengaz bike û careke din biceribîne.", - "noname": "Navê ku te nivîsand derbas nabe.", + "noname": "Navê ku te nivîsand ne derbasdar e.", "loginsuccesstitle": "Têketin serkevtî bû!", "loginsuccess": "Tu niha di {{SITENAME}} de tomarkirî yî wek \"$1\".", "nosuchuser": "Bikarhênerê bi navê \"$1\" tune. Navê rast binivîse an bi vê formê hesabekî nû çêke. (Ji bo hevalên nû \"Têkeve\" çênabe!)", diff --git a/languages/i18n/mk.json b/languages/i18n/mk.json index 1e22c0056f..4dd3759501 100644 --- a/languages/i18n/mk.json +++ b/languages/i18n/mk.json @@ -895,6 +895,7 @@ "showingresultsinrange": "Долу {{PLURAL:$1|е прикажан до еден резултат|се прикажани до $1 резултати}} во опсег од $2 до $3.", "search-showingresults": "{{PLURAL:$4|Резултат $1 од $3|Резултати $1 - $2 од $3}}", "search-nonefound": "Нема резултати што одговараат на бараното.", + "search-nonefound-thiswiki": "Нема резултати што одговараат на бараното на ова мрежно место.", "powersearch-legend": "Напредно пребарување", "powersearch-ns": "Пребарај во следниве именски простори:", "powersearch-togglelabel": "Одбери:", diff --git a/languages/i18n/mr.json b/languages/i18n/mr.json index c6e7f2cc2f..c8f4d16aac 100644 --- a/languages/i18n/mr.json +++ b/languages/i18n/mr.json @@ -268,7 +268,7 @@ "pool-errorunknown": "अपरिचित त्रुटी", "pool-servererror": "पूल काउंटर सेवा उपलब्ध नाही($1).", "poolcounter-usage-error": "वापर त्रूटी:$1", - "aboutsite": "{{SITENAME}}बद्दल", + "aboutsite": "{{SITENAME}} बद्दल", "aboutpage": "Project:माहितीपृष्ठ", "copyright": "येथील मजकूर $1च्या अंतर्गत उपलब्ध आहे जोपर्यंत इतर नोंदी केलेल्या नाहीत.", "copyrightpage": "{{ns:project}}:प्रताधिकार", @@ -1056,7 +1056,7 @@ "grouppage-suppress": "{{ns:project}}:झापडबंद", "right-read": "पृष्ठे वाचा", "right-edit": "पाने संपादा", - "right-createpage": "पृष्ठे तयार करा (जी चर्चापानांव्यतिरिक्त)", + "right-createpage": "पृष्ठे तयार करा (जी चर्चापानांव्यतिरिक्त आहेत)", "right-createtalk": "चर्चा पृष्ठे तयार करा", "right-createaccount": "नवीन सदस्य खाती तयार करा", "right-minoredit": "बदल किरकोळ म्हणून जतन करा", @@ -1493,6 +1493,7 @@ "download": "अधिभारण करा", "unwatchedpages": "देखरेख नसलेली पाने", "listredirects": "पुनर्निर्देशनांची यादी", + "listduplicatedfiles": "प्रतिलिपी(डुप्लिकेट) संचिकांची यादी", "unusedtemplates": "न वापरलेले साचे", "unusedtemplatestext": "या पानावर साचा नामविश्वातील अशी सर्व पाने आहेत जी कुठल्याही पानात वापरलेली नाहीत. वगळण्यापूर्वी साच्यांना जोडणारे इतर दुवे पाहण्यास विसरू नका.", "unusedtemplateswlh": "इतर दुवे", @@ -1561,6 +1562,7 @@ "unusedimages": "न वापरलेल्या संचिका", "wantedcategories": "पाहिजे असलेले वर्ग", "wantedpages": "पाहिजे असलेले लेख", + "wantedpages-summary": "ही,ज्यांना अधिकांश दुवे आहेत अश्या अस्तित्वात नसलेल्या पानांची यादी आहे. यात ती पाने वगळली आहेत, ज्यांना फक्त पुनर्निर्देशनाचा दुवा आहे. अस्तित्वात नसलेली पण पुनर्निर्देशनाने जोडलेली जी पाने आहेत, अश्यांच्या यादीसाठी [[{{#special:BrokenRedirects}}|मोडकी पुनर्निर्देशने असलेल्या पानांची यादी]] बघा.", "wantedpages-badtitle": "परिणामाच्या यादीत अवैध शीर्षक: $1", "wantedfiles": "पाहिजे असलेल्या संचिका", "wantedfiletext-cat": "पुढील फाइल्स वापरल्या असतील पण आता अस्तित्वात नाहीत. बाहेरील ठिकाणांच्या फाइल्स येथे दिसतात पण असतीलच असे नाही. अशा फाइल्स आढळल्यास वगळल्या जातील. अतिरिक्तपणे,अशी पाने, ज्यात टाकलेल्या संचिका अस्तित्वात नाहीत,त्याची यादी [[:$1]] येथे दिसेल.", @@ -2587,7 +2589,7 @@ "exif-label": "लेबल", "exif-datetimemetadata": "मेटाडाटाच्या शेवटच्या बदलाची तारीख", "exif-nickname": "चित्राचे / फोटोचे सामान्य नाव", - "exif-rating": "गुण (५ पैकी)", + "exif-rating": "गुणानुक्रम (५ पैकी)", "exif-rightscertificate": "अधिकार व्यवस्थापन प्रमाणपत्र", "exif-copyrighted": "प्रताधिकार स्थिती", "exif-copyrightowner": "प्रताधिकार धारक", diff --git a/languages/i18n/nap.json b/languages/i18n/nap.json index 04b324e05e..266cbb1e96 100644 --- a/languages/i18n/nap.json +++ b/languages/i18n/nap.json @@ -881,6 +881,7 @@ "showingresultsinrange": "{{PLURAL:$1|Vene mmustato|Veneno mmustate}} abbascio {{PLURAL:$1|1 risultato|$1 risultate}} d' 'o $2 a 'o $3.", "search-showingresults": "{{PLURAL:$4|Risultato $1 'e $3|Risultate $1 - $2 'e $3}}", "search-nonefound": "'A ricerca nun ha produtto risultate.", + "search-nonefound-thiswiki": "Nun ce stevano risultate p' 'a ricerca fatta.", "powersearch-legend": "Ricerca avanzata", "powersearch-ns": "Ascìa dint' 'o namespace:", "powersearch-togglelabel": "Cuntrolla:", diff --git a/languages/i18n/olo.json b/languages/i18n/olo.json index f146b2790c..600b7dc148 100644 --- a/languages/i18n/olo.json +++ b/languages/i18n/olo.json @@ -15,7 +15,7 @@ "tog-numberheadings": "Automuattizesti numeroija kirjutuksien nimet", "tog-showtoolbar": "Ozuta ruadovälinehpalki", "tog-editondblclick": "Edituiče sivuloi kaksoispainalduksel", - "tog-editsectiononrightclick": "Korjua sektsielöi painamal sektsien nimie hiiren oigiel näppäimel", + "tog-editsectiononrightclick": "Kohendele sektsielöi painamal sektsien nimie hiiren oigiel näppäimel", "tog-watchcreations": "Ližiä minun luajitut sivut da ližätyt failat minun valvonduluvetteloh", "tog-watchdefault": "Ližiä minun kohendetut sivut da failat minun valvonduluvetteloh", "tog-watchmoves": "Ližiä minun siirretyt sivut da failat minun valvonduluvetteloh", @@ -33,7 +33,7 @@ "tog-fancysig": "Allekirjutus wikitekstannu (ilmai automuattistu linkii)", "tog-uselivepreview": "Käytä välittömiä ezikaččeluu", "tog-forceeditsummary": "Huomavuta minuu, gu en olle kirjutannuh yhtehveduo", - "tog-watchlisthideown": "Peitä minun korjavukset valvonduluvettelospäi", + "tog-watchlisthideown": "Peitä minun kohendelut valvonduluvettelospäi", "tog-watchlisthidebots": "Peitä botan kohendukset valdondulistalpäi", "tog-watchlisthideminor": "Peitä pienet kohendukset valvondulistalpäi", "tog-watchlisthideliu": "Peitä kirjutannuhuoloin käyttäjien kohendukset valvondulistalpäi", @@ -169,11 +169,11 @@ "print": "Pane bumuagale", "view": "Kačo", "view-foreign": "Kačo saital $1", - "edit": "Korjua", + "edit": "Kohendele", "edit-local": "Edituiče paikallistu kuvavustu", "create": "Luaji", "create-local": "Ližiä paikalline kuvavus", - "editthispage": "Korjua tädä sivuu", + "editthispage": "Kohendele tädä sivuu", "create-this-page": "Luaji tämä sivu", "delete": "Ota iäre", "deletethispage": "Ota tämä sivu iäre", @@ -219,7 +219,7 @@ "disclaimers": "Kieldävymine vastuos", "disclaimerpage": "Project:Vastuos kieldävymine", "edithelp": "Abu korjuamizeh", - "helppage-top-gethelp": "Kyzyö abuu", + "helppage-top-gethelp": "Kyzy abuu", "mainpage": "Piäsivu", "mainpage-description": "Piäsivu", "policy-url": "Project:Käytändöt", @@ -238,12 +238,12 @@ "newmessageslinkplural": "{{PLURAL:$1|uuzi viesti|999=uuzii viestilöi}}", "newmessagesdifflinkplural": "{{PLURAL:$1|jälgimäine muutos|jälgimästy muutostu}}", "youhavenewmessagesmulti": "Sinul on uuzii viestilöi sivuloil $1", - "editsection": "Korjua", - "editold": "korjua", + "editsection": "Kohendele", + "editold": "kohendele", "viewsourceold": "Kačo algukoodu", - "editlink": "korjua", + "editlink": "kohendele", "viewsourcelink": "Kačo algukoodu", - "editsectionhint": "Korjua tädä kohtua: $1", + "editsectionhint": "Kohendele tädä kohtua: $1", "toc": "Sizäldö", "showtoc": "ozuttua", "hidetoc": "peittiä", @@ -434,12 +434,12 @@ "hr_tip": "Horizontualine viivu (älä käytä liijakse)", "summary": "Yhtehvedo:", "subject": "Tiemu/rubriekku:", - "minoredit": "Tämä on pieni korjavus", + "minoredit": "Tämä on pieni kohendelu", "watchthis": "Tarkaile tädä sivuu", "savearticle": "Tallenda sivu", "preview": "Ezikačo", "showpreview": "Ezikačo", - "showdiff": "Luajitut korjavukset", + "showdiff": "Luajitut kohendelut", "anoneditwarning": "Varaitus: Et ole kirjutannuhes. Luadinet muutoksii syväindölöih, sinun Ip-adressu tulou nägövih kaikile. Ku [$1 kirjutannuttos] libo [$2 registriiruičettos], sinun syväindömuutokset nävytäh sinun käyttäinimel, toizien eduloin ližäkse.", "summary-preview": "Yhtehvevon ezikačondu:", "subject-preview": "Teeman ezikačondu:", @@ -470,7 +470,7 @@ "template-semiprotected": "(puolekse suojattu)", "hiddencategories": "Tämä sivu kuuluu {{PLURAL:$1|1 peitettyh kategourieh|$1 peitettyh kategourieh}}:", "sectioneditnotsupported-title": "Sektsien kohendustu ei tuveta.", - "sectioneditnotsupported-text": "Sektsieb kohendustu ei tuveta täl sivul.", + "sectioneditnotsupported-text": "Sektsien kohendustu ei tuveta täl sivul.", "permissionserrors": "Ei oigevuksii", "permissionserrorstext": "Sinul ei ole lubua toimindoh {{PLURAL:$1|tämän syyn periä|nämmien syylöin periä}}:", "permissionserrorstext-withaction": "Sinul ei ole lubua toimindoh $2 niškoi, {{PLURAL:$1|tämän syyn periä|nämmien syylöin periä}}:", @@ -647,11 +647,11 @@ "action-move-subpages": "siirrä tämä sivu, da sen alisivut", "action-movefile": "siirrä tämä failu", "enhancedrc-history": "histourii", - "recentchanges": "Uvvet korjavukset", + "recentchanges": "Uvvet kohendelut", "recentchanges-legend": "Tuorehien muutoksien azetukset", "recentchanges-summary": "Jällitä kaikkii jälgimäzet muutokset wikih täl sivul.", "recentchanges-label-newpage": "Tämä korjavus on johtanuh uvven sivun luadimizeh", - "recentchanges-label-minor": "Tämä on pieni korjavus", + "recentchanges-label-minor": "Tämä on pieni kohendelu", "recentchanges-label-bot": "Tämän muutoksen on luadinuh bot", "recentchanges-label-unpatrolled": "Tädä korjuandua vie ei ole tarkistettu", "recentchanges-label-plusminus": "Sivu on kazvanuh [] baital", @@ -684,7 +684,7 @@ "boteditletter": "b", "rc-change-size-new": "Kogo jälles muutoksii: $1 {{PLURAL:$1|baitu|baitua}}", "recentchangeslinked": "Koskijat muutokset", - "recentchangeslinked-toolbox": "Toine toizeh liittyjät korjavukset", + "recentchangeslinked-toolbox": "Toine toizeh liittyjät kohendelut", "recentchangeslinked-title": "Muutokset koskijen sivuu \"$1\"", "recentchangeslinked-summary": "Tämä on nengomien sivuloin korjavuksien luvettelo, kudamih viittuau tämä sivu (libo sih kategourieh kuulujat).Sivut, kudamat kuulutah [[Special:Watchlist|teijän valvonduluvettelo]], ollah bold.", "recentchangeslinked-page": "Sivun nimi:", @@ -800,7 +800,7 @@ "whatlinkshere-hidelinks": "$1 linkat", "whatlinkshere-filters": "Filtrat", "blocklink": "Lukiče", - "contribslink": "korjavukset", + "contribslink": "kohendelut", "movelogpage": "Siirrä loga", "export": "Vie sivut", "allmessages-language": "Kieli:", @@ -817,7 +817,7 @@ "tooltip-pt-logout": "Kirjuttai ullos", "tooltip-pt-createaccount": "Voit registriiruijakseh da kirjuttuakseh järjestelmäh, ga se ei ole vältämätöi", "tooltip-ca-talk": "Pagize piäsivun sizäldös", - "tooltip-ca-edit": "Korjua tädä sivuu", + "tooltip-ca-edit": "Kohendele tädä sivuu", "tooltip-ca-addsection": "Luaji uuzi alalugu", "tooltip-ca-viewsource": "Tämä sivu on suojattu. Voit kaččuo sen lähtehkoudan", "tooltip-ca-history": "Tämän sivun jälgimäzet muutokset", @@ -852,7 +852,7 @@ "tooltip-save": "Tallenda muutokset", "tooltip-preview": "Ezikačo muutokset. Ole hyvä, luaji nenga ainos enne tallendamistu!", "tooltip-diff": "Ozuta sinun luajitut muutokset tekstah", - "tooltip-rollback": "Yhtel painalluksel poistua jälgimäine korjavus", + "tooltip-rollback": "Yhtel painalluksel poistua jälgimäine kohendelu", "tooltip-undo": "\"Kumua\" tuou järilleh aijemban edituičenduversien da avuau edituičenduankietan ezikaččelendutilas. Sen vuoh voi ližätä kumuandumotiivan yhtehvevos.", "tooltip-summary": "Kirjuta lyhyt kuvavus", "simpleantispam-label": "Anti-spam-tarkistus. \nälä täytä!", diff --git a/languages/i18n/pl.json b/languages/i18n/pl.json index a4ae895524..5a61cc9047 100644 --- a/languages/i18n/pl.json +++ b/languages/i18n/pl.json @@ -952,6 +952,7 @@ "showingresultsinrange": "Poniżej wyświetlono co najwyżej {{PLURAL:$1|1 wynik|$1 wyniki|$1 wyników}} w zakresie od $2 do $3.", "search-showingresults": "{{PLURAL:$4|Wynik $1 z $3|Wyniki $1 - $2 z $3}}", "search-nonefound": "Brak wyników spełniających kryteria podane w zapytaniu.", + "search-nonefound-thiswiki": "Brak wyników spełniających kryteria podane w zapytaniu.", "powersearch-legend": "Wyszukiwanie zaawansowane", "powersearch-ns": "Przeszukaj przestrzenie nazw:", "powersearch-togglelabel": "Zaznacz:", @@ -1004,6 +1005,7 @@ "prefs-help-recentchangescount": "Uwzględnia ostatnie zmiany, historię stron i rejestry.", "prefs-help-watchlist-token2": "To jest tajny klucz umożliwiający dostęp do kanału internetowego zmian w obserwowanych przez Ciebie stronach.\nKażdy, kto go zna, będzie mógł je zobaczyć, więc zachowaj go dla siebie.\n[[Special:ResetTokens|Kliknij tu, jeśli musisz go zresetować]].", "savedprefs": "Twoje preferencje zostały zapisane.", + "savedrights": "Zapisano uprawnienia {{GENDER:$1|użytkownika $1|użytkowniczki $1}}.", "timezonelegend": "Strefa czasowa:", "localtime": "Czas lokalny:", "timezoneuseserverdefault": "Użyj domyślnej dla tej wiki ($1)", diff --git a/languages/i18n/ps.json b/languages/i18n/ps.json index f3f39a9ff7..73dea04e0f 100644 --- a/languages/i18n/ps.json +++ b/languages/i18n/ps.json @@ -12,6 +12,7 @@ "tog-hideminor": "په وروستيو بدلونو کې واړه سمونونه پټول", "tog-hidepatrolled": "په وروستيو بدلونونو کې څارل شوې سمونونه پټول", "tog-newpageshidepatrolled": "د نوؤ مخونو په لړليک کې کتل شوي مخونه پټول", + "tog-hidecategorization": "په وېشنيزو کې د مخونو وېشنه پټول", "tog-extendwatchlist": "يوازې د وروستني بدلونونو د ښکاره کولو لپاره نه بلکه د ټولو بدلونونو د ښکاره کولو لپاره کتنلړ غځول", "tog-usenewrc": "په کتنلړ او وروستي بدلونو مخ باندې ډله ايز بدلونونه", "tog-numberheadings": "د سرليکونو خپلکاره شمېرايښودنه", @@ -41,6 +42,7 @@ "tog-watchlisthideliu": "په کتنلړ کې د ثبت شويو کارنانو سمونې پټول", "tog-watchlisthideanons": "په کتنلړ کې د ورکنومو کارنانو سمونې پټول", "tog-watchlisthidepatrolled": "په کتنلړ کې څارل شوې سمونې پټول", + "tog-watchlisthidecategorization": "په وېشنيزو کې د مخونو وېشنه پټول", "tog-ccmeonemails": "هغه برېښليکونه چې زه يې نورو ته لېږم، د هغو يوه کاپي دې ماته هم راشي", "tog-diffonly": "د توپيرونو نه لاندې د مخ مېنځپانگه پټول", "tog-showhiddencats": "پټې وېشنيزې ښکاره کول", @@ -398,8 +400,6 @@ "createaccountreason": "سبب:", "createacct-reason": "سبب", "createacct-reason-ph": "ولې تاسې بل گڼون جوړول غوااړۍ", - "createacct-captcha": "امنيتي تدبير", - "createacct-imgcaptcha-ph": "پورته ښکاره شوی متن دلته وټاپئ", "createacct-submit": "گڼون مو جوړ کړئ", "createacct-another-submit": "گڼون جوړول", "createacct-benefit-heading": "{{SITENAME}} ستاسې په شان خلکو لخوا جوړ شوی.", @@ -517,7 +517,7 @@ "sig_tip": "ستاسې لاسليک د وخت د ټاپې سره", "hr_tip": "څنډيزه ليکه (ددې په کارولو کې سپما وکړۍ)", "summary": "لنډيز:", - "subject": "سکالو/سرليک:", + "subject": "سکالو:", "minoredit": "دا يو وړوکی سمون دی", "watchthis": "همدا مخ کتل", "savearticle": "مخ خوندي کول", @@ -528,7 +528,7 @@ "anonpreviewwarning": "''تاسې غونډال ته نه ياست ننوتي. خوندي کولو سره به ستاسې IP پته به د دې مخ د سمونونو په پېښليک کې ثبت شي.''", "missingcommenttext": "لطفاً تبصره لاندې وليکۍ.", "summary-preview": "د لنډيز مخليدنه:", - "subject-preview": "سکالو/سرليک مخليدنه:", + "subject-preview": "د سکالو مخليدنه:", "previewerrortext": "د بدلونونو د مخليدنو په وخت کې مو يوه ستونزه رامېنځ ته شوه.", "blockedtitle": "پر کارن بنديز لگېدلی", "blockedtext": "'''ستاسې د کارن-نوم يا آی پي پتې مخنيوی شوی.'''\n\nهمدا بنديز د $1 له خوا پر تاسې لږېدلی. او د همدې کړنې سبب ''$2'' دی.\n\n* د بنديز د پېل نېټه: $8\n* د بنديز د پای نېټه: $6\n* بنديزونه دي پر: $7\n\nتاسې کولای شی چې د $1 او يا هم د يو بل [[{{MediaWiki:Grouppage-sysop}}|پازوال]] سره اړيکې ټينگې کړی او د بنديز ستونزې مو هوارې کړی.\nتاسې نه شی کولای چې د 'کارن ته برېښلک لېږل' کړنې نه گټه پورته کړی تر څو چې تاسې د خپل گڼون په [[Special:Preferences|غوره توبونو]] کې يوه کره برېښليک پته نه وي ځانگړې کړې او تر دې بريده چې پر تاسې د هغې د کارولو بنديز نه وي لگېدلی.\nستاسې د دم مهال آی پي پته $3 ده، او ستاسې د بنديز پېژند #$5 دی. مهرباني وکړۍ د خپلې يادونې پر مهال د دغو دوو څخه د يوه او يا هم د دواړو ورکول مه هېروۍ.", @@ -997,6 +997,8 @@ "rcshowhidemine": "زما سمونې $1", "rcshowhidemine-show": "ښکاره کول", "rcshowhidemine-hide": "پټول", + "rcshowhidecategorization-show": "ښکاره کول", + "rcshowhidecategorization-hide": "پټول", "rclinks": "هغه وروستي $1 بدلونونه ښکاره کړی چې په $2 ورځو کې پېښ شوي
    $3", "diff": "توپير", "hist": "پېښليک", @@ -1006,7 +1008,7 @@ "newpageletter": "ن", "boteditletter": "ر", "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|کتونکی کارن|کتونکي کارنان}}]", - "rc_categories": "د وېشنيزو بريدونه (په \"|\" بېلول)", + "rc_categories": "د وېشنيزو تر بريده (په \"|\" بېلول)", "rc_categories_any": "له ټاکل شويو هر يو", "rc-change-size-new": "$1 {{PLURAL:$1|بايټ|بايټونه}} د بدلون وروسته", "newsectionsummary": "/* $1 */ نوې برخه", @@ -1020,6 +1022,11 @@ "recentchangeslinked-summary": "دا د هغه بدلونونو لړليک دی چې وروستۍ ځل په تړن لرونکيو مخونو کې د يوه ځانگړي مخ (او يا هم د يوې ځانگړې وېشنيزې غړو) نه رامېنځ ته شوي.\n[[Special:Watchlist|ستاسې د کتنلړ]] مخونه په '''زغرد ليک''' کې ښکاري.", "recentchangeslinked-page": "د مخ نوم:", "recentchangeslinked-to": "د ورکړل شوي مخ پر ځای د اړونده تړلي مخونو بدلونونه ښکاره کول", + "recentchanges-page-added-to-category": "[[:$1]] وېشنيزې کې ورگډ شو", + "recentchanges-page-added-to-category-bundled": "[[:$1]] او {{PLURAL:$2|يو مخ|$2 مخونه}} وېشنيزې کې ورگډ شول", + "recentchanges-page-removed-from-category": "[[:$1]] له وېشنيزې وغورځول شو", + "recentchanges-page-removed-from-category-bundled": "[[:$1]] او {{PLURAL:$2|يو مخ|$2 مخونه}} له وېشنيزې وغورځول شول", + "autochange-username": "د مېډياويکي خپلکاره بدلون", "upload": "دوتنه پورته کول", "uploadbtn": "دوتنه پورته کول", "reuploaddesc": "پورته کېدنه ناگارل او بېرته د پورته کېدنې فورمې ته ورگرځېدل", @@ -1097,6 +1104,8 @@ "upload-form-label-infoform-description": "څرگندونه", "upload-form-label-usage-title": "کارېدنې", "upload-form-label-usage-filename": "د دوتنې نوم", + "foreign-structured-upload-form-label-infoform-categories": "وېشنيزې", + "foreign-structured-upload-form-label-infoform-date": "نېټه", "backend-fail-notexists": "د $1 په نوم دوتنه نشته.", "backend-fail-delete": "د \"$1\" دوتنه ړنګه نه شوه.", "backend-fail-alreadyexists": "د $1 دوتنه له پخوا نه شته.", @@ -1684,7 +1693,7 @@ "movenotallowedfile": "تاسې د دوتنو د لېږدولو پرېښله نلرۍ.", "cant-move-user-page": "تاسې د کارن مخونو د لېږدولو پرېښله نلرۍ (د څېرمه مخونو نه پرته).", "cant-move-to-user-page": "تاسې د يو کارن مخ ته د يوه بل مخ د لېږدولو پرېښله نلرۍ (د يو کارن د څېرمه مخ نه پرته).", - "newtitle": "نوي سرليک ته:", + "newtitle": "نوی سرليک:", "move-watch": "همدا مخ کتل", "movepagebtn": "مخ لېږدول", "pagemovedsub": "لېږدول په برياليتوب سره ترسره شوه", @@ -2327,6 +2336,8 @@ "htmlform-cloner-create": "نور ورگډول", "htmlform-cloner-delete": "غورځول", "htmlform-cloner-required": "لږ تر لږه يو ارزښت ته اړتيا شته.", + "htmlform-title-not-exists": "[[:$1]] نشته.", + "htmlform-user-not-exists": "$1 نشته.", "logentry-delete-delete": "$1 د $3 مخ {{GENDER:$2|ړنگ کړ}}", "revdelete-content-hid": "مېنځپانگه پټېدلې", "revdelete-uname-hid": "کارن نوم پټ شوی", @@ -2442,6 +2453,6 @@ "special-characters-group-thai": "تايلنډي", "special-characters-group-lao": "لاوي", "special-characters-group-khmer": "خمري", - "mw-widgets-titleinput-description-new-page": "پدې نوم لا تر اوسه پورې مخ نشته", - "mw-widgets-titleinput-description-redirect": "$1 ته ورگرځول" + "mw-widgets-titleinput-description-new-page": "تر اوسه پورې دا مخ نشته", + "mw-widgets-titleinput-description-redirect": "$1 ته ورگرځېدنه" } diff --git a/languages/i18n/ru.json b/languages/i18n/ru.json index d8c2cb196a..183c3cbc3b 100644 --- a/languages/i18n/ru.json +++ b/languages/i18n/ru.json @@ -957,6 +957,7 @@ "showingresultsinrange": "Ниже показано до {{PLURAL:$1|1 результата|$1 результата|$1 результатов}} в диапазоне от $2 до $3.", "search-showingresults": "{{PLURAL:$4|Результат $1 из $3|Результаты $1 — $2 из $3}}", "search-nonefound": "Соответствий запросу не найдено.", + "search-nonefound-thiswiki": "Нет результатов, соответствующих запросу на этом сайте.", "powersearch-legend": "Расширенный поиск", "powersearch-ns": "Поиск в пространствах имён:", "powersearch-togglelabel": "Отметить:", diff --git a/languages/i18n/sah.json b/languages/i18n/sah.json index a9df913e3e..17f1e1b883 100644 --- a/languages/i18n/sah.json +++ b/languages/i18n/sah.json @@ -2278,13 +2278,14 @@ "import-error-special": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ кини угуллубут аатын далыгар саҥа сирэйдэри оҥорор көҥүллэммэт эбит.", "import-error-invalid": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ маннык аат туттуллара бу биикигэ бобуулаах.", "import-error-unserialize": "«$1» сирэй $2 барыла структуураланар (десериализация) кыаҕа суох. Барылга иһинээҕитин модела маннык: $3, маннык серияланар: $4.", + "import-error-bad-location": "$2 уларытыы, $3 мадьыалы туһанар буолан бу биики «$1» сирэйигэр бигэргэтиллэр кыаҕа суох эбит, тоҕо диэтэххэ ити мадьыал бу сирэйгэ өйөнүллүбэт.", "import-options-wrong": "Алҕастаах {{PLURAL:$2|опция|опциялар}}: $1", "import-rootpage-invalid": "Тирэх сирэй ыйыллыбыт аата алҕастаах.", "import-rootpage-nosubpage": "\"$1\" тирэх сирэй аатын далыгар сирэй үөдүҥнэрэ (подстраницалар) көҥүллэммэттэр", "importlogpage": "Импорт сурунаала", "importlogpagetext": "Сирэйдэри историяларын кытта холбуу атын биикилэртэн импортааһын.", - "import-logentry-upload-detail": "$1 {{PLURAL:$1|барыл|барыл баар}}", - "import-logentry-interwiki-detail": "барыта $2 барылтан $1 барыл", + "import-logentry-upload-detail": "$1 {{PLURAL:$1|барыл угулунна|барылы уктубут}}", + "import-logentry-interwiki-detail": "Барыта $2 барылтан $1 барыл угулунна", "javascripttest": "JavaScript тургутуу", "javascripttest-pagetext-noframework": "Бу сирэй JavaScript тургутууларга анаммыт.", "javascripttest-pagetext-unknownframework": "\"$1\" тургутуу биллибэт эйгэтэ.", @@ -2398,6 +2399,7 @@ "pageinfo-watchers": "Кэтээнэр сирэйдэр ахсааннара", "pageinfo-visiting-watchers": "Сирэйи кэтиир уонна тиһэх көннөрүүлэри көрбүт дьон ахсаана", "pageinfo-few-watchers": "$1 кыттааччыттан аҕыйах кэтээччи", + "pageinfo-few-visiting-watchers": "Сирэйи кэтиир уонна тиһэх уларыйыылары көрбүт да, атын да кыттааччылар буолуохтарын сөп", "pageinfo-redirects-name": "Бу сирэйгэ утаарыы ахсаана", "pageinfo-subpages-name": "Сирэй аннынааҕы сирэйдэр ахсааннара", "pageinfo-subpages-value": "$1 ($2 утаарыы; $3 көннөрү (утаарыыта суох))", @@ -2939,6 +2941,7 @@ "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|ырытыы]])", "duplicate-defaultsort": "Болҕой: Наардааһын «$2» күлүүһэ урукку «$1» күлүүһү сабар (Ключ сортировки переопределяет прежний ключ).", "duplicate-displaytitle": "Болҕой: Көрдөрүллүбүт «$2» аат урут көрдөрүллүбүт «$1» ааты уларытар.", + "invalid-indicator-name": "Алҕас:Сирэй туругун көрдөрөр индикатор атрибута name кураанах буолуо суохтаах.", "version": "MediaWiki барыла (биэрсийэтэ)", "version-extensions": "Туруоруллубут расширениялар", "version-skins": "Туруоруллубут тас көстүү барыллара", @@ -3056,6 +3059,8 @@ "tags-create-invalid-chars": "Бэлиэ аатыгар сопутуой (,) эбэтэр слэш (/) буолуохтаах.", "tags-create-invalid-title-chars": "Тиэк аатыгар сирэй баһыгар туттуллуо суохтаах бэлиэ киириэ суохтаах", "tags-create-already-exists": "«$1» тиэк хайыы-үйэ баар эбит.", + "tags-create-warnings-above": "«$1» тиэги оҥорорго маннык {{PLURAL:$2|сэрэтии көһүннэ|сэрэтиилэр көһүннүлэр}}:", + "tags-create-warnings-below": "Тиэги салгыы оҥоруоххун баҕараҕын дуо?", "tags-delete-title": "Тиэги сот", "tags-delete-explanation-initial": "«$1» тиэги билии олоҕуттан сотон эрэҕин.", "tags-delete-reason": "Төрүөтэ:", diff --git a/languages/i18n/sgs.json b/languages/i18n/sgs.json index 160d151491..a72edcf2b2 100644 --- a/languages/i18n/sgs.json +++ b/languages/i18n/sgs.json @@ -275,6 +275,7 @@ "nstab-template": "Šabluons", "nstab-help": "Pagelbuos poslapis", "nstab-category": "Kateguorėjė", + "mainpage-nstab": "Pėrms poslapis", "nosuchaction": "Nier tuokė vēksma", "nosuchspecialpage": "Nier tuokė specēlėjė poslapė", "nospecialpagetext": "Tamsta prašiet nelaistėna specēlė̄jė poslapė, laistėnū specēliūju poslapiu sōraša rasėt [[Special:SpecialPages|specēliūju poslapiu sārošė]].", @@ -767,6 +768,7 @@ "right-read": "Skaitītė poslapius", "right-edit": "Keistė poslapius", "right-upload": "Ikeltė failus", + "right-writeapi": "Nauduotė API rašīmō", "right-delete": "Trintė poslapius", "newuserlogpage": "Nauduotuojė kūrėma sārošos", "rightslog": "Nauduotuoju teisiu istuorėjė", @@ -1284,7 +1286,9 @@ "undelete-show-file-submit": "Tēp", "namespace": "Vardū srėtės:", "invert": "Žīmietė prīšėngā", + "tooltip-invert": "Pažīmiekat ton varnalė, ka pakavuotomiet pakeitėmus pasėrinktūs poslapiūs (ė prėgolontės vardū srėtis)", "namespace_association": "Prėgolontė vardū srėtės", + "tooltip-namespace_association": "Pažīmiekat ton varnalė, ka prėgoldītomat aptarėmus, katrėi ī sosėjė so parinkta vardū srėtim", "blanknamespace": "(Pagrėndinė)", "contributions": "Nauduotuojė duovis", "contributions-title": "Nauduotuojė $1 duovis", @@ -1505,7 +1509,7 @@ "tooltip-t-permalink": "Nūlatėnė nūruoda ton poslapė atmainuō", "tooltip-ca-nstab-main": "Ruodītė poslapė torėni", "tooltip-ca-nstab-user": "Ruodītė nauduotuojė poslapi", - "tooltip-ca-nstab-special": "Tas poslapis īr specēlos - anuo nagalėm keistė.", + "tooltip-ca-nstab-special": "Tas poslapis īr specēlos - anon nagalam keistė.", "tooltip-ca-nstab-project": "Ruodītė pruojekta poslapi", "tooltip-ca-nstab-image": "Ruodītė abruozdielė poslapi", "tooltip-ca-nstab-mediawiki": "Veizietė sėstėmas pranešėma", @@ -1535,6 +1539,7 @@ "spambot_username": "''MediaWiki'' reklamu šalėnėms", "spam_reverting": "Atkoriama i onkstesne versėje, katra nator nūruodu i $1", "spam_blanking": "Vėsos versėjės toriejė nūruodu i $1. Ėšvaluoma", + "simpleantispam-label": "Patikrėnėms nug šiokšlėnėma.\nNapildėkat šėton!", "pageinfo-header-basic": "Poslapė žėnės", "pageinfo-header-edits": "Keitėma istuorėjė", "pageinfo-header-restrictions": "Poslapė apsauga", @@ -1608,6 +1613,7 @@ "exif-make": "Puortaparata dėrbies", "exif-model": "Puortaparata muodelis", "exif-artist": "Autuorios", + "exif-exifversion": "Exif atmains", "exif-colorspace": "Spalvū lauks", "exif-compressedbitsperpixel": "Abruozdielė sospaudėma rėžėms", "exif-pixelydimension": "Abruozdielė platoms", @@ -1757,11 +1763,13 @@ "external_image_whitelist": " #Palikėt ta eilotė, tuokė kāp īr
    \n#Īrašīkat standartėniu ėšraišku fragmentus (tėktās dali terp //)\n#Anūs bus miegėnama sotapatintė so ėšuorėniu abruozdieliu adresās\n#Tė, katrėi sotaps, bus ruodomė kāp abruozdielē, a kėtė bus ruodomė tėktās kāp nūoruodas\n#Raidiu dėdoms nier svarbos\n#Eilotės, katuos prasided # īr kuomentarā \n\n#Īterpkat vėsus standartiėniu ėšraišku fragmentus prīš šėta eilote. Palikat šėta eilote, tuokė kāp ana īr 
    ", "tag-filter": "[[Special:Tags|Žīmiū]] kuošeklis:", "tag-filter-submit": "Kuošeklis", + "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Žīms|Žīmā|Žīmū}}]]: $2)", "tags-edit": "taisītė", "comparepages": "Primestė poslapio", "logentry-delete-delete": "$1 ėštrīnė poslapi $3", "logentry-delete-restore": "$1 atkūrė poslapi $3", "revdelete-content-hid": "torėnīs pakavuots", + "logentry-block-block": "ožgīnė „[[$1]]“ nug dėrbėma, tas vēk ton čiesa - $2 $3", "logentry-move-move": "$1 {{GENDER:$2|parvadėna}} poslapi $3 i $4", "logentry-move-move-noredirect": "$1 {{GENDER:$2|parvadėna}} poslapi nug $3 i $4 nepalinkdoms nusokėma", "logentry-move-move_redir": "$1 {{GENDER:$2|parvadėna}} poslapi nog $3 i $4 ont bovosė nusokėma", diff --git a/languages/i18n/sl.json b/languages/i18n/sl.json index a9336dad81..51d00dcc40 100644 --- a/languages/i18n/sl.json +++ b/languages/i18n/sl.json @@ -885,6 +885,7 @@ "showingresultsinrange": "Spodaj prikazujem {{PLURAL:$1|1=1 rezultat|$1 rezultata|$1 rezultate|$1 rezultatov}} v razponu od št. $2 do št. $3.", "search-showingresults": "{{PLURAL:$4|Rezultat $1 od $3|Rezultati $1–$2 od $3}}", "search-nonefound": "Ni bilo zadetkov, ki ustrezajo poizvedbi.", + "search-nonefound-thiswiki": "Našli nismo nobenega rezultata, ki bi se ujemal s poizvedbo na tej strani.", "powersearch-legend": "Napredno iskanje", "powersearch-ns": "Iskanje v imenskih prostorih:", "powersearch-togglelabel": "Izberi:", diff --git a/languages/i18n/sr-ec.json b/languages/i18n/sr-ec.json index b364e93e87..3c0adf82f6 100644 --- a/languages/i18n/sr-ec.json +++ b/languages/i18n/sr-ec.json @@ -716,7 +716,7 @@ "parser-template-recursion-depth-warning": "Дубина укључивања шаблона је прекорачена ($1)", "language-converter-depth-warning": "Прекорачена је граница дубине језичког претварача ($1)", "node-count-exceeded-category": "Странице у којима је прекорачен број чворова", - "node-count-exceeded-category-desc": "Страница је прекорачила број чворова.", + "node-count-exceeded-category-desc": "Странице са превише чворова (node).", "node-count-exceeded-warning": "Страница у којој је прекорачен број чворова", "expansion-depth-exceeded-category": "Странице у којима је прекорачена дубина проширења", "expansion-depth-exceeded-category-desc": "Страница је прекорачила највећу дубину проширења.", diff --git a/languages/i18n/sv.json b/languages/i18n/sv.json index 1692650726..a16a14126d 100644 --- a/languages/i18n/sv.json +++ b/languages/i18n/sv.json @@ -66,7 +66,8 @@ "Marfuas", "Macofe", "Aaoo", - "Josve05a" + "Josve05a", + "Pipetricker" ] }, "tog-underline": "Stryk under länkar:", @@ -3276,7 +3277,7 @@ "logentry-upload-upload": "$1 {{GENDER:$2|laddade upp}} $3", "logentry-upload-overwrite": "$1 {{GENDER:$2|laddade upp}} en ny version av $3", "logentry-upload-revert": "$1 {{GENDER:$2|laddade upp}} $3", - "log-name-managetags": "Tagghanterings logg", + "log-name-managetags": "Märkeshanteringslogg", "log-description-managetags": "Denna sida innehåller administrativa [[Special:Tags|märke]]srelaterade uppgifter. Loggen innehåller bara åtgärder som utförts manuellt av en administratör; märken kan skapas eller raderas av wikins mjukvara utan att en post registreras i loggen.", "logentry-managetags-create": "$1 {{GENDER:$2|skapade}} taggen \"$4\"", "logentry-managetags-delete": "$1 {{GENDER:$2|raderade}} taggen \"$4\" (borttagen från $5 {{PLURAL:$5|version eller loggpost|versioner och/eller loggposter}})", diff --git a/languages/i18n/tcy.json b/languages/i18n/tcy.json index 73b3b6a316..839e128279 100644 --- a/languages/i18n/tcy.json +++ b/languages/i18n/tcy.json @@ -5,7 +5,10 @@ "VASANTH S.N.", "VinodSBangera", "아라", - "Vishwanatha Badikana" + "Vishwanatha Badikana", + "Bharathesha Alasandemajalu", + "Soundarya shetty s", + "రహ్మానుద్దీన్" ] }, "tog-underline": "ಲಿಂಕ್’ಲೆದ ತಿರ್ತ್ ಗೆರೆ(ಅಂಡರ್ ಲೈನ್) ಪಾಡ್’ಲೆ", @@ -49,8 +52,8 @@ "editfont-sansserif": "ಸಾನ್ಸ್-ಸೆರಿಫ್ ಲಿಪಿ", "editfont-serif": "ಸೆರಿಫ್ ಲಿಪಿ", "sunday": "ಐತಾರ", - "monday": "ಸೋಮವಾರ", - "tuesday": "ಅಂಗರೆ", + "monday": "ಸೋಮಾರೊ", + "tuesday": "ಅಂಗಾರೆ", "wednesday": "ಬುಧವಾರ", "thursday": "ಗುರುವಾರ", "friday": "ಶುಕ್ರವಾರ", @@ -134,12 +137,12 @@ "faqpage": "Project:ಸಾಮಾನ್ಯವಾದ್ ಕೇನುನ ಪ್ರಶ್ನೆಲು", "actions": "ಕ್ರಿಯೆಕ್ಕುಲು", "namespaces": "ಪುದರ್ ದ ವರ್ಗೊಲು", - "variants": "ರೂಪಾಂತರ ಹೊಂದ್‘ನ", - "navigation-heading": "ಸಂಚರಣೆ ಮೆನು", + "variants": "ರೂಪಾಂತರ ಹೊಂದ್‍ನ", + "navigation-heading": "ಸಂಚಾರೊದ ಪರಿವಿಡಿ", "errorpagetitle": "ದೋಷ", "returnto": "$1 ಗ್ ಪಿರ ಪೋಲೆ.", "tagline": "{{SITENAME}} ರ್ದ್", - "help": "ಸಹಾಯ", + "help": "ಸಹಾಯೊ", "search": "ನಾಡ್", "searchbutton": "ನಾಡ್", "go": "ಪೋ", @@ -148,11 +151,11 @@ "history_short": "ಇತಿಹಾಸ", "updatedmarker": "ಎನ್ನ ಅಕೇರಿದ ವೀಕ್ಷಣೆ ಡ್ದ್ ಬುಕ್ಕ ಆಯಿನ ಬದಲಾವಣೆಲು", "printableversion": "ಪ್ರಿಂಟ್ ಆವೃತ್ತಿ", - "permalink": "ಸ್ಥಿರ ಸಂಪರ್ಕ", + "permalink": "ಸ್ತಿರೊ ಸಂಪರ್ಕ", "print": "ಪ್ರಿ೦ಟ್ ಮನ್ಪುಲೆ", "view": "ತೂಲೆ", "view-foreign": "$1ಡ್ ತೂಲೆ", - "edit": "ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ(Edit this page)", + "edit": "ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ(ಪಾಲೆನ್ ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ)", "create": "ಸೃಷ್ಟಿಸಾಲೆ", "create-local": "ಸ್ಥಳೀಯ ವಿವರಣೆ ಸೇರಾಲೆ", "editthispage": "ಈ ಪುಟೊನು ಬದಲಾಯಿಸಾಲೆ", @@ -183,32 +186,33 @@ "viewhelppage": "ಸಹಾಯ ಪುಟೊನು ತೂಲೆ", "categorypage": "ವರ್ಗ ಪುಟೊನು ತೂಲೆ", "viewtalkpage": "ಚರ್ಚೆನ್ ತೂಲೆ", - "otherlanguages": "ಬೇತೆ ಭಾಷೆಲೆಡ್", + "otherlanguages": "ಬೇತೆ ಬಾಸೆಲೆಡ್", "redirectedfrom": "($1 ರ್ದ್ ಪುನರ್ನಿರ್ದೇಶಿತ)", "redirectpagesub": "ಪುನರ್ನಿರ್ದೇಶನ ಪುಟ", - "lastmodifiedat": "ಈ ಪುಟ ಇಂದೆತ ದುಂಬು $2, $1 ಕ್ ಬದಲಾತ್’ನ್ಡ್.", + "redirectto": "ಪಿರ ಕಡಪುಡ್ಲೆ:", + "lastmodifiedat": "ಈ ಪಾಲೆ ಇಂದೆತ ದುಂಬು $2, $1 ಕ್ ಬದಲಾತ್ಂಡ್.", "viewcount": "ಈ ಪುಟೊನು {{PLURAL:$1|1 ಸರಿ|$1 ಸರಿ}} ತೂತೆರ್.", "protectedpage": "ಸಂರಕ್ಷಿತ ಪುಟ", - "jumpto": "ಇಡೆ ಪೋಲೆ:", + "jumpto": "ಇಡೆ ಪೋ:", "jumptonavigation": "ಸಂಚಾರ", "jumptosearch": "ನಾಡ್’ಲೆ", "pool-errorunknown": "ಗೊತ್ತಿಂಜಂದಿನ ದೋಷ", "aboutsite": "{{SITENAME}} ದ ಬಗ್ಗೆ", - "aboutpage": "Project:ನಮ್ಮ ಬಗ್ಗೆ", + "aboutpage": "ಯೋಜನೆ:ಬಗೆಟ್", "copyright": "ಉಂದು ಈ ಕಾಪಿರೈಟ್‌ಡ್ ಲಭ್ಯವುಂಡು $1.", "copyrightpage": "{{ns:project}}:ಕೃತಿಸ್ವಾಮ್ಯತೆಲು", "currentevents": "ಇತ್ತೆದ ಸಂಗತಿಲು", - "currentevents-url": "Project:ಇತ್ತೆದ ಸಂಗತಿಲು", - "disclaimers": "ಅಬಾಧ್ಯತೆಲು", - "disclaimerpage": "Project:ಸಾಮಾನ್ಯ ಅಬಾಧ್ಯತೆಲು", + "currentevents-url": "ಯೋಜನೆ:ಇತ್ತೆದ ಸಂಗತಿಲು", + "disclaimers": "ಹಕ್ಕ್‌ದಕುಲತ್ತ್", + "disclaimerpage": "ಯೋಜನೆ:ಸಾಮಾನ್ಯೊ ಹಕ್ಕುದಕುಲತ್ತ್", "edithelp": "ಸಂಪಾದನೆ(ಎಡಿಟ್) ಮಲ್ಪೆರೆ ಸಹಾಯ", "mainpage": "ಮುಖ್ಯ ಪುಟ", - "mainpage-description": "ಮುಖ್ಯ ಪುಟ", + "mainpage-description": "ಮುಕ್ಯೊ ಪಾಲೆ", "policy-url": "Project:ನಿಯಮಾವಳಿ", - "portal": "ಸಮುದಾಯ ಪುಟ", - "portal-url": "Project:ಸಮುದಾಯ ಪುಟ", + "portal": "ಸಮುದಾಯ ಪಾಲೆ", + "portal-url": "Project:ಸಮುದಾಯ ಪಾಲೆ", "privacy": "ಖಾಸಗಿ ನಿಯಮಾವಳಿ", - "privacypage": "Project:ಖಾಸಗಿಮಾಹಿತಿ ನಿಯಮ", + "privacypage": "ಯೋಜನೆ:ಗುಟ್ಟುದ ನೀತಿ", "badaccess": "ಅನುಮತಿ ದೋಷ", "badaccess-group0": "ಈರ್ ಕೇನಿನ ಬೇಲೆನ್ ಮಲ್ಪೆರೆ ಇರೆಗ್ ಅನುಮತಿ ಇಜ್ಜಿ.", "badaccess-groups": "ಈರ್ ಕೇನಿನಂಚಿನ ಕ್ರಿಯೆ ಖಾಲಿ $1 ಗುಂಪುಲೆಡ್ ಒಂಜೆಕ್ ಸೇರ್ದುಪ್ಪುನ ಬಳಕೆದಾರೆರೆಗ್ ಮಾತ್ರ.", @@ -223,7 +227,7 @@ "viewsourceold": "ಮೂಲೊನು ತೂಲೆ", "editlink": "ಎಡಿಟ್ ಮಲ್ಪುಲೆ", "viewsourcelink": "ಮೂಲೊನು ತೂಲೆ", - "editsectionhint": "$1 ವಿಭಾಗದ ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ", + "editsectionhint": "$1 ವಿಬಾಗದ ಸಂಪಾದಿಸಲೆ", "toc": "ಪರಿವಿಡಿ", "showtoc": "ತೊಜ್ಪಾವು", "hidetoc": "ದೆಂಗಾವು", @@ -244,7 +248,7 @@ "red-link-title": "$1 (ಈ ಪುಟ ನನಲ ಅಸ್ತಿತ್ವಡ್ ಇಜ್ಜಿ)", "sort-descending": "ಇಳಿಕೆ ಕ್ರಮೊಟ್ಟು ಜೋಡಿಸಾಲ", "sort-ascending": "ಏರಿಕೆ ಕ್ರಮೊಟ್ಟು ಜೋಡಿಸಾಲ", - "nstab-main": "ಪುಟ", + "nstab-main": "ಪಾಲೆ", "nstab-user": "ಸದಸ್ಯೆರ್ನ ಪುಟ", "nstab-media": "ಮೀಡಿಯ ಪುಟ", "nstab-special": "ವಿಶೇಷ ಪುಟ", @@ -254,6 +258,7 @@ "nstab-template": "ಫಲಕ", "nstab-help": "ಸಹಾಯ ಪುಟ", "nstab-category": "ವರ್ಗ", + "mainpage-nstab": "ಮುಕ್ಯ ಪಾಲೆ", "nosuchaction": "ಈ ರೀತಿದ ಓವು ಕ್ರಿಯೆಲಾ(ಆಕ್ಶನ್) ಇಜ್ಜಿ", "nosuchactiontext": "ಈ URLದ ಒಟ್ಟಿಗೆ ಉಪ್ಪುನ ಕ್ರಿಯೆನ್ ವಿಕಿ ಗುರ್ತ ಪತ್ತುಜಿ", "nosuchspecialpage": "ಈ ಪುದರ್’ದ ಒವುಲಾ ವಿಷೇಶ ಪುಟ ಇಜ್ಜಿ", @@ -324,6 +329,7 @@ "createaccountmail": "ಇ ಮೈಲ್ ಮೂಲಕ", "createaccountreason": "ಕಾರಣ", "createacct-submit": "ಪೊಸ ಖಾತೆ ಸುರು ಮಲ್ಪುಲೆ", + "createacct-benefit-heading": "{{SITENAME}}ನಿಕ್ಲೆನಂಚಿತ್ತಿನ ಜನೊಕ್ಲೆಡ್ದ್ ಉಂಡಾಪುಂಡು.", "createacct-benefit-body1": "{{PLURAL:$1|ಸಂಪಾದನೆ|ಸಂಪಾದನೆಲು}}", "createacct-benefit-body2": "{{PLURAL:$1|ಪುಟ|ಪುಟಕ್ಕುಲು}}", "createacct-benefit-body3": "{{PLURAL:$1|ಕೊಡುಗೆ|ಕೊಡುಗೆಲು}}", @@ -397,7 +403,7 @@ "preview": "ಮುನ್ನೋಟ", "showpreview": "ಮುನ್ನೋಟ ತೊಜ್ಪಾವ್", "showdiff": "ಬದಲಾವಣೆಲೆನ್ ತೊಜ್ಪಾವ್", - "anoneditwarning": "'''ಜಾಗ್ರತೆ:''' ಈರ್ ಇತ್ತೆ ಲಾಗ್ ಇನ್ ಆತಿಜರ್.\nಈರ್ನ ಐ.ಪಿ ಎಡ್ರೆಸ್ ಈ ಪುಟೊತ ಬದಲಾವಣೆ ಇತಿಹಾಸೊಡು ದಾಖಲಾಪು೦ಡು.", + "anoneditwarning": "ಜಾಗ್‍ರ್ತೆ: ಈರ್ ಇತ್ತೆ ಲಾಗ್ ಇನ್ ಆತಿಜರ್. ಈರ್ ಸಂಪೊಲಿತರ್ಂಡ ಈರೆನ ಐ.ಪಿ ಎಡ್ರೆಸ್ ಮಾಂತೆರೆಗ್ಲಾ ತೆರಿವುಂಡು. ಒಂಜೇಲ್ಯೆ [$1 ಲಾಗಿನ್ ಆಯರ್ಂದಾಂಡ] ಅತ್ತಂಡ [$2 ಈ ಅಕೌಂಟ್ ಮಲ್ತರ್ಂಡ], ಈರ ಸಂಪೊಲ್ತಿನ ಪೂರ ಬೇತೆ ಲಾಬೊದೊಟ್ಟುಗು ಈರೆನ ಪುದರ್‍ಗ್ ಸೇರುಂಡು.'", "anonpreviewwarning": "ಈರ್ ಇತ್ತೆ ಲಾಗ್ ಇನ್ ಆತಿಜರ್. ಈರ್ನ ಐ.ಪಿ ಎಡ್ರೆಸ್ ಈ ಪುಟೊತ ಬದಲಾವಣೆ ಇತಿಹಾಸೊಡು ದಾಖಲಾಪು೦ಡು", "missingsummary": "'''ಗಮನಿಸಾಲೆ:''' ಈರ್ ಬದಲಾವಣೆದ ಸಾರಾ೦ಶನ್ ಕೊರ್ತಿಜರ್.\nಈರ್ ಪಿರ 'ಒರಿಪಾಲೆ' ಬಟನ್ ನ್ ಒತ್ತ್೦ಡ ಸಾರಾ೦ಶ ಇಜ್ಜ೦ದೆನೇ ಈರ್ನ ಬದಲಾವಣೆ ದಾಖಲಾಪು೦ಡು.", "missingcommenttext": "ದಯ ಮಲ್ತ್ ದ ಈರ್ನ ಅಭಿಪ್ರಾಯನ್ ತಿರ್ತ್ ಕೊರ್ಲೆ", @@ -409,7 +415,8 @@ "accmailtitle": "ಪ್ರವೇಶಪದ ಕಡಪುಡ್‘ದುಂಡು", "newarticle": "(ಪೊಸತ್)", "newarticletext": "ನನಲ ಅಸ್ಥಿತ್ವಡ್ ಉಪ್ಪಂದಿನ ಪುಟೊಗು ಈರ್ ಬೈದರ್.\nಈ ಪುಟೊನು ಸೃಷ್ಟಿ ಮಲ್ಪೆರೆ ತಿರ್ತ್’ದ ಚೌಕೊಡು ಬರೆಯೆರೆ ಸುರು ಮಲ್ಪುಲೆ.\n(ಜಾಸ್ತಿ ಮಾಹಿತಿಗ್ [$1 ಸಹಾಯ ಪುಟೊನು] ತೂಲೆ).\nಈ ಪುಟೊಕು ಈರ್ ತಪ್ಪಾದ್ ಬತ್ತಿತ್ತ್’ನ್ಡ ಇರೆನ ಬ್ರೌಸರ್’ದ '''back''' ಬಟನ್’ನ್ ಒತ್ತ್’ಲೆ.", - "noarticletext": "ಈ ಪುಟೊಟು ಸದ್ಯಗ್ ಓ ಬರಹಲಾ ಇಜ್ಜಿ, ಈರ್ ಬೇತೆ ಪೂಟೊಲೆಡ್ [[Special:Search/{{PAGENAME}}|ಈ ಲೇಖನೊನು ನಾಡೊಲಿ]] ಅತ್ತ್’ನ್ಡ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ಈ ಪುಟೊನು ಸಂಪಾದನೆ ಮಲ್ಪೊಲಿ].", + "noarticletext": "ಈ ಪಾಲೆಡ್ ಸದ್ಯಗ್ ಒವ್ವೇ ಬರವುಲಾ ಇಜ್ಜಿ, ಈರ್ ಬೇತೆ ಪಾಲೆಡ್ [[Special:Search/{{PAGENAME}}|ಈ ಲೇಕನೊನು ನಾಡೊಲಿ]] [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ಸಂಬಂಧ ಇತ್ತಿನ ಲಾಗ್ಸ್ ನ್ ನಾಡ್ ಲೆ], ಅತ್ತ್ಂಡ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ಈ ಪಾಲೆನು ಸಂಪೊಲಿಪೊಲಿ].", + "noarticletext-nopermission": "ಈ ಪಾಲೆಡ್ ಸದ್ಯಗ್ ಒವ್ವೇ ಬರವುಲಾ ಇಜ್ಜಿ, ಈರ್ ಬೇತೆ ಪಾಲೆಡ್ [[Special:Search/{{PAGENAME}}|ಈ ಲೇಕನೊನು ನಾಡೊಲಿ]] [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ಸಂಬಂಧ ಇತ್ತಿನ ಲಾಗ್ಸ್ ನ್ ನಾಡ್ ಲೆ], ಅತ್ತ್ಂಡ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ಈ ಪಾಲೆನು ಸಂಪೊಲಿಪೊಲಿ].", "userpage-userdoesnotexist": "ಬಳಕೆದಾರ ಖಾತೆ \"$1\" ದಾಖಲಾತ್‘ಜ್ಜಿ. ಈರ್ ಉಂದುವೇ ಪುಟನ್ ಸಂಪಾದನೆ ಮಲ್ಪರ ಉಂಡಾಂದ್ ಖಾತ್ರಿ ಮಲ್ತೊನಿ.", "previewnote": "'''ಉಂದು ಕೇವಲ ಮುನ್ನೋಟ; ಪುಟೊನು ನನಲ ಒರಿಪಾದಿಜಿ ಪನ್ಪುನೇನ್ ಮರಪೊರ್ಚಿ!'''", "editing": "$1 ಲೇಖನೊನು ಈರ್ ಸಂಪಾದನೆ ಮಲ್ತೊಂದುಲ್ಲರ್", @@ -424,7 +431,7 @@ "template-semiprotected": "(ಅರೆ-ಸಂರಕ್ಷಿತ)", "hiddencategories": "ಈ ಪುಟ {{PLURAL:$1|೧ ಗುಪ್ತ ವರ್ಗಗ್|$1 ಗುಪ್ತ ವರ್ಗೊಲೆಗ್}} ಸೇರ್ದ್’ನ್ಡ್:", "permissionserrorstext-withaction": "$2 ಗ್ ಇರೆಗ್ ಅನುಮತಿ ಇಜ್ಜಿ, ಐಕ್ {{PLURAL:$1|ಕಾರಣ|ಕಾರಣೊಲು}}:", - "moveddeleted-notice": "ಈ ಪೇಜ್ ಅಸ್ತಿತ್ವಡ್ ಇಜ್ಜಿ.\nಪೂಟೊತ ಡಿಲೀಶನ್ ಲಾಗ್’ನ್ ತಿರ್ತ್ ಕೊರ್ತುಂಡು.", + "moveddeleted-notice": "ಈ ಪಾಲೆ ಅಸ್ತಿತ್ವಡ್ ಇಜ್ಜಿ.\nಪಾಲೆದ ಡಿಲೀಶನ್ ಅತ್ತ್ಂಡ್ ಕಡಪ್ಪುಡುನೆ ಲಾಗ್‍ನ್ ತೂಯರೆ ತಿರ್ತ್ ಕೊರ್ತ್ಂಡ್.", "viewpagelogs": "ಈ ಪುಟೊತ ದಾಖಲೆಲೆನ್ ತೂಲೆ", "nohistory": "ಈ ಪುಟಕ್ ಬದಲಾವಣೆದ ಇತಿಹಾಸ ಇಜ್ಜಿ", "currentrev": "ಇತ್ತೆದ ಆವೃತ್ತಿ", @@ -466,16 +473,20 @@ "mergehistory-reason": "ಕಾರಣ:", "revertmerge": "ಅನ್-ಮರ್ಜ್ ಮಲ್ಪುಲೆ", "history-title": "\"$1\" ಪುಟೊತ ಆವೃತ್ತಿ ಇತಿಹಾಸೊ", + "difference-title": "ಪುನರ್ ಪರಿಸೀಲನೆದ ನಡುತ ಯತ್ವಾಸೊ \"$1\"", "lineno": "$1 ನೇ ಸಾಲ್:", "compareselectedversions": "ಆಯ್ಕೆ ಮಲ್ತಿನ ಆವೃತ್ತಿಲೆನ್ ಹೊಂದಾಣಿಕೆ ಮಲ್ತ್ ತೂಲೆ", "editundo": "ದುಂಬುದಲೆಕ", + "diff-multi-sameuser": "({{PLURAL:$1|One intermediate revision|$1 intermediate revisions }} ಅವ್ವೇ ಬಳಕೆದಾರೆರೆನ್ ತೋಜಾದ್‍ಜಿ)", "searchresults": "ನಾಡಟದ ಫಲಿತಾಂಶೊಲು", "searchresults-title": "\"$1\" ಕ್ ನಾಡಟದ ಫಲಿತಾಂಶೊಲು", "notextmatches": "ವಾ ಪುಟೊತ ಪಠ್ಯೊಡುಲಾ ಹೋಲಿಕೆ ಇಜ್ಜಿ", "prevn": "ದುಂಬುದ {{PLURAL:$1|$1}}", "nextn": "ಬೊಕ್ಕದ {{PLURAL:$1|$1}}", + "nextn-title": "ದುಂಬುದ $1 {{PLURAL:$1|result|ಪಲಿತಾಂಸೊಲ}}", "shown-title": "ಪ್ರತಿ ಪುಟೊಡುಲಾ $1 {{PLURAL:$1|result|results}} ತೋಜಿಪಾವು", "viewprevnext": "ತೂಲೆ ($1 {{int:pipe-separator}} $2) ($3)", + "searchmenu-new": "ಈ ಪಾಲೆನ್ ರಚಿಸಲೆ \"[[:$1]]\" ಈ ವಿಕಿಡ್! {{PLURAL:$2|0=|See also the page found with your search.|See also the search results found.}}", "searchprofile-articles": "ಲೇಖನ ಪುಟೊ", "searchprofile-images": "ಬಹುಮಾಧ್ಯಮ", "searchprofile-everything": "ಪ್ರತಿಯೊಂಜಿ", @@ -483,6 +494,7 @@ "searchprofile-articles-tooltip": "$1 ಟ್ ನಾಡ್ಲೆ", "searchprofile-images-tooltip": "ಫೈಲ್ ನಾಡ್ಲೆ", "searchprofile-everything-tooltip": "ಮಾತಾ ಪುಟಕ್ಕುಲೆಡ್ ನಾಡ್ಲೆ (ಪಾತೆರದ ಪುತಲ ಸೇರ್ದ್)", + "searchprofile-advanced-tooltip": "ಪುದರ್‍ದ ಕ್ರಮೊನು ನಾಡ್‍ಲೆ", "search-result-size": "$1 ({{PLURAL:$2|೧ ಪದ|$2 ಪದೊಲು}})", "search-redirect": "(ಪುನರ್ನಿರ್ದೇಶನ $1)", "search-section": "(ವಿಭಾಗ $1)", @@ -492,7 +504,9 @@ "search-interwiki-more": "(ಮಸ್ತ್)", "searchrelated": "ಸ೦ಬ೦ಧ ಇತ್ತಿನ", "searchall": "ಮಾತಾ", + "search-showingresults": "{{PLURAL:$4|ಫಲಿತಾಂಶ$1 of $3|ಫಲಿತಾಂಶ $1 - $2 of $3}}", "search-nonefound": "ಈರೆನ ವಿಚಾರಣೆಗ್ ತಕ್ಕುದಾಯಿನ ಪಲಿತಾಂಶೊಲು ಇಜ್ಜಿ.", + "search-nonefound-thiswiki": "ಈ ಸೈಟ್‍ಡ್ ಪ್ರಶ್ನೆದ ಫಲಿತಾಂಶ ಕೂಡೊಂದಿಜ್ಜಿ", "powersearch-legend": "ಅಡ್ವಾನ್ಸ್’ಡ್ ಸರ್ಚ್", "powersearch-ns": "ನೇಮ್-ಸ್ಪೇಸ್’ಲೆಡ್ ನಾಡ್ಲೆ", "powersearch-toggleall": "ಮಾತಾ", @@ -530,6 +544,7 @@ "grouppage-sysop": "{{ns:project}}:ನಿರ್ವಾಹಕೆರ್", "right-read": "ಪುಟಕ್‍ಲೆನ್ ಓದುಲೆ", "right-edit": "ಪುಟೊನ್ ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ", + "right-writeapi": "ಬರೆಯಿನ ಎಪಿಐದ ಬಳಕೆ", "right-delete": "ಪುಟೊಕುಲೆನ್ ಮಾಜಾಲೆ", "right-undelete": "ಪುಟೊನ್ ಮಾಜಾವಡೆ", "newuserlogpage": "ಸದಸ್ಯ ರಚನೆ ಲಾಗ್", @@ -556,6 +571,9 @@ "recentchanges-label-minor": "ಉಂದು ಎಲ್ಯ ಬದಲಾವಣೆ", "recentchanges-label-bot": "ಈ ಸಂಪಾದನೆನ್ ಒಂಜಿ ಬಾಟ್ ಮಲ್ತ್‍ದುಂಡು", "recentchanges-label-unpatrolled": "ಈ ಸಂಪಾದನೆನ್ ನನಲಾ ಪರೀಕ್ಷೆ ಮಲ್ತ್‍ದಿಜ್ಜಿ.", + "recentchanges-label-plusminus": "ಬೈಟ್ಸ್‌ದ ಲೆಕ್ಕೊಡು ಈ ಪಾಲೆದ ಗಾತ್ರೊ ಬದಲಾತ್ಂಡ್", + "recentchanges-legend-heading": "'''ಟಿಪ್ಪಣಿ:'''", + "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ಬೊಕ್ಕೊಲಾ ತೂಲೆ [[Special:NewPages|ಪೊಸ ಪಾಲೆದ ಪಟ್ಟಿ]])", "rclistfrom": "$3 $2 ರ್ದ್ ಶುರುವಾತಿನ ಪೊಸ ಬದಲಾವಣೆಲೆನ್ ತೊಜ್ಪಾವು", "rcshowhideminor": "$1 ಎಲ್ಯೆಲ್ಯ ಬದಲಾವಣೆಲು", "rcshowhideminor-show": "ತೋಜಾಲೆ", @@ -581,13 +599,13 @@ "newpageletter": "ಪೊ", "boteditletter": "ಬಾ", "rc_categories_any": "ಒವ್ವೇ", - "rc-change-size-new": "$1 {{ಬಹುವಚನೊ:$1|ಬೈಟ್|ಬೈಟ್‍ಲು}}ಬದಲಾವಣೆದ ಬುಕ್ಕೊ", + "rc-change-size-new": "$1 {{PLURAL:$1|ಬೈಟ್|ಬೈಟ್‍ಲು}}ಬದಲಾವಣೆದ ಬುಕ್ಕೊ", "newsectionsummary": "\n/* $1 */ಪೊಸ ವಿಭಾಗ", "rc-enhanced-expand": "ವಿವರೊಲೆನ್ ತೊಜ್ಪಾವು (ಜಾವ ಸ್ಕ್ರಿಪ್ಟ್ ಬೋಡಾಪುಂಡು)", "rc-enhanced-hide": "ವಿವರೊಲೆನ್ ದೆಂಗಾವು", "recentchangeslinked": "ಸಂಬಂಧ ಉಪ್ಪುನಂಚಿನ ಬದಲಾವಣೆಲು", "recentchangeslinked-feed": "ಸಂಬಂಧ ಉಪ್ಪುನಂಚಿನ ಬದಲಾವಣೆಲು", - "recentchangeslinked-toolbox": "ಸಂಬಂಧ ಉಪ್ಪುನಂಚಿನ ಬದಲಾವಣೆಲು", + "recentchangeslinked-toolbox": "ಸಂಬಂದೊ ಉಪ್ಪುನಂಚಿನ ಬದಲಾವಣೆಲು", "recentchangeslinked-title": "\"$1\" ಪುಟೊಟು ಆತಿನ ಬದಲಾವಣೆಲು", "recentchangeslinked-summary": "ಒಂಜಿ ನಿರ್ದಿಷ್ಟ ಪುಟೊರ್ದು (ಅತ್ತ್’ನ್ಡ ನಿರ್ದಿಷ್ಟ ವರ್ಗೊಗು ಸೇರ್ದಿನ ಪುಟೊಲೆರ್ದ್) ಸಂಪರ್ಕ ಉಪ್ಪುನ ಪುಟೊಲೆಡ್ ಇಂಚಿಪ ಮಲ್ತಿನಂಚಿನ ಬದಲಾವಣೆಲೆನ್ ತಿರ್ತ್ ಪಟ್ಟಿ ಮಲ್ಪೆರಾತ್’ನ್ಡ್.\n[[Special:Watchlist|ಇರೆನ ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್]] ಉಪ್ಪುನ ಪುಟೊಲು '''ದಪ್ಪ ಅಕ್ಷರೊಡು''' ಉಂಡು.", "recentchangeslinked-page": "ಪುಟೊತ ಪುದರ್:", @@ -620,10 +638,11 @@ "linkstoimage": "ಈ ಫೈಲ್’ಗ್ ತಿರ್ತ್’ದ ಈ {{PLURAL:$1|ಪುಟ|$1 ಪುಟೊಲು}} ಲಿಂಕ್ ಕೊರ್ಪುಂಡು.", "nolinkstoimage": "ಈ ಫೈಲ್‍ಗ್ ಸಂಪರ್ಕ ಉಪ್ಪುನ ವಾ ಪುಟಲಾ ಇಜ್ಜಿ.", "sharedupload": "ಈ ಫೈಲ್’ನ್ ಮಸ್ತ್ ಜನ ಪಟ್ಟ್’ದುಲ್ಲೆರ್ ಅಂಚೆನೆ ಉಂದು ಮಸ್ತ್ ಪ್ರೊಜೆಕ್ಟ್’ಲೆಡ್ ಉಪಯೋಗಡುಪ್ಪು.", + "sharedupload-desc-here": "ಈ ಪಾಲೆ $1ಡ್ದ್ ಬೊಕ್ಕ ಬೇತೆ ಯೋಜನೆಡ್ದ್ ಗಳಸೊಲಿ.\nಈ ಪಾಲೆದ ವಿವರೊ [$2 ಪಾಲೆದ ವಿವರೊ] ತಿರ್ತ ಸಾಲ್‍ಡ್ ತೋಜಾದ್ಂಡ್", "upload-disallowed-here": "ಈರ್ ಈ ಕಡತನ್ ಕುಡ ಬರೆವರೆ ಸಾದ್ಯ ಇಜ್ಜಿ.", "filedelete-comment": "ಕಾರಣ", "filedelete-submit": "ಮಾಜಾಲೆ", - "randompage": "ಯಾದೃಚ್ಛಿಕ ಪುಟ", + "randompage": "ಯಾದೃಚ್ಛಿಕ ಪಾಲೆ", "statistics": "ಅಂಕಿ ಅಂಶೊಲು", "statistics-header-pages": "ಪುಟೊತ ಅಂಕಿ ಅಂಶಲು", "nbytes": "$1 {{PLURAL:$1|ಬೈಟ್|ಬೈಟ್‍ಲು}}", @@ -660,6 +679,7 @@ "actioncomplete": "ಕಾರ್ಯ ಸಂಪೂರ್ಣ", "dellogpage": "ಡಿಲೀಟ್ ಮಲ್ತಿನ ಫೈಲ್’ಲೆದ ದಾಖಲೆ", "rollbacklink": "ಪಿರ ಪೋಲೆ", + "rollbacklinkcount": "ಪಿರ ದೆತೊನ್ಲೆ $1 {{PLURAL:$1|edit|ಸಂಪಾದನೆಲು}}", "protectlogpage": "ಸಂರಕ್ಷಣೆ ದಿನಚರಿ", "protectedarticle": "\"[[$1]]\" ಸಂರಕ್ಷಿತವಾದುಂಡು.", "modifiedarticleprotection": "\"[[$1]]\" ಪುಟೊತ ಸಂರಕ್ಷಣೆ ಮಟ್ಟ ಬದಲಾಂಡ್", @@ -667,7 +687,10 @@ "undeleteviewlink": "ತೂಲೆ", "namespace": "ನೇಮ್-ಸ್ಪೇಸ್:", "invert": "ಆಯ್ಕೆನ್ ತಿರ್ಗಾಲೆ", - "blanknamespace": "(ಮುಖ್ಯ)", + "tooltip-invert": "ಈ ಚೌಕೊದುಲಯಿಡ್ ಅಡೆಂಗಿನ ಪುದರ್‍ನ್ ಈ ಚೌಕೊಡೆ ಪರೀಕ್ಷಿಪುಲೆ(ಬೊಕ್ಕೊ ಒಟ್ಟುಗಿತ್ತಿನ ಪುದರ್‍ನ್‍ಲಾ ಪರೀಕ್ಷಿಪೊಲಿ)", + "namespace_association": "ಜತೆಟಿತ್ತಿನ ಪುದರ್", + "tooltip-namespace_association": "ಈ ಚೌಕೊನು ಚರ್ಚೆನ್ ಸೇರಾದ್ ಪರೀಕ್ಷಿಪುಲೆ ಅತ್ತ್ಂಡ ವಿಸಯೊಗು ಸರಿಯಾಯಿನ ಪುದರ್ದ ಜತೆಟ್ ಸೇರಾಲೆ", + "blanknamespace": "(ಮುಕ್ಯೊ)", "contributions": "{{$1ಸದಸ್ಯೆರ್ನ}} ಕಾಣಿಕೆಲು", "contributions-title": "$1 ಗ್ ಸದಸ್ಯೆರ್ನ ಕಾಣಿಕೆ", "mycontris": "ಎನ್ನ ಕಾಣಿಕೆಲು", @@ -682,7 +705,7 @@ "sp-contributions-search": "ಕಾಣಿಕೆಲೆನ್ ನಾಡ್ಲೆ", "sp-contributions-username": "ಐ.ಪಿ ವಿಳಾಸ ಅತ್ತ್’ನ್ಡ ಬಳಕೆದ ಪುದರ್:", "sp-contributions-submit": "ನಾಡ್", - "whatlinkshere": "ಇಡೆ ವಾ ಪುಟೊಲು ಲಿಂಕ್ ಕೊರ್ಪುಂಡು", + "whatlinkshere": "ಇಡೆ ವಾ ಪಾಲೆ ಲಿಂಕ್ ಕೊರ್ಪುಂಡು", "whatlinkshere-title": "\"$1\" ಪುಟೊಗು ಲಿಂಕ್ ಕೊರ್ಪುನ ಪುಟೊಲು", "whatlinkshere-page": "ಪುಟ:", "linkshere": "'''[[:$1]]'''ಗ್ ಈ ತಿರ್ತ್’ದ ಪುಟೊಲು ಲಿಂಕ್ ಕೊರ್ಪುಂಡು.", @@ -723,10 +746,10 @@ "tooltip-pt-logout": "ಲಾಗ್ ಔಟ್", "tooltip-pt-createaccount": "ನಿಕುಲು ಪೊಸ ಖಾತೆ ಸುರುಮಲ್ತ್‍ದ್ ಲಾಗಿನ್ ಆಪುನೈನ್ ಸ್ವಾಗತ ಮಲ್ಪುವೊ, ಆಂಡಲಾ ಉಂದು ಕಡ್ಡಾಯ ಅತ್ತ್.", "tooltip-ca-talk": "ಮಾಹಿತಿ ಪುಟೊತ ಬಗ್ಗೆ ಚರ್ಚೆ", - "tooltip-ca-edit": "ಈ ಪುಟೊನು ಈರ್ ಸಂಪಾದನೆ ಮಲ್ಪೊಲಿ. ಸೇವ್ ಮಲ್ಪುನ ದುಂಬು ಮುನ್ನೋಟದ ಉಪಯೊಗ ಮನ್ತೊನ್ಲೆ.", + "tooltip-ca-edit": "ಈ ಪಾಲೆನ್ ಸಂಪೊಲಿಪುಲೆ", "tooltip-ca-addsection": "ಪೊಸ ಸೆಶನ್ನ್ ಶರು ಮಲ್ಪುಲೆ", "tooltip-ca-viewsource": "ಉಂದೊಂಜಿ ಸಂರಕ್ಷಿತ ಪುಟ.\nಇಂದೆತ ಮೂಲೊನು ಈರ್ ತೂವೊಲಿ.", - "tooltip-ca-history": "ಈ ಪುಟೊತ ಪರತ್ತ್ ಆವೃತ್ತಿಲು", + "tooltip-ca-history": "ಈ ಪಾಲೆದ ಪರತ್ತ್ ಆವೃತ್ತಿಲು", "tooltip-ca-protect": "ಈ ಪುಟೊನು ಸಂರಕ್ಷಣೆ ಮಲ್ಪುಲೆ", "tooltip-ca-delete": "ಈ ಪುಟೊನು ಡಿಲೀಟ್ ಮಲ್ಪುಲೆ", "tooltip-ca-move": "ಈ ಪೂಟೊನು ಮೂವ್(ಸ್ಥಳಾಂತರ) ಮಲ್ಪುಲೆ", @@ -737,25 +760,25 @@ "tooltip-search-fulltext": "ಈ ಪಠ್ಯ ಉಪ್ಪುನಂಚಿನ ಪುಟೊಲೆನ್ ನಾಡ್’ಲ", "tooltip-p-logo": "ಮುಖ್ಯ ಪುಟೊನು ತೂಲೆ", "tooltip-n-mainpage": "ಮುಖ್ಯ ಪುಟೊನು ತೂಲೆ", - "tooltip-n-mainpage-description": "ಮುಖ್ಯ ಪುಟೊನು ತೂಲೆ", + "tooltip-n-mainpage-description": "ಮುಕ್ಯೊ ಪಾಲೆನ್ ತೂಲೆ", "tooltip-n-portal": "ಪ್ರೊಜೆಕ್ಟ್’ದ ಬಗ್ಗೆ, ಈರ್ ದಾದ ಮಲ್ಪೊಲಿ, ಓಲು ಇಂದೆತ ಬಗ್ಗೆ ತೆರಿಯೊನೊಲಿ", - "tooltip-n-currentevents": "ಪ್ರಸಕ್ತ ಘಟನೆಲ್ದ ಬಗ್ಗೆ ಹಿನ್ನೆಲೆ ಮಾಹಿತಿ ತೆರಿಯೊನ್ಲೆ", + "tooltip-n-currentevents": "ಇತ್ತೆದ ಘಟನೆಲೆ ಬಗೆತ ಹಿನ್ನೆಲೆ ಮಾಹಿತಿ ತೆರಿಯೊನ್ಲೆ", "tooltip-n-recentchanges": "ವಿಕಿಡ್ ದುಂಬುದ ಒಂತೆ ಸಮಯಡ್ ಆತಿನಂಚಿನ ಬದಲಾವಣೆಲ್ದ ಪಟ್ಟಿ", - "tooltip-n-randompage": "ಯಾದೃಚ್ಛಿಕ ಪುಟವೊಂಜೇನ್ ತೊಜ್ಪಾವ್", - "tooltip-n-help": "ತೆರಿತೊನೆರೆ ಜಾಗ", - "tooltip-t-whatlinkshere": "ಇಡೆ ಲಿಂಕ್ ಕೊರ್ಪುನಂಚಿನ ಪೂರ ವಿಕಿ ಪುಟೊಲ್ದ ಪಟ್ಟಿ", - "tooltip-t-recentchangeslinked": "ಈ ಪುಟೊರ್ದು ಸಂಪರ್ಕ ಉಪ್ಪುನಂಚಿನ ಪುಟೊಲೆಡ್ ಇಂಚಿಪದ ಬದಲಾವಣೆಲು", + "tooltip-n-randompage": "ಯಾದೃಚ್ಛಿಕ ಪಾಲೆ ಒಂಜೇನ್ ತೊಜ್ಪಾವ್", + "tooltip-n-help": "ಜಾಗೆ ನಾಡ್ದ್ ಪತ್ತೆರೆ", + "tooltip-t-whatlinkshere": "ಇಡೆ ಲಿಂಕ್ ಕೊರ್ಪುನಂಚಿನ ಪೂರ ವಿಕಿ ಪಾಲೆಲೆನ ಪಟ್ಟಿ", + "tooltip-t-recentchangeslinked": "ಈ ಪಾಲೆಡ್ದ್ ಸಂಪರ್ಕ ಉಪ್ಪುನಂಚಿನ ಪಾಲೆಡ್ ಇಂಚಿಪದ ಬದಲಾವಣೆಲು", "tooltip-feed-rss": "ಈ ಪುಟೊಗು ಆರ್.ಎಸ್.ಎಸ್ ಫೀಡ್", "tooltip-feed-atom": "ಈ ಪುಟೊಗು Atom ಫೀಡ್", "tooltip-t-contributions": "ಈ ಸದಸ್ಯೆರ್ನ ಕಾಣಿಕೆಲ್ದ ಪಟ್ಟಿನ್ ತೊಜ್ಪಾವು", "tooltip-t-emailuser": "ಈ ಸದಸ್ಯೆರೆಗ್ ಇ-ಮೇಲ್ ಕಡಪುಡ್ಲೆ", "tooltip-t-upload": "ಫೈಲ್’ನ್ ಅಪ್ಲೋಡ್ ಮಲ್ಪುಲೆ", - "tooltip-t-specialpages": "ಪೂರ ವಿಷೇಶ ಪುಟೊಲ್ದ ಪಟ್ಟಿ", - "tooltip-t-print": "ಈ ಪುಟೊತ ಪ್ರಿಂಟ್ ಆವೃತ್ತಿ", - "tooltip-t-permalink": "ಪುಟೊತ ಈ ಆವೃತ್ತಿಗ್ ಶಾಶ್ವತ ಲಿಂಕ್", - "tooltip-ca-nstab-main": "ಮಾಹಿತಿ ಪುಟೊನು ತೂಲೆ", + "tooltip-t-specialpages": "ಪೂರ ಪಾಲೆಲೆನ ವಿಸೇಸೊ ಪಟ್ಟಿ", + "tooltip-t-print": "ಈ ಪಾಲೆದ ಪ್ರಿಂಟ್ ಆವೃತ್ತಿ", + "tooltip-t-permalink": "ಪಾಲೆದ ಈ ಆವೃತ್ತಿಗ್ ಸಾಸ್ವತ ಲಿಂಕ್", + "tooltip-ca-nstab-main": "ಮಾಹಿತಿ ಪಾಲೆನ್ ತೂಲೆ", "tooltip-ca-nstab-user": "ಸದಸ್ಯೆರ್ನ ಪುಟೊನು ತೂಲೆ", - "tooltip-ca-nstab-special": "ಉಂದೊಂಜಿ ವಿಶೇಷ ಪುಟ, ಇಂದೆನ್ ಈರ್ ಎಡಿಟ್ ಮಲ್ಪೆರೆ ಆಪುಜಿ", + "tooltip-ca-nstab-special": "ಉಂದೊಂಜಿ ಇಸೇಸ ಪಾಲೆ, ಇಂದೆನ್ ಈರ್ ಸಂಪೊಲಿಪೆರೆ ಆಪುಜಿ", "tooltip-ca-nstab-project": "ಪ್ರೊಜೆಕ್ಟ್ ಪುಟೊನು ತೂಲೆ", "tooltip-ca-nstab-image": "ಫೈಲ್’ದ ಪುಟೊನು ತೂಲೆ", "tooltip-ca-nstab-template": "ಟೆಂಪ್ಲೇಟ್’ನ್ ತೂಲೆ", @@ -771,13 +794,16 @@ "tooltip-rollback": "\"Rollback\", ಈ ಪುಟದ ಕರಿನ ಬದಾಲವಣೆಗ್ ಒ೦ಜಿ ಕ್ಲಿಕ್ ಡ್ ಕೊನೊಪು೦ಡು", "tooltip-undo": "\"Undo\" ಈ ಬದಲಾವಣೆನ್ ದೆತೊನುಜಿ ಬುಕ ಪ್ರಿವ್ಯೂ ಮೋಡ್ ಡ್ ಬದಲಾವಣೆ ಮಲ್ಪೆರ್ ಕೊನೊಪು೦ಡು. ಅ೦ಚೆನೆ ಸಮ್ಮರಿ ಡ್ ಬದಲಾವಣೆ ಗ್ ಕಾರಣ ಕೊರ್ರ್‍ಎ ಆಪು೦ಡು.", "tooltip-summary": "ಒಂಜಿ ಎಲ್ಯ ಸಾರಾಂಶ ಕೊರ್ಲೆ", - "pageinfo-toolboxlink": "ಪುಟೊತ ಮಾಹಿತಿ", + "simpleantispam-label": "ಯಾಂಟಿ-ಸ್ಪಾಮ್ ಚೆಕ್.\nಮುಲ್ಪ ದಿಂಜಾವೊಡ್ಚಿ", + "pageinfo-toolboxlink": "ಪಾಲೆದ ಮಾಹಿತಿ", "previousdiff": "← ದುಂಬುದ ಸಂಪಾದನೆ", "nextdiff": "ಪೊಸ ಎಡಿಟ್ →", "file-info-size": "$1 × $2 ಪಿಕ್ಸೆಲ್, ಫೈಲ್’ದ ಗಾತ್ರ: $3, MIME ಪ್ರಕಾರ: $4", "file-nohires": "ಇಂದೆರ್ದ್ ಜಾಸ್ತಿ ವಿವರವಾಯಿನ ನೋಟ ಇಜ್ಜಿ.", "svg-long-desc": "ಎಸ್.ವಿ.ಜಿ ಫೈಲ್, ಸುಮಾರಾದ್ $1 × $2 ಪಿಕ್ಸೆಲ್, ಫೈಲ್’ದ ಗಾತ್ರ: $3", - "show-big-image": "ಮೂಲ ಕಡತ", + "show-big-image": "ನಿಜಾವಾಯಿನ ಕಡತ", + "show-big-image-preview": "ಪಿರವುದ ಪಾಲೆದ ಗಾತ್ರೊ: $1.", + "show-big-image-other": "ಬೇತೆ{{PLURAL:$2|resolution|ನಿರ್ನಯೊಲು}}: $1.", "show-big-image-size": "$1 × $2 ಪಿಕ್ಸೆಲ್‌ಸ್", "bad_image_list": "ವ್ಯವಸ್ಥೆದ ಆಕಾರ ಈ ರೀತಿ ಉಂಡು:\n\nಪಟ್ಟಿಡುಪ್ಪುನಂಚಿನ ದಾಖಲೆಲೆನ್ (* ರ್ದ್ ಶುರು ಆಪುನ ಸಾಲ್’ಲು) ಮಾತ್ರ ಪರಿಗಣನೆಗ್ ದೆತೊನೆರಾಪುಂಡು.\nಪ್ರತಿ ಸಾಲ್’ದ ಶುರುತ ಲಿಂಕ್ ಒಂಜಿ ದೋಷ ಉಪ್ಪುನಂಚಿನ ಫೈಲ್’ಗ್ ಲಿಂಕಾದುಪ್ಪೊಡು.\nಅವ್ವೇ ಸಾಲ್’ದ ಶುರುತ ಪೂರಾ ಲಿಂಕ್’ಲೆನ್ ಪರಿಗನೆರ್ದ್ ದೆಪ್ಪೆರಾಪುಂಡು, ಪಂಡ ಓವು ಪುಟೊಲೆಡ್ ಫೈಲ್’ದ ಬಗ್ಗೆ ಬರ್ಪುಂಡೋ ಔಲು.", "metadata": "ಮೇಲ್ದರ್ಜೆ ಮಾಹಿತಿ", @@ -785,11 +811,15 @@ "metadata-expand": "ವಿಸ್ತಾರವಾಯಿನ ವಿವರೊಲೆನ್ ತೊಜ್ಪಾವು", "metadata-collapse": "ವಿಸ್ತಾರವಾಯಿನ ವಿವರೊಲೆನ್ ದೆಂಗಾವು", "metadata-fields": "ಈ ಸಂದೇಶೊಡು ಪಟ್ಟಿ ಮಲ್ತಿನಂಚಿನ EXIF ಮಿತ್ತ ದರ್ಜೆದ ಮಾಹಿತಿನ್ ಚಿತ್ರ ಪುಟೊಕು ಸೇರ್ಪಾಯೆರೆ ಆವೊಂದುಂಡು. ಪುಟೊಟು ಮಿತ್ತ ದರ್ಜೆ ಮಾಹಿತಿದ ಪಟ್ಟಿನ್ ದೆಪ್ಪುನಗ ಉಂದು ತೋಜುಂಡು.\nಒರಿದನವು ಮೂಲಸ್ಥಿತಿಟ್ ಅಗೋಚರವಾದುಪ್ಪುಂಡು.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude \n* gpsaltitude", + "exif-orientation": "ದಿಕ್ಕ್ ದಿಸೆ", + "exif-xresolution": "ಅಡ್ಡಗಲದ ರೆಜ಼ಲ್ಯೂಶನ್", + "exif-yresolution": "ಉದ್ದೊದ ರೆಜ಼ಲ್ಯೂಶನ್", "exif-datetime": "ಕಡೊತೊನು ಬದಲಾವಣೆ ಮಲ್ತ್‍ನ ದಿನಾಂಕೊ ಬೊಕ್ಕ ಸಮಯೊ", "exif-make": "ಕ್ಯಾಮೆರಾದ ತಯಾರೆಕೆರ್", "exif-model": "ಕ್ಯಾಮೆರಾ ಮಾದರಿ", "exif-software": "ಉಪಯೋಗ ಮಲ್ತಿನ ತಂತ್ರಾಂಶ", "exif-exifversion": "Exif ಆವೃತ್ತಿ", + "exif-colorspace": "ಬಣ್ಣದ ಜಾಗೆ", "exif-datetimeoriginal": "ಮಾಹಿತಿ ಸೃಷ್ಟಿಯಾಯಿನ ದಿನಾಂಕೊ ಬೊಕ್ಕ ಸಮಯ", "exif-datetimedigitized": "ಗಣಕೀಕರಣದ ದಿನಾಂಕೊ ಬೊಕ್ಕ ಸಮಯೊ", "exif-orientation-1": "ಸಾಧಾರಣ", @@ -797,9 +827,12 @@ "monthsall": "ಪೂರಾ", "watchlisttools-view": "ಪ್ರಸ್ತುತ ಬದಲಾವಣೆಲ್ ತೋಜಾಲೆ", "watchlisttools-edit": "ವೀಕ್ಷಣಾಪಟ್ಟಿನ್ ತೂಲೆ ಬೊಕ್ಕ ಎಡಿಟ್ ಮಲ್ಪುಲೆ", - "specialpages": "ವಿಷೇಶ ಪುಟೊಲು", - "tag-list-wrapper": "([[ವಿಸೇಸೊ:ಟ್ಯಾಗುಲು|{{ಬಹುವಚನೊ:$1|ಟ್ಯಾಗ್|ಟ್ಯಾಗುಲು}}]]:$2)", + "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|ಪಾತೆರ್ಲೆ]])", + "specialpages": "ವಿಸೇಸೊ ಪಾಲೆಲು", + "tag-filter": "[[Special:Tags|ಟ್ಯಾಗ್]] ಅರಿಪು:", + "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|ಟ್ಯಾಗುಲು}}]]: $2)", "logentry-delete-delete": "$1 {{GENDER:$2|ಮಾಜಾದ್‍ಂಡ್}} ಪುಟ $3", + "logentry-move-move": "$1 {{GENDER:$2|ಜಾರಲೆ}} ಪಾಲೆ $3 ಡ್ದ್ $4", "logentry-newusers-create": "ಬಳಕೆದಾರ ಖಾತೆ $1 ನ್ನು {{GENDER:$2|ಸೃಷ್ಟಿ ಮಲ್ತ್‍ದುಂಡು}}", "logentry-upload-upload": "$1 {{GENDER:$2|ಅಪ್ಲೋಡ್ ಮಲ್ತ್‍ದೆರ್}} $3", "searchsuggest-search": "ನಾಡ್‍ಲೆ" diff --git a/languages/i18n/uz.json b/languages/i18n/uz.json index 37df5613de..c2f5a22674 100644 --- a/languages/i18n/uz.json +++ b/languages/i18n/uz.json @@ -247,12 +247,12 @@ "versionrequiredtext": "Bu sahifada ishlash uchun MediaWikining $1-versiyasi talab etiladi.\n[[Special:Version|Dasturiy taʼminot haqida axborot]]ni koʻring.", "ok": "OK", "retrievedfrom": " \"$1\" dan olindi", - "youhavenewmessages": "Sizga $1 keldi ($2).", + "youhavenewmessages": "{{PLURAL:$3|Sizga}} $1 keldi ($2).", "youhavenewmessagesfromusers": "Siz {{PLURAL:$3|boshqa foydalanuvchidan|$3 ta foydalanuvchidan}} $1 oldingiz ($2).", "youhavenewmessagesmanyusers": "Siz ko'p foydalanuvchilardan $1 oldingiz ($2).", "newmessageslinkplural": "{{PLURAL:$1|yangi xabar|999=yangi xabarlar}}", "newmessagesdifflinkplural": "oxirgi {{PLURAL:$1|oʻzgarish|oʻzgarishlar}}", - "youhavenewmessagesmulti": "Siz $1ga yangi xat oldingiz", + "youhavenewmessagesmulti": "Sizga $1da yangi xat keldi", "editsection": "tahrirlash", "editold": "tahrirlash", "viewsourceold": "manbasini koʻrish", @@ -320,7 +320,7 @@ "protectedpagetext": "Bu sahifa tahrirlash va boshqa oʻzgarishlar kiritishdan himoyalangan.", "viewsourcetext": "Siz bu sahifaning manbasini koʻrishingiz va uni nusxasini olishingiz mumkin:", "protectedinterface": "Ushbu sahifada dasturiy taʼminot interfeysi xabari mavjud. Bezoriliklardan saqlash uchun uni oʻzgartirish taʼqiqlangan.\nUshbu xabar tarjimasini qoʻshish yoki oʻzgartirish uchun, iltimos, MediaWikining [//translatewiki.net/ translatewiki.net] mahalliylashtirish saytidan foydalaning.", - "editinginterface": "'''Diqqat:''' Siz dasturiy taʼminot interfeysi matni mavjud boʻlgan sahifani tahrirlamoqdasiz.\nUning oʻzgartirilishi ushbu vikidagi boshqa foydalanuvchilar uchun ham interfeys oʻzgarishiga olib keladi.\nUshbu xabar tarjimasini qoʻshish yoki oʻzgartirish uchun, iltimos, MediaWikining [//translatewiki.net/ translatewiki.net] mahalliylashtirish saytidan foydalaning.", + "editinginterface": "Eʼtibor bering: Siz interfeys matnini aks ettiruvchi sahifani tahrirlamoqdasiz.\nUning oʻzgartirilishi barcha ushbu vikidan foydalanuvchilar uchun ham interfeys oʻzgarishiga olib keladi.", "namespaceprotected": "Sizda '''$1''' nomfazosi sahifalarini tahrirlash huquqi yoʻq", "customcssprotected": "Sizda uchbu CSS sahifani tahrirlash huquqi yoʻq, chunki bu yerda boshqa foydalanuvchining shaxsiy moslamalari saqlanadi.", "customjsprotected": "Sizda uchbu JavaScript sahifani tahrirlash huquqi yoʻq, chunki bu yerda boshqa foydalanuvchining shaxsiy moslamalari saqlanadi.", @@ -731,7 +731,7 @@ "prefs-displayrc": "Tasvirlash moslamalari", "prefs-displaywatchlist": "Tasvirlash moslamalari", "prefs-diffs": "Versiyalar farqi", - "userrights": "Foydalanuvchi huquqlarini oʻzgartirish", + "userrights": "Huquqlarini oʻzgartirish", "userrights-lookup-user": "Foydalanuvchini tanlash", "userrights-user-editname": "Foydalanuvchi nomi:", "editusergroup": "Shu foydalanuvchi huquqlarini oʻzgartirish", @@ -985,17 +985,17 @@ "listgrouprights-rights": "Huquqlar", "listgrouprights-helppage": "Help:Guruhlar huquqlari", "listgrouprights-members": "(a’zolar ro‘yxati)", - "emailuser": "Foydalanuvchiga maktub", + "emailuser": "Maktub yuborish", "emailuser-title-target": "Ushbu {{GENDER:$1|foydalanuvchi}}ga maktub joʻnatish", "emailuser-title-notarget": "Foydalanuvchiga elektron maktub yozish", "defemailsubject": "{{SITENAME}} — $1 tomonidan maktub", "usermaildisabled": "Foydalanuvchi elektron pochtasi o‘chirilgan", "noemailtitle": "Elektron pochta manzili mavjud emas", "noemailtext": "Bu foydalanuvchi e-mail manzil koʻrsatgani yoʻq.", - "emailtarget": "Oluvchi ishtirokchining ismini kiriting", + "emailtarget": "Kimga xat joʻnatmoqchisiz?", "emailusername": "Foydalanuvchi nomi:", - "emailusernamesubmit": "Jo'natish", - "email-legend": "Boshqa {{SITENAME}} ishtirokchisiga xat jo'natish", + "emailusernamesubmit": "Joʻnatish", + "email-legend": "{{SITENAME}} loyihasining boshqa bir foydalanuvchisiga xat joʻnatish", "emailfrom": "Kimdan:", "emailto": "Kimga:", "emailsubject": "Sarlavha:", @@ -1003,7 +1003,7 @@ "emailsend": "Joʻnatish", "emailccme": "Maktub nusxasi mening elektron pochtamga joʻnatilsin", "emailccsubject": "$1ga maktubingizning nusxasi: $2", - "emailsent": "Xat jo'natildi", + "emailsent": "Xat joʻnatildi", "emailsenttext": "Sizning elektron maktubingiz jo'natildi.", "usermessage-summary": "Tizimli xabar qoldirish.", "usermessage-editor": "Tizimli etkazish", @@ -1296,7 +1296,7 @@ "tooltip-feed-rss": "Bu sahifa uchun RSS ta'minot", "tooltip-feed-atom": "Bu sahifa uchun Atom ta'minot", "tooltip-t-contributions": "Ushbu foydalanuvchi qoʻshgan hissasini koʻrish", - "tooltip-t-emailuser": "Ushbu foydalanuvchiga xat jo‘natish", + "tooltip-t-emailuser": "Ushbu foydalanuvchiga elektron maktub yozish", "tooltip-t-upload": "Rasmlar yoki media fayllar yuklash", "tooltip-t-specialpages": "Maxsus sahifalar ro‘yxati", "tooltip-t-print": "Ushbu sahifaning bosma uchun versiyasi", diff --git a/languages/i18n/war.json b/languages/i18n/war.json index d5ef92ca15..25e2a0b7ee 100644 --- a/languages/i18n/war.json +++ b/languages/i18n/war.json @@ -624,6 +624,7 @@ "log-fulllog": "Kitaa an bug-os nga taramdan", "edit-conflict": "Diri pagkakauroyon han pagliwat.", "edit-no-change": "Ginpabay-an an im pagliwat, mahitungod nga waray pagbalyo nga nabuhat ha nakasurat.", + "postedit-confirmation-created": "Nahimo an pakli.", "postedit-confirmation-saved": "Natipig an imo ginliwat.", "edit-already-exists": "Diri nakakahimo hin bag-o nga pakli.\nAada na ito.", "defaultmessagetext": "Aada-nga-daan nga teksto han mensahe", @@ -1064,6 +1065,18 @@ "upload-file-error": "Sayop ha sulod", "upload-misc-error": "Waray kasasabti nga sayop hin pagkarga-paigbaw", "upload-http-error": "Mayda nahitabo nga sayop hin HTTP: $1", + "upload-dialog-button-cancel": "Pasagda", + "upload-dialog-button-done": "Tima na", + "upload-dialog-button-save": "Igtipig", + "upload-dialog-button-upload": "Upload", + "upload-form-label-select-file": "Pagpili hin file", + "upload-form-label-infoform-title": "Mga detalye", + "upload-form-label-infoform-name": "Ngaran", + "upload-form-label-usage-title": "Paggamit", + "upload-form-label-usage-filename": "Ngaran han file", + "foreign-structured-upload-form-label-own-work": "Buhat ko ini", + "foreign-structured-upload-form-label-infoform-categories": "Mga kategorya", + "foreign-structured-upload-form-label-infoform-date": "Petsa", "backend-fail-notexists": "Waray ngada an paypay nga $1.", "backend-fail-delete": "Diri nakakapara han paypay nga \"$1\".", "backend-fail-alreadyexists": "May-ada na paypay nga \"$1\".", diff --git a/languages/i18n/wo.json b/languages/i18n/wo.json index 80de25e414..288734ae3a 100644 --- a/languages/i18n/wo.json +++ b/languages/i18n/wo.json @@ -54,21 +54,21 @@ "editfont-monospace": "Dayoob mbind genn dig-digal", "editfont-sansserif": "Dayoob mbind bu amul-dig", "editfont-serif": "Dayoob mbind bu am-dig", - "sunday": "dibéer", - "monday": "altine", - "tuesday": "talaata", + "sunday": "Dibéer", + "monday": "Altine", + "tuesday": "Talaata", "wednesday": "àllarba", "thursday": "alxamis", - "friday": "àjjuma", - "saturday": "gaawu", + "friday": "Àjjuma", + "saturday": "Gaawu", "sun": "dib", "mon": "alt", - "tue": "tal", + "tue": "Tal", "wed": "àll", "thu": "alx", "fri": "àjj", "sat": "gaa", - "january": "Semwiyee", + "january": "Samwiyee", "february": "Fewriyee", "march": "Maars", "april": "Awril", @@ -79,7 +79,7 @@ "september": "Sattumbar", "october": "Oktoobar", "november": "Nowembar", - "december": "Deesàmbar", + "december": "Samwiye", "january-gen": "Samwie", "february-gen": "Fewirie", "march-gen": "Maars", @@ -138,7 +138,7 @@ "actions": "Jëf", "namespaces": "Barabu tur", "variants": "Wuute", - "navigation-heading": "Njëlul joowiin", + "navigation-heading": "Njëlu joowiin", "errorpagetitle": "Njuumte", "returnto": "Dellu ci wii xët $1.", "tagline": "Jóge {{SITENAME}}.", @@ -170,13 +170,13 @@ "unprotectthispage": "Aaradil wii xët", "newpage": "Xët wu bees", "talkpage": "Xëtu waxtaanuwaay", - "talkpagelinktext": "Diisoo", + "talkpagelinktext": "Waxtaan", "specialpage": "Xëtu jagleel", "personaltools": "Samay jumtukaay", "articlepage": "Gis jukki bi", "talk": "Waxtaan", - "views": "Xool yo", - "toolbox": "Boyotu jumtukaay yi", + "views": "Wone yi", + "toolbox": "Boyotu jumtukaay", "userpage": "Xëtu jëfandikukat", "projectpage": "Wone xëtu sémb wi", "imagepage": "Wone xëtu dencukaay bi", @@ -189,10 +189,10 @@ "redirectedfrom": "(Yoonalaat gu jóge $1)", "redirectpagesub": "Xëtu yoonalaat", "redirectto": "Jëmalewaat:", - "lastmodifiedat": "Coppite bu mujj bu xët wii $1 ci $2.
    ", + "lastmodifiedat": "Coppite gu mujj gu xët wii $1 ci $2.
    ", "viewcount": "Xët wii nemmeeku nañ ko {{PLURAL:$1|$1 yoon|$1 yoon}}.", "protectedpage": "Xët wees aar", - "jumpto": "Dem :", + "jumpto": "Dem:", "jumptonavigation": "Joowiin", "jumptosearch": "Seet", "view-pool-error": "jéggalu, joxekaay yi dañoo xat nii-nii.\nJëfandikukat yiy jéem a ubbi xët wii dañoo bari.\nTaaxiirlul ba ci kanam nga jéemaat.\n\n$1", @@ -200,8 +200,8 @@ "aboutpage": "Project:Ci mbiri", "copyright": "Ëmbit laa ngi jàppandi ci $1.", "copyrightpage": "{{ns:project}}:Copyright", - "currentevents": "Luy xew", - "currentevents-url": "Project:Luy xew", + "currentevents": "Liy xew", + "currentevents-url": "Project:Liy xew", "disclaimers": "Ay aartu", "disclaimerpage": "Project:Aartu yu daj", "edithelp": "Ndimbal", @@ -227,7 +227,7 @@ "editlink": "soppi", "viewsourcelink": "xool gongikuwaayam", "editsectionhint": "Soppi bii xaaj : $1", - "toc": "Tëraliin", + "toc": "Ëmbiit", "showtoc": "Wone", "hidetoc": "Nëbb", "thisisdeleted": "Da ngaa bëgg a wone walla delloowaat $1 ?", @@ -329,8 +329,6 @@ "createacct-emailoptional": "Màkkaanu m-bataaxal (mu-neex-la)", "createacct-email-ph": "Duggalal sa màkkaanu m-bataaxal", "createaccountmail": "Jaare ko ci m-bataaxal", - "createacct-captcha": "Caytug kaaraange", - "createacct-imgcaptcha-ph": "Duggalal mbind miy toftal mi ngay gis", "createacct-submit": "Sos sa sàq", "createacct-benefit-heading": "{{SITENAME}} ñu mel ni yaw a koy toppatoo.", "createacct-benefit-body1": "{{PLURAL:$1|Coppite}}", @@ -479,7 +477,7 @@ "templatesused": "{{PLURAL:$1| Royuwaay bi| Royuwaay yi}} nekk ci wii xët :", "templatesusedpreview": "{{PLURAL:$1| Royuwaay bi|Royuwaay yi}} nekk ci gii wonendi :", "templatesusedsection": "Royuwaay yi ne ci bii xaaj:", - "template-protected": "(aar)", + "template-protected": "(aarees)", "template-semiprotected": "(aar-diggu)", "hiddencategories": "{{PLURAL:$1|wàll bu nëbbu bu|wàll yu nëbbu yu }} xët wii bokk :", "nocreatetext": "Jëfandikukat yi bindu rekk a man a sosi xët ci {{SITENAME}}. Man nga dellu ginnaaw walla soppi aw xët wu am ba noppi, [[Special:UserLogin|duggu walla sos am sàq]].", @@ -881,7 +879,7 @@ "rcshowhidemine-hide": "Nëbb", "rclinks": "Wone $1 coppite yi mujj ci $2 fan yi mujj
    $3.", "diff": "wuute", - "hist": "Jaar", + "hist": "jaar", "hide": "Nëbb", "show": "Wone", "minoreditletter": "m", @@ -970,7 +968,7 @@ "filehist-current": "teew", "filehist-datetime": "Taariix ak Waxtu", "filehist-thumb": "Tuutal", - "filehist-thumbtext": "Tuutal gu sumb bu $1", + "filehist-thumbtext": "Tuutal gu sumb bu $1", "filehist-user": "Jëfandikukat", "filehist-dimensions": "Dayoo", "filehist-filesize": "Dayoo ŋara wi", @@ -1487,7 +1485,7 @@ "tooltip-pt-preferences": "Say tànneef", "tooltip-pt-watchlist": "Limu xët yi ngay topp", "tooltip-pt-mycontris": "Limu say cëru", - "tooltip-pt-login": "Woo nan la ngir nga xammeku, waaye doonul lu manuta ñakk.", + "tooltip-pt-login": "Woo nan la ngir nga xammeku, waaye doonul lu manul-ñàkk.", "tooltip-pt-logout": "Génn", "tooltip-pt-createaccount": "Dees na la digal nga bindu te dugg, donte doonul lu manul-ñàkk", "tooltip-ca-talk": "Waxtaan yi ñeel xët wii", @@ -1501,18 +1499,18 @@ "tooltip-ca-move": "Tuddewaatal xët wii", "tooltip-ca-watch": "Yokk xët wii ci sa limu toppte", "tooltip-ca-unwatch": "Jële xët wii ci sa limu toppte", - "tooltip-search": "Seet ci biir {{SITENAME}}", + "tooltip-search": "Seet ci {{SITENAME}}", "tooltip-search-go": "Dem ci xët wi tudd ni nga wax, su dee am na.", - "tooltip-search-fulltext": "Seet xët yi ëmb kàddu gi", + "tooltip-search-fulltext": "Seet mbind mi ci biir xët yi", "tooltip-p-logo": "Xët wu njëkk", - "tooltip-n-mainpage": "Nemmeeku xëtu njëlbéen", - "tooltip-n-mainpage-description": "Nemmeku xët wu njëkk wi", - "tooltip-n-portal": "Ngir xam dara ci mbiri sémb bi, noo ci mana jàppe", + "tooltip-n-mainpage": "Nemmeeku xët wu njëkk wi", + "tooltip-n-mainpage-description": "Nemmeeku xët wu njëkk wi", + "tooltip-n-portal": "Ngir xam dara ci mbiri sémb bi, noo ci man a jàppe", "tooltip-n-currentevents": "Xibaar ci xew-xew yu teew yi", "tooltip-n-recentchanges": "Limu coppite yi mujj ci wiki bi", "tooltip-n-randompage": "Wone aw xët ci mbetteel", - "tooltip-n-help": "Xëtu ndimbal wi", - "tooltip-t-whatlinkshere": "limu xët yi ci wiki bi yi lëkkalook wii", + "tooltip-n-help": "Xëtu ndimbal", + "tooltip-t-whatlinkshere": "Limu xët yi ci wiki bi te lëkkalook wii", "tooltip-t-recentchangeslinked": "Limu coppite yu mujj yu xët yi lëkkalook wii", "tooltip-feed-rss": "Walug RSS ngir wii xët", "tooltip-feed-atom": "Walug Atom ngir wii xët", @@ -1520,12 +1518,12 @@ "tooltip-t-emailuser": "Yónne ab m-bataaxal bii jëfandikukat", "tooltip-t-upload": "Yeb ay dencukaay", "tooltip-t-specialpages": "Limu xëti jagleel yépp", - "tooltip-t-print": "Sumb bu móolu bu xët wii", + "tooltip-t-print": "Sumb bu móolu bu wii xët", "tooltip-t-permalink": "Lëkkalekaay bu sax buy jëme ci bii sumb bu xët wi", "tooltip-ca-nstab-main": "Xool jukki bi", "tooltip-ca-nstab-user": "Xool xëtu jëfandikukat wi", "tooltip-ca-nstab-media": "Xool xëtu dencukaay wi", - "tooltip-ca-nstab-special": "Lii aw xëtu jagleel la, kenn manu kaa soppi.", + "tooltip-ca-nstab-special": "Lii aw xëtu jagleel la, kenn manu koo soppi.", "tooltip-ca-nstab-project": "Xool xëtu sémb wi", "tooltip-ca-nstab-image": "Xool xëtu dencukaay wi", "tooltip-ca-nstab-mediawiki": "Xool bataaxalu noste bi", @@ -1540,7 +1538,7 @@ "tooltip-watch": "Yokk xët wii ci sa limu toppte", "tooltip-recreate": "Sosaat xët wi donte dañ kaa faroon", "tooltip-upload": "Door yeb gi", - "tooltip-rollback": "\"Delloowaat\" dafay neenal coppitey cërukat bi mujj ci xët wii ci benn cuq.", + "tooltip-rollback": "\"Delloowaat\" dafay neenal coppitey cërukat bi mujj ci xët wii ci benn kilig.", "tooltip-undo": "\"Neenal\" dafay far coppite yi te ubbi palanteeru coppite bi ci anamug wonendi.\nDafay tax nga man a bind ngirte li ci boyotu tënk bi.", "tooltip-summary": "Def ci ab tënk", "common.css": "/* CSS yiñ def fii dañuy am ay njeexit ci col yépp */", diff --git a/languages/i18n/yi.json b/languages/i18n/yi.json index c7704ae6fb..fbae18abf1 100644 --- a/languages/i18n/yi.json +++ b/languages/i18n/yi.json @@ -23,6 +23,7 @@ "tog-hideminor": "באַהאַלטן מינערדיקע רעדאַקטירונגען אין לעצטע ענדערונגען", "tog-hidepatrolled": "באַהאַלטן פאַטראלירטע רעדאַקטירונגען אין לעצטע ענדערונגען", "tog-newpageshidepatrolled": "באַהאַלטן פאַטראלירטע בלעטער פון דער ליסטע פון נײַע בלעטער", + "tog-hidecategorization": "באהאלטן קאעגאריזירן בלעטער", "tog-extendwatchlist": "פארברייטערן די אויפפאסן ליסטע צו צייגן אלע פאסנדע ענדערונגען (אנדערשט: בלויז די לעצטע ענדערונג פון יעדן בלאט)", "tog-usenewrc": "גרופירן ענדערונגען לויטן בלאט אין \"לעצטע ענדערונגען\" און אויפֿפאסן ליסטע", "tog-numberheadings": "נומערירן קעפּלעך אויטאָמאַטיש", @@ -52,6 +53,7 @@ "tog-watchlisthideliu": "באהאלטן רעדאקטירונגען פון איינלאגירטע באניצערס פון דער אויפֿפאסונג ליסטע", "tog-watchlisthideanons": "באהאלטן רעדאקטירונגען פון אנאנימע באניצערס פון דער אויפֿפאסונג ליסטע", "tog-watchlisthidepatrolled": "באַהאַלטן פאַטראלירטע רעדאַקטירונגען פֿון דער אויפֿפאַסונג ליסטע", + "tog-watchlisthidecategorization": "באהאלטן קאעגאריזירן בלעטער", "tog-ccmeonemails": "שיק מיר קאפיעס פון בליצבריוו וואס איך שיק צו אנדערע באַניצער", "tog-diffonly": "ווייז נישט אינהאלט אונטער די דיפערענץ", "tog-showhiddencats": "ווײַזן באהאלטענע קאטעגאריעס", @@ -1206,6 +1208,9 @@ "rcshowhidemine": "$1 מײַנע רעדאַקטירוננגען", "rcshowhidemine-show": "ווײַזן", "rcshowhidemine-hide": "באַהאַלטן", + "rcshowhidecategorization": "$1 בלאט קאטעגאריזירונג", + "rcshowhidecategorization-show": "ווײַזן", + "rcshowhidecategorization-hide": "באַהאַלטן", "rclinks": "װײַזן די לעצטע $1 ענדערונגען אין די לעצטע $2 טעג.
    $3", "diff": "אונטערשייד", "hist": "היסטאריע", diff --git a/languages/i18n/zh-hans.json b/languages/i18n/zh-hans.json index b64ff91c52..7b41fed26c 100644 --- a/languages/i18n/zh-hans.json +++ b/languages/i18n/zh-hans.json @@ -961,6 +961,7 @@ "showingresultsinrange": "下面显示区间#$2至#$3的$1条结果。", "search-showingresults": "{{PLURAL:$4|$3条结果中的$1条|$3条结果中的$1~$2条}}", "search-nonefound": "找不到和查询相匹配的结果。", + "search-nonefound-thiswiki": "在此网站找不到匹配查询的结果。", "powersearch-legend": "高级搜索", "powersearch-ns": "搜索名字空间:", "powersearch-togglelabel": "选择:", diff --git a/load.php b/load.php index 22f62fe9ca..e65b09ef17 100644 --- a/load.php +++ b/load.php @@ -1,6 +1,6 @@ begin( __METHOD__ ); - $updates = $content->getSecondaryDataUpdates( $page->getTitle() ); DataUpdate::runUpdates( $updates ); - - $dbw->commit( __METHOD__ ); } /** diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 638b99d6ff..35e7ec2df4 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -1496,7 +1496,7 @@ CREATE TABLE /*_*/l10n_cache ( ) /*$wgDBTableOptions*/; CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); --- Table for caching JSON message blobs for the resource loader +-- Table for caching JSON message blobs for ResourceLoader CREATE TABLE /*_*/msg_resource ( -- Resource name mr_resource varbinary(255) NOT NULL, diff --git a/resources/Resources.php b/resources/Resources.php index 59ce155497..b1b1541b6a 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1318,6 +1318,9 @@ return array( 'scripts' => 'resources/src/mediawiki/mediawiki.experiments.js', 'targets' => array( 'desktop', 'mobile' ), ), + 'mediawiki.raggett' => array( + 'styles' => 'resources/src/mediawiki/mediawiki.raggett.css' + ), /* MediaWiki Action */ diff --git a/resources/lib/oojs-ui/i18n/bs.json b/resources/lib/oojs-ui/i18n/bs.json index d6f61ae53d..162790e2e6 100644 --- a/resources/lib/oojs-ui/i18n/bs.json +++ b/resources/lib/oojs-ui/i18n/bs.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "DzWiki", - "Semso98" + "Semso98", + "Srdjan m" ] }, "ooui-outline-control-move-down": "Premjesti stavku dolje", @@ -16,5 +17,6 @@ "ooui-dialog-process-error": "Nešto je pošlo naopako", "ooui-dialog-process-dismiss": "Odbaci", "ooui-dialog-process-retry": "Pokušajte ponovo", - "ooui-dialog-process-continue": "Nastavi" + "ooui-dialog-process-continue": "Nastavi", + "ooui-selectfile-placeholder": "Nijedna datoteka nije izabrana" } diff --git a/resources/lib/oojs-ui/i18n/cs.json b/resources/lib/oojs-ui/i18n/cs.json index 0d86aa64f1..fb93e3a36c 100644 --- a/resources/lib/oojs-ui/i18n/cs.json +++ b/resources/lib/oojs-ui/i18n/cs.json @@ -26,6 +26,7 @@ "ooui-dialog-process-dismiss": "Zavřít", "ooui-dialog-process-retry": "Zkusit znovu", "ooui-dialog-process-continue": "Pokračovat", + "ooui-selectfile-button-select": "Vybrat soubor", "ooui-selectfile-not-supported": "Výběr souboru není podporován", "ooui-selectfile-placeholder": "Nebyl vybrán žádný soubor" } diff --git a/resources/lib/oojs-ui/i18n/fi.json b/resources/lib/oojs-ui/i18n/fi.json index bdf015f9c9..0b9e14999a 100644 --- a/resources/lib/oojs-ui/i18n/fi.json +++ b/resources/lib/oojs-ui/i18n/fi.json @@ -29,7 +29,8 @@ "ooui-dialog-process-dismiss": "Hylkää", "ooui-dialog-process-retry": "Yritä uudelleen", "ooui-dialog-process-continue": "Jatka", + "ooui-selectfile-button-select": "Valitse tiedosto", "ooui-selectfile-not-supported": "Tiedoston valitsemista ei tueta", "ooui-selectfile-placeholder": "Tiedostoa ei ole valittu", - "ooui-selectfile-dragdrop-placeholder": "Pudota tiedosto (tai selaa tiedostoja napsauttamalla)" + "ooui-selectfile-dragdrop-placeholder": "Pudota tiedosto tähän" } diff --git a/resources/lib/oojs-ui/i18n/nl.json b/resources/lib/oojs-ui/i18n/nl.json index 515eadab6e..d222d1c04c 100644 --- a/resources/lib/oojs-ui/i18n/nl.json +++ b/resources/lib/oojs-ui/i18n/nl.json @@ -32,6 +32,8 @@ "ooui-dialog-process-dismiss": "Sluiten", "ooui-dialog-process-retry": "Opnieuw proberen", "ooui-dialog-process-continue": "Doorgaan", + "ooui-selectfile-button-select": "Selecteer een bestand", "ooui-selectfile-not-supported": "Selectie van een bestand wordt niet ondersteund", - "ooui-selectfile-placeholder": "Er is geen bestand geselecteerd" + "ooui-selectfile-placeholder": "Er is geen bestand geselecteerd", + "ooui-selectfile-dragdrop-placeholder": "Sleep hier een bestand heen" } diff --git a/resources/lib/oojs-ui/oojs-ui-apex-noimages.css b/resources/lib/oojs-ui/oojs-ui-apex-noimages.css index 50129348fc..131413e37f 100644 --- a/resources/lib/oojs-ui/oojs-ui-apex-noimages.css +++ b/resources/lib/oojs-ui/oojs-ui-apex-noimages.css @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.12.12 + * OOjs UI v0.13.0 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-10-13T20:38:26Z + * Date: 2015-10-27T17:53:00Z */ @-webkit-keyframes oo-ui-progressBarWidget-slide { from { @@ -123,10 +123,6 @@ width: 1.875em; height: 1.875em; } -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - /* Don't animate opacities for now, causes wiggling in Chrome (bug 63020) */ - /*.oo-ui-transition(opacity @medium-ease);*/ -} .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover, .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus { outline: none; @@ -176,8 +172,6 @@ border: 1px #c9c9c9 solid; -webkit-transition: border-color 100ms ease; -moz-transition: border-color 100ms ease; - -ms-transition: border-color 100ms ease; - -o-transition: border-color 100ms ease; transition: border-color 100ms ease; background: #eeeeee; filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#ffffff', endColorstr='#dddddd'); @@ -218,7 +212,6 @@ margin-right: 0.3em; } .oo-ui-buttonElement-framed.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - /* -0.5 - 0.475 */ margin-left: -0.005em; margin-right: -0.005em; } @@ -308,11 +301,6 @@ } .oo-ui-draggableElement { cursor: -webkit-grab -moz-grab, url(images/grab.cur), move; - /* - * HACK: In order to style horizontally, we must override - * OO.ui.OptionWidget's display rule that is currently set - * to be 'block' - */ } .oo-ui-draggableElement-dragging { cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move; @@ -575,8 +563,6 @@ position: absolute; -webkit-transition: all 200ms ease; -moz-transition: all 200ms ease; - -ms-transition: all 200ms ease; - -o-transition: all 200ms ease; transition: all 200ms ease; } .oo-ui-menuLayout-menu { @@ -724,8 +710,6 @@ border: 1px solid transparent; -webkit-transition: border-color 250ms ease; -moz-transition: border-color 250ms ease; - -ms-transition: border-color 250ms ease; - -o-transition: border-color 250ms ease; transition: border-color 250ms ease; } .oo-ui-toolGroup-empty { @@ -1085,6 +1069,9 @@ background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg"); background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg"); background-image: -o-linear-gradient(transparent, transparent), url("themes/apex/images/icons/check.png"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; } .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover { background-color: #e1f3ff; @@ -1182,7 +1169,6 @@ .oo-ui-toolbar-actions > .oo-ui-buttonElement-frameless:last-child.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { margin: 0 1em; line-height: 3.40625em; - /* 43/12.8 */ } .oo-ui-toolbar-shadow { background-image: /* @embed */ url(themes/apex/images/toolbar-shadow.png); @@ -1191,8 +1177,6 @@ opacity: 0.5; -webkit-transition: opacity 500ms ease; -moz-transition: opacity 500ms ease; - -ms-transition: opacity 500ms ease; - -o-transition: opacity 500ms ease; transition: opacity 500ms ease; } .oo-ui-optionWidget { @@ -1404,7 +1388,6 @@ -webkit-transform: translateZ(0px); -moz-transform: translateZ(0px); -ms-transform: translateZ(0px); - -o-transform: translateZ(0px); transform: translateZ(0px); height: 2em; width: 4em; @@ -1462,8 +1445,6 @@ border: 1px #c9c9c9 solid; -webkit-transition: left 250ms ease, margin-left 250ms ease; -moz-transition: left 250ms ease, margin-left 250ms ease; - -ms-transition: left 250ms ease, margin-left 250ms ease; - -o-transition: left 250ms ease, margin-left 250ms ease; transition: left 250ms ease, margin-left 250ms ease; background: #eeeeee; filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#ffffff', endColorstr='#dddddd'); @@ -1482,8 +1463,6 @@ box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07); -webkit-transition: opacity 250ms ease; -moz-transition: opacity 250ms ease; - -ms-transition: opacity 250ms ease; - -o-transition: opacity 250ms ease; transition: opacity 250ms ease; background: #cde7f4; filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#b0d9ee', endColorstr='#eaf4fa'); @@ -1519,8 +1498,6 @@ border-right: 1px solid #cccccc; -webkit-transition: width 250ms ease, margin-left 250ms ease; -moz-transition: width 250ms ease, margin-left 250ms ease; - -ms-transition: width 250ms ease, margin-left 250ms ease; - -o-transition: width 250ms ease, margin-left 250ms ease; transition: width 250ms ease, margin-left 250ms ease; background: #cde7f4; filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#eaf4fa', endColorstr='#b0d9ee'); @@ -1533,8 +1510,6 @@ .oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar { -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear; -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -ms-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -o-animation: oo-ui-progressBarWidget-slide 2s infinite linear; animation: oo-ui-progressBarWidget-slide 2s infinite linear; width: 40%; margin-left: -10%; @@ -1620,8 +1595,6 @@ .oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup { -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease; -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease; - -ms-transition: width 100ms ease, height 100ms ease, left 100ms ease; - -o-transition: width 100ms ease, height 100ms ease, left 100ms ease; transition: width 100ms ease, height 100ms ease, left 100ms ease; } .oo-ui-popupWidget-head { @@ -1724,6 +1697,9 @@ -moz-box-sizing: border-box; box-sizing: border-box; } +.oo-ui-textInputWidget textarea { + overflow: auto; +} .oo-ui-textInputWidget input[type="search"] { -webkit-appearance: none; } @@ -1795,8 +1771,6 @@ border-radius: 0.25em; -webkit-transition: border-color 250ms ease, box-shadow 250ms ease; -moz-transition: border-color 250ms ease, box-shadow 250ms ease; - -ms-transition: border-color 250ms ease, box-shadow 250ms ease; - -o-transition: border-color 250ms ease, box-shadow 250ms ease; transition: border-color 250ms ease, box-shadow 250ms ease; } .oo-ui-textInputWidget input.oo-ui-pendingElement-pending, @@ -2010,7 +1984,6 @@ opacity: 0; z-index: 1; cursor: pointer; - /* Push the button part of the native control out of view, as it changes the cursor */ padding-top: 100px; } .oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] { @@ -2804,7 +2777,6 @@ .oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-framed .oo-ui-buttonElement-button { padding: 0 1em; vertical-align: middle; - /* Adjust for border so text aligns with title */ margin: -1px; } .oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless, @@ -2916,8 +2888,6 @@ opacity: 0; -webkit-transition: opacity 250ms ease; -moz-transition: opacity 250ms ease; - -ms-transition: opacity 250ms ease; - -o-transition: opacity 250ms ease; transition: opacity 250ms ease; } .oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame { @@ -2928,25 +2898,19 @@ -webkit-transform: scale(0.5); -moz-transform: scale(0.5); -ms-transform: scale(0.5); - -o-transform: scale(0.5); transform: scale(0.5); -webkit-transition: all 250ms ease; -moz-transition: all 250ms ease; - -ms-transition: all 250ms ease; - -o-transition: all 250ms ease; transition: all 250ms ease; } .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready { - /* Fade window overlay */ opacity: 1; } .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready > .oo-ui-window-frame { - /* Fade frame */ opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); - -o-transform: scale(1); transform: scale(1); } .oo-ui-windowManager-modal.oo-ui-windowManager-floating > .oo-ui-dialog > .oo-ui-window-frame { diff --git a/resources/lib/oojs-ui/oojs-ui-apex.js b/resources/lib/oojs-ui/oojs-ui-apex.js index dab3c78d7f..3158bfe438 100644 --- a/resources/lib/oojs-ui/oojs-ui-apex.js +++ b/resources/lib/oojs-ui/oojs-ui-apex.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.12.12 + * OOjs UI v0.13.0 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-10-13T20:38:18Z + * Date: 2015-10-27T17:52:51Z */ /** * @class diff --git a/resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css b/resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css index a19ebeaba0..96ba45c683 100644 --- a/resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css +++ b/resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.12.12 + * OOjs UI v0.13.0 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-10-13T20:38:26Z + * Date: 2015-10-27T17:53:00Z */ @-webkit-keyframes oo-ui-progressBarWidget-slide { from { @@ -206,8 +206,6 @@ position: relative; -webkit-transition: background 100ms ease, color 100ms ease, border-color 100ms ease, box-shadow 100ms ease; -moz-transition: background 100ms ease, color 100ms ease, border-color 100ms ease, box-shadow 100ms ease; - -ms-transition: background 100ms ease, color 100ms ease, border-color 100ms ease, box-shadow 100ms ease; - -o-transition: background 100ms ease, color 100ms ease, border-color 100ms ease, box-shadow 100ms ease; transition: background 100ms ease, color 100ms ease, border-color 100ms ease, box-shadow 100ms ease; } .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover, @@ -407,11 +405,6 @@ } .oo-ui-draggableElement { cursor: -webkit-grab -moz-grab, url(images/grab.cur), move; - /* - * HACK: In order to style horizontally, we must override - * OO.ui.OptionWidget's display rule that is currently set - * to be 'block' - */ } .oo-ui-draggableElement-dragging { cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move; @@ -673,8 +666,6 @@ position: absolute; -webkit-transition: all 200ms ease; -moz-transition: all 200ms ease; - -ms-transition: all 200ms ease; - -o-transition: all 200ms ease; transition: all 200ms ease; } .oo-ui-menuLayout-menu { @@ -1236,7 +1227,6 @@ .oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { margin: 0 1em; line-height: 3.125em; - /* 40/12.8 */ } .oo-ui-optionWidget { position: relative; @@ -1443,19 +1433,16 @@ -webkit-transform: translateZ(0px); -moz-transform: translateZ(0px); -ms-transform: translateZ(0px); - -o-transform: translateZ(0px); transform: translateZ(0px); height: 2em; width: 3.5em; + border: 1px solid #777777; border-radius: 1em; - border: 1px #555555 solid; - background: #ffffff; - -webkit-transition: background-color 100ms ease; - -moz-transition: background-color 100ms ease; - -ms-transition: background-color 100ms ease; - -o-transition: background-color 100ms ease; - transition: background-color 100ms ease; + background-color: #ffffff; margin-right: 0.5em; + -webkit-transition: background-color 100ms ease, border-color 100ms ease; + -moz-transition: background-color 100ms ease, border-color 100ms ease; + transition: background-color 100ms ease, border-color 100ms ease; } .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled { cursor: default; @@ -1485,29 +1472,37 @@ .oo-ui-toggleSwitchWidget:last-child { margin-right: 0; } -.oo-ui-toggleSwitchWidget-grip { - top: 0.5em; - left: 0.5em; - width: 1em; - height: 1em; - margin-top: -1px; +.oo-ui-toggleSwitchWidget:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border: 1px solid transparent; border-radius: 1em; - background: #555555; + z-index: 1; +} +.oo-ui-toggleSwitchWidget-grip { + top: 0.35em; + width: 1.2em; + height: 1.2em; + border-radius: 1.2em; + background-color: #555555; -webkit-transition: left 100ms ease, margin-left 100ms ease; -moz-transition: left 100ms ease, margin-left 100ms ease; - -ms-transition: left 100ms ease, margin-left 100ms ease; - -o-transition: left 100ms ease, margin-left 100ms ease; transition: left 100ms ease, margin-left 100ms ease; } .oo-ui-toggleSwitchWidget-glow { display: none; } .oo-ui-toggleSwitchWidget.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip { - left: 2em; + left: 1.9em; margin-left: -2px; } .oo-ui-toggleSwitchWidget.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-grip { - left: 0.5em; + left: 0.4em; margin-left: 0; } .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on { @@ -1516,25 +1511,34 @@ } .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip { background: #ffffff; -} -.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus { - outline: none; - border-color: #347bff; -} -.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus.oo-ui-toggleWidget-on { - border-color: #ffffff; - box-shadow: 0 0 0 1px #347bff; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); } .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover { border-color: #2962cc; - box-shadow: 0 0 0 1px #2962cc; } .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover.oo-ui-toggleWidget-on { background: #2962cc; border-color: #2962cc; } -.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip { +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus { + border-color: #347bff; + outline: none; +} +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus.oo-ui-toggleWidget-on { + border-color: #347bff; +} +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus.oo-ui-toggleWidget-on:before { + border-color: #ffffff; +} +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active, +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover { + background-color: #347bff; + border-color: #347bff; +} +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active .oo-ui-toggleSwitchWidget-grip, +.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover .oo-ui-toggleSwitchWidget-grip { background: #ffffff; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); } .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled { background: #dddddd; @@ -1555,15 +1559,11 @@ background: #dddddd; -webkit-transition: width 200ms, margin-left 200ms; -moz-transition: width 200ms, margin-left 200ms; - -ms-transition: width 200ms, margin-left 200ms; - -o-transition: width 200ms, margin-left 200ms; transition: width 200ms, margin-left 200ms; } .oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar { -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear; -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -ms-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -o-animation: oo-ui-progressBarWidget-slide 2s infinite linear; animation: oo-ui-progressBarWidget-slide 2s infinite linear; width: 40%; margin-left: -10%; @@ -1646,8 +1646,6 @@ .oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup { -webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease; -moz-transition: width 100ms ease, height 100ms ease, left 100ms ease; - -ms-transition: width 100ms ease, height 100ms ease, left 100ms ease; - -o-transition: width 100ms ease, height 100ms ease, left 100ms ease; transition: width 100ms ease, height 100ms ease, left 100ms ease; } .oo-ui-popupWidget-head { @@ -1715,8 +1713,6 @@ .oo-ui-checkboxInputWidget input[type="checkbox"] + span { -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - -ms-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - -o-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -1751,8 +1747,10 @@ .oo-ui-checkboxInputWidget input[type="checkbox"]:hover + span { border-bottom-width: 3px; } -.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled + span { +.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled { cursor: default; +} +.oo-ui-checkboxInputWidget input[type="checkbox"]:disabled + span { background-color: #eeeeee; border-color: #eeeeee; } @@ -1824,8 +1822,6 @@ .oo-ui-radioInputWidget input[type="radio"] + span { -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - -ms-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); - -o-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -1860,8 +1856,10 @@ .oo-ui-radioInputWidget input[type="radio"]:hover + span { border-bottom-width: 3px; } -.oo-ui-radioInputWidget input[type="radio"]:disabled + span { +.oo-ui-radioInputWidget input[type="radio"]:disabled { cursor: default; +} +.oo-ui-radioInputWidget input[type="radio"]:disabled + span { background-color: #eeeeee; border-color: #eeeeee; } @@ -1892,6 +1890,9 @@ -moz-box-sizing: border-box; box-sizing: border-box; } +.oo-ui-textInputWidget textarea { + overflow: auto; +} .oo-ui-textInputWidget input[type="search"] { -webkit-appearance: none; } @@ -1964,8 +1965,6 @@ border-radius: 0.1em; -webkit-transition: box-shadow 100ms ease; -moz-transition: box-shadow 100ms ease; - -ms-transition: box-shadow 100ms ease; - -o-transition: box-shadow 100ms ease; transition: box-shadow 100ms ease; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -1982,8 +1981,6 @@ .oo-ui-textInputWidget.oo-ui-widget-enabled textarea { -webkit-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1); -moz-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1); - -ms-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1); - -o-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1); transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1); } .oo-ui-textInputWidget.oo-ui-widget-enabled input:focus, @@ -2219,7 +2216,6 @@ opacity: 0; z-index: 1; cursor: pointer; - /* Push the button part of the native control out of view, as it changes the cursor */ padding-top: 100px; } .oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > input[type="file"] { @@ -3081,8 +3077,6 @@ opacity: 0; -webkit-transition: opacity 250ms ease; -moz-transition: opacity 250ms ease; - -ms-transition: opacity 250ms ease; - -o-transition: opacity 250ms ease; transition: opacity 250ms ease; } .oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame { @@ -3093,25 +3087,19 @@ -webkit-transform: scale(0.5); -moz-transform: scale(0.5); -ms-transform: scale(0.5); - -o-transform: scale(0.5); transform: scale(0.5); -webkit-transition: all 250ms ease; -moz-transition: all 250ms ease; - -ms-transition: all 250ms ease; - -o-transition: all 250ms ease; transition: all 250ms ease; } .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready { - /* Fade window overlay */ opacity: 1; } .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-ready > .oo-ui-window-frame { - /* Fade frame */ opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); - -o-transform: scale(1); transform: scale(1); } .oo-ui-windowManager-modal.oo-ui-windowManager-floating > .oo-ui-dialog > .oo-ui-window-frame { diff --git a/resources/lib/oojs-ui/oojs-ui-mediawiki.js b/resources/lib/oojs-ui/oojs-ui-mediawiki.js index e6344f244f..95e8b47ba0 100644 --- a/resources/lib/oojs-ui/oojs-ui-mediawiki.js +++ b/resources/lib/oojs-ui/oojs-ui-mediawiki.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.12.12 + * OOjs UI v0.13.0 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-10-13T20:38:18Z + * Date: 2015-10-27T17:52:51Z */ /** * @class diff --git a/resources/lib/oojs-ui/oojs-ui.js b/resources/lib/oojs-ui/oojs-ui.js index aeff69e0fa..fbffe09a77 100644 --- a/resources/lib/oojs-ui/oojs-ui.js +++ b/resources/lib/oojs-ui/oojs-ui.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.12.12 + * OOjs UI v0.13.0 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2015 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2015-10-13T20:38:18Z + * Date: 2015-10-27T17:52:51Z */ ( function ( OO ) { @@ -1289,10 +1289,10 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) { } } } ); + // pick up dynamic state, like focus, value of form inputs, scroll position, etc. + state = cls.static.gatherPreInfuseState( $elem, data ); // jscs:disable requireCapitalizedConstructors obj = new cls( data ); // rebuild widget - // pick up dynamic state, like focus, value of form inputs, scroll position, etc. - state = obj.gatherPreInfuseState( $elem ); // now replace old DOM with this new DOM. if ( top ) { $elem.replaceWith( obj.$element ); @@ -1310,6 +1310,24 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) { return obj; }; +/** + * Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of a HTML DOM node + * (and its children) that represent an Element of the same class and the given configuration, + * generated by the PHP implementation. + * + * This method is called just before `node` is detached from the DOM. The return value of this + * function will be passed to #restorePreInfuseState after the newly created widget's #$element + * is inserted into DOM to replace `node`. + * + * @protected + * @param {HTMLElement} node + * @param {Object} config + * @return {Object} + */ +OO.ui.Element.static.gatherPreInfuseState = function () { + return {}; +}; + /** * Get a jQuery function within a specific document. * @@ -1888,23 +1906,6 @@ OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config ); }; -/** - * Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of a HTML DOM node - * (and its children) that represent an Element of the same type and configuration as the current - * one, generated by the PHP implementation. - * - * This method is called just before `node` is detached from the DOM. The return value of this - * function will be passed to #restorePreInfuseState after this widget's #$element is inserted into - * DOM to replace `node`. - * - * @protected - * @param {HTMLElement} node - * @return {Object} - */ -OO.ui.Element.prototype.gatherPreInfuseState = function () { - return {}; -}; - /** * Restore the pre-infusion dynamic state for this widget. * @@ -9730,6 +9731,8 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { this.stackLayout.connect( this, { set: 'onStackLayoutSet' } ); if ( this.outlined ) { this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } ); + this.scrolling = false; + this.stackLayout.connect( this, { visibleItemChange: 'onStackLayoutVisibleItemChange' } ); } if ( this.autoFocus ) { // Event 'focus' does not bubble, but 'focusin' does @@ -9801,6 +9804,22 @@ OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) { } }; +/** + * Handle visibleItemChange events from the stackLayout + * + * The next visible page is set as the current page by selecting it + * in the outline + * + * @param {OO.ui.PageLayout} page The next visible page in the layout + */ +OO.ui.BookletLayout.prototype.onStackLayoutVisibleItemChange = function ( page ) { + // Set a flag to so that the resulting call to #onStackLayoutSet doesn't + // try and scroll the item into view again. + this.scrolling = true; + this.outlineSelectWidget.selectItemByData( page.getName() ); + this.scrolling = false; +}; + /** * Handle stack layout set events. * @@ -9809,7 +9828,7 @@ OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) { */ OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) { var layout = this; - if ( page ) { + if ( !this.scrolling && page ) { page.scrollElementIntoView( { complete: function () { if ( layout.autoFocus ) { layout.focus(); @@ -11020,6 +11039,7 @@ OO.ui.StackLayout = function OoUiStackLayout( config ) { this.$element.addClass( 'oo-ui-stackLayout' ); if ( this.continuous ) { this.$element.addClass( 'oo-ui-stackLayout-continuous' ); + this.$element.on( 'scroll', OO.ui.debounce( this.onScroll.bind( this ), 250 ) ); } if ( Array.isArray( config.items ) ) { this.addItems( config.items ); @@ -11041,8 +11061,66 @@ OO.mixinClass( OO.ui.StackLayout, OO.ui.mixin.GroupElement ); * @param {OO.ui.Layout|null} item Current panel or `null` if no panel is shown */ +/** + * When used in continuous mode, this event is emitted when the user scrolls down + * far enough such that currentItem is no longer visible. + * + * @event visibleItemChange + * @param {OO.ui.PanelLayout} panel The next visible item in the layout + */ + /* Methods */ +/** + * Handle scroll events from the layout element + * + * @param {jQuery.Event} e + * @fires visibleItemChange + */ +OO.ui.StackLayout.prototype.onScroll = function () { + var currentRect, + len = this.items.length, + currentIndex = this.items.indexOf( this.currentItem ), + newIndex = currentIndex, + containerRect = this.$element[ 0 ].getBoundingClientRect(); + + if ( !containerRect || ( !containerRect.top && !containerRect.bottom ) ) { + // Can't get bounding rect, possibly not attached. + return; + } + + function getRect( item ) { + return item.$element[ 0 ].getBoundingClientRect(); + } + + function isVisible( item ) { + var rect = getRect( item ); + return rect.bottom > containerRect.top && rect.top < containerRect.bottom; + } + + currentRect = getRect( this.currentItem ); + + if ( currentRect.bottom < containerRect.top ) { + // Scrolled down past current item + while ( ++newIndex < len ) { + if ( isVisible( this.items[ newIndex ] ) ) { + break; + } + } + } else if ( currentRect.top > containerRect.bottom ) { + // Scrolled up past current item + while ( --newIndex >= 0 ) { + if ( isVisible( this.items[ newIndex ] ) ) { + break; + } + } + } + + if ( newIndex !== currentIndex ) { + this.emit( 'visibleItemChange', this.items[ newIndex ] ); + } +}; + /** * Get the current panel. * @@ -14540,6 +14618,21 @@ OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.AccessKeyedElement ); OO.ui.InputWidget.static.supportsSimpleLabel = true; +/* Static Methods */ + +/** + * @inheritdoc + */ +OO.ui.InputWidget.static.gatherPreInfuseState = function ( node, config ) { + var + state = OO.ui.InputWidget.parent.static.gatherPreInfuseState( node, config ), + $input = state.$input || $( node ).find( '.oo-ui-inputWidget-input' ); + state.value = $input.val(); + // Might be better in TabIndexedElement, but it's awkward to do there because mixins are awkward + state.focus = $input.is( ':focus' ); + return state; +}; + /* Events */ /** @@ -14718,19 +14811,6 @@ OO.ui.InputWidget.prototype.blur = function () { return this; }; -/** - * @inheritdoc - */ -OO.ui.InputWidget.prototype.gatherPreInfuseState = function ( node ) { - var - state = OO.ui.InputWidget.parent.prototype.gatherPreInfuseState.call( this, node ), - $input = state.$input || $( node ).find( '.oo-ui-inputWidget-input' ); - state.value = $input.val(); - // Might be better in TabIndexedElement, but it's awkward to do there because mixins are awkward - state.focus = $input.is( ':focus' ); - return state; -}; - /** * @inheritdoc */ @@ -14936,6 +15016,20 @@ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget ); +/* Static Methods */ + +/** + * @inheritdoc + */ +OO.ui.CheckboxInputWidget.static.gatherPreInfuseState = function ( node, config ) { + var + state = OO.ui.CheckboxInputWidget.parent.static.gatherPreInfuseState( node, config ), + $input = $( node ).find( '.oo-ui-inputWidget-input' ); + state.$input = $input; // shortcut for performance, used in InputWidget + state.checked = $input.prop( 'checked' ); + return state; +}; + /* Methods */ /** @@ -14990,18 +15084,6 @@ OO.ui.CheckboxInputWidget.prototype.isSelected = function () { return this.selected; }; -/** - * @inheritdoc - */ -OO.ui.CheckboxInputWidget.prototype.gatherPreInfuseState = function ( node ) { - var - state = OO.ui.CheckboxInputWidget.parent.prototype.gatherPreInfuseState.call( this, node ), - $input = $( node ).find( '.oo-ui-inputWidget-input' ); - state.$input = $input; // shortcut for performance, used in InputWidget - state.checked = $input.prop( 'checked' ); - return state; -}; - /** * @inheritdoc */ @@ -15225,6 +15307,20 @@ OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) { OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget ); +/* Static Methods */ + +/** + * @inheritdoc + */ +OO.ui.RadioInputWidget.static.gatherPreInfuseState = function ( node, config ) { + var + state = OO.ui.RadioInputWidget.parent.static.gatherPreInfuseState( node, config ), + $input = $( node ).find( '.oo-ui-inputWidget-input' ); + state.$input = $input; // shortcut for performance, used in InputWidget + state.checked = $input.prop( 'checked' ); + return state; +}; + /* Methods */ /** @@ -15263,18 +15359,6 @@ OO.ui.RadioInputWidget.prototype.isSelected = function () { return this.$input.prop( 'checked' ); }; -/** - * @inheritdoc - */ -OO.ui.RadioInputWidget.prototype.gatherPreInfuseState = function ( node ) { - var - state = OO.ui.RadioInputWidget.parent.prototype.gatherPreInfuseState.call( this, node ), - $input = $( node ).find( '.oo-ui-inputWidget-input' ); - state.$input = $input; // shortcut for performance, used in InputWidget - state.checked = $input.prop( 'checked' ); - return state; -}; - /** * @inheritdoc */ @@ -15341,6 +15425,17 @@ OO.inheritClass( OO.ui.RadioSelectInputWidget, OO.ui.InputWidget ); OO.ui.RadioSelectInputWidget.static.supportsSimpleLabel = false; +/* Static Methods */ + +/** + * @inheritdoc + */ +OO.ui.RadioSelectInputWidget.static.gatherPreInfuseState = function ( node, config ) { + var state = OO.ui.RadioSelectInputWidget.parent.static.gatherPreInfuseState( node, config ); + state.value = $( node ).find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input:checked' ).val(); + return state; +}; + /* Methods */ /** @@ -15416,15 +15511,6 @@ OO.ui.RadioSelectInputWidget.prototype.setOptions = function ( options ) { return this; }; -/** - * @inheritdoc - */ -OO.ui.RadioSelectInputWidget.prototype.gatherPreInfuseState = function ( node ) { - var state = OO.ui.RadioSelectInputWidget.parent.prototype.gatherPreInfuseState.call( this, node ); - state.value = $( node ).find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input:checked' ).val(); - return state; -}; - /** * TextInputWidgets, like HTML text inputs, can be configured with options that customize the * size of the field as well as its presentation. In addition, these widgets can be configured @@ -15517,6 +15603,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { this.minRows = config.rows !== undefined ? config.rows : ''; this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 ); this.validate = null; + this.styleHeight = null; // Clone for resizing if ( this.autosize ) { @@ -15603,6 +15690,22 @@ OO.ui.TextInputWidget.static.validationPatterns = { integer: /^\d+$/ }; +/* Static Methods */ + +/** + * @inheritdoc + */ +OO.ui.TextInputWidget.static.gatherPreInfuseState = function ( node, config ) { + var + state = OO.ui.TextInputWidget.parent.static.gatherPreInfuseState( node, config ), + $input = $( node ).find( '.oo-ui-inputWidget-input' ); + state.$input = $input; // shortcut for performance, used in InputWidget + if ( config.multiline ) { + state.scrollTop = $input.scrollTop(); + } + return state; +}; + /* Events */ /** @@ -15613,6 +15716,12 @@ OO.ui.TextInputWidget.static.validationPatterns = { * @event enter */ +/** + * A `resize` event is emitted when autosize is set and the widget resizes + * + * @event resize + */ + /* Methods */ /** @@ -15802,9 +15911,10 @@ OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () { * This only affects #multiline inputs that are {@link #autosize autosized}. * * @chainable + * @fires resize */ OO.ui.TextInputWidget.prototype.adjustSize = function () { - var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight; + var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight, newHeight; if ( this.multiline && this.autosize && this.$input.val() !== this.valCache ) { this.$clone @@ -15839,11 +15949,12 @@ OO.ui.TextInputWidget.prototype.adjustSize = function () { this.$clone.addClass( 'oo-ui-element-hidden' ); // Only apply inline height when expansion beyond natural height is needed - if ( idealHeight > innerHeight ) { - // Use the difference between the inner and outer height as a buffer - this.$input.css( 'height', idealHeight + ( outerHeight - innerHeight ) ); - } else { - this.$input.css( 'height', '' ); + // Use the difference between the inner and outer height as a buffer + newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : ''; + if ( newHeight !== this.styleHeight ) { + this.$input.css( 'height', newHeight ); + this.styleHeight = newHeight; + this.emit( 'resize' ); } } return this; @@ -15892,30 +16003,74 @@ OO.ui.TextInputWidget.prototype.isAutosizing = function () { }; /** - * Select the entire text of the input. + * Focus the input and select a specified range within the text. * + * @param {number} from Select from offset + * @param {number} [to] Select to offset, defaults to from * @chainable */ -OO.ui.TextInputWidget.prototype.select = function () { - this.$input.select(); - return this; -}; - -/** - * Focus the input and move the cursor to the end. - */ -OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () { - var textRange, +OO.ui.TextInputWidget.prototype.selectRange = function ( from, to ) { + var textRange, isBackwards, start, end, element = this.$input[ 0 ]; + + to = to || from; + + isBackwards = to < from; + start = isBackwards ? to : from; + end = isBackwards ? from : to; + this.focus(); - if ( element.selectionStart !== undefined ) { - element.selectionStart = element.selectionEnd = element.value.length; + + if ( element.setSelectionRange ) { + element.setSelectionRange( start, end, isBackwards ? 'backward' : 'forward' ); } else if ( element.createTextRange ) { // IE 8 and below textRange = element.createTextRange(); - textRange.collapse( false ); + textRange.collapse( true ); + textRange.moveStart( 'character', start ); + textRange.moveEnd( 'character', end - start ); textRange.select(); } + return this; +}; + +/** + * Get the length of the text input value. + * + * This could differ from the length of #getValue if the + * value gets filtered + * + * @return {number} Input length + */ +OO.ui.TextInputWidget.prototype.getInputLength = function () { + return this.$input[ 0 ].value.length; +}; + +/** + * Focus the input and select the entire text. + * + * @chainable + */ +OO.ui.TextInputWidget.prototype.select = function () { + return this.selectRange( 0, this.getInputLength() ); +}; + +/** + * Focus the input and move the cursor to the start. + * + * @chainable + */ +OO.ui.TextInputWidget.prototype.moveCursorToStart = function () { + return this.selectRange( 0 ); +}; + +/** + * Focus the input and move the cursor to the end. + * + * @chainable + */ +OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () { + return this.selectRange( this.getInputLength() ); }; /** @@ -16117,20 +16272,6 @@ OO.ui.TextInputWidget.prototype.positionLabel = function () { return this; }; -/** - * @inheritdoc - */ -OO.ui.TextInputWidget.prototype.gatherPreInfuseState = function ( node ) { - var - state = OO.ui.TextInputWidget.parent.prototype.gatherPreInfuseState.call( this, node ), - $input = $( node ).find( '.oo-ui-inputWidget-input' ); - state.$input = $input; // shortcut for performance, used in InputWidget - if ( this.multiline ) { - state.scrollTop = $input.scrollTop(); - } - return state; -}; - /** * @inheritdoc */ @@ -19612,104 +19753,4 @@ OO.ui.ToggleSwitchWidget.prototype.onKeyPress = function ( e ) { } }; -/*! - * Deprecated aliases for classes in the `OO.ui.mixin` namespace. - */ - -/** - * @inheritdoc OO.ui.mixin.ButtonElement - * @deprecated Use {@link OO.ui.mixin.ButtonElement} instead. - */ -OO.ui.ButtonElement = OO.ui.mixin.ButtonElement; - -/** - * @inheritdoc OO.ui.mixin.ClippableElement - * @deprecated Use {@link OO.ui.mixin.ClippableElement} instead. - */ -OO.ui.ClippableElement = OO.ui.mixin.ClippableElement; - -/** - * @inheritdoc OO.ui.mixin.DraggableElement - * @deprecated Use {@link OO.ui.mixin.DraggableElement} instead. - */ -OO.ui.DraggableElement = OO.ui.mixin.DraggableElement; - -/** - * @inheritdoc OO.ui.mixin.DraggableGroupElement - * @deprecated Use {@link OO.ui.mixin.DraggableGroupElement} instead. - */ -OO.ui.DraggableGroupElement = OO.ui.mixin.DraggableGroupElement; - -/** - * @inheritdoc OO.ui.mixin.FlaggedElement - * @deprecated Use {@link OO.ui.mixin.FlaggedElement} instead. - */ -OO.ui.FlaggedElement = OO.ui.mixin.FlaggedElement; - -/** - * @inheritdoc OO.ui.mixin.GroupElement - * @deprecated Use {@link OO.ui.mixin.GroupElement} instead. - */ -OO.ui.GroupElement = OO.ui.mixin.GroupElement; - -/** - * @inheritdoc OO.ui.mixin.GroupWidget - * @deprecated Use {@link OO.ui.mixin.GroupWidget} instead. - */ -OO.ui.GroupWidget = OO.ui.mixin.GroupWidget; - -/** - * @inheritdoc OO.ui.mixin.IconElement - * @deprecated Use {@link OO.ui.mixin.IconElement} instead. - */ -OO.ui.IconElement = OO.ui.mixin.IconElement; - -/** - * @inheritdoc OO.ui.mixin.IndicatorElement - * @deprecated Use {@link OO.ui.mixin.IndicatorElement} instead. - */ -OO.ui.IndicatorElement = OO.ui.mixin.IndicatorElement; - -/** - * @inheritdoc OO.ui.mixin.ItemWidget - * @deprecated Use {@link OO.ui.mixin.ItemWidget} instead. - */ -OO.ui.ItemWidget = OO.ui.mixin.ItemWidget; - -/** - * @inheritdoc OO.ui.mixin.LabelElement - * @deprecated Use {@link OO.ui.mixin.LabelElement} instead. - */ -OO.ui.LabelElement = OO.ui.mixin.LabelElement; - -/** - * @inheritdoc OO.ui.mixin.LookupElement - * @deprecated Use {@link OO.ui.mixin.LookupElement} instead. - */ -OO.ui.LookupElement = OO.ui.mixin.LookupElement; - -/** - * @inheritdoc OO.ui.mixin.PendingElement - * @deprecated Use {@link OO.ui.mixin.PendingElement} instead. - */ -OO.ui.PendingElement = OO.ui.mixin.PendingElement; - -/** - * @inheritdoc OO.ui.mixin.PopupElement - * @deprecated Use {@link OO.ui.mixin.PopupElement} instead. - */ -OO.ui.PopupElement = OO.ui.mixin.PopupElement; - -/** - * @inheritdoc OO.ui.mixin.TabIndexedElement - * @deprecated Use {@link OO.ui.mixin.TabIndexedElement} instead. - */ -OO.ui.TabIndexedElement = OO.ui.mixin.TabIndexedElement; - -/** - * @inheritdoc OO.ui.mixin.TitledElement - * @deprecated Use {@link OO.ui.mixin.TitledElement} instead. - */ -OO.ui.TitledElement = OO.ui.mixin.TitledElement; - }( OO ) ); diff --git a/resources/src/mediawiki.ui/components/buttons.less b/resources/src/mediawiki.ui/components/buttons.less index d66973a8ea..600b7711c1 100644 --- a/resources/src/mediawiki.ui/components/buttons.less +++ b/resources/src/mediawiki.ui/components/buttons.less @@ -12,9 +12,6 @@ // // Styleguide 2. -@transitionDuration: .1s; -@transitionFunction: ease-in-out; - // Neutral button styling // // These are the main actions on the page/workflow. The page should have only one of progressive, constructive and desctructive buttons, the rest being quiet. @@ -66,8 +63,6 @@ cursor: default; } - .transition(background @transitionDuration @transitionFunction, color @transitionDuration @transitionFunction, box-shadow @transitionDuration @transitionFunction;); - // Styling for specific button types // ----------------------------------------- diff --git a/resources/src/mediawiki.ui/components/checkbox.less b/resources/src/mediawiki.ui/components/checkbox.less index ac5becb800..bd5dd4a297 100644 --- a/resources/src/mediawiki.ui/components/checkbox.less +++ b/resources/src/mediawiki.ui/components/checkbox.less @@ -72,7 +72,6 @@ // the pseudo before element of the label after the checkbox now looks like a checkbox & + label::before { - .transition( 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) ); content: ''; cursor: pointer; .box-sizing(border-box); diff --git a/resources/src/mediawiki.ui/components/inputs.less b/resources/src/mediawiki.ui/components/inputs.less index 2f76131286..62f0e8345f 100644 --- a/resources/src/mediawiki.ui/components/inputs.less +++ b/resources/src/mediawiki.ui/components/inputs.less @@ -39,7 +39,6 @@ font-family: inherit; font-size: inherit; line-height: inherit; - .transition(~"border 0.2s cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 0.2s cubic-bezier(0.39, 0.575, 0.565, 1)"); // Placeholder text styling must be set individually for each browser @winter &::-webkit-input-placeholder { // webkit diff --git a/resources/src/mediawiki.ui/components/radio.less b/resources/src/mediawiki.ui/components/radio.less index 1928699bb5..52effd65b2 100644 --- a/resources/src/mediawiki.ui/components/radio.less +++ b/resources/src/mediawiki.ui/components/radio.less @@ -63,7 +63,6 @@ // the pseudo before element of the label after the radio now looks like a radio & + label::before { - .transition( 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) ); content: ''; cursor: pointer; .box-sizing(border-box); diff --git a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js index 0ef50950c0..f764513192 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js @@ -180,7 +180,9 @@ redirect: suggestionPage.redirect !== undefined, disambiguation: OO.getProp( suggestionPage, 'pageprops', 'disambiguation' ) !== undefined, imageUrl: OO.getProp( suggestionPage, 'thumbnail', 'source' ), - description: OO.getProp( suggestionPage, 'terms', 'description' ) + description: OO.getProp( suggestionPage, 'terms', 'description' ), + // sort index + index: suggestionPage.index }; // Throw away pages from wrong namespaces. This can happen when 'showRedirectTargets' is true @@ -201,6 +203,10 @@ } } + titles.sort( function ( a, b ) { + return pageData[ a ].index - pageData[ b ].index; + } ); + // If not found, run value through mw.Title to avoid treating a match as a // mismatch where normalisation would make them matching (bug 48476) diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js index 01917f8a4d..717e2ecfab 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js @@ -39,15 +39,20 @@ * @inheritdoc */ mw.ForeignStructuredUpload.BookletLayout.prototype.initialize = function () { - mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ); - // Point the CategorySelector to the right wiki as soon as we know what the right wiki is - this.upload.apiPromise.done( function ( api ) { - // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything - if ( api.apiUrl ) { - // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance - this.categoriesWidget.api = new mw.ForeignApi( api.apiUrl ); - } - }.bind( this ) ); + var deferred = $.Deferred(); + mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ) + .done( function () { + // Point the CategorySelector to the right wiki + this.upload.apiPromise.done( function ( api ) { + // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything + if ( api.apiUrl ) { + // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance + this.categoriesWidget.api = new mw.ForeignApi( api.apiUrl ); + } + deferred.resolve(); + }.bind( this ) ); + }.bind( this ) ); + return deferred.promise(); }; /** diff --git a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js index dd199ceff7..c616829d89 100644 --- a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js @@ -147,11 +147,14 @@ /** * Initialize for a new upload + * + * @return {jQuery.Promise} Promise resolved when everything is initialized */ mw.Upload.BookletLayout.prototype.initialize = function () { this.clear(); this.upload = this.createUpload(); this.setPage( 'upload' ); + return $.Deferred().resolve().promise(); }; /** diff --git a/resources/src/mediawiki/mediawiki.Upload.Dialog.js b/resources/src/mediawiki/mediawiki.Upload.Dialog.js index 220a3fe3bb..53afca812d 100644 --- a/resources/src/mediawiki/mediawiki.Upload.Dialog.js +++ b/resources/src/mediawiki/mediawiki.Upload.Dialog.js @@ -174,7 +174,7 @@ mw.Upload.Dialog.prototype.getSetupProcess = function ( data ) { return mw.Upload.Dialog.parent.prototype.getSetupProcess.call( this, data ) .next( function () { - this.uploadBooklet.initialize(); + return this.uploadBooklet.initialize(); }, this ); }; diff --git a/resources/src/mediawiki/mediawiki.jqueryMsg.js b/resources/src/mediawiki/mediawiki.jqueryMsg.js index de63a18ce9..2e89f6b4c6 100644 --- a/resources/src/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/src/mediawiki/mediawiki.jqueryMsg.js @@ -189,8 +189,7 @@ * @return {string} return.return Rendered HTML. */ mw.jqueryMsg.getMessageFunction = function ( options ) { - var failableParserFn = getFailableParserFn( options ), - format; + var failableParserFn, format; if ( options && options.format !== undefined ) { format = options.format; @@ -199,6 +198,9 @@ } return function () { + if ( !failableParserFn ) { + failableParserFn = getFailableParserFn( options ); + } var failableResult = failableParserFn( arguments ); if ( format === 'text' || format === 'escaped' ) { return failableResult.text(); @@ -231,9 +233,12 @@ * @return {jQuery} return.return */ mw.jqueryMsg.getPlugin = function ( options ) { - var failableParserFn = getFailableParserFn( options ); + var failableParserFn; return function () { + if ( !failableParserFn ) { + failableParserFn = getFailableParserFn( options ); + } var $target = this.empty(); appendWithoutParsing( $target, failableParserFn( arguments ) ); return $target; @@ -256,29 +261,6 @@ }; mw.jqueryMsg.parser.prototype = { - /** - * Cache mapping MediaWiki message keys and the value onlyCurlyBraceTransform, to the AST of the message. - * - * In most cases, the message is a string so this is identical. - * (This is why we would like to move this functionality server-side). - * - * The two parts of the key are separated by colon. For example: - * - * "message-key:true": ast - * - * if they key is "message-key" and onlyCurlyBraceTransform is true. - * - * This cache is shared by all instances of mw.jqueryMsg.parser. - * - * NOTE: We promise, it's static - when you create this empty object - * in the prototype, each new instance of the class gets a reference - * to the same object. - * - * @static - * @property {Object} - */ - astCache: {}, - /** * Where the magic happens. * Parses a message from the key, and swaps in replacements as necessary, wraps in jQuery @@ -300,17 +282,11 @@ * @return {string|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing */ getAst: function ( key ) { - var wikiText, - cacheKey = [ key, this.settings.onlyCurlyBraceTransform ].join( ':' ); - - if ( this.astCache[ cacheKey ] === undefined ) { - wikiText = this.settings.messages.get( key ); - if ( typeof wikiText !== 'string' ) { - wikiText = '\\[' + key + '\\]'; - } - this.astCache[ cacheKey ] = this.wikiTextToAst( wikiText ); + var wikiText = this.settings.messages.get( key ); + if ( typeof wikiText !== 'string' ) { + wikiText = '\\[' + key + '\\]'; } - return this.astCache[ cacheKey ]; + return this.wikiTextToAst( wikiText ); }, /** @@ -899,7 +875,6 @@ // I am deferring the work of turning it into prototypes & objects. It's quite fast enough // finally let's do some actual work... - // If you add another possible rootExpression, you must update the astCache key scheme. result = start( this.settings.onlyCurlyBraceTransform ? curlyBraceTransformExpression : expression ); /* diff --git a/resources/src/mediawiki/mediawiki.raggett.css b/resources/src/mediawiki/mediawiki.raggett.css new file mode 100644 index 0000000000..1e1e973ca7 --- /dev/null +++ b/resources/src/mediawiki/mediawiki.raggett.css @@ -0,0 +1,3 @@ +.mw-empty-li { + display: none; +} diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php index 093748d4a8..1c4851544d 100644 --- a/tests/TestsAutoLoader.php +++ b/tests/TestsAutoLoader.php @@ -77,9 +77,6 @@ $wgAutoloadClasses += array( 'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php", # tests/phpunit/includes/db - 'ORMRowTest' => "$testDir/phpunit/includes/db/ORMRowTest.php", - 'ORMTableTest' => "$testDir/phpunit/includes/db/ORMTableTest.php", - 'PageORMTableForTesting' => "$testDir/phpunit/includes/db/ORMTableTest.php", 'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php", # tests/phpunit/includes/diff diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index f245826d3d..4e6c591695 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -26405,3 +26405,19 @@ B foobar !! end + +!! test +Empty LI (T49673) +!! wikitext +* a +* +* +* b +!! html/php+tidy +
      +
    • a
    • +
    • +
    • +
    • b
    • +
    +!! end diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index 4ce51c6a27..a5b65f2320 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -149,9 +149,6 @@ class WfTimestampTest extends MediaWikiTestCase { } /** - * The Resource Loader uses wfTimestamp() to convert timestamps - * from If-Modified-Since header. Thus it must be able to parse all - * rfc2616 date formats * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 * @dataProvider provideHttpDates */ diff --git a/tests/phpunit/includes/ImportTest.php b/tests/phpunit/includes/ImportTest.php index 8ee2ad5aca..9c224309bb 100644 --- a/tests/phpunit/includes/ImportTest.php +++ b/tests/phpunit/includes/ImportTest.php @@ -9,16 +9,8 @@ */ class ImportTest extends MediaWikiLangTestCase { - private function getInputStreamSource( $xml ) { - if ( ini_get( 'allow_url_fopen' ) != 1 ) { - $this->markTestSkipped( 'bug 73283: this test needs allow_url_fopen to be enabled' ); - } - $file = 'data:application/xml,' . $xml; - $status = ImportStreamSource::newFromFile( $file ); - if ( !$status->isGood() ) { - throw new MWException( "Cannot create InputStreamSource." ); - } - return $status->value; + private function getDataSource( $xml ) { + return new ImportStringSource( $xml ); } /** @@ -28,7 +20,7 @@ class ImportTest extends MediaWikiLangTestCase { * @param string|null $redirectTitle */ public function testHandlePageContainsRedirect( $xml, $redirectTitle ) { - $source = $this->getInputStreamSource( $xml ); + $source = $this->getDataSource( $xml ); $redirect = null; $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount, @@ -114,7 +106,7 @@ EOF * @param array|null $namespaces */ public function testSiteInfoContainsNamespaces( $xml, $namespaces ) { - $source = $this->getInputStreamSource( $xml ); + $source = $this->getDataSource( $xml ); $importNamespaces = null; $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) { diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php deleted file mode 100644 index 807bd14e47..0000000000 --- a/tests/phpunit/includes/db/ORMRowTest.php +++ /dev/null @@ -1,225 +0,0 @@ - - */ -abstract class ORMRowTest extends \MediaWikiTestCase { - - /** - * @since 1.20 - * @return string - */ - abstract protected function getRowClass(); - - /** - * @since 1.20 - * @return IORMTable - */ - abstract protected function getTableInstance(); - - /** - * @since 1.20 - * @return array - */ - abstract public function constructorTestProvider(); - - /** - * @since 1.20 - * @param IORMRow $row - * @param array $data - */ - protected function verifyFields( IORMRow $row, array $data ) { - foreach ( array_keys( $data ) as $fieldName ) { - $this->assertEquals( $data[$fieldName], $row->getField( $fieldName ) ); - } - } - - /** - * @since 1.20 - * @param array $data - * @param bool $loadDefaults - * @return IORMRow - */ - protected function getRowInstance( array $data, $loadDefaults ) { - $class = $this->getRowClass(); - - return new $class( $this->getTableInstance(), $data, $loadDefaults ); - } - - /** - * @since 1.20 - * @return array - */ - protected function getMockValues() { - return array( - 'id' => 1, - 'str' => 'foobar4645645', - 'int' => 42, - 'float' => 4.2, - 'bool' => true, - 'array' => array( 42, 'foobar' ), - 'blob' => new stdClass() - ); - } - - /** - * @since 1.20 - * @return array - */ - protected function getMockFields() { - $mockValues = $this->getMockValues(); - $mockFields = array(); - - foreach ( $this->getTableInstance()->getFields() as $name => $type ) { - if ( $name !== 'id' ) { - $mockFields[$name] = $mockValues[$type]; - } - } - - return $mockFields; - } - - /** - * @since 1.20 - * @return array Array of IORMRow - */ - public function instanceProvider() { - $instances = array(); - - foreach ( $this->constructorTestProvider() as $arguments ) { - $instances[] = array( call_user_func_array( array( $this, 'getRowInstance' ), $arguments ) ); - } - - return $instances; - } - - /** - * @dataProvider constructorTestProvider - */ - public function testConstructor( array $data, $loadDefaults ) { - $this->verifyFields( $this->getRowInstance( $data, $loadDefaults ), $data ); - } - - /** - * @dataProvider constructorTestProvider - */ - public function testSaveAndRemove( array $data, $loadDefaults ) { - $item = $this->getRowInstance( $data, $loadDefaults ); - - $this->assertTrue( $item->save() ); - - $this->assertTrue( $item->hasIdField() ); - $this->assertTrue( is_integer( $item->getId() ) ); - - $id = $item->getId(); - - $this->assertTrue( $item->save() ); - - $this->assertEquals( $id, $item->getId() ); - - $this->verifyFields( $item, $data ); - - $this->assertTrue( $item->remove() ); - - $this->assertFalse( $item->hasIdField() ); - - $this->assertTrue( $item->save() ); - - $this->verifyFields( $item, $data ); - - $this->assertTrue( $item->remove() ); - - $this->assertFalse( $item->hasIdField() ); - - $this->verifyFields( $item, $data ); - } - - /** - * @dataProvider instanceProvider - */ - public function testSetField( IORMRow $item ) { - foreach ( $this->getMockFields() as $name => $value ) { - $item->setField( $name, $value ); - $this->assertEquals( $value, $item->getField( $name ) ); - } - } - - /** - * @since 1.20 - * @param array $expected - * @param IORMRow $item - */ - protected function assertFieldValues( array $expected, IORMRow $item ) { - foreach ( $expected as $name => $type ) { - if ( $name !== 'id' ) { - $this->assertEquals( $expected[$name], $item->getField( $name ) ); - } - } - } - - /** - * @dataProvider instanceProvider - */ - public function testSetFields( IORMRow $item ) { - $originalValues = $item->getFields(); - - $item->setFields( array(), false ); - - foreach ( $item->getTable()->getFields() as $name => $type ) { - $originalHas = array_key_exists( $name, $originalValues ); - $newHas = $item->hasField( $name ); - - $this->assertEquals( $originalHas, $newHas ); - - if ( $originalHas && $newHas ) { - $this->assertEquals( $originalValues[$name], $item->getField( $name ) ); - } - } - - $mockFields = $this->getMockFields(); - - $item->setFields( $mockFields, false ); - - $this->assertFieldValues( $originalValues, $item ); - - $item->setFields( $mockFields, true ); - - $this->assertFieldValues( $mockFields, $item ); - } - - // TODO: test all of the methods! - -} diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php deleted file mode 100644 index 764560d528..0000000000 --- a/tests/phpunit/includes/db/ORMTableTest.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @author Daniel Kinzler - */ - -class ORMTableTest extends MediaWikiTestCase { - - /** - * @since 1.21 - * @return string - */ - protected function getTableClass() { - return 'PageORMTableForTesting'; - } - - /** - * @since 1.21 - * @return IORMTable - */ - public function getTable() { - $class = $this->getTableClass(); - - return $class::singleton(); - } - - /** - * @since 1.21 - * @return string - */ - public function getRowClass() { - return $this->getTable()->getRowClass(); - } - - /** - * @since 1.21 - */ - public function testSingleton() { - $class = $this->getTableClass(); - - $this->assertInstanceOf( $class, $class::singleton() ); - $this->assertTrue( $class::singleton() === $class::singleton() ); - } -} - -/** - * Dummy ORM table for testing, reading Title objects from the page table. - * - * @since 1.21 - */ - -class PageORMTableForTesting extends ORMTable { - - public function __construct() { - $this->fieldPrefix = 'page_'; - } - - /** - * @see ORMTable::getName - * - * @return string - */ - public function getName() { - return 'page'; - } - - /** - * @see ORMTable::getRowClass - * - * @return string - */ - public function getRowClass() { - return 'Title'; - } - - /** - * @see ORMTable::newRow - * - * @return IORMRow - */ - public function newRow( array $data, $loadDefaults = false ) { - return Title::makeTitle( $data['namespace'], $data['title'] ); - } - - /** - * @see ORMTable::getFields - * - * @return array - */ - public function getFields() { - return array( - 'id' => 'int', - 'namespace' => 'int', - 'title' => 'str', - ); - } -} diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php deleted file mode 100644 index 04bb9f3814..0000000000 --- a/tests/phpunit/includes/db/TestORMRowTest.php +++ /dev/null @@ -1,207 +0,0 @@ - - */ - -/** - * The database group has as a side effect that temporal database tables are created. This makes - * it possible to test without poisoning a production database. - * - * Some of the tests takes more time, and needs therefor longer time before they can be aborted - * as non-functional. The reason why tests are aborted is assumed to be set up of temporal databases - * that hold the first tests in a pending state awaiting access to the database. - * - * @since 1.20 - * - * @group ORM - * @group Database - * @group medium - * @covers TestORMRow - */ -class TestORMRowTest extends ORMRowTest { - - /** - * @since 1.20 - * @return string - */ - protected function getRowClass() { - return 'TestORMRow'; - } - - /** - * @since 1.20 - * @return IORMTable - */ - protected function getTableInstance() { - return TestORMTable::singleton(); - } - - protected function setUp() { - parent::setUp(); - - $dbw = wfGetDB( DB_MASTER ); - - $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite'; - $isPostgres = $GLOBALS['wgDBtype'] === 'postgres'; - - $idField = $isSqlite ? 'INTEGER' : 'INT unsigned'; - $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 'auto_increment PRIMARY KEY'; - - if ( $isPostgres ) { - $dbw->query( - 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . "( - test_id serial PRIMARY KEY, - test_name TEXT NOT NULL DEFAULT '', - test_age INTEGER NOT NULL DEFAULT 0, - test_height REAL NOT NULL DEFAULT 0, - test_awesome INTEGER NOT NULL DEFAULT 0, - test_stuff BYTEA, - test_moarstuff BYTEA, - test_time TIMESTAMPTZ - );", - __METHOD__ - ); - } else { - $dbw->query( - 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( - test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', - test_name VARCHAR(255) NOT NULL, - test_age TINYINT unsigned NOT NULL, - test_height FLOAT NOT NULL, - test_awesome TINYINT unsigned NOT NULL, - test_stuff BLOB NOT NULL, - test_moarstuff BLOB NOT NULL, - test_time varbinary(14) NOT NULL - );', - __METHOD__ - ); - } - } - - protected function tearDown() { - $dbw = wfGetDB( DB_MASTER ); - $dbw->dropTable( 'orm_test', __METHOD__ ); - - parent::tearDown(); - } - - public function constructorTestProvider() { - $dbw = wfGetDB( DB_MASTER ); - return array( - array( - array( - 'name' => 'Foobar', - 'time' => $dbw->timestamp( '20120101020202' ), - 'age' => 42, - 'height' => 9000.1, - 'awesome' => true, - 'stuff' => array( 13, 11, 7, 5, 3, 2 ), - 'moarstuff' => (object)array( 'foo' => 'bar', 'bar' => array( 4, 2 ), 'baz' => true ) - ), - true - ), - ); - } - - /** - * @since 1.21 - * @return array - */ - protected function getMockValues() { - return array( - 'id' => 1, - 'str' => 'foobar4645645', - 'int' => 42, - 'float' => 4.2, - 'bool' => '', - 'array' => array( 42, 'foobar' ), - 'blob' => new stdClass() - ); - } -} - -class TestORMRow extends ORMRow { -} - -class TestORMTable extends ORMTable { - - public function __construct() { - $this->fieldPrefix = 'test_'; - } - - /** - * Returns the name of the database table objects of this type are stored in. - * - * @since 1.20 - * - * @return string - */ - public function getName() { - return 'orm_test'; - } - - /** - * Returns the name of a IORMRow implementing class that - * represents single rows in this table. - * - * @since 1.20 - * - * @return string - */ - public function getRowClass() { - return 'TestORMRow'; - } - - /** - * Returns an array with the fields and their types this object contains. - * This corresponds directly to the fields in the database, without prefix. - * - * field name => type - * - * Allowed types: - * * id - * * str - * * int - * * float - * * bool - * * array - * * blob - * - * @since 1.20 - * - * @return array - */ - public function getFields() { - return array( - 'id' => 'id', - 'name' => 'str', - 'age' => 'int', - 'height' => 'float', - 'awesome' => 'bool', - 'stuff' => 'array', - 'moarstuff' => 'blob', - 'time' => 'str', // TS_MW - ); - } -} diff --git a/tests/phpunit/includes/libs/RunningStatTest.php b/tests/phpunit/includes/libs/RunningStatTest.php deleted file mode 100644 index 35a8e4ffac..0000000000 --- a/tests/phpunit/includes/libs/RunningStatTest.php +++ /dev/null @@ -1,79 +0,0 @@ -points as $point ) { - $rstat->push( $point ); - } - - $mean = array_sum( $this->points ) / count( $this->points ); - $variance = array_sum( array_map( function ( $x ) use ( $mean ) { - return pow( $mean - $x, 2 ); - }, $this->points ) ) / ( count( $rstat ) - 1 ); - $stddev = sqrt( $variance ); - - $this->assertEquals( count( $rstat ), count( $this->points ) ); - $this->assertEquals( $rstat->min, min( $this->points ) ); - $this->assertEquals( $rstat->max, max( $this->points ) ); - $this->assertEquals( $rstat->getMean(), $mean ); - $this->assertEquals( $rstat->getVariance(), $variance ); - $this->assertEquals( $rstat->getStdDev(), $stddev ); - } - - /** - * When one RunningStat instance is merged into another, the state of the - * target RunningInstance should have the state that it would have had if - * all the data had been accumulated by it alone. - * @covers RunningStat::merge - * @covers RunningStat::count - */ - public function testRunningStatMerge() { - $expected = new RunningStat(); - - foreach ( $this->points as $point ) { - $expected->push( $point ); - } - - // Split the data into two sets - $sets = array_chunk( $this->points, floor( count( $this->points ) / 2 ) ); - - // Accumulate the first half into one RunningStat object - $first = new RunningStat(); - foreach ( $sets[0] as $point ) { - $first->push( $point ); - } - - // Accumulate the second half into another RunningStat object - $second = new RunningStat(); - foreach ( $sets[1] as $point ) { - $second->push( $point ); - } - - // Merge the second RunningStat object into the first - $first->merge( $second ); - - $this->assertEquals( count( $first ), count( $this->points ) ); - $this->assertEquals( $first, $expected ); - } -} diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php index c3702c5945..a1fdd91f62 100644 --- a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php +++ b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php @@ -175,7 +175,6 @@ class WANObjectCacheTest extends MediaWikiTestCase { return $value; }; - $cache->delete( $key ); $ret = $cache->getWithSetCallback( $key, 30, $func, array( 'lockTSE' => 5 ) ); $this->assertEquals( $value, $ret ); $this->assertEquals( 1, $calls, 'Value was populated' ); @@ -187,6 +186,36 @@ class WANObjectCacheTest extends MediaWikiTestCase { $this->assertEquals( 1, $calls, 'Callback was not used' ); } + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testLockTSESlow() { + $cache = $this->cache; + $key = wfRandomString(); + $value = wfRandomString(); + + $calls = 0; + $func = function( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) { + ++$calls; + $setOpts['since'] = microtime( true ) - 10; + return $value; + }; + + // Value should be marked as stale due to snapshot lag + $curTTL = null; + $ret = $cache->getWithSetCallback( $key, 30, $func, array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' ); + $this->assertLessThan( 0, $curTTL, 'Value has negative curTTL' ); + $this->assertEquals( 1, $calls, 'Value was generated' ); + + // Acquire a lock to verify that getWithSetCallback uses lockTSE properly + $this->internalCache->lock( $key, 0 ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Callback was not used' ); + } + /** * @covers WANObjectCache::getMulti() */ @@ -313,4 +342,32 @@ class WANObjectCacheTest extends MediaWikiTestCase { $t6 = $this->cache->getCheckKeyTime( $key ); $this->assertEquals( $t5, $t6, 'Check key time did not change' ); } + + public function testSetWithLag() { + $value = 1; + + $key = wfRandomString(); + $opts = array( 'lag' => 300, 'since' => microtime( true ) ); + $this->cache->set( $key, $value, 30, $opts ); + $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." ); + + $key = wfRandomString(); + $opts = array( 'lag' => 0, 'since' => microtime( true ) - 300 ); + $this->cache->set( $key, $value, 30, $opts ); + $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." ); + + $key = wfRandomString(); + $opts = array( 'lag' => 5, 'since' => microtime( true ) - 5 ); + $this->cache->set( $key, $value, 30, $opts ); + $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." ); + } + + public function testWritePending() { + $value = 1; + + $key = wfRandomString(); + $opts = array( 'pending' => true ); + $this->cache->set( $key, $value, 30, $opts ); + $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." ); + } } diff --git a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php index b0c9e4f389..3e3bac37cb 100644 --- a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php @@ -12,7 +12,7 @@ class MemcachedBagOStuffTest extends MediaWikiTestCase { } /** - * @covers MemcachedBagOStuff::makeKeyInternal + * @covers MemcachedBagOStuff::makeKey */ public function testKeyNormalization() { $this->assertEquals( @@ -67,4 +67,39 @@ class MemcachedBagOStuffTest extends MediaWikiTestCase { $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) ) ); } + + /** + * @dataProvider validKeyProvider + */ + public function testValidateKeyEncoding( $key ) { + $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) ); + } + + public function validKeyProvider() { + return array( + 'empty' => array( '' ), + 'digits' => array( '09' ), + 'letters' => array( 'AZaz' ), + 'ASCII special characters' => array( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ), + ); + } + + /** + * @dataProvider invalidKeyProvider + */ + public function testValidateKeyEncodingThrowsException( $key ) { + $this->setExpectedException( 'Exception' ); + $this->cache->validateKeyEncoding( $key ); + } + + public function invalidKeyProvider() { + return array( + array( "\x00" ), + array( ' ' ), + array( "\x1F" ), + array( "\x7F" ), + array( "\x80" ), + array( "\xFF" ), + ); + } } diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index 4fca00237a..77c3c02368 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -261,6 +261,16 @@ class LanguageTest extends LanguageClassesTestCase { $this->getLang()->truncate( "1234567890", 5, 'XXX', false ), 'truncate without adjustment' ); + $this->assertEquals( + "泰乐菌...", + $this->getLang()->truncate( "泰乐菌素123456789", 11, '...', false ), + 'truncate does not chop Unicode characters in half' + ); + $this->assertEquals( + "\n泰乐菌...", + $this->getLang()->truncate( "\n泰乐菌素123456789", 12, '...', false ), + 'truncate does not chop Unicode characters in half if there is a preceding newline' + ); } /** diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index 49b91c3808..aaa77514f6 100755 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -113,6 +113,11 @@ class PHPUnitMaintClass extends Maintenance { ); // xdebug's default of 100 is too low for MediaWiki ini_set( 'xdebug.max_nesting_level', 1000 ); + + // Bug T116683 serialize_precision of 100 + // may break testing against floating point values + // treated with PHP's serialize() + ini_set( 'serialize_precision', 17 ); } public function execute() { diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php index ae0d325948..3282665b67 100644 --- a/tests/phpunit/structure/ResourcesTest.php +++ b/tests/phpunit/structure/ResourcesTest.php @@ -232,7 +232,6 @@ class ResourcesTest extends MediaWikiTestCase { 'lists' => array( 'scripts', 'debugScripts', - 'loaderScripts', 'styles', ), diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 4f273bc5f6..24528bbe90 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1078,4 +1078,33 @@ assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' ); } ); + QUnit.test( 'Integration', 4, function ( assert ) { + var expected, logSpy; + + expected = 'Bold!'; + mw.messages.set( 'integration-test', '[[Bold]]!' ); + + this.suppressWarnings(); + logSpy = this.sandbox.spy( mw.log, 'warn' ); + assert.equal( + window.gM( 'integration-test' ), + expected, + 'Global function gM() works correctly' + ); + assert.equal( logSpy.callCount, 1, 'mw.log.warn called' ); + this.restoreWarnings(); + + assert.equal( + mw.message( 'integration-test' ).parse(), + expected, + 'mw.message().parse() works correctly' + ); + + assert.equal( + $( '' ).msg( 'integration-test' ).html(), + expected, + 'jQuery plugin $.fn.msg() works correctly' + ); + } ); + }( mediaWiki, jQuery ) ); diff --git a/thumb.php b/thumb.php index bd14e417a6..c699bb19ab 100644 --- a/thumb.php +++ b/thumb.php @@ -376,23 +376,28 @@ function wfStreamThumb( array $params ) { * @return array (MediaTransformOutput|bool, string|bool error message HTML) */ function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath ) { - global $wgMemc, $wgAttemptFailureEpoch; + global $wgAttemptFailureEpoch; - $key = wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch, - $file->getRepo()->getName(), $file->getSha1(), md5( $thumbName ) ); + $cache = ObjectCache::getLocalClusterInstance(); + $key = $cache->makeKey( + 'attempt-failures', + $wgAttemptFailureEpoch, + $file->getRepo()->getName(), + $file->getSha1(), + md5( $thumbName ) + ); // Check if this file keeps failing to render - if ( $wgMemc->get( $key ) >= 4 ) { + if ( $cache->get( $key ) >= 4 ) { return array( false, wfMessage( 'thumbnail_image-failure-limit', 4 ) ); } $done = false; // Record failures on PHP fatals in addition to caching exceptions - register_shutdown_function( function () use ( &$done, $key ) { + register_shutdown_function( function () use ( $cache, &$done, $key ) { if ( !$done ) { // transform() gave a fatal - global $wgMemc; // Randomize TTL to reduce stampedes - $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) ); + $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) ); } } ); @@ -445,7 +450,7 @@ function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath if ( !$thumb || $thumb->isError() ) { // Randomize TTL to reduce stampedes - $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) ); + $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) ); } return array( $thumb, $errorHtml );