Merge "Fix password policy handling in temporary password provider"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 20 Feb 2019 20:48:33 +0000 (20:48 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 20 Feb 2019 20:48:33 +0000 (20:48 +0000)
46 files changed:
.fresnel.yml [new file with mode: 0644]
.gitignore
Gruntfile.js
RELEASE-NOTES-1.33
includes/api/ApiBase.php
includes/api/ApiEditPage.php
includes/api/ApiMove.php
includes/cache/MessageBlobStore.php
includes/cache/localisation/LocalisationCache.php
includes/changes/ChangesList.php
includes/collation/IcuCollation.php
includes/diff/DifferenceEngine.php
includes/installer/DatabaseInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/i18n/is.json
includes/jobqueue/JobQueueDB.php
includes/libs/MultiHttpClient.php
includes/libs/objectcache/WANObjectCache.php
includes/parser/LinkHolderArray.php
includes/resourceloader/ResourceLoader.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialContributions.php
includes/specials/pagers/ActiveUsersPager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/NewPagesPager.php
includes/specials/pagers/ProtectedPagesPager.php
languages/data/Names.php
languages/i18n/bqi.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/et.json
languages/i18n/gom-latn.json
languages/i18n/he.json
languages/i18n/is.json
languages/i18n/qqq.json
languages/i18n/zh-hant.json
languages/messages/MessagesGcr.php
maintenance/includes/DeleteLocalPasswords.php
resources/src/mediawiki.toc.styles/screen.less
tests/phpunit/includes/CommentStoreTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/api/ApiBaseTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/api/ApiMoveTest.php
tests/phpunit/includes/resourceloader/MessageBlobStoreTest.php

diff --git a/.fresnel.yml b/.fresnel.yml
new file mode 100644 (file)
index 0000000..f081fb5
--- /dev/null
@@ -0,0 +1,17 @@
+warmup: true
+runs: 3
+scenarios:
+  # View the Main Page without redirect
+  - url: "{MW_SERVER}{MW_SCRIPT_PATH}/index.php?mainpage"
+    viewport:
+      width: 1100
+      height: 700
+    reports:
+      - navtiming
+      - paint
+      - transfer
+    probes:
+      - screenshot
+      - trace
+    # alerts:
+    #   navtiming/loadEventEnd: 10%
index d25d525..def5a08 100644 (file)
@@ -52,6 +52,7 @@ npm-debug.log
 node_modules/
 /tests/phpunit/phpunit.phar
 /tests/selenium/log
+.eslintcache
 
 # Composer
 /vendor
index 2592815..fbb93bf 100644 (file)
@@ -22,7 +22,8 @@ module.exports = function ( grunt ) {
        grunt.initConfig( {
                eslint: {
                        options: {
-                               reportUnusedDisableDirectives: true
+                               reportUnusedDisableDirectives: true,
+                               cache: true
                        },
                        all: [
                                '**/*.js',
index af67dc7..c26f453 100644 (file)
@@ -100,6 +100,8 @@ production.
   deletion will be processed via the job queue.
 * action=setnotificationtimestamp will now update the watchlist asynchronously
   if entirewatchlist is set, so updates may not be visible immediately
+* Block info will be added to "blocked" errors from more modules.
+* (T216245) Autoblocks will now be spread by action=edit and action=move.
 
 === Action API internal changes in 1.33 ===
 * A number of deprecated methods for API documentation, intended for overriding
@@ -116,6 +118,8 @@ production.
   hyphen. Methods such as ApiBase::dieWithError() and
   ApiMessageTrait::setApiCode() will throw an InvalidArgumentException if
   passed a bad code.
+* ApiBase::checkTitleUserPermissions() now takes an options array as its third
+  parameter. Passing a User object or null is deprecated.
 
 === Languages updated in 1.33 ===
 MediaWiki supports over 350 languages. Many localisations are updated regularly.
@@ -253,6 +257,9 @@ because of Phabricator reports.
 * Removed deprecated class property WikiRevision::$importer.
 * ResourceLoaderFileModule::readStyleFiles() now requires its $context
   parameter.
+* The ChangeList::insertArticleLink() method, that was deprecated in 1.27, has
+  been removed.
+* MessageBlobStore::__construct() now requires its $rl parameter.
 
 === Deprecations in 1.33 ===
 * The configuration option $wgUseESI has been deprecated, and is expected
@@ -302,6 +309,9 @@ because of Phabricator reports.
   Use require( 'mediawiki.language.specialCharacters' ) instead.
 * ChangeTags::purgeTagUsageCache() has been deprecated, and is expected to be
   removed in a future release.
+* Passing a User object or null as the third parameter to
+  ApiBase::checkTitleUserPermissions() has been deprecated. Pass an array
+  [ 'user' => $user ] instead.
 
 === Other changes in 1.33 ===
 * (T201747) Html::openElement() warns if given an element name with a space
index 21e20c2..dfaff8b 100644 (file)
@@ -271,6 +271,14 @@ abstract class ApiBase extends ContextSource {
        /** @var int[][][] Cache for self::filterIDs() */
        private static $filterIDsCache = [];
 
+       /** $var array Map of web UI block messages to corresponding API messages and codes */
+       private static $blockMsgMap = [
+               'blockedtext' => [ 'apierror-blocked', 'blocked' ],
+               'blockedtext-partial' => [ 'apierror-blocked', 'blocked' ],
+               'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
+               'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
+       ];
+
        /** @var ApiMain */
        private $mMainModule;
        /** @var string */
@@ -1797,28 +1805,9 @@ abstract class ApiBase extends ContextSource {
 
                $status = Status::newGood();
                foreach ( $errors as $error ) {
-                       if ( is_array( $error ) && $error[0] === 'blockedtext' && $user->getBlock() ) {
-                               $status->fatal( ApiMessage::create(
-                                       'apierror-blocked',
-                                       'blocked',
-                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
-                               ) );
-                       } elseif ( is_array( $error ) && $error[0] === 'blockedtext-partial' && $user->getBlock() ) {
-                               $status->fatal( ApiMessage::create(
-                                       'apierror-blocked-partial',
-                                       'blocked',
-                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
-                               ) );
-                       } elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
-                               $status->fatal( ApiMessage::create(
-                                       'apierror-autoblocked',
-                                       'autoblocked',
-                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
-                               ) );
-                       } elseif ( is_array( $error ) && $error[0] === 'systemblockedtext' && $user->getBlock() ) {
-                               $status->fatal( ApiMessage::create(
-                                       'apierror-systemblocked',
-                                       'blocked',
+                       if ( is_array( $error ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
+                               list( $msg, $code ) = self::$blockMsgMap[$error[0]];
+                               $status->fatal( ApiMessage::create( $msg, $code,
                                        [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
                                ) );
                        } else {
@@ -1828,6 +1817,26 @@ abstract class ApiBase extends ContextSource {
                return $status;
        }
 
+       /**
+        * Add block info to block messages in a Status
+        * @since 1.33
+        * @param StatusValue $status
+        * @param User|null $user
+        */
+       public function addBlockInfoToStatus( StatusValue $status, User $user = null ) {
+               if ( $user === null ) {
+                       $user = $this->getUser();
+               }
+
+               foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
+                       if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
+                               $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
+                                       [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
+                               ) );
+                       }
+               }
+       }
+
        /**
         * Call wfTransactionalTimeLimit() if this request was POSTed
         * @since 1.26
@@ -2065,6 +2074,7 @@ abstract class ApiBase extends ContextSource {
                        $status = $newStatus;
                }
 
+               $this->addBlockInfoToStatus( $status );
                throw new ApiUsageException( $this, $status );
        }
 
@@ -2102,15 +2112,21 @@ abstract class ApiBase extends ContextSource {
        /**
         * Helper function for permission-denied errors
         * @since 1.29
+        * @since 1.33 Changed the third parameter from $user to $options.
         * @param Title $title
         * @param string|string[] $actions
-        * @param User|null $user
+        * @param array $options Additional options
+        *   - user: (User) User to use rather than $this->getUser()
+        *   - autoblock: (bool, default false) Whether to spread autoblocks
+        *  For compatibility, passing a User object is treated as the value for the 'user' option.
         * @throws ApiUsageException if the user doesn't have all of the rights.
         */
-       public function checkTitleUserPermissions( Title $title, $actions, $user = null ) {
-               if ( !$user ) {
-                       $user = $this->getUser();
+       public function checkTitleUserPermissions( Title $title, $actions, $options = [] ) {
+               if ( !is_array( $options ) ) {
+                       wfDeprecated( '$user as the third parameter to ' . __METHOD__, '1.33' );
+                       $options = [ 'user' => $options ];
                }
+               $user = $options['user'] ?? $this->getUser();
 
                $errors = [];
                foreach ( (array)$actions as $action ) {
@@ -2123,6 +2139,10 @@ abstract class ApiBase extends ContextSource {
                                $this->trackBlockNotices( $errors );
                        }
 
+                       if ( !empty( $options['autoblock'] ) ) {
+                               $user->spreadAnyEditBlock();
+                       }
+
                        $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
                }
        }
index 5e5efa5..8131ea5 100644 (file)
@@ -116,7 +116,8 @@ class ApiEditPage extends ApiBase {
                // Now let's check whether we're even allowed to do this
                $this->checkTitleUserPermissions(
                        $titleObj,
-                       $titleObj->exists() ? 'edit' : [ 'edit', 'create' ]
+                       $titleObj->exists() ? 'edit' : [ 'edit', 'create' ],
+                       [ 'autoblock' => true ]
                );
 
                $toMD5 = $params['text'];
index f6b6b35..cc4490e 100644 (file)
@@ -86,6 +86,7 @@ class ApiMove extends ApiBase {
                $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'],
                        $params['tags'] ?: [] );
                if ( !$status->isOK() ) {
+                       $user->spreadAnyEditBlock();
                        $this->dieStatus( $status );
                }
 
index 19c4997..ceb51f2 100644 (file)
@@ -37,7 +37,7 @@ use Wikimedia\Rdbms\Database;
  */
 class MessageBlobStore implements LoggerAwareInterface {
 
-       /* @var ResourceLoader|null */
+       /* @var ResourceLoader */
        private $resourceloader;
 
        /**
@@ -51,13 +51,13 @@ class MessageBlobStore implements LoggerAwareInterface {
        protected $wanCache;
 
        /**
-        * @param ResourceLoader|null $rl
+        * @param ResourceLoader $rl
         * @param LoggerInterface|null $logger
         */
-       public function __construct( ResourceLoader $rl = null, LoggerInterface $logger = null ) {
+       public function __construct( ResourceLoader $rl, LoggerInterface $logger = null ) {
                $this->resourceloader = $rl;
                $this->logger = $logger ?: new NullLogger();
-               $this->wanCache = ObjectCache::getMainWANInstance();
+               $this->wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
        }
 
        /**
@@ -191,12 +191,6 @@ class MessageBlobStore implements LoggerAwareInterface {
         * @return ResourceLoader
         */
        protected function getResourceLoader() {
-               // Back-compat: This class supports instantiation without a ResourceLoader object.
-               // Lazy-initialise this property because most callers don't need it.
-               if ( $this->resourceloader === null ) {
-                       $this->logger->warning( __CLASS__ . ' created without a ResourceLoader instance' );
-                       $this->resourceloader = MediaWikiServices::getInstance()->getResourceLoader();
-               }
                return $this->resourceloader;
        }
 
index 21b262a..1d00d19 100644 (file)
@@ -1034,7 +1034,9 @@ class LocalisationCache {
                # HACK: If using a null (i.e. disabled) storage backend, we
                # can't write to the MessageBlobStore either
                if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
-                       $blobStore = new MessageBlobStore();
+                       $blobStore = new MessageBlobStore(
+                               MediaWikiServices::getInstance()->getResourceLoader()
+                       );
                        $blobStore->clear();
                }
        }
index 7a54f95..b8ab971 100644 (file)
@@ -472,17 +472,6 @@ class ChangesList extends ContextSource {
                        ' <span class="mw-changeslist-separator"></span> ';
        }
 
-       /**
-        * @param string &$s Article link will be appended to this string, in place.
-        * @param RecentChange $rc
-        * @param bool $unpatrolled
-        * @param bool $watched
-        * @deprecated since 1.27, use getArticleLink instead.
-        */
-       public function insertArticleLink( &$s, RecentChange $rc, $unpatrolled, $watched ) {
-               $s .= $this->getArticleLink( $rc, $unpatrolled, $watched );
-       }
-
        /**
         * @param RecentChange &$rc
         * @param bool $unpatrolled
index 8fea3ec..d45b2ce 100644 (file)
@@ -22,7 +22,7 @@
  * @since 1.16.3
  */
 class IcuCollation extends Collation {
-       const FIRST_LETTER_VERSION = 3;
+       const FIRST_LETTER_VERSION = 4;
 
        /** @var Collator */
        private $primaryCollator;
@@ -225,6 +225,7 @@ class IcuCollation extends Collation {
                'tl' => [ "Ñ", "Ng" ], // not in libicu
                'to' => [ "Ng", "ʻ" ],
                'tr' => [ "Ç", "Ğ", "İ", "Ö", "Ş", "Ü" ],
+               '-tr' => [ "ı" ],
                'tt' => [ "Ә", "Ө", "Ү", "Җ", "Ң", "Һ" ], // not in libicu
                'uk' => [ "Ґ", "Ь" ],
                'uz' => [ "Ch", "G'", "Ng", "O'", "Sh" ], // not in libicu
index 43bc6e4..87863a4 100644 (file)
@@ -1033,7 +1033,7 @@ class DifferenceEngine extends ContextSource {
                        // Try cache
                        if ( !$this->mRefreshCache ) {
                                $difftext = $cache->get( $key );
-                               if ( $difftext ) {
+                               if ( is_string( $difftext ) ) {
                                        wfIncrStats( 'diff_cache.hit' );
                                        $difftext = $this->localiseDiff( $difftext );
                                        $difftext .= "\n<!-- diff cache key $key -->\n";
index bb30d3d..6315de4 100644 (file)
@@ -373,6 +373,7 @@ abstract class DatabaseInstaller {
        /**
         * Perform database upgrades
         *
+        * @suppress SecurityCheck-XSS Escaping provided by $this->outputHandler
         * @return bool
         */
        public function doUpgrade() {
index d64e2d7..7a92807 100644 (file)
@@ -1074,7 +1074,9 @@ abstract class DatabaseUpdater {
                }
 
                // ResourceLoader: Message cache
-               $blobStore = new MessageBlobStore();
+               $blobStore = new MessageBlobStore(
+                       MediaWikiServices::getInstance()->getResourceLoader()
+               );
                $blobStore->clear();
 
                // ResourceLoader: File-dependency cache
index 5a3d77a..20661f2 100644 (file)
@@ -1464,6 +1464,7 @@ abstract class Installer {
        /**
         * Installs the auto-detected extensions.
         *
+        * @suppress SecurityCheck-OTHER It thinks $exts/$IP is user controlled but they are not.
         * @return Status
         */
        protected function includeExtensions() {
index 93039ff..707fc24 100644 (file)
        "config-env-hhvm": "HHVM $1 er uppsett.",
        "config-apc": "[https://secure.php.net/apc APC] er uppsett",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] er uppsett",
-       "config-diff3-bad": "GNU diff3 fannst ekki.",
+       "config-diff3-bad": "GNU diff3 textasamanburðartólið fannst ekki. Þú getur hunsað þetta núna, en þú gætir lent oftar í breytingaárekstrum.",
        "config-using-server": "Nota \"<nowiki>$1</nowiki>\" sem heiti á þjóni.",
        "config-using-uri": "Nota \"<nowiki>$1$2</nowiki>\" sem slóð á þjón.",
        "config-db-type": "Tegund gagnagrunns:",
        "config-db-host": "Netþjónn gagnagrunns:",
-       "config-db-name": "Heiti gagnagrunns:",
+       "config-db-name": "Heiti gagnagrunns (engin bandstrik):",
        "config-db-name-oracle": "Gagnagrunnsskema:",
        "config-db-username": "Notandanafn á gagnagrunni:",
        "config-db-password": "Lykilorð gagnagrunns:",
        "config-db-port": "Gátt gagnagrunns:",
        "config-sqlite-dir": "Gagnamappa SQLite:",
-       "config-type-mysql": "MySQL (eða samhæft)",
+       "config-type-mysql": "MariaDB, MySQL, eða samhæft",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
-       "config-header-mysql": "Stillingar MySQL",
+       "config-header-mysql": "Stillingar MariaDB/MySQL",
        "config-header-postgres": "Stillingar PostgreSQL",
        "config-header-sqlite": "Stillingar SQLite",
        "config-header-oracle": "Stillingar Oracle",
@@ -61,7 +61,7 @@
        "config-show-table-status": "<code>SHOW TABLE STATUS</code> beiðni mistókst!",
        "config-db-web-account": "Gagnagrunnsreikningur fyrir vefaðgang",
        "config-mysql-engine": "Gagnagrunnshýsing:",
-       "config-mysql-innodb": "InnoDB",
+       "config-mysql-innodb": "InnoDB (mælt með)",
        "config-mysql-myisam": "MyISAM",
        "config-mssql-auth": "Tegund auðkenningar:",
        "config-mssql-sqlauth": "SQL Server auðkenning",
index fa17284..cda0636 100644 (file)
@@ -206,6 +206,7 @@ class JobQueueDB extends JobQueue {
        /**
         * This function should *not* be called outside of JobQueueDB
         *
+        * @suppress SecurityCheck-SQLInjection Bug in phan-taint-check handling bulk inserts
         * @param IDatabase $dbw
         * @param IJobSpecification[] $jobs
         * @param int $flags
index 6ce8f45..536177e 100644 (file)
@@ -384,7 +384,7 @@ class MultiHttpClient implements LoggerAwareInterface {
 
                curl_setopt( $ch, CURLOPT_HEADERFUNCTION,
                        function ( $ch, $header ) use ( &$req ) {
-                               if ( !empty( $req['flags']['relayResponseHeaders'] ) ) {
+                               if ( !empty( $req['flags']['relayResponseHeaders'] ) && trim( $header ) !== '' ) {
                                        header( $header );
                                }
                                $length = strlen( $header );
index 2329140..40030c3 100644 (file)
@@ -159,6 +159,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
 
        /** Seconds to keep lock keys around */
        const LOCK_TTL = 10;
+       /** Seconds to no-op key set() calls to avoid large blob I/O stampedes */
+       const COOLOFF_TTL = 1;
        /** Default remaining TTL at which to consider pre-emptive regeneration */
        const LOW_TTL = 30;
 
@@ -190,6 +192,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */
        const TINY_NEGATIVE = -0.000001;
 
+       /** Seconds of delay after get() where set() storms are a consideration with 'lockTSE' */
+       const SET_DELAY_HIGH_SEC = 0.1;
+
        /** Cache format version number */
        const VERSION = 1;
 
@@ -213,6 +218,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        const INTERIM_KEY_PREFIX = 'WANCache:i:';
        const TIME_KEY_PREFIX = 'WANCache:t:';
        const MUTEX_KEY_PREFIX = 'WANCache:m:';
+       const COOLOFF_KEY_PREFIX = 'WANCache:c:';
 
        const PURGE_VAL_PREFIX = 'PURGED:';
 
@@ -1034,21 +1040,28 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *      is useful if thousands or millions of keys depend on the same entity. The entity can
         *      simply have its "check" key updated whenever the entity is modified.
         *      Default: [].
-        *   - graceTTL: If the key is invalidated (by "checkKeys") less than this many seconds ago,
-        *      consider reusing the stale value. The odds of a refresh becomes more likely over time,
-        *      becoming certain once the grace period is reached. This can reduce traffic spikes
-        *      when millions of keys are compared to the same "check" key and touchCheckKey() or
-        *      resetCheckKey() is called on that "check" key. This option is not useful for the
-        *      case of the key simply expiring on account of its TTL (use "lowTTL" instead).
+        *   - graceTTL: If the key is invalidated (by "checkKeys"/"touchedCallback") less than this
+        *      many seconds ago, consider reusing the stale value. The odds of a refresh becomes
+        *      more likely over time, becoming certain once the grace period is reached. This can
+        *      reduce traffic spikes when millions of keys are compared to the same "check" key and
+        *      touchCheckKey() or resetCheckKey() is called on that "check" key. This option is not
+        *      useful for avoiding traffic spikes in the case of the key simply expiring on account
+        *      of its TTL (use "lowTTL" instead).
         *      Default: WANObjectCache::GRACE_TTL_NONE.
-        *   - lockTSE: If the key is tombstoned or invalidated (by "checkKeys") less than this many
-        *      seconds ago, try to have a single thread handle cache regeneration at any given time.
-        *      Other threads will try to use stale values if possible. If, on miss, the time since
-        *      expiration is low, the assumption is that the key is hot and that a stampede is worth
-        *      avoiding. Setting this above WANObjectCache::HOLDOFF_TTL makes no difference. The
-        *      higher this is set, the higher the worst-case staleness can be. This option does not
-        *      by itself handle the case of the key simply expiring on account of its TTL, so make
-        *      sure that "lowTTL" is not disabled when using this option.
+        *   - lockTSE: If the key is tombstoned or invalidated (by "checkKeys"/"touchedCallback")
+        *      less than this many seconds ago, try to have a single thread handle cache regeneration
+        *      at any given time. Other threads will use stale values if possible. If, on miss,
+        *      the time since expiration is low, the assumption is that the key is hot and that a
+        *      stampede is worth avoiding. Note that if the key falls out of cache then concurrent
+        *      threads will all run the callback on cache miss until the value is saved in cache.
+        *      The only stampede protection in that case is from duplicate cache sets when the
+        *      callback takes longer than WANObjectCache::SET_DELAY_HIGH_SEC seconds; consider
+        *      using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
+        *      set, the higher the worst-case staleness of returned values can be. Also note that
+        *      this option does not by itself handle the case of the key simply expiring on account
+        *      of its TTL, so make sure that "lowTTL" is not disabled when using this option. Avoid
+        *      combining this option with delete() as it can always cause a stampede due to their
+        *      being no stale value available until after a thread completes the callback.
         *      Use WANObjectCache::TSE_NONE to disable this logic.
         *      Default: WANObjectCache::TSE_NONE.
         *   - busyValue: If no value exists and another thread is currently regenerating it, use this
@@ -1267,12 +1280,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        // This avoids stampedes on eviction or preemptive regeneration taking too long.
                        ( $busyValue !== null && $value === false );
 
-               $lockAcquired = false;
+               $hasLock = false;
                if ( $useMutex ) {
                        // Acquire a datacenter-local non-blocking lock
                        if ( $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL ) ) {
                                // Lock acquired; this thread will recompute the value and update cache
-                               $lockAcquired = true;
+                               $hasLock = true;
                        } elseif ( $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
                                // Lock not acquired and a stale value exists; use the stale value
                                $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
@@ -1315,26 +1328,31 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $valueIsCacheable = ( $value !== false && $ttl >= 0 );
 
                if ( $valueIsCacheable ) {
+                       $ago = max( $this->getCurrentTime() - $preCallbackTime, 0.0 );
                        if ( $isKeyTombstoned ) {
-                               // When delete() is called, writes are write-holed by the tombstone,
-                               // so use a special INTERIM key to pass the new value among threads.
-                               $tempTTL = max( self::INTERIM_KEY_TTL, (int)$lockTSE ); // set() expects seconds
-                               $newAsOf = $this->getCurrentTime();
-                               $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
-                               // Avoid using set() to avoid pointless mcrouter broadcasting
-                               $this->setInterimValue( $key, $wrapped, $tempTTL );
-                       } elseif ( !$useMutex || $lockAcquired ) {
-                               // Save the value unless a lock-winning thread is already expected to do that
-                               $setOpts['lockTSE'] = $lockTSE;
-                               $setOpts['staleTTL'] = $staleTTL;
-                               // Use best known "since" timestamp if not provided
-                               $setOpts += [ 'since' => $preCallbackTime ];
-                               // Update the cache; this will fail if the key is tombstoned
-                               $this->set( $key, $value, $ttl, $setOpts );
+                               if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
+                                       // When delete() is called, writes are write-holed by the tombstone,
+                                       // so use a special INTERIM key to pass the new value among threads.
+                                       $tempTTL = max( self::INTERIM_KEY_TTL, (int)$lockTSE ); // set() expects seconds
+                                       $newAsOf = $this->getCurrentTime();
+                                       $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
+                                       // Avoid using set() to avoid pointless mcrouter broadcasting
+                                       $this->setInterimValue( $key, $wrapped, $tempTTL );
+                               }
+                       } elseif ( !$useMutex || $hasLock ) {
+                               if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
+                                       // Save the value unless a lock-winning thread is already expected to do that
+                                       $setOpts['lockTSE'] = $lockTSE;
+                                       $setOpts['staleTTL'] = $staleTTL;
+                                       // Use best known "since" timestamp if not provided
+                                       $setOpts += [ 'since' => $preCallbackTime ];
+                                       // Update the cache; this will fail if the key is tombstoned
+                                       $this->set( $key, $value, $ttl, $setOpts );
+                               }
                        }
                }
 
-               if ( $lockAcquired ) {
+               if ( $hasLock ) {
                        // Avoid using delete() to avoid pointless mcrouter broadcasting
                        $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
                }
@@ -1345,6 +1363,41 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                return $value;
        }
 
+       /**
+        * @param string $key
+        * @param string $kClass
+        * @param float $elapsed Seconds spent regenerating the value
+        * @param float $lockTSE
+        * @param $hasLock bool
+        * @return bool Whether it is OK to proceed with a key set operation
+        */
+       private function checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock ) {
+               // If $lockTSE is set, the lock was bypassed because there was no stale/interim value,
+               // and $elapsed indicates that regeration is slow, then there is a risk of set()
+               // stampedes with large blobs. With a typical scale-out infrastructure, CPU and query
+               // load from $callback invocations is distributed among appservers and replica DBs,
+               // but cache operations for a given key route to a single cache server (e.g. striped
+               // consistent hashing).
+               if ( $lockTSE < 0 || $hasLock ) {
+                       return true; // either not a priori hot or thread has the lock
+               } elseif ( $elapsed <= self::SET_DELAY_HIGH_SEC ) {
+                       return true; // not enough time for threads to pile up
+               }
+
+               $this->cache->clearLastError();
+               if (
+                       !$this->cache->add( self::COOLOFF_KEY_PREFIX . $key, 1, self::COOLOFF_TTL ) &&
+                       // Don't treat failures due to I/O errors as the key being in cooloff
+                       $this->cache->getLastError() === BagOStuff::ERR_NONE
+               ) {
+                       $this->stats->increment( "wanobjectcache.$kClass.cooloff_bounce" );
+
+                       return false;
+               }
+
+               return true;
+       }
+
        /**
         * @param mixed $value
         * @param float $asOf
index f4856be..078c819 100644 (file)
@@ -275,6 +275,7 @@ class LinkHolderArray {
 
        /**
         * Replace internal links
+        * @suppress SecurityCheck-XSS Gets confused with $entry['pdbk']
         * @param string &$text
         */
        protected function replaceInternal( &$text ) {
@@ -418,6 +419,7 @@ class LinkHolderArray {
        /**
         * Replace interwiki links
         * @param string &$text
+        * @suppress SecurityCheck-XSS Gets confused with $this->interwikis['pdbk']
         */
        protected function replaceInterwiki( &$text ) {
                if ( empty( $this->interwikis ) ) {
index 1a23258..4158082 100644 (file)
@@ -423,6 +423,11 @@ class ResourceLoader implements LoggerAwareInterface {
 
                // Add the QUnit testrunner as implicit dependency to extension test suites.
                foreach ( $testModules['qunit'] as &$module ) {
+                       // Shuck any single-module dependency as an array
+                       if ( is_string( $module['dependencies'] ) ) {
+                               $module['dependencies'] = [ $module['dependencies'] ];
+                       }
+
                        $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
                }
 
index ea1cf59..dfdbc07 100644 (file)
@@ -848,23 +848,23 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
                                $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
 
-                               // Hit counts disabled for perf reasons, see T169997
-                               /*
                                $tagStats = ChangeTags::tagUsageStatistics();
                                $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
 
-                               // Sort by hits
-                               arsort( $tagHitCounts );
-                               */
-                               $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags );
+                               // Sort by hits (disabled for now)
+                               //arsort( $tagHitCounts );
 
                                // Build the list and data
                                $result = [];
                                foreach ( $tagHitCounts as $tagName => $hits ) {
                                        if (
-                                               // Only get active tags
-                                               isset( $explicitlyDefinedTags[ $tagName ] ) ||
-                                               isset( $softwareActivatedTags[ $tagName ] )
+                                               (
+                                                       // Only get active tags
+                                                       isset( $explicitlyDefinedTags[ $tagName ] ) ||
+                                                       isset( $softwareActivatedTags[ $tagName ] )
+                                               ) &&
+                                               // Only get tags with more than 0 hits
+                                               $hits > 0
                                        ) {
                                                $result[] = [
                                                        'name' => $tagName,
index 5b939ef..3a180db 100644 (file)
@@ -152,6 +152,15 @@ class SpecialContributions extends IncludableSpecialPage {
                }
                $this->opts = ContribsPager::processDateFilter( $this->opts );
 
+               if ( $this->opts['namespace'] < NS_MAIN ) {
+                       $this->getOutput()->wrapWikiMsg(
+                               "<div class=\"mw-negative-namespace-not-supported error\">\n\$1\n</div>",
+                               [ 'negative-namespace-not-supported' ]
+                       );
+                       $out->addHTML( $this->getForm() );
+                       return;
+               }
+
                $feedType = $request->getVal( 'feed' );
 
                $feedParams = [
index 3fac73c..aedb9e6 100644 (file)
@@ -99,7 +99,7 @@ class ActiveUsersPager extends UsersPager {
                ];
                $options = [];
                if ( $data !== null ) {
-                       $options['ORDER BY'] = 'qcc_title ' . $data['dir'];
+                       $options['ORDER BY'] = 'qcc_title ' . $data['order'];
                        $options['LIMIT'] = $data['limit'];
                        $conds = array_merge( $conds, $data['conds'] );
                }
index 69dce53..2fc946e 100644 (file)
@@ -32,7 +32,6 @@ use Wikimedia\Rdbms\IResultWrapper;
 class BlockListPager extends TablePager {
 
        protected $conds;
-       protected $page;
 
        /**
         * Array of restrictions.
@@ -46,7 +45,6 @@ class BlockListPager extends TablePager {
         * @param array $conds
         */
        public function __construct( $page, $conds ) {
-               $this->page = $page;
                $this->conds = $conds;
                $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                parent::__construct( $page->getContext() );
index 2b094b1..d03401d 100644 (file)
  */
 class NewPagesPager extends ReverseChronologicalPager {
 
-       // Stored opts
+       /**
+        * @var FormOptions
+        */
        protected $opts;
 
        /**
-        * @var HTMLForm
+        * @var SpecialNewpages
         */
        protected $mForm;
 
+       /**
+        * @param SpecialNewpages $form
+        * @param FormOptions $opts
+        */
        public function __construct( $form, FormOptions $opts ) {
                parent::__construct( $form->getContext() );
                $this->mForm = $form;
index f457d2f..bc4202e 100644 (file)
@@ -23,7 +23,7 @@ use MediaWiki\Linker\LinkRenderer;
 
 class ProtectedPagesPager extends TablePager {
 
-       public $mForm, $mConds;
+       public $mConds;
        private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
 
        /**
@@ -32,7 +32,7 @@ class ProtectedPagesPager extends TablePager {
        private $linkRenderer;
 
        /**
-        * @param SpecialProtectedpages $form
+        * @param SpecialPage $form
         * @param array $conds
         * @param string $type
         * @param string $level
@@ -48,7 +48,6 @@ class ProtectedPagesPager extends TablePager {
                $sizetype, $size, $indefonly, $cascadeonly, $noredirect,
                LinkRenderer $linkRenderer
        ) {
-               $this->mForm = $form;
                $this->mConds = $conds;
                $this->type = ( $type ) ? $type : 'edit';
                $this->level = $level;
index 4f24713..aaa5d16 100644 (file)
@@ -173,7 +173,7 @@ class Names {
                'gan' => '贛語', # Gan (multiple scripts - defaults to Traditional)
                'gan-hans' => "赣语(简体)\u{200E}", # Gan (Simplified Han)
                'gan-hant' => "贛語(繁體)\u{200E}", # Gan (Traditional Han)
-               'gcr' => 'kréyòl gwiyanè', # Guianan Creole
+               'gcr' => 'kriyòl gwiyannen', # Guianan Creole
                'gd' => 'Gàidhlig', # Scots Gaelic
                'gl' => 'galego', # Galician
                'glk' => 'گیلکی', # Gilaki
index c65ddbb..ab3b572 100644 (file)
        "mergehistory-list": "ڤیرگار آلشدکاریٱل سٱریٱک بیڌنی",
        "mergehistory-go": "دیاری کردن آلشدکاریٱل سٱریٱک بیڌنی",
        "mergehistory-submit": "سر یٱک کردن ڤانیاریٱل",
+       "mergehistory-fail-invalid-source": "سرچشمٱ بٱلگاْ نادیاراْ.",
+       "mergehistory-fail-invalid-dest": "بٱلگاْ مٱقسٱڌ نادیاراْ.",
        "mergehistory-reason": "دلیل:",
        "mergelog": "سیائاْ ؤریٱک",
        "revertmerge": "سٱڤا کردن",
        "history-title": "دوڤارتاْ دیڌن ڤیرگار $1",
        "difference-title": "فٱرخ میٛنجقا ڤاناٛیریا \"$1\"",
-       "difference-multipage": "(فٱخ میٛنجقا بٱلگاْیٱل)",
+       "difference-title-multipage": "فٱرخ میٛنجقا بٱلگاْیٱل \"$1\" و \"$2\"",
+       "difference-multipage": "(فٱرخ میٛنجقا بٱلگاْیٱل)",
        "lineno": "خٱت $1:",
        "compareselectedversions": "کنار یٱک ناهاڌن ڤانیٱریٱل گولاْڤورچین ڤابیڌاْ",
        "editundo": "ٱنجومشیڤ کردن",
        "diff-empty": "(یٱ جۊر)",
        "diff-multi-sameuser": "({{PLURAL:$1|یٱ دۊناٛ نوسقاٛ مؽنجخایی|$1 نوسقاٛیٱل مؽنجخایی}} ب دٱسد{{PLURAL:$2|کاریاری ديٱ|$2 کاريارا}} نشۊن دیاری نٱکرداْ)",
        "diff-multi-otherusers": "({{PLURAL:$1|یٱ نوسقاْ میٛنجقایی|$1 نوسقاْیٱل میٛنجایی}} ڤا دٱسد {{PLURAL:$2|کاریاری دیٱ|$2 کاریارٱل}} نشۊن داڌاْ نٱڤابیڌاْ)",
+       "diff-paragraph-moved-tonew": "پاراگراف جا ب جا ڤابی، یٱ کاْرٱت بپۊرنین تا رۉین یٱ جا دیٱر.",
+       "diff-paragraph-moved-toold": "پاراگراف جا ب جا ڤابی، یٱ کاْرٱت بپۊرنین تا رۉین ب جا نیایی.",
        "searchresults": "نتيجاْیٱل پاٛی جۊری",
        "search-filter-title-prefix-reset": "پاٛی جۊری میٛن تٱموم بٱلگاْیٱل",
        "searchresults-title": "نتيجاْیٱل پاٛی جۊری سی \"$1\"",
        "search-redirect": "(ڤاگٱردونی ز $1)",
        "search-section": "(بٱرجا $1)",
        "search-category": "(دٱسداْ $1)",
-       "search-file-match": "(Û\8cÚ©Û\8c Ú©Ø±Ø¯Ù\86 Ù\85Û\8cÙ\86Û\8aناْ جانیا)",
+       "search-file-match": "(Û\8cÚ©Û\8c Ú©Ø±Ø¯Ù\86 Ù\85Û\8cÙ\9bÙ\86Ù\88ناْ جانیا)",
        "search-suggest": "مٱنزۊرت یو بی:$1",
        "search-interwiki-default": "نتیجاْیٱل $1:",
        "search-interwiki-more": "(بيشتر)",
        "right-upload_by_url": "سوڤار کرد جانیایٱل ز یٱ یۊ آر اْل",
        "right-writeapi": "سي نڤشدن اْی پی آی ڤٱنين ڤاکار",
        "right-delete": "پاکسا کردن بٱلگاْیٱل",
+       "right-bigdelete": "بٱلگاْیٱلی ناْ کاْ ڤیرگار گٱپ دارن پاکسا کونین.",
+       "right-browsearchive": "پاٛی جۊری میٛن بٱلگاْیٱل پاکسا ڤابیڌاْ.",
+       "right-undelete": "بٱلگاْ ناْ پاکسا نٱکونین",
        "right-block": "نیا کاریارٱل دیٱر ناْ ز آلشدکاری بگرین.",
        "right-blockemail": "نیا کاریار ناْ سی بیٛسی کردن ٱنجوماناماْ بگرین",
        "right-hideuser": "نیاگری یٱ نوم کاریاری، قام کردنس ز ڤٱر تی خٱلک",
        "rcfilters-activefilters-hide": "قام کردن",
        "rcfilters-activefilters-show": "دیاری کردن",
        "rcfilters-advancedfilters": "فیلترٱل پیشکرداْ",
+       "rcfilters-limit-title": "نتیجاْیٱل سی دیاری کردن",
+       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|آلشدکاری|آلشدکاریٱل}}, $2",
        "rcfilters-days-title": "رۊزٱل ایسنی",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|رۊز|رۊزٱل}}",
        "rcfilters-highlighted-filters-list": "پورٱنڳ ڤابیڌاْ:$1",
        "rcfilters-invalid-filter": "فیلتر نادیار",
        "rcfilters-filterlist-title": "فیلترٱل",
        "rcfilters-filterlist-whatsthis": "یونو چ جۊر کار اْکونن؟",
+       "rcfilters-highlightmenu-title": "یٱ رٱنڳ گولاْڤورچین کونین",
+       "rcfilters-filterlist-noresults": "هیچ فیلتری دیاری نکرد.",
        "rcfilters-filter-editsbyself-label": "ایسا آلشدس کردین",
        "rcfilters-filter-editsbyother-label": "آلشدکاریٱل دیٱرون",
        "rcfilters-filter-user-experience-level-registered-label": "سٱڤت نام ڤابیڌاْ",
        "rcfilters-filtergroup-lastRevision": "آخری ڤانیٱریٱل",
        "rcfilters-filter-lastrevision-label": "آخری ڤانیٱری",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:نیڌ</strong> $1",
+       "rcfilters-target-page-placeholder": "نوم یٱ بٱلگاْ(یا دٱسداْ) ناْ بزنین",
        "rcnotefrom": "ڤازیر {{PLURAL:$5|آلشدکاری|آلشدکاریٱل}}ز ڤیرگار strong>$3, $4</strong> تا ڤیرگار <strong>$1</strong>  دیاری اْکونن .",
        "rclistfrom": "دیار کردن آلشدکاریٱل ز $3 $2",
        "rcshowhideminor": "آلشدکاری کۊچیر $1",
        "sourcefilename": "سرچشماْ نوم جانیا:",
        "sourceurl": "سرچشماْ يۊ آر ال:",
        "destfilename": "نوم جانیا مٱقسٱڌ:",
+       "upload-description": "تۉزی جانیا",
        "watchthisupload": "ساٛیل ای جانیا کونین",
        "upload-proto-error": "پورتوکول نادوروسد",
        "upload-file-error": "خٱتا میٛنجقایی",
        "upload-form-label-own-work": "یو کار موناْ.",
        "upload-form-label-infoform-categories": "دٱسداْیٱل",
        "upload-form-label-infoform-date": "ڤیرگار",
+       "http-invalid-url": "یۊ آر اْل لیش:$1",
        "http-read-error": "خٱتا خوندن اْچ تی تی پی.",
        "http-internal-error": "خٱتا میٛنجقایی اْچ تی تی پی",
        "license": "میٛن هال و بال لیسانس دار ڤابیڌن",
        "uploadnewversion-linktext": "یٱ نوسقاْ تازاْ زی جانیا سوڤار کونین",
        "shared-repo-from": "ز $1",
        "upload-disallowed-here": "ايسا ناْترین ای جانیا نٱ ز نۉ سوڤار کونین",
+       "filerevert": "ز سرگرهڌن سی $1",
+       "filerevert-legend": "ز سرگرهڌن جانیا",
        "filerevert-comment": "دلیل:",
+       "filerevert-submit": "لرنیڌن",
        "filedelete": "$1 ناْ پاکسا کو",
        "filedelete-legend": "پاکسا کردن جانیا",
        "filedelete-comment": "دلیل:",
        "filedelete-submit": "پاکسا کردن",
+       "filedelete-success": "<strong>$1</strong> پاکسا ڤابیڌاْ.",
+       "filedelete-otherreason": "دیٱری/دلیل اْزافی:",
        "filedelete-reason-otherlist": "دلیل هیٛنی",
+       "filedelete-reason-dropdown": "*دلیل جاڤٱسداْ سی پاکسا کردن\n*تی پۊشنیڌن ز کوپی رایت\n*جانیا تکراری",
+       "filedelete-edit-reasonlist": "دلیل پاکسا کردن ناْ آلشد کونین",
+       "filedelete-maintenance-title": "نیبۊ جانیا ناْ پاکسا کونین",
        "mimesearch": "MIME جستجو رو پایه",
+       "mimetype": "جۊر MIME:",
        "download": "گرهڌن",
        "listredirects": "نومگاْ ڤاگٱردونیٱل",
        "unusedtemplates": "چۊئاْیٱل ڤا کار نٱڤٱسداْ",
+       "unusedtemplateswlh": "هومپاٛیڤٱندٱل هیٛنی",
        "randompage": "بٱلگاْ شامسٱکی",
        "randomincategory-category": "دٱسداْ:",
        "randomincategory-submit": "رۉ",
        "statistics-header-pages": "آمار بٱلگاْ",
        "statistics-header-edits": "آمار آلشدکاریٱل",
        "statistics-header-users": "آمار کاریارٱل",
+       "statistics-articles": "بٱلگاْیٱل مینۊناْ دار",
        "statistics-pages": "بٱگاْیٱل",
        "statistics-files": "جانیایٱل سوڤار ڤابیڌاْ",
        "statistics-users": "کاریارٱل سٱڤت نام کرداْ",
        "statistics-users-active": "کاریارٱل کونشتکار",
+       "pageswithprop-prop": "نوم خاسیٱت:",
        "pageswithprop-submit": "رۉ",
        "doubleredirects": "ڤاگٱردونی دۊبلاْ",
        "double-redirect-fixer": "ساموݩکار آلشڌتورٱل",
        "mostrevisions": "بٱلگاْیٱلی کاْ بیشترین ڤانیٱری ناْ دارن",
        "prefixindex": "نماواٛ نهاڤٱنڌ",
        "prefixindex-submit": "دیاری کردن",
+       "prefixindex-strip": "دیندادیس ناْ میٛن نتیچاْیٱل قام کو",
        "shortpages": "بٱلگاْیٱل کۊچیر",
        "longpages": "بٱلگاْیٱل بولوند",
        "deadendpages": "بٱلگاْیٱل بون بٱست",
        "protectedpages-performer": "کاریار پٱر و پیم ڤابیڌاْ.",
        "protectedpages-params": "پینیارٱل پٱر و پیم کاری",
        "protectedpages-reason": "دلیل",
+       "protectedpages-submit": "نشوݩ داڌن بٱلگاْیٱل",
        "protectedpages-unknown-timestamp": "نادیار",
        "protectedpages-unknown-performer": "کاریار نادیار",
        "listusers": "نومگاْ کاریار",
+       "listusers-creationsort": "میزونکاری ز ری گات راست ڤابیڌن",
+       "listusers-desc": "میزونکاری ز ری گٱپ کۊچیری",
        "usereditcount": "$1 {{PLURAL:$1|آلشدکاری|آلشدکاریٱل}}",
        "usercreated": "{{GENDER:$3|راسد ڤابیڌاْ}} سی $1 تا $2",
        "newpages": "بٱلگاْیٱل نۏ",
        "apisandbox-submit": "خاستن",
        "apisandbox-reset": "پاکسا کردن",
        "apisandbox-retry": "ز نۉ تلاش کردن",
+       "apisandbox-helpurls": "هومپاٛیڤٱند هومیاری",
        "apisandbox-examples": "نموناْیٱل",
        "apisandbox-dynamic-parameters": "پینیارٱل اْزافی",
        "apisandbox-dynamic-parameters-add-label": "اْزاف کردن پینیار:",
        "apisandbox-dynamic-parameters-add-placeholder": "نوم پینیار.",
        "apisandbox-add-multi": "اْزاف کردن",
        "apisandbox-results": "نتیجاْیٱل",
+       "apisandbox-request-url-label": "درخاست یۊ آر اْل:",
+       "apisandbox-request-json-label": "درخاست JSON:",
        "apisandbox-continue": "ديندا گرهڌن",
        "apisandbox-continue-clear": "پاکسا کردن",
+       "apisandbox-multivalue-all-values": "$1 (تٱموم ٱرزایشتٱل)",
        "booksources": "سرچشماْیٱل کتاو",
        "booksources-search-legend": "پاٛ جۊری سی سٱرچٱشمٱیٱل کتاو",
        "booksources-search": "پاٛی جۊری",
        "specialloguserlabel": "مؽنتور:",
        "speciallogtitlelabel": "دال(داسۊن یا {{ns:user}}:نوم کاریاری سی کاریار):",
        "log": "پاْرستنوماْیٱل",
+       "logeventslist-submit": "نشوݩ دائن",
        "all-logs-page": "گشڌنمائیٱل",
        "alllogstext": "نمایشت یٱ جا کاْ تٱموم پهرستنۊماْیٱل میٛن {{SITENAME}}.\nایسا تاْرین ڤا گولاْڤورچین کردن جۊر پهرستنۊماْ ، نوم کاریاری(هساس ب کۊچیری و گٱپی هٱرفا) و بٱلگاْیٱل آلشت کرداْ(هساس ب گٱپی و کۊچیری هٱرفا) نمایشت نٱ دیر ز ڤیر کونین.",
        "logempty": "چونو چی کاْ ایسا خاسدین میٛن پهرستنۊماْ نیڌس",
+       "checkbox-select": "گولاْڤورچین کردن: $1",
        "checkbox-all": "هٱماْ",
        "checkbox-none": "هيش کوم",
        "allpages": "تٱموم بٱلگاْیٱل",
        "allpages-hide-redirects": "بؽ دیارنیڌن آلشڌتورٱل",
        "categories": "دٱسداْیٱل",
        "categories-submit": "دیاری کردن",
+       "sp-deletedcontributions-contribs": "هومياریٱل",
+       "linksearch": "هومپاٛیڤٱند پاٛی جۊری خارجی",
+       "linksearch-pat": "سازاْیار پاٛی جۊری:",
+       "linksearch-ns": "نوم جا:",
        "linksearch-ok": "پاٛی جۊری",
        "listusers-submit": "نشوݩ دائن",
+       "listusers-noresult": "چونو کاریاری ڤوجۊڌ ناراْ.",
+       "listusers-blocked": "(نیاگری ڤابیڌاْ)",
+       "activeusers": "نومگٱ کاریارٱل کونشتکار",
+       "activeusers-noresult": "چونو کاریارٱلی ڤوجۊڌ نارن.",
+       "activeusers-submit": "دیاری کردن کاریارٱل کونتشکار",
        "listgrouprights-group": "جٱرغاْ",
+       "listgrouprights-rights": "هوقۊق",
+       "listgrouprights-helppage": "هومیاری:هوقۊق جٱرغاْ",
        "listgrouprights-members": "(نومگاْ ٱندومٱل)",
+       "listgrouprights-addgroup": "{{PLURAL:$2|جٱرغاْ|جٱرغاْیٱل}} ناْ اْزاف کونین: $1",
+       "listgrouprights-removegroup": "{{PLURAL:$2|جٱرغاْ|جٱرغاْیٱل}} ناْ ڤوردارین: $1",
+       "listgrouprights-addgroup-all": "اْزاف کردن تٱموم جٱرغاْیٱل",
+       "listgrouprights-removegroup-all": "ڤورداشتن تٱموم جٱرغاْیٱل",
+       "listgrants-rights": "هوقۊق",
+       "trackingcategories-name": "نوم پاٛیغوم",
        "emailuser": "ٱنجوماناماْ کاریار",
+       "emailuser-title-notarget": "ٱنجوماناماْ کاریار",
+       "defemailsubject": "{{SITENAME}} ٱنجوماناماْ ز کاریار \"$1\"",
+       "emailusername": "نوم کاریاری:",
+       "emailusernamesubmit": "داڌن",
+       "email-legend": "یٱ ٱنجومانٱماْ سی کاریار دیٱری {{SITENAME}} بیٛسی کو",
        "emailfrom": "ز:",
        "emailto": "سی:",
        "emailsubject": "داسوݩ:",
        "emailccme": "یٱ کوپی ز پاٛیغوموم سیم بیٛسی کو",
        "emailsent": "ٱنجوماناماْ بیٛسی ڤابیڌاْ",
        "emailsenttext": "پاٛیغومتوݩ ٱنجوماناماْیی بیٛسی ڤابیڌاْ.",
-       "usermessage-editor": "پاٛیغوم فرشن سامۊناْیی",
+       "usermessage-summary": "لاهاڌن پاٛیغوم سیستمی.",
+       "usermessage-editor": "پاٛیغوم فرشن ساموناْیی",
        "watchlist": "لیسڌ دیناگریٱل مو",
-       "mywatchlist": "سئیل بٱرگ",
+       "mywatchlist": "ساÙ\9bیل بٱرگ",
        "watchlistfor2": "سی $1 $2",
        "watchnologin": "هٱنی نٱڤۊڌیناْ ڤامیٛن",
        "addwatch": "اْزاف کردن ب ساٛیل بٱرگ",
        "addedwatchtext": "\"[[:$1]]\" و بٱلگاْ چٱک چناْس  اْزاف ڤابی ب [[Special:ساٛیل بٱرگ|ساٛیل بٱرگ]]توݩ .",
-       "removedwatchtext": "آن صفحه\"[[:$1]]\" جابجا وابیده زه[[Special:لیست پیگیری|لیست پیگیری ایسا]].",
+       "removewatch": "ڤورداشتن ز ساٛیل بٱرگ",
+       "removedwatchtext": "\"[[:$1]]\" و بٱلگاْ چٱک چناْس  ز [[Special:نومگاْ ساٛیل بٱرگ|نومگٱ ساٛیل بٱرگ]]ایسا ڤورداشتاْ ڤابی.",
+       "removedwatchtext-talk": "\"[[:$1]]\" و بٱلگاْ هومیاریس  ز [[Special:نومگاْ ساٛیل بٱرگ|نومگٱ ساٛیل بٱرگ]]ایسا ڤورداشتاْ ڤابی.",
        "watch": "پاٛیگری",
        "watchthispage": "پاٛگری ای بٱلگاْ",
        "unwatch": "پاٛیگری نٱڤابیڌاْ",
        "enotif_impersonal_salutation": "{{SITENAME}} کاریار",
        "enotif_minoredit": "یو یٱ هیرداْ ڤیرایشداْ",
        "deletepage": "پاکسا کردن بٱلگاْ",
+       "confirm": "پوشت راست کاری کردن",
+       "excontent": "میٛنوناْ: \"$1\" بی",
+       "delete-confirm": "پاکسا کردن \"$1\"",
+       "delete-legend": "پاکسا کردن",
        "historywarning": "ب ڤیرتوݩ بۊ:بٱلگاْیی کاْ ایسا خاین پاکساس کونین یٱ ڤیرگار $1 ڤا{{PLURAL:$1|ڤانیٱری|ڤانیٱریٱل}} داراْ:",
        "historyaction-submit": "نشوݩ دائن",
        "confirmdeletetext": "ایسا اْخاین یٱ بٱلگاْ بولوند ناْ ڤا تٱموم ڤیرگارس پاکسا کونن.\nلوتف کونین پوشت راستکاری کونین کاْ چونو کاری ناْ اْخاین ٱنجوم بڌین، ایسا دونین کاْ نتیجاْ و آریٛنگ کاری کاْ خاین ٱنجوم بڌین ڤا زی ری  [[{{MediaWiki:Policy-url}}|سیاستٱل]] بۊ.",
        "actionfailed": "کار کاْ ناخوش سرٱنجوم بی",
        "deletedtext": "\"$1\" پاکسا ڤابیڌاْ.\nبیٛنیٱر ب $2 سی سٱڤت آخری پاکسا کاریٱل.",
        "dellogpage": "پهرستنۊماْ پاکسا کردن",
+       "deletionlog": "پهرستنۊماْ پاکسا کردن",
        "deletecomment": "دلیل:",
        "deleteotherreason": "دیٱر/دلیل اْزافی:",
        "deletereasonotherlist": "دلیل دیٱر",
        "rollbacklink": "ڤورگٱشتن",
        "rollbacklinkcount": "چٱڤاساْ کردن $1 {{PLURAL:$1|آلشدکاری|آلشدکاریٱل}}",
        "changecontentmodel-title-label": "داسوݩ بٱلگاْ",
+       "changecontentmodel-model-label": "مودل میٛنوناْ تازاْ",
        "changecontentmodel-reason-label": "دلیل:",
        "changecontentmodel-submit": "آلشد کردن",
        "protectlogpage": "پهرستنۊماْ پٱر و پیم کاری",
        "protect_expiry_old": "گات تٱموم ڤابیڌن ز دینداتراْ.",
        "protect-text": "گاشا ایسا ریتراز پٱر و پیم کاری بٱلگاْ ناْ بیٛنیٱرین و آلشدس کونین سی '''$1'''.",
        "protect-locked-access": "هساو کاریاری ایسا سلا آلشدکاری ریتراز پٱر و پیم کاری ناْ ناراْ.\nمیزونکاری ایسنی سی بلگاْ ایچوناْ '''$1''':",
-       "protect-cascadeon": "ای صفحه  در حال حاضر حفاظت وحمایت وابیده چون که در {{PLURAL:$1|صفحه|صفحات}}\nزیر که گزینه حفاظت وحمایت موجی {{PLURAL:$1|آن|آن‌ها}} فعال هده ،\nایسا ترین سطح حفاظت ای صفحه را تغییر بدین اما ای کارنتره تاثیری رو\nحفاظت وحمایت موجی صفحه داشته بوه.",
+       "protect-cascadeon": "This page is currently protected because it is transcluded in the following {{PLURAL:$1|page, which has|pages, which have}} cascading protection turned on.\nChanges to this page's protection level will not affect the cascading protection.",
        "protect-default": "هٱماْ کاریارٱل سلادارن",
-       "protect-fallback": "درخواست\"$1\" اجازه",
-       "protect-level-autoconfirmed": "بستن کاربران ثبت نام نوابیده",
-       "protect-level-sysop": "Sysops فقط",
-       "protect-summary-cascade": "موجی کردن",
-       "protect-expiring": "سپری وابیده $1 (UTC)",
+       "protect-fallback": "فٱقٱت کاریارٱلی کاْ ب «$1» دٱسرسی دارن، سلادار ای کارن.",
+       "protect-level-autoconfirmed": "فٱقٱت کاریارٱلی کاْ خودپوشت راست کاری ڤابیڌناْ سلا چونو کاری دارن",
+       "protect-level-sysop": "فٱقٱت سردیڤونکارٱل",
+       "protect-summary-cascade": "مۉجی کردن",
+       "protect-expiring": "گات تٱمو ڤابیڌاْ $1 (UTC)",
+       "protect-expiry-indefinite": "بی گات",
        "protect-cascade": "حمایت صفحات دربرگیرنده در ای صفحه (cascading protection)",
        "protect-cantedit": "ایسا نترین تغییر بدین سطوح حمایتی ای صفحه را, زیرا ایسا اجازه اصلاح آن را ندارین.",
+       "protect-othertime": "گات هیٛنی:",
+       "protect-othertime-op": "گات هیٛنی:",
+       "protect-otherreason-op": "دلیل هیٛنی",
        "protect-expiry-options": "۱ ساعت:1 hour,۱ روز:1 day,۱ هفته:1 week,۲ هفته:2 weeks,۱ ماه:1 month,۳ ماه:3 months,۶ ماه:6 months,۱ سال:1 year,بی‌نهایت:infinite",
        "restriction-type": "اجازه:",
        "restriction-level": "سطح محدودیت:",
-       "restriction-edit": "ڤيرایشت کردن",
+       "pagesize": "(بایتٱل)",
+       "restriction-edit": "آلشدکاری کردن",
        "restriction-move": "جا ڤا جا کردن",
+       "restriction-create": "راس كردن",
+       "restriction-upload": "سوڤارکرد",
        "undeletebtn": "بازیافت",
+       "undeleteviewlink": "ديڌن",
+       "undeletecomment": "دلیل:",
+       "undelete-search-submit": "پاٛی جۊری",
+       "undelete-show-file-submit": "هٱراْ",
        "namespace": "نوم جا:",
        "invert": "گولڤورچین کردن بٱرٱسگ بۊ",
        "tooltip-invert": "ز ری ای جٱڤاْ بپۊرنین و آلشدٱلی ناْ کاْ ماٛنجقا نوم ڤٱرگٱ گولاْڤورچین ڤابیڌن و ٱنجوم داڌاْ ڤابیڌناْ قام کونین.",
        "sp-contributions-blocklog": "پهرستنوماْ قولف ڤابیڌاْ",
        "sp-contributions-uploads": "سوڤارکردٱل",
        "sp-contributions-logs": "پاْرستنۊماْیٱل",
-       "sp-contributions-talk": "Ú\86Ù±Ú© Ú\86Ù\86اÙ\9b",
+       "sp-contributions-talk": "Ú\86Ù±Ú© Ú\86Ù\86اÙ\92",
        "sp-contributions-search": "سی هومیاریٱل پاٛی جۊری ڤابۊ",
        "sp-contributions-username": "نوم ناٛشۊن آی پی یا نوم کاریاری",
        "sp-contributions-toponly": "فقٱت آلشدکاریٱلی کاْ جۏزڤاْ آخریݩ دۉران دیاری کو",
        "whatlinkshere-hidelinks": "هومپاٛیڤٱند سی $1",
        "whatlinkshere-hideimages": "جانیا هومپاٛیڤٱندٱل $1",
        "whatlinkshere-filters": "فيلترٱل",
-       "blockip": "بستن کاربر",
+       "whatlinkshere-submit": "رۉ",
+       "autoblockid": "خود نیاگری #$1",
+       "block": "قولف کردن کاریار",
+       "unblock": "قولف نکردن کاریار",
+       "blockip": "قولف کردن {{GENDER:$1|کاریار}}",
+       "ipaddressorusername": "نوم نشوݩ آی پی یا نوم کاریاری",
+       "ipbreason": "دلیل:",
        "ipbcreateaccount": "راسد کردن هساو کاریاری",
+       "ipbsubmit": "ای کاریار ناْ نیاگری کو",
+       "ipbother": "گات هیٛنی:",
        "ipboptions": "۲ سات:2 hours,۱ رۊز:1 day,۳ رۊز:3 days,۱ هٱفتاْ:1 week,۲ هٱفتاْ:2 weeks,۱ ما:1 month,۳ ما:3 months,۶ ما:6 months,۱ سال:1 year,بی ڤیرگار:infinite",
+       "ipb-pages-label": "بٱگاْیٱل",
+       "ipb-namespaces-label": "نوم ڤارگٱیٱل",
+       "autoblocklist-submit": "پاٛی جۊری",
        "ipblocklist": "آدرسهای  آی پی وکاربران بسته وابیدند",
        "blocklist-userblocks": "قام کردن هساو نیاگری ڤابیڌاْ.",
        "infiniteblock": "بؽ تٱ",
index 3005afc..c661dba 100644 (file)
        "delete": "Bestere",
        "undelete_short": "{{PLURAL:$1|Yew vırnayışi|$1 Vırnayışan}} mestere",
        "viewdeleted_short": "{{PLURAL:$1|Jew vurnayış esternayi|$1 Vurnayışanê esternayan}} bımotne",
-       "protect": "Bısıtarne",
+       "protect": "Bışevekne",
        "protect_change": "bıvırne",
        "unprotect": "Starnayışi bıvurne",
        "newpage": "Perra newi",
        "templatesused": "{{PLURAL:$1|Şablon|Şabloni}} ke ena perrer de karneyayê:",
        "templatesusedpreview": "{{PLURAL:$1|Sablon|Sabloni}}  ke na verqayt de xebetnayê:",
        "templatesusedsection": "{{PLURAL:$1|Template|Templateyan}}  ke na qısım de xebetniyenê:",
-       "template-protected": "(sıtarna)",
+       "template-protected": "(şeveknaye)",
        "template-semiprotected": "(nime staryayış)",
        "hiddencategories": "Ena per de {{PLURAL:$1|1 kategoriyo nımıte|$1 kategoriyê nımıtey}} muhtewa benê:",
        "edittools": "<div id=\"specialcharss\" class=\"toccolours specialchars\" style=\"margin-top:.5em; padding: .3em .5em; font-size: 100%; color:#aaa; text-align:left;\" title=\"{{int:bw-edittools-tooltip}}\">\n<p class=\"specialbasic\" id=\"Standard\">\n'''{{int:bw-edittools-lead-in}}''' \n<charinsert>Á á É é Í í Ó ó Ú ú Ý ý</charinsert> –\n<charinsert>À à È è Ì ì Ò ò Ù ù </charinsert> –\n<charinsert> â Ê ê Î î Ô ô Û û </charinsert> –\n<charinsert>Ä ä Ë ë Ï ï Ö ö Ü ü Ÿ ÿ</charinsert> –\n<charinsert>Æ æ Ø ø Œ œ ẞ ß </charinsert> –\n<charinsert>Å å Ů ů </charinsert> –\n<charinsert>àã Ẽ ẽ ɛ̃ Ĩ ĩ Ñ ñ Õ õ ɔ̃ Ũ ũ </charinsert> –\n<charinsert>Рð Þ þ </charinsert> –\n<charinsert>Ç ç Ģ ģ Ķ ķ Ļ ļ Ņ ņ Ŗ ŗ Ş ş Ţ ţ </charinsert> –\n<charinsert>Ć ć Ĺ ĺ Ń ń Ŕ ŕ Ś ś Ý ý Ź ź </charinsert> –\n<charinsert>Č č Ď ď Ľ ľ Ň ň Ř ř Š š Ť ť Ž ž </charinsert> –\n<charinsert>Ǎ ǎ Ě ě Ǐ ǐ Ǒ ǒ Ǔ ǔ </charinsert> –\n<charinsert>Ā ā Ē ē Ī ī Ō ō Ū ū </charinsert> –\n<charinsert>ǖ ǘ ǚ ǜ </charinsert> –\n<charinsert>Ĉ ĉ Ĝ ĝ Ĥ ĥ Ĵ ĵ Ŝ ŝ Ŵ ŵ Ŷ ŷ </charinsert> –\n<charinsert>Ă ă Ğ ğ Ŭ ŭ </charinsert> –\n<charinsert>Ċ ċ Ė ė Ġ ġ Għ għ İ ı Ż ż </charinsert> –\n<charinsert>Ą ą Ę ę Į į Ų ų </charinsert> –\n<charinsert>Ő ő Ű ű </charinsert> –\n<charinsert>Đ đ Ħ ħ Ł ł Ŀ ŀ </charinsert> –\n<charinsert>Ɖ ɖ Ɛ ɛ Ƒ ƒ Ɣ ɣ Ŋ ŋ Ɔ ɔ Ʋ ʋ </charinsert> -\n<charinsert>Ə ə </charinsert> –\n<charinsert>– — ’</charinsert> –\n<charinsert>~ | ° ¹ ² ³ ⅛ ¼ ⅓ ⅜ ½ ⅝ ¾ ⅔ ⅞ € $ ¥ £ † × ← → ↔ ↑ ± ≠ © ® ™ ‰ «+» ‹+› „+“ „+” ‚+‘ ¡ ¿ …</charinsert> –\n<charinsert>&amp;nbsp; &nbsp; [[Category:+]] #REDIRECT[[+]] {{msg-mw|+|notext=1}} &#33;!FUZZY!! ~~~~  &lt;nowiki>+</nowiki></charinsert>\n<charinsert>ڈ ڑ ٹ </charinsert>\n<charinsert>ټ څ ځ ډ ړ ږ ښ ڼ ؤ ي ې ۍ ئ </charinsert>\n<charinsert>{{{+}}} {{+}} {{subst:+}} <noinclude>+</noinclude></charinsert>\n<charinsert>&lt;!--&nbsp;+&nbsp;--> &lt;br&nbsp;/></charinsert>\n</p></div>",
        "group": "Grube:",
        "group-user": "Karberi",
        "group-autoconfirmed": "Karberê ke otomatikmen biyê araşt",
-       "group-bot": "Roboti",
+       "group-bot": "Boti",
        "group-sysop": "İdarekari",
        "group-interface-admin": "İdarekarê namnişani",
        "group-bureaucrat": "Buroqrati",
        "right-upload_by_url": "Yew URL ra dosyeyan bar ke",
        "right-purge": "Virê sita seba yew pele bêdestur bestere.",
        "right-autoconfirmed": "Perê ke nême kılit biyê, inan bıvurne",
-       "right-bot": "Zey yew kardê otomotiki kar bıvin",
+       "right-bot": "Zey yew karê otomatiki kar bıvêne",
        "right-nominornewtalk": "Pelanê werênayışan rê vurnayışê qıckeki çıniyê, qutiya mesacanê newiyan bıgurene",
        "right-apihighlimits": "Persanê API de sinoranê berzêran bıgurene",
        "right-writeapi": "Xebtnayışê API nusnayışi",
        "rcshowhideminor": "Vırnayışê werdiy $1",
        "rcshowhideminor-show": "Bımocne",
        "rcshowhideminor-hide": "Bınımne",
-       "rcshowhidebots": "botan $1",
+       "rcshowhidebots": "boti $1",
        "rcshowhidebots-show": "Bımocne",
        "rcshowhidebots-hide": "Bınımne",
        "rcshowhideliu": "karberê qeydbiyay $1",
        "logentry-newusers-create2": "Hesabê karberi $1 terefê $3 ra {{GENDER:$2|vıraziya}}",
        "logentry-newusers-byemail": "Karber $1 hesabe $3 {{GENDER:$2|virast}} u parola rist epostadaci",
        "logentry-newusers-autocreate": "Hesabê karberi $1 otomatikmen {{GENDER:$2|vıraşt}}",
-       "logentry-protect-protect": "$1, pela $3 gırewte {{GENDER:$2|sıtare}} $4",
-       "logentry-protect-protect-cascade": "$1, pela $3 gırewte {{GENDER:$2|sıtare}} $4 [qademeyın]",
+       "logentry-protect-protect": "$1, pela $3 {{GENDER:$2|şeveknê}} $4",
+       "logentry-protect-protect-cascade": "$1, pela $3 {{GENDER:$2|şeveknê}} $4 [qademeyın]",
        "logentry-rights-rights": "$1 qandê {{GENDER:$6|$3}} rê ezayiya grube $4 ra $5 {{GENDER:$2|vuriye}}",
        "logentry-rights-rights-legacy": "$1 qandê $3 rê ezayiya grube {{GENDER:$2|vuriye}}",
        "logentry-rights-autopromote": "$1 otomatikmen $4 ra $5 {{GENDER:$2|terfi bi}}",
index 81c305f..7416d3f 100644 (file)
        "anoncontribs": "Contributions",
        "contribsub2": "For {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "User account \"$1\" is not registered.",
+       "negative-namespace-not-supported": "Namespaces with negative values are not supported.",
        "nocontribs": "No changes were found matching these criteria.",
        "uctop": "current",
        "month": "From month (and earlier):",
index 5f5cc2c..655f524 100644 (file)
@@ -31,7 +31,8 @@
                        "Metsavend",
                        "Cumbril",
                        "Ilmarine",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Mardus"
                ]
        },
        "tog-underline": "Linkide allakriipsutus:",
        "pagecategories": "{{PLURAL:$1|Kategooria|Kategooriad}}",
        "category_header": "Leheküljed kategoorias \"$1\"",
        "subcategories": "Alamkategooriad",
-       "category-media-header": "Meediumifailid kategoorias \"$1\"",
-       "category-empty": "<em>Selles kategoorias pole praegu ühtegi lehekülge ega meediumifaili.</em>",
+       "category-media-header": "Meediafaile kategoorias \"$1\"",
+       "category-empty": "<em>Selles kategoorias pole praegu ühtegi lehekülge ega meediafaili.</em>",
        "hidden-categories": "{{PLURAL:$1|Peidetud kategooria|Peidetud kategooriad}}",
        "hidden-category-category": "Peidetud kategooriad",
        "category-subcat-count": "{{PLURAL:$2|Selles kategoorias on ainult järgmine alamkategooria.|{{PLURAL:$1|Järgmine alamkategooria|Järgmised $1 alamkategooriat}} on selles kategoorias (kokku $2).}}",
        "sort-ascending": "Järjesta tõusvalt",
        "nstab-main": "Artikkel",
        "nstab-user": "Kasutaja leht",
-       "nstab-media": "Meediumileht",
+       "nstab-media": "Meedialeht",
        "nstab-special": "Eri",
        "nstab-project": "Projektileht",
        "nstab-image": "Fail",
        "right-deletechangetags": "Kustutada andmebaasist [[Special:Tags|märgiseid]]",
        "grant-generic": "Volituse \"$1\" õiguste komplekt",
        "grant-group-page-interaction": "Interaktsioon lehekülgedega",
-       "grant-group-file-interaction": "Interaktsioon meediumifailidega",
+       "grant-group-file-interaction": "Käsitle meediafaile",
        "grant-group-watchlist-interaction": "Interaktsioon sinu jälgimisloendiga",
        "grant-group-email": "E-kirja saatmine",
        "grant-group-high-volume": "Suuremahuline tegevus",
        "tooltip-t-permalink": "Püsilink lehekülje sellele redaktsioonile",
        "tooltip-ca-nstab-main": "Vaata sisulehekülge",
        "tooltip-ca-nstab-user": "Näita kasutaja lehte",
-       "tooltip-ca-nstab-media": "Vaata meediumifaili lehekülge",
+       "tooltip-ca-nstab-media": "Vaata meedialehte",
        "tooltip-ca-nstab-special": "See on erilehekülg ja seda ei saa redigeerida.",
        "tooltip-ca-nstab-project": "Näita projekti lehte",
        "tooltip-ca-nstab-image": "Vaata faili lehekülge",
        "version-editors": "Toimetid",
        "version-antispam": "Rämpsposti tõkestus",
        "version-other": "Muu",
-       "version-mediahandlers": "Meediumitöötlejad",
+       "version-mediahandlers": "Meedia käsitlejad",
        "version-hooks": "Haagid",
        "version-parser-extensiontags": "Parseri lisasildid",
        "version-parser-function-hooks": "Parserifunktsioonid",
        "default-skin-not-found-no-skins": "Oih! Sinu viki vaikekujundus, milleks muutuja <code dir=\"ltr\">$wgDefaultSkin</code> järgi on <code>$1</code>, pole saadaval.\n\nÜhtegi kujundust pole paigaldatud.\n\n; Kui oled MediaWiki just paigaldanud või täiendasid seda:\n: Paigaldasid tarkvara ilmselt Giti kaudu või otse lähtekoodist või mõnel muul viisil. See on ootuspärane. MediaWiki 1.24 ja uuemad versioonid ei sisalda peahoidlas ühtegi kujundust. Proovi [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org-i kujunduste kataloogist] mõni kujundus paigaldada. Selleks saad:\n:* laadida alla [https://www.mediawiki.org/wiki/Download lintarhiivi paigaldaja], mis sisaldab mitut kujundust ja tarkvaralisa. Saad sealt kleepimiseks kopeerida kausta <code dir=\"ltr\">skins/</code>;\n:* [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org-ist] kindla kujunduse lintarhiivi alla laadida;\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins kasutada Giti, et kujundusi alla laadida].\n: Selle tegemine ei tohiks häirida Giti hoidlat, kui oled MediaWiki arendaja. Vaata [https://www.mediawiki.org/wiki/Manual:Skin_configuration kujunduste häälestusjuhendist], kuidas kujundusi lubada ja kuidas valida vaikekujundus.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (lubatud)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>keelatud</strong>)",
-       "mediastatistics": "Meediumifailide arvandmestik",
+       "mediastatistics": "Meediastatistika",
        "mediastatistics-summary": "Arvandmed üles laaditud failitüüpide kohta. See käib ainult failide viimaste versioonide kohta. Vanu ja kustutatud versioone pole arvesse võetud.",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 bait|$1 baiti}} ($2; $3%)",
        "mediastatistics-bytespertype": "Failide kogusuurus selles alaosas: $1 {{PLURAL:$1|bait|baiti}} ($2; $3%).",
        "mw-widgets-dateinput-no-date": "Kuupäev valimata",
        "mw-widgets-dateinput-placeholder-day": "AAAA-KK-PP",
        "mw-widgets-dateinput-placeholder-month": "AAAA-KK",
-       "mw-widgets-mediasearch-input-placeholder": "Otsi meediumifaile",
+       "mw-widgets-mediasearch-input-placeholder": "Otsi meediat",
        "mw-widgets-mediasearch-noresults": "Tulemusi ei leitud.",
        "mw-widgets-titleinput-description-new-page": "lehekülge pole veel",
        "mw-widgets-titleinput-description-redirect": "ümbersuunamine leheküljele \"$1\"",
index ce64a0a..1729226 100644 (file)
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|uloi]])",
        "duplicate-defaultsort": "'''Chotrai:''' Default arin manddunk chavi ''$2'' rodd korta adhlem default arin manddunk chavi ''$1'', haka.",
        "redirect": "Fayl, vaporpi, pan, uzollnni vo sotr ank‎ vorvim punornirdexon kor",
-       "redirect-summary": "Hem vixex pan punornirdexit korta eka faylik (faylichem nanv dilear), eke panak (uziollnecho ank vo panacho ank dilear), ek vaporpeachem panak (eke vaporpeache ank dilear), vo ek sotr provishtt (sotrachem ank dilear). Vapor: [[{{#Special:Redirect}}/file/Dekhik.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], vo [[{{#Special:Redirect}}/logid/186]].",
+       "redirect-summary": "Hem vixex pan punornirdexit korta eka faylik (faylichem nanv dilear), eke panak (uziollnecho ank vo panacho ank dilear), ek vaporpeachem panak (eke vaporpeache ank dilear), vo ek sotr nond (sotrachem ank dilear). Vapor: [[{{#Special:Redirect}}/file/Dekhik.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], vo [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Voch‎",
        "redirect-lookup": "Suchien polloi:",
        "redirect-value": "Mol:",
index 41842e4..1f0d32e 100644 (file)
        "anoncontribs": "תרומות",
        "contribsub2": "עבור {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "חשבון המשתמש \"$1\" אינו רשום.",
+       "negative-namespace-not-supported": "אין תמיכה במרחבי שם בעלי ערך שלילי.",
        "nocontribs": "לא נמצאו שינויים המתאימים לקריטריונים אלו.",
        "uctop": "נוכחי",
        "month": "עד החודש:",
index 501a4b0..1692f47 100644 (file)
        "grant-delete": "Eyða síðum, yfirferðum og annálsfærslum",
        "grant-editinterface": "Breyta nafnrými MediaWiki og JSON notanda/vefsvæðis",
        "grant-editmycssjs": "Breyta þínum eigin CSS/JSON/JavaScript",
-       "grant-editmyoptions": "Breyta notandastillingunum þínum",
+       "grant-editmyoptions": "Breyta notandastillingunum þínum og JSON-uppsetningu",
        "grant-editmywatchlist": "Breyta vaktlistanum þínum",
        "grant-editpage": "Breyta fyrirliggjandi síðum",
        "grant-editprotected": "Breyta vernduðum síðum",
        "uploadstash-zero-length": "Lengd skráar er núll.",
        "invalid-chunk-offset": "Ógild raðbreyting bunka",
        "img-auth-accessdenied": "Aðgangur óheimill",
-       "img-auth-nopathinfo": "PATH_INFO vantar.\nBiðlarinn þínn er ekki stilltur til að gefa upp þessar upplýsingar.\nÞær mega vera CGI-byggðar og mega ekki styðja img_auth.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
+       "img-auth-nopathinfo": "Vantar upplýsingar um slóð.\nÞjónninn þinn er ekki stilltur til að senda REQUEST_URI og/eða PATH_INFO breyturnar.\nEf svo er, prófaðu að virkja $wgUsePathInfo.\nSkoðaðu https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Umbeðin slóð var ekki í stilltri innhleðslumöppu.",
        "img-auth-badtitle": "Mistókst að búa til gildan titil útfrá „$1”.",
        "img-auth-nologinnWL": "Þú ert ekki skráð(ur) inn og „$1“ er ekki á hvítlista.",
        "ipbreason": "Ástæða:",
        "ipbreason-dropdown": "* Algengar bannástæður\n** Setur inn rangar upplýsingar\n** Fjarlægir efni af síðum\n** Setur inn rusltengla á utanaðkomandi síður\n** Setur inn vitleysu/þvaður á síður\n** Yfirþyrmandi framkoma/áreitni\n** Misnotkun á fjölda notandanafna\n** Óásættanlegt notandanafn",
        "ipb-hardblock": "Banna innskráðum notendum að breyta frá þessu IP-vistfangi.",
-       "ipbcreateaccount": "Banna nýskráningu notandanafns",
-       "ipbemailban": "Banna notanda að senda tölvupóst",
+       "ipbcreateaccount": "Gerð notandaaðgangs",
+       "ipbemailban": "Senda tölvupóst",
        "ipbenableautoblock": "Banna síðasta vistfang notanda sjálfkrafa; og þau vistföng sem viðkomandi notar til að breyta síðum",
        "ipbsubmit": "Banna notanda",
        "ipbother": "Annar tími:",
        "ipboptions": "2 tíma:2 hours,1 dag:1 day,3 daga:3 days,1 viku:1 week,2 vikur:2 weeks,1 mánuð:1 month,3 mánuði:3 months,6 mánuði:6 months,1 ár:1 year,aldrei:infinite",
        "ipbhidename": "Fela notandanafn úr breytingaskrá og listum",
        "ipbwatchuser": "Vakta notanda- og spjallsíður þessa notanda",
-       "ipb-disableusertalk": "Banna þessum notanda að breyta eigin spjallsíðu",
+       "ipb-disableusertalk": "Breyta eigin spjallsíðu",
        "ipb-change-block": "Endurbanna notanda með þessum stillingum",
        "ipb-confirm": "Staðfesta bann",
        "ipb-sitewide": "Á öllum vefnum",
index 0c43ccc..796d436 100644 (file)
        "anoncontribs": "Same as {{msg-mw|mycontris}} but used for non-logged-in users.\n\nSee also:\n* {{msg-mw|Accesskey-pt-anoncontribs}}\n* {{msg-mw|Tooltip-pt-anoncontribs}}\n{{Identical|Contribution}}",
        "contribsub2": "Contributions for \"user\" (links). Parameters:\n* $1 is an IP address or a username, with a link which points to the user page (if registered user).\n* $2 is list of tool links. The list contains a link which has text {{msg-mw|Sp-contributions-talk}}.\n* $3 is a plain text username used for GENDER.\n{{Identical|For $1}}",
        "contributions-userdoesnotexist": "This message is used in [[Special:Contributions]]. It is used to tell the user that the name he searched for doesn't exist.\n\nParameters:\n* $1 - a username\n{{Identical|Userdoesnotexist}}",
+       "negative-namespace-not-supported": "This message is used in [[Special:Contributions]] to tell users that use namespaces with negative value. It not supported as associated namespace(s) doesn't exist.",
        "nocontribs": "Used in [[Special:Contributions]] and [[Special:DeletedContributions]].\n\nSee examples: [[Special:Contributions/x]] and [[Special:DeletedContributions/x]].\n\nParameters:\n* $1 - (Unused) the user name",
        "uctop": "This message is used in [[Special:Contributions]]. It is used to show that a particular edit was the last made to a page. Example: 09:57, 11 February 2008 (hist) (diff) Pagename‎ (edit summary) (current)\n{{Identical|Current}}",
        "month": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for a dropdown box to select a specific month to view the edits made in that month, and the earlier months. See also {{msg-mw|year}}.",
index 825aad0..841019f 100644 (file)
        "nospecialpagetext": "<strong>您請求的特定頁面無效。</strong>\n\n欲取得有效的特定頁面清單可至 [[Special:SpecialPages|{{int:specialpages}}]]。",
        "error": "錯誤",
        "databaseerror": "資料庫錯誤",
-       "databaseerror-text": "出現資料庫查詢錯誤。\n這可能表示系統有問題存在。",
+       "databaseerror-text": "出現資料庫查詢錯誤。\n這表示系統可能有問題存在。",
        "databaseerror-textcl": "資料庫查詢錯誤。",
        "databaseerror-query": "查詢:$1",
        "databaseerror-function": "功能:$1",
index 6e460fa..a880114 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Guianan Creole (kréyòl gwiyanè)
+/** Guianan Creole (Kriyòl Gwiyannen)
  *
  * To improve a translation please visit https://translatewiki.net
  *
index b964417..a79d9f3 100644 (file)
@@ -23,6 +23,7 @@
 
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IMaintainableDatabase;
 
 require_once __DIR__ . '/../Maintenance.php';
 
index 16b2591..ff41b5e 100644 (file)
@@ -1,11 +1,16 @@
 /* This style adds a toggle button with internationalized message for the TOC. */
 
-/* When the browser supports :checked then overwrite the style="display:none" and make the /*
+/* When the browser supports :checked then overwrite the style="display:none" and make the */
 /* checkbox invisible on another way to allow to focus the checkbox with keyboard. */
 :not( :checked ) > .toctogglecheckbox {
+       // Make the checkbox visible to allow it to focus with keyboard.
        display: inline !important; /* stylelint-disable-line declaration-no-important */
+       // Remove any size of the checkbox.
        position: absolute;
+       // Make the checkbox invisible.
        opacity: 0;
+       // Prevent that the checkbox is clickable and changes the cursor.
+       z-index: -1;
 }
 
 .toctogglespan {
index 7361047..9c08b9f 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\ScopedCallback;
 use Wikimedia\TestingAccessWrapper;
 
index 7d40d8c..7bb5c38 100644 (file)
@@ -2531,7 +2531,7 @@ class OutputPageTest extends MediaWikiTestCase {
                $nonce->setAccessible( true );
                $nonce->setValue( $out, 'secret' );
                $rl = $out->getResourceLoader();
-               $rl->setMessageBlobStore( new NullMessageBlobStore() );
+               $rl->setMessageBlobStore( $this->createMock( MessageBlobStore::class ) );
                $rl->register( [
                        'test.foo' => new ResourceLoaderTestModule( [
                                'script' => 'mw.test.foo( { a: true } );',
@@ -2647,7 +2647,7 @@ class OutputPageTest extends MediaWikiTestCase {
                        ->method( 'buildCssLinksArray' )
                        ->willReturn( [] );
                $rl = $op->getResourceLoader();
-               $rl->setMessageBlobStore( new NullMessageBlobStore() );
+               $rl->setMessageBlobStore( $this->createMock( MessageBlobStore::class ) );
 
                // Register custom modules
                $rl->register( [
@@ -3051,21 +3051,3 @@ class OutputPageTest extends MediaWikiTestCase {
                return new OutputPage( $context );
        }
 }
-
-/**
- * MessageBlobStore that doesn't do anything
- */
-class NullMessageBlobStore extends MessageBlobStore {
-       public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
-               return [];
-       }
-
-       public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
-       }
-
-       public function updateMessage( $key ) {
-       }
-
-       public function clear() {
-       }
-}
index 8049a47..4600551 100644 (file)
@@ -1343,6 +1343,54 @@ class ApiBaseTest extends ApiTestCase {
                ], $user ) );
        }
 
+       public function testAddBlockInfoToStatus() {
+               $mock = new MockApi();
+
+               // Sanity check empty array
+               $expect = Status::newGood();
+               $test = Status::newGood();
+               $mock->addBlockInfoToStatus( $test );
+               $this->assertEquals( $expect, $test );
+
+               // No blocked $user, so no special block handling
+               $expect = Status::newGood();
+               $expect->fatal( 'blockedtext' );
+               $expect->fatal( 'autoblockedtext' );
+               $expect->fatal( 'systemblockedtext' );
+               $expect->fatal( 'mainpage' );
+               $expect->fatal( 'parentheses', 'foobar' );
+               $test = clone $expect;
+               $mock->addBlockInfoToStatus( $test );
+               $this->assertEquals( $expect, $test );
+
+               // Has a blocked $user, so special block handling
+               $user = $this->getMutableTestUser()->getUser();
+               $block = new \Block( [
+                       'address' => $user->getName(),
+                       'user' => $user->getID(),
+                       'by' => $this->getTestSysop()->getUser()->getId(),
+                       'reason' => __METHOD__,
+                       'expiry' => time() + 100500,
+               ] );
+               $block->insert();
+               $blockinfo = [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ];
+
+               $expect = Status::newGood();
+               $expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
+               $expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
+               $expect->fatal( ApiMessage::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
+               $expect->fatal( 'mainpage' );
+               $expect->fatal( 'parentheses', 'foobar' );
+               $test = Status::newGood();
+               $test->fatal( 'blockedtext' );
+               $test->fatal( 'autoblockedtext' );
+               $test->fatal( 'systemblockedtext' );
+               $test->fatal( 'mainpage' );
+               $test->fatal( 'parentheses', 'foobar' );
+               $mock->addBlockInfoToStatus( $test, $user );
+               $this->assertEquals( $expect, $test );
+       }
+
        public function testDieStatus() {
                $mock = new MockApi();
 
index de0af0b..1706ad1 100644 (file)
@@ -1474,8 +1474,7 @@ class ApiEditPageTest extends ApiTestCase {
        public function testEditWhileBlocked() {
                $name = 'Help:' . ucfirst( __FUNCTION__ );
 
-               $this->setExpectedException( ApiUsageException::class,
-                       'You have been blocked from editing.' );
+               $this->assertNull( Block::newFromTarget( '127.0.0.1' ), 'Sanity check' );
 
                $block = new Block( [
                        'address' => self::$users['sysop']->getUser()->getName(),
@@ -1483,6 +1482,7 @@ class ApiEditPageTest extends ApiTestCase {
                        'reason' => 'Capriciousness',
                        'timestamp' => '19370101000000',
                        'expiry' => 'infinity',
+                       'enableAutoblock' => true,
                ] );
                $block->insert();
 
@@ -1492,6 +1492,10 @@ class ApiEditPageTest extends ApiTestCase {
                                'title' => $name,
                                'text' => 'Some text',
                        ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
+                       $this->assertNotNull( Block::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
                } finally {
                        $block->delete();
                        self::$users['sysop']->getUser()->clearInstanceCache();
index 1e66a7d..b9c49b1 100644 (file)
@@ -130,6 +130,39 @@ class ApiMoveTest extends ApiTestCase {
                }
        }
 
+       public function testMoveWhileBlocked() {
+               $this->assertNull( Block::newFromTarget( '127.0.0.1' ), 'Sanity check' );
+
+               $block = new Block( [
+                       'address' => self::$users['sysop']->getUser()->getName(),
+                       'by' => self::$users['sysop']->getUser()->getId(),
+                       'reason' => 'Capriciousness',
+                       'timestamp' => '19370101000000',
+                       'expiry' => 'infinity',
+                       'enableAutoblock' => true,
+               ] );
+               $block->insert();
+
+               $name = ucfirst( __FUNCTION__ );
+               $id = $this->createPage( $name );
+
+               try {
+                       $this->doApiRequestWithToken( [
+                               'action' => 'move',
+                               'from' => $name,
+                               'to' => "$name 2",
+                       ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( ApiUsageException $ex ) {
+                       $this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
+                       $this->assertNotNull( Block::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
+               } finally {
+                       $block->delete();
+                       self::$users['sysop']->getUser()->clearInstanceCache();
+                       $this->assertSame( $id, Title::newFromText( $name )->getArticleID() );
+               }
+       }
+
        // @todo File moving
 
        public function testPingLimiter() {
index 58e6d7d..70bf39f 100644 (file)
@@ -9,6 +9,7 @@ use Wikimedia\TestingAccessWrapper;
 class MessageBlobStoreTest extends PHPUnit\Framework\TestCase {
 
        use MediaWikiCoversValidator;
+       use PHPUnit4And6Compat;
 
        protected function setUp() {
                parent::setUp();
@@ -37,7 +38,7 @@ class MessageBlobStoreTest extends PHPUnit\Framework\TestCase {
 
        protected function makeBlobStore( $methods = null, $rl = null ) {
                $blobStore = $this->getMockBuilder( MessageBlobStore::class )
-                       ->setConstructorArgs( [ $rl ] )
+                       ->setConstructorArgs( [ $rl ?? $this->createMock( ResourceLoader::class ) ] )
                        ->setMethods( $methods )
                        ->getMock();