Merge "installer: Remove additional newline in LocalSettings.php"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 25 Oct 2015 20:33:04 +0000 (20:33 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 25 Oct 2015 20:33:04 +0000 (20:33 +0000)
114 files changed:
autoload.php
includes/DefaultSettings.php
includes/MagicWord.php
includes/Setup.php
includes/api/i18n/ru.json
includes/api/i18n/zh-hans.json
includes/clientpool/RedisConnectionPool.php
includes/db/DatabaseError.php
includes/filebackend/FileBackendStore.php
includes/installer/Installer.php
includes/installer/LocalSettingsGenerator.php
includes/installer/WebInstaller.php
includes/installer/i18n/el.json
includes/installer/i18n/en.json
includes/installer/i18n/ksh.json
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/ObjectFactory.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/MemcachedBagOStuff.php [new file with mode: 0644]
includes/libs/objectcache/MemcachedClient.php [new file with mode: 0644]
includes/libs/objectcache/MemcachedPhpBagOStuff.php [new file with mode: 0644]
includes/libs/objectcache/MultiWriteBagOStuff.php [new file with mode: 0644]
includes/objectcache/MemcachedBagOStuff.php [deleted file]
includes/objectcache/MemcachedClient.php [deleted file]
includes/objectcache/MemcachedPeclBagOStuff.php
includes/objectcache/MemcachedPhpBagOStuff.php [deleted file]
includes/objectcache/MultiWriteBagOStuff.php [deleted file]
includes/objectcache/ObjectCache.php
includes/page/WikiPage.php
includes/parser/CacheTime.php
includes/parser/Parser.php
includes/poolcounter/PoolWorkArticleView.php
includes/specials/SpecialAllPages.php
includes/specials/SpecialPrefixindex.php
includes/specials/SpecialUserlogin.php
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bgn.json
languages/i18n/bn.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/gsw.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/km.json
languages/i18n/ko.json
languages/i18n/ksh.json
languages/i18n/lb.json
languages/i18n/lrc.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/mr.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/olo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sco.json
languages/i18n/sd.json
languages/i18n/sgs.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/war.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
resources/src/mediawiki.action/mediawiki.action.edit.preview.js
resources/src/mediawiki.special/mediawiki.special.css
resources/src/mediawiki.special/mediawiki.special.userlogin.common.css
resources/src/mediawiki/mediawiki.notification.css
tests/phpunit/includes/libs/ObjectFactoryTest.php
tests/phpunit/includes/libs/objectcache/BagOStuffTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/objectcache/MultiWriteBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php [new file with mode: 0644]
tests/phpunit/includes/objectcache/BagOStuffTest.php [deleted file]
tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php [deleted file]
tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php [deleted file]
tests/phpunit/includes/objectcache/WANObjectCacheTest.php [deleted file]

index 8720f33..9b5e10f 100644 (file)
@@ -720,7 +720,7 @@ $wgAutoloadLocalClasses = array(
        'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
        'MWHookException' => __DIR__ . '/includes/Hooks.php',
        'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
-       'MWMemcached' => __DIR__ . '/includes/objectcache/MemcachedClient.php',
+       'MWMemcached' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
        'MWMessagePack' => __DIR__ . '/includes/libs/MWMessagePack.php',
        'MWNamespace' => __DIR__ . '/includes/MWNamespace.php',
        'MWOldPassword' => __DIR__ . '/includes/password/MWOldPassword.php',
@@ -778,11 +778,11 @@ $wgAutoloadLocalClasses = array(
        'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
        'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
-       'MemCachedClientforWiki' => __DIR__ . '/includes/objectcache/MemcachedClient.php',
+       'MemCachedClientforWiki' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
        'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php',
-       'MemcachedBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedBagOStuff.php',
+       'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
        'MemcachedPeclBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPeclBagOStuff.php',
-       'MemcachedPhpBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPhpBagOStuff.php',
+       'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
        'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
        'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
        'MergeHistoryPager' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
@@ -816,7 +816,7 @@ $wgAutoloadLocalClasses = array(
        'MssqlUpdater' => __DIR__ . '/includes/installer/MssqlUpdater.php',
        'MultiConfig' => __DIR__ . '/includes/config/MultiConfig.php',
        'MultiHttpClient' => __DIR__ . '/includes/libs/MultiHttpClient.php',
-       'MultiWriteBagOStuff' => __DIR__ . '/includes/objectcache/MultiWriteBagOStuff.php',
+       'MultiWriteBagOStuff' => __DIR__ . '/includes/libs/objectcache/MultiWriteBagOStuff.php',
        'MutableConfig' => __DIR__ . '/includes/config/MutableConfig.php',
        'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
        'MwSql' => __DIR__ . '/maintenance/sql.php',
index 189ce42..04f3f31 100644 (file)
@@ -5202,80 +5202,85 @@ $wgApplyIpBlocksToXff = false;
  * @par Example:
  * To set a generic maximum of 4 hits in 60 seconds:
  * @code
- * $wgRateLimits = array( 4, 60 );
+ *     $wgRateLimits = array( 4, 60 );
  * @endcode
  *
- * You could also limit per action and then type of users. See the inline
- * code for a template to use.
- *
- * This option set is experimental and likely to change.
+ * @par Example:
+ * You could also limit per action and then type of users.
+ * @code
+ *     $wgRateLimits = array(
+ *         'edit' => array(
+ *             'anon' => array( x, y ), // any and all anonymous edits (aggregate)
+ *             'user' => array( x, y ), // each logged-in user
+ *             'newbie' => array( x, y ), // each new autoconfirmed accounts; overrides 'user'
+ *             'ip' => array( x, y ), // each anon and recent account
+ *             'subnet' => array( x, y ), // ... within a /24 subnet in IPv4 or /64 in IPv6
+ *         )
+ *     )
+ * @endcode
  *
- * @warning Requires memcached.
+ * @warning Requires that $wgMainCacheType is set to something persistent
  */
 $wgRateLimits = array(
+       // Page edits
        'edit' => array(
-               'anon' => null, // for any and all anonymous edits (aggregate)
-               'user' => null, // for each logged-in user
-               'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user'
-               'ip' => null, // for each anon and recent account
-               'subnet' => null, // ... within a /24 subnet in IPv4 or /64 in IPv6
+               'ip' => array( 8, 60 ),
+               'newbie' => array( 8, 60 ),
+       ),
+       // Page moves
+       'move' => array(
+               'newbie' => array( 2, 120 ),
+               'user' => array( 8, 60 ),
        ),
+       // File uploads
        'upload' => array(
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+               'ip' => array( 8, 60 ),
+               'newbie' => array( 8, 60 ),
        ),
-       'move' => array(
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Page rollbacks
+       'rollback' => array(
+               'user' => array( 10, 60 ),
+               'newbie' => array( 5, 120 )
        ),
-       'mailpassword' => array( // triggering password resets emails
-               'anon' => null,
+       // Triggering password resets emails
+       'mailpassword' => array(
+               'ip' => array( 5, 3600 ),
        ),
-       'emailuser' => array( // emailing other users using MediaWiki
-               'user' => null,
+       // Emailing other users using MediaWiki
+       'emailuser' => array(
+               'ip' => array( 5, 86400 ),
+               'newbie' => array( 5, 86400 ),
+               'user' => array( 20, 86400 ),
        ),
-       'linkpurge' => array( // purges of link tables
-               'anon' => null,
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Purging pages
+       'purge' => array(
+               'ip' => array( 30, 60 ),
+               'user' => array( 30, 60 ),
        ),
-       'renderfile' => array( // files rendered via thumb.php or thumb_handler.php
-               'anon' => null,
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Purges of link tables
+       'linkpurge' => array(
+               'ip' => array( 30, 60 ),
+               'user' => array( 30, 60 ),
        ),
-       'renderfile-nonstandard' => array( // same as above but for non-standard thumbnails
-               'anon' => null,
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Files rendered via thumb.php or thumb_handler.php
+       'renderfile' => array(
+               'ip' => array( 700, 30 ),
+               'user' => array( 700, 30 ),
        ),
-       'stashedit' => array( // stashing edits into cache before save
-               'anon' => null,
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Same as above but for non-standard thumbnails
+       'renderfile-nonstandard' => array(
+               'ip' => array( 70, 30 ),
+               'user' => array( 70, 30 ),
        ),
-       'changetag' => array( // adding or removing change tags
-               'user' => null,
-               'newbie' => null,
+       // Stashing edits into cache before save
+       'stashedit' => array(
+               'ip' => array( 30, 60 ),
+               'newbie' => array( 30, 60 ),
        ),
-       'purge' => array( // purging pages
-               'anon' => null,
-               'user' => null,
-               'newbie' => null,
-               'ip' => null,
-               'subnet' => null,
+       // Adding or removing change tags
+       'changetag' => array(
+               'ip' => array( 8, 60 ),
+               'newbie' => array( 8, 60 ),
        ),
 );
 
index 2c7ba91..424735e 100644 (file)
@@ -941,6 +941,7 @@ class MagicWordArray {
         *
         * @param string $text
         *
+        * @throws Exception
         * @return array
         */
        public function matchAndRemove( &$text ) {
@@ -951,13 +952,22 @@ class MagicWordArray {
                                continue;
                        }
                        $matches = array();
-                       if ( preg_match_all( $regex, $text, $matches, PREG_SET_ORDER ) ) {
+                       $matched = preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
+                       if ( $matched === false ) {
+                               throw new Exception( __METHOD__ . ': preg_match_all returned false' );
+                       }
+                       if ( $matched ) {
                                foreach ( $matches as $m ) {
                                        list( $name, $param ) = $this->parseMatch( $m );
                                        $found[$name] = $param;
                                }
                        }
-                       $text = preg_replace( $regex, '', $text );
+                       $replaced = preg_replace( $regex, '', $text );
+                       if ( $replaced !== null ) {
+                               $text = $replaced;
+                       } else {
+                               throw new Exception( __METHOD__ . ': preg_replace returned null' );
+                       }
                }
                return $found;
        }
index fbfef1f..0b6b018 100644 (file)
@@ -689,7 +689,7 @@ $wgTitle = null;
 Profiler::instance()->scopedProfileOut( $ps_globals );
 $ps_extensions = Profiler::instance()->scopedProfileIn( $fname . '-extensions' );
 
-// Extension setup functions for extensions other than skins
+// Extension setup functions
 // Entries should be added to this variable during the inclusion
 // of the extension file. This allows the extension to perform
 // any necessary initialisation in the fully initialised environment
index 5ce21c5..134ca2c 100644 (file)
        "api-pageset-param-revids": "Список идентификаторов версий для работы.",
        "api-help-title": "Справка MediaWiki API",
        "api-help-main-header": "Главный модуль",
-       "api-help-flag-deprecated": "ЭÑ\82оÑ\82 Ð¼Ð¾Ð´Ñ\83лÑ\8c Ñ\8fвлÑ\8fеÑ\82Ñ\81Ñ\8f Ñ\83Ñ\81Ñ\82аÑ\80евÑ\88им.",
+       "api-help-flag-deprecated": "ЭÑ\82оÑ\82 Ð¼Ð¾Ð´Ñ\83лÑ\8c Ñ\83Ñ\81Ñ\82аÑ\80ел.",
        "api-help-flag-readrights": "Этот модуль требует прав на чтение.",
        "api-help-flag-writerights": "Этот модуль требует права на запись.",
        "api-help-flag-mustbeposted": "Этот модуль принимает только Post-запросы.",
index ffd3c01..4d0b796 100644 (file)
        "apihelp-query+allrevisions-param-namespace": "只列出此名字空间的页面。",
        "apihelp-query+allrevisions-param-generatetitles": "当作为生成器使用时,生成标题而不是修订ID。",
        "apihelp-query+allrevisions-example-user": "列出由用户<kbd>Example</kbd>作出的最近50次贡献。",
+       "apihelp-query+allrevisions-example-ns-main": "列举主名字空间中的前50次修订。",
        "apihelp-query+alltransclusions-description": "列出所有嵌入页面(使用&#123;&#123;x&#125;&#125;嵌入的页面),包括不存在的。",
        "apihelp-query+alltransclusions-param-from": "要列举的起始嵌入标题。",
        "apihelp-query+alltransclusions-param-to": "要列举的最终嵌入标题。",
        "apihelp-query+watchlist-paramvalue-type-external": "外部更改。",
        "apihelp-query+watchlist-paramvalue-type-new": "页面创建。",
        "apihelp-query+watchlist-paramvalue-type-log": "日志记录。",
+       "apihelp-query+watchlist-paramvalue-type-categorize": "分类成员组更改。",
        "apihelp-query+watchlist-param-token": "允许访问其他用户监视列表的安全密钥(可通过用户的[[Special:Preferences#mw-prefsection-watchlist|参数设置]]找到)。",
        "apihelp-query+watchlist-example-simple": "在当前用户的监视列表中列出用于最近更改页面的最新修订。",
        "apihelp-query+watchlist-example-props": "在当前用户的监视列表中检索有关用于最近更改页面的最新修订的额外信息。",
index 8a88fab..64db0d6 100644 (file)
@@ -79,11 +79,11 @@ class RedisConnectionPool implements LoggerAwareInterface {
 
        /**
         * @param array $options
-        * @throws MWException
+        * @throws Exception
         */
        protected function __construct( array $options ) {
                if ( !class_exists( 'Redis' ) ) {
-                       throw new MWException( __CLASS__ . ' requires a Redis client library. ' .
+                       throw new Exception( __CLASS__ . ' requires a Redis client library. ' .
                                'See https://www.mediawiki.org/wiki/Redis#Setup' );
                }
                if ( isset( $options['logger'] ) ) {
@@ -102,7 +102,7 @@ class RedisConnectionPool implements LoggerAwareInterface {
                } elseif ( $options['serializer'] === 'none' ) {
                        $this->serializer = Redis::SERIALIZER_NONE;
                } else {
-                       throw new MWException( "Invalid serializer specified." );
+                       throw new InvalidArgumentException( "Invalid serializer specified." );
                }
        }
 
@@ -219,7 +219,9 @@ class RedisConnectionPool implements LoggerAwareInterface {
                        // TCP connection
                        $hostPort = IP::splitHostAndPort( $server );
                        if ( !$server || !$hostPort ) {
-                               throw new MWException( __CLASS__ . ": invalid configured server \"$server\"" );
+                               throw new InvalidArgumentException(
+                                       __CLASS__ . ": invalid configured server \"$server\""
+                               );
                        }
                        list( $host, $port ) = $hostPort;
                        if ( $port === false ) {
index 6453854..e6c285e 100644 (file)
@@ -455,5 +455,5 @@ class DBUnexpectedError extends DBError {
 /**
  * @ingroup Database
  */
-class DBReadOnlyError extends DBError {
+class DBReadOnlyError extends DBExpectedError {
 }
index 9433964..e5ce968 100644 (file)
@@ -1752,7 +1752,7 @@ abstract class FileBackendStore extends FileBackend {
                                $pathNames[$this->fileCacheKey( $path )] = $path;
                        }
                }
-               // Get all cache entries for these container cache keys...
+               // Get all cache entries for these file cache keys...
                $values = $this->memCache->getMulti( array_keys( $pathNames ) );
                foreach ( $values as $cacheKey => $val ) {
                        $path = $pathNames[$cacheKey];
index 064bd6d..91589e1 100644 (file)
@@ -691,17 +691,6 @@ abstract class Installer {
                return Status::newGood();
        }
 
-       /**
-        * Exports all wg* variables stored by the installer into global scope.
-        */
-       public function exportVars() {
-               foreach ( $this->settings as $name => $value ) {
-                       if ( substr( $name, 0, 2 ) == 'wg' ) {
-                               $GLOBALS[$name] = $value;
-                       }
-               }
-       }
-
        /**
         * Environment check for DB types.
         * @return bool
index f8b46db..526d9b5 100644 (file)
@@ -334,7 +334,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 \$wgScriptPath = \"{$this->values['wgScriptPath']}\";
 ${serverSetting}
 
-## The relative URL path to the logo.  Make sure you change this from the default,
+## The URL path to static resources (images, scripts, etc.)
+\$wgResourceBasePath = \$wgScriptPath;
+
+## The URL path to the logo.  Make sure you change this from the default,
 ## or else you'll overwrite your logo when you upgrade!
 \$wgLogo = \"{$this->values['wgLogo']}\";
 
index 67a4def..9edc25a 100644 (file)
@@ -159,7 +159,6 @@ class WebInstaller extends Installer {
                        $this->settings = $session['settings'] + $this->settings;
                }
 
-               $this->exportVars();
                $this->setupLanguage();
 
                if ( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) )
index 8109c2e..d58e92f 100644 (file)
        "config-desc": "Το πρόγραμμα εγκατάστασης για το MediaWiki",
        "config-title": "Εγκατάσταση MediaWiki $1",
        "config-information": "Πληροφορίες",
+       "config-localsettings-upgrade": "Ένα  αρχείο <code>LocalSettings.php</code> έχει εντοπιστεί.\nΓια να αναβαθμίσετε αυτή την εγκατάσταση, παρακαλούμε να εισάγετε την τιμή των <code>$wgUpgradeKey</code> στο παρακάτω πλαίσιο.\nΘα το βρείτε στο <code>LocalSettings.php</code>.",
+       "config-localsettings-cli-upgrade": "Ένα αρχείο <code>LocalSettings.php</code> έχει εντοπιστεί.\nΓια να αναβαθμίσετε αυτή την εγκατάσταση, εκτελέστε το <code>update.php</code> αντ' αυτού.",
        "config-localsettings-key": "Κλειδί αναβάθμισης:",
        "config-localsettings-badkey": "Το κλειδί που δώσατε είναι εσφαλμένο.",
        "config-upgrade-key-missing": "Έχει εντοπιστεί μια υπάρχουσα εγκατάσταση του MediaWiki.\nΓια να αναβαθμίσετε αυτήν την εγκατάσταση, παρακαλούμε να βάλετε την ακόλουθη γραμμή στο κάτω μέρος του <code>LocalSettings.php</code> σας:\n\n$1",
+       "config-localsettings-incomplete": "Το υπάρχον <code>LocalSettings.php</code> φαίνεται να είναι ελλιπές.\nΤο $1 μεταβλητή δεν έχει οριστεί.\nΠαρακαλούμε να αλλάξετε  το <code>LocalSettings.php</code> έτσι ώστε αυτή η μεταβλητή έχει οριστεί, και κάντε κλικ στο \"{{int:Config-continue}}\".",
        "config-session-error": "Σφάλμα κατά την εκκίνηση συνεδρίας: $1",
+       "config-session-expired": "Τα δεδομένα συνόδου φαίνεται να έχουν λήξει.\nΣυνεδρίες έχουν ρυθμιστεί για μια διάρκεια ζωής $1.\nΜπορείτε να αυξήσετε αυτό βάζοντας  <code>session.gc_maxlifetime</code> στο php.ini.\nΚάντε επανεκκίνηση της διαδικασίας εγκατάστασης.",
        "config-your-language": "Η γλώσσα σας:",
        "config-your-language-help": "Επιλέξτε μία γλώσσα για τη διαδικασία της εγκατάστασης.",
        "config-wiki-language": "Γλώσσα του wiki:",
        "config-env-hhvm": "Το HHVM $1 είναι εγκατεστημένο.",
        "config-unicode-using-intl": "Χρησιμοποιώντας την [http://pecl.php.net/intl επέκταση intl PECL] για κανονικοποίηση Unicode.",
        "config-unicode-pure-php-warning": "<strong>Προειδοποίηση:</strong> Η [http://pecl.php.net/intl επέκταση intl PECL] δεν είναι διαθέσιμη για να χειριστεί την κανονικοποίηση Unicode, επιστρέφουμε στην αργή αμιγώς PHP εφαρμογή.\nΕάν λειτουργείτε έναν ιστότοπο υψηλής επισκεψιμότητας, θα πρέπει να ρίξετε μια ματιά στην [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations κανονικοποίηση Unicode].",
+       "config-memory-bad": "<strong>Προειδοποίηση:</strong> η <code>memory_limit</code> της PHP είναι $1.\nΑυτό είναι πιθανώς πολύ χαμηλό.\n\nΗ εγκατάσταση ενδέχεται να αποτύχει!",
        "config-xcache": "[http://xcache.lighttpd.net/ Το XCache] είναι εγκατεστημένο",
        "config-apc": "Το [http://www.php.net/apc APC] είναι εγκατεστημένο",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp Το WinCache] είναι εγκατεστημένο",
        "config-diff3-bad": "Το GNU diff3 δεν βρέθηκε.",
+       "config-git": "Βρέθηκε η Git έκδοση λογισμικού ελέγχου: <code>$1</code>.",
+       "config-git-bad": "Η Git έκδοση του λογισμικού ελέγχου δε βρέθηκε.",
+       "config-using-server": "Χρησιμοποιώντας το όνομα του διακομιστή \"<nowiki>$1</nowiki>\".",
+       "config-using-uri": "Χρησιμοποιώντας την διεύθυνση URL του διακομιστή \"<nowiki>$1$2</nowiki>\".",
+       "config-brokenlibxml": "Το σύστημά σας έχει ένα συνδυασμό εκδόσεων της PHP και libxml2 που είναι προβληματικές και μπορεί να προκαλέσει κρυμμένα στοιχεία διαφθοράς στο MediaWiki και άλλες εφαρμογές web.\nΑναβαθμίστε σε libxml2 2.7.3 ή μεταγενέστερο ([https://bugs.php.net/bug.php?id=45996 bug κατατεθεί με την PHP]).\nΗ εγκατάσταση ματαιώθηκε.",
        "config-db-type": "Τύπος βάσης δεδομένων:",
        "config-db-host": "Φιλοξενία βάσης δεδομένων:",
        "config-db-host-oracle": "Βάση δεδομένων TNS:",
@@ -61,6 +71,7 @@
        "config-db-install-account": "Λογαριασμός χρήστη για την εγκατάσταση",
        "config-db-username": "Όνομα χρήστη βάσης δεδομένων:",
        "config-db-password": "Κωδικός πρόσβασης βάσης δεδομένων:",
+       "config-db-account-lock": "Χρησιμοποιήστε το ίδιο όνομα χρήστη και κωδικό πρόσβασης στη διάρκεια της κανονικής λειτουργίας",
        "config-db-wiki-account": "Λογαριασμός χρήστη για κανονική λειτουργία",
        "config-db-prefix": "Πρόθεμα πίνακα βάσης δεδομένων:",
        "config-db-charset": "Σύνολο χαρακτήρων βάσης δεδομένων",
        "config-admin-password": "Κωδικός πρόσβασης:",
        "config-admin-password-confirm": "Επανάληψη κωδικού πρόσβασης:",
        "config-admin-name-blank": "Εισαγάγετε όνομα χρήστη διαχειριστή.",
+       "config-admin-name-invalid": "Το συγκεκριμένο όνομα χρήστη  \"<nowiki>$1</nowiki>\" δεν είναι έγκυρο. Δώστε ένα διαφορετικό όνομα χρήστη.",
+       "config-admin-password-blank": "Εισάγετε έναν κωδικό για τον λογαριασμό του διαχειριστή.",
        "config-admin-password-mismatch": "Οι δύο κωδικοί πρόσβασης που εισηγάγατε δεν ταιριάζουν.",
        "config-admin-email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "config-admin-error-bademail": "Έχετε εισαγάγει μη έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου.",
index 1eb8e03..ac3178f 100644 (file)
        "config-nofile": "File \"$1\" could not be found. Has it been deleted?",
        "config-extension-link": "Did you know that your wiki supports [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions]?\n\nYou can browse [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.",
        "mainpagetext": "<strong>MediaWiki has been successfully installed.</strong>",
-       "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
+       "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index 0c11be0..0b722dd 100644 (file)
        "config-nofile": "De Dattei „$1“ ham_mer nit jefonge. Es di fottjeschmeße?",
        "config-extension-link": "Häs De jewoß, dat et Wiki [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions Zohsazprojramme] hann kann?\n\nDo kanns [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Zohsazprojramme noh Saachjroppe] söhke udder en de [//www.mediawiki.org/wiki/Extension_Matrix Tabäll met de Zohsazprojramme] kike, öm de kumplätte Leß met de Zohsazprojramme ze krijje.",
        "mainpagetext": "'''MehdijaWikki es jäz enschtalleht.'''",
-       "mainpagedocfooter": "Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbohch] wann De weße wells wi de Wikki-ẞoffwähr jebruch un bedehnt wähde moß.\n\n== För der Aanfang ==\nDat es och all op Änglesch:\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings En leß met müjjelesche Enschtällonge för et MehdijaWikki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Öff jefrooch övver et Mehdijawikki&nbsp;&hellip;]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce De Meilengleß met Annköndijonge övver neuje Aßjahbe vum MehdijaWikki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Donn MediaWiki op Ding Schprohch aanpaße]\n\n== Un dann ==\nDonn heh di Sigg ömbenänne un/udder jähje en ääschte Aanfangssigg för heh dat Wikki ußtuusche!\n\nAlles Johde!"
+       "mainpagedocfooter": "Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbohch] wann De weße wells wi de Wikki-ẞoffwähr jebruch un bedehnt wähde moß.\n\n== För der Aanfang ==\nDat es och all op Änglesch:\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings En leß met müjjelesche Enschtällonge för et MehdijaWikki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Öff jefrooch övver et Mehdijawikki&nbsp;&hellip;]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce De Meilengleß met Annköndijonge övver neuje Ußjahbe vum MehdijaWikki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Donn MediaWiki op Ding Schprohch aanpaße]\n\n== Un dann ==\nDonn heh di Sigg ömbenänne un/udder jähje en ääschte Aanfangssigg för heh dat Wikki ußtuusche!\n\nAlles Johde!"
 }
index ef9fec0..6b1a1e3 100644 (file)
@@ -103,6 +103,7 @@ class HTMLCacheUpdateJob extends Job {
                // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
                foreach ( array_chunk( $pageIds, $wgUpdateRowsPerQuery ) as $batch ) {
                        $dbw->commit( __METHOD__, 'flush' );
+                       wfWaitForSlaves();
 
                        $dbw->update( 'page',
                                array( 'page_touched' => $dbw->timestamp( $touchTimestamp ) ),
index 1d59b32..3a83cb8 100644 (file)
@@ -111,12 +111,7 @@ class RefreshLinksJob extends Job {
         * @param Title $title
         * @return bool
         */
-       protected function runForTitle( Title $title = null ) {
-               if ( is_null( $title ) ) {
-                       $this->setLastError( "refreshLinks: Invalid title" );
-                       return false;
-               }
-
+       protected function runForTitle( Title $title ) {
                // Wait for the DB of the current/next slave DB handle to catch up to the master.
                // This way, we get the correct page_latest for templates or files that just changed
                // milliseconds ago, having triggered this job to begin with.
index 0b9aa7c..6191612 100644 (file)
@@ -35,13 +35,6 @@ class ObjectFactory {
         * an 'args' key that provides arguments to pass to the
         * constructor/callable.
         *
-        * Object construction using a specification having both 'class' and
-        * 'args' members will call the constructor of the class using
-        * ReflectionClass::newInstanceArgs. The use of ReflectionClass carries
-        * a performance penalty and should not be used to create large numbers of
-        * objects. If this is needed, consider introducing a factory method that
-        * can be called via call_user_func_array() instead.
-        *
         * Values in the arguments collection which are Closure instances will be
         * expanded by invoking them with no arguments before passing the
         * resulting value on to the constructor/callable. This can be used to
@@ -77,8 +70,7 @@ class ObjectFactory {
                        if ( !$args ) {
                                $obj = new $clazz();
                        } else {
-                               $ref = new ReflectionClass( $clazz );
-                               $obj = $ref->newInstanceArgs( $args );
+                               $obj = static::constructClassInstance( $clazz, $args );
                        }
                } elseif ( isset( $spec['factory'] ) ) {
                        $obj = call_user_func_array( $spec['factory'], $args );
@@ -117,4 +109,86 @@ class ObjectFactory {
                        }
                }, $list );
        }
+
+       /**
+        * Construct an instance of the given class using the given arguments.
+        *
+        * PHP's `call_user_func_array()` doesn't work with object construction so
+        * we have to use other measures. Starting with PHP 5.6.0 we could use the
+        * "splat" operator (`...`) to unpack the array into an argument list.
+        * Sadly there is no way to conditionally include a syntax construct like
+        * a new operator in a way that allows older versions of PHP to still
+        * parse the file. Instead, we will try a loop unrolling technique that
+        * works for 0-10 arguments. If we are passed 11 or more arguments we will
+        * take the performance penalty of using
+        * `ReflectionClass::newInstanceArgs()` to construct the desired object.
+        *
+        * @param string $clazz Class name
+        * @param array $args Constructor arguments
+        * @return mixed Constructed instance
+        */
+       public static function constructClassInstance( $clazz, $args ) {
+               // TODO: when PHP min version supported is >=5.6.0 replace this
+               // function body with `return new $clazz( ... $args );`.
+               $obj = null;
+               switch ( count( $args ) ) {
+                       case 0:
+                               $obj = new $clazz();
+                               break;
+                       case 1:
+                               $obj = new $clazz( $args[0] );
+                               break;
+                       case 2:
+                               $obj = new $clazz( $args[0], $args[1] );
+                               break;
+                       case 3:
+                               $obj = new $clazz( $args[0], $args[1], $args[2] );
+                               break;
+                       case 4:
+                               $obj = new $clazz( $args[0], $args[1], $args[2], $args[3] );
+                               break;
+                       case 5:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4]
+                               );
+                               break;
+                       case 6:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4],
+                                       $args[5]
+                               );
+                               break;
+                       case 7:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4],
+                                       $args[5], $args[6]
+                               );
+                               break;
+                       case 8:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4],
+                                       $args[5], $args[6], $args[7]
+                               );
+                               break;
+                       case 9:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4],
+                                       $args[5], $args[6], $args[7], $args[8]
+                               );
+                               break;
+                       case 10:
+                               $obj = new $clazz(
+                                       $args[0], $args[1], $args[2], $args[3], $args[4],
+                                       $args[5], $args[6], $args[7], $args[8], $args[9]
+                               );
+                               break;
+                       default:
+                               // Fall back to using ReflectionClass and curse the developer
+                               // who decided that 11+ args was a reasonable method
+                               // signature.
+                               $ref = new ReflectionClass( $clazz );
+                               $obj = $ref->newInstanceArgs( $args );
+               }
+               return $obj;
+       }
 }
index c3c357f..ecc5e37 100644 (file)
@@ -627,7 +627,11 @@ abstract class BagOStuff implements LoggerAwareInterface {
         * @return string
         */
        public function makeKeyInternal( $keyspace, $args ) {
-               $key = $keyspace . ':' . implode( ':', $args );
+               $key = $keyspace;
+               foreach ( $args as $arg ) {
+                       $arg = str_replace( ':', '%3A', $arg );
+                       $key = $key . ':' . $arg;
+               }
                return strtr( $key, ' ', '_' );
        }
 
diff --git a/includes/libs/objectcache/MemcachedBagOStuff.php b/includes/libs/objectcache/MemcachedBagOStuff.php
new file mode 100644 (file)
index 0000000..ef6b3c7
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Base class for memcached clients.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * Base class for memcached clients.
+ *
+ * @ingroup Cache
+ */
+class MemcachedBagOStuff extends BagOStuff {
+       /** @var MWMemcached|Memcached */
+       protected $client;
+
+       /**
+        * Fill in some defaults for missing keys in $params.
+        *
+        * @param array $params
+        * @return array
+        */
+       protected function applyDefaultParams( $params ) {
+               if ( !isset( $params['compress_threshold'] ) ) {
+                       $params['compress_threshold'] = 1500;
+               }
+               if ( !isset( $params['connect_timeout'] ) ) {
+                       $params['connect_timeout'] = 0.5;
+               }
+               return $params;
+       }
+
+       protected function doGet( $key, $flags = 0 ) {
+               $casToken = null;
+
+               return $this->getWithToken( $key, $casToken, $flags );
+       }
+
+       protected function getWithToken( $key, &$casToken, $flags = 0 ) {
+               return $this->client->get( $this->validateKeyEncoding( $key ), $casToken );
+       }
+
+       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               return $this->client->set( $this->validateKeyEncoding( $key ), $value,
+                       $this->fixExpiry( $exptime ) );
+       }
+
+       protected function cas( $casToken, $key, $value, $exptime = 0 ) {
+               return $this->client->cas( $casToken, $this->validateKeyEncoding( $key ),
+                       $value, $this->fixExpiry( $exptime ) );
+       }
+
+       public function delete( $key ) {
+               return $this->client->delete( $this->validateKeyEncoding( $key ) );
+       }
+
+       public function add( $key, $value, $exptime = 0 ) {
+               return $this->client->add( $this->validateKeyEncoding( $key ), $value,
+                       $this->fixExpiry( $exptime ) );
+       }
+
+       public function merge( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
+               if ( !is_callable( $callback ) ) {
+                       throw new Exception( "Got invalid callback." );
+               }
+
+               return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+       }
+
+       /**
+        * Get the underlying client object. This is provided for debugging
+        * purposes.
+        * @return BagOStuff
+        */
+       public function getClient() {
+               return $this->client;
+       }
+
+       /**
+        * Construct a cache key.
+        *
+        * @since 1.27
+        * @param string $keyspace
+        * @param array $args
+        * @return string
+        */
+       public function makeKeyInternal( $keyspace, $args ) {
+               // Memcached keys have a maximum length of 255 characters. From that,
+               // subtract the number of characters we need for the keyspace and for
+               // the separator character needed for each argument.
+               $charsLeft = 255 - strlen( $keyspace ) - count( $args );
+
+               $args = array_map(
+                       function ( $arg ) use ( &$charsLeft ) {
+                               $arg = strtr( $arg, ' ', '_' );
+
+                               // Make sure %, #, and non-ASCII chars are escaped
+                               $arg = preg_replace_callback(
+                                       '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/',
+                                       function ( $m ) {
+                                               return rawurlencode( $m[0] );
+                                       },
+                                       $arg
+                               );
+
+                               // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
+                               if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
+                                       $arg = '#' . md5( $arg );
+                               }
+
+                               $charsLeft -= strlen( $arg );
+                               return $arg;
+                       },
+                       $args
+               );
+
+               if ( $charsLeft < 0 ) {
+                       return $keyspace . ':##' . md5( implode( ':', $args ) );
+               }
+
+               return $keyspace . ':' . implode( ':', $args );
+       }
+
+       /**
+        * Ensure that a key is safe to use (contains no control characters and no
+        * characters above the ASCII range.)
+        *
+        * @param string $key
+        * @return string
+        * @throws Exception
+        */
+       public function validateKeyEncoding( $key ) {
+               if ( preg_match( '/[^\x21-\x7e]+/', $key ) ) {
+                       throw new Exception( "Key contains invalid characters: $key" );
+               }
+               return $key;
+       }
+
+       /**
+        * TTLs higher than 30 days will be detected as absolute TTLs
+        * (UNIX timestamps), and will result in the cache entry being
+        * discarded immediately because the expiry is in the past.
+        * Clamp expires >30d at 30d, unless they're >=1e9 in which
+        * case they are likely to really be absolute (1e9 = 2011-09-09)
+        * @param int $expiry
+        * @return int
+        */
+       function fixExpiry( $expiry ) {
+               if ( $expiry > 2592000 && $expiry < 1000000000 ) {
+                       $expiry = 2592000;
+               }
+               return (int)$expiry;
+       }
+
+       /**
+        * Send a debug message to the log
+        * @param string $text
+        */
+       protected function debugLog( $text ) {
+               $this->logger->debug( $text );
+       }
+
+       public function modifySimpleRelayEvent( array $event ) {
+               if ( array_key_exists( 'val', $event ) ) {
+                       $event['flg'] = 0; // data is not serialized nor gzipped (for memcached driver)
+               }
+
+               return $event;
+       }
+}
diff --git a/includes/libs/objectcache/MemcachedClient.php b/includes/libs/objectcache/MemcachedClient.php
new file mode 100644 (file)
index 0000000..5010b89
--- /dev/null
@@ -0,0 +1,1276 @@
+<?php
+// @codingStandardsIgnoreFile It's an external lib and it isn't. Let's not bother.
+/**
+ * Memcached client for PHP.
+ *
+ * +---------------------------------------------------------------------------+
+ * | memcached client, PHP                                                     |
+ * +---------------------------------------------------------------------------+
+ * | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net>                 |
+ * | All rights reserved.                                                      |
+ * |                                                                           |
+ * | Redistribution and use in source and binary forms, with or without        |
+ * | modification, are permitted provided that the following conditions        |
+ * | are met:                                                                  |
+ * |                                                                           |
+ * | 1. Redistributions of source code must retain the above copyright         |
+ * |    notice, this list of conditions and the following disclaimer.          |
+ * | 2. Redistributions in binary form must reproduce the above copyright      |
+ * |    notice, this list of conditions and the following disclaimer in the    |
+ * |    documentation and/or other materials provided with the distribution.   |
+ * |                                                                           |
+ * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
+ * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+ * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
+ * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+ * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
+ * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
+ * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
+ * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
+ * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
+ * +---------------------------------------------------------------------------+
+ * | Author: Ryan T. Dean <rtdean@cytherianage.net>                            |
+ * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
+ * |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
+ * |   client logic under 2-clause BSD license.                                |
+ * +---------------------------------------------------------------------------+
+ *
+ * @file
+ * $TCAnet$
+ */
+
+/**
+ * This is the PHP client for memcached - a distributed memory cache daemon.
+ * More information is available at http://www.danga.com/memcached/
+ *
+ * Usage example:
+ *
+ * require_once 'memcached.php';
+ *
+ * $mc = new MWMemcached(array(
+ *              'servers' => array('127.0.0.1:10000',
+ *                                 array('192.0.0.1:10010', 2),
+ *                                 '127.0.0.1:10020'),
+ *              'debug'   => false,
+ *              'compress_threshold' => 10240,
+ *              'persistent' => true));
+ *
+ * $mc->add( 'key', array( 'some', 'array' ) );
+ * $mc->replace( 'key', 'some random string' );
+ * $val = $mc->get( 'key' );
+ *
+ * @author  Ryan T. Dean <rtdean@cytherianage.net>
+ * @version 0.1.2
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+// {{{ requirements
+// }}}
+
+// {{{ class MWMemcached
+/**
+ * memcached client class implemented using (p)fsockopen()
+ *
+ * @author  Ryan T. Dean <rtdean@cytherianage.net>
+ * @ingroup Cache
+ */
+class MWMemcached {
+       // {{{ properties
+       // {{{ public
+
+       // {{{ constants
+       // {{{ flags
+
+       /**
+        * Flag: indicates data is serialized
+        */
+       const SERIALIZED = 1;
+
+       /**
+        * Flag: indicates data is compressed
+        */
+       const COMPRESSED = 2;
+
+       /**
+        * Flag: indicates data is an integer
+        */
+       const INTVAL = 4;
+
+       // }}}
+
+       /**
+        * Minimum savings to store data compressed
+        */
+       const COMPRESSION_SAVINGS = 0.20;
+
+       // }}}
+
+       /**
+        * Command statistics
+        *
+        * @var array
+        * @access public
+        */
+       public $stats;
+
+       // }}}
+       // {{{ private
+
+       /**
+        * Cached Sockets that are connected
+        *
+        * @var array
+        * @access private
+        */
+       public $_cache_sock;
+
+       /**
+        * Current debug status; 0 - none to 9 - profiling
+        *
+        * @var bool
+        * @access private
+        */
+       public $_debug;
+
+       /**
+        * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
+        *
+        * @var array
+        * @access private
+        */
+       public $_host_dead;
+
+       /**
+        * Is compression available?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_have_zlib;
+
+       /**
+        * Do we want to use compression?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_compress_enable;
+
+       /**
+        * At how many bytes should we compress?
+        *
+        * @var int
+        * @access private
+        */
+       public $_compress_threshold;
+
+       /**
+        * Are we using persistent links?
+        *
+        * @var bool
+        * @access private
+        */
+       public $_persistent;
+
+       /**
+        * If only using one server; contains ip:port to connect to
+        *
+        * @var string
+        * @access private
+        */
+       public $_single_sock;
+
+       /**
+        * Array containing ip:port or array(ip:port, weight)
+        *
+        * @var array
+        * @access private
+        */
+       public $_servers;
+
+       /**
+        * Our bit buckets
+        *
+        * @var array
+        * @access private
+        */
+       public $_buckets;
+
+       /**
+        * Total # of bit buckets we have
+        *
+        * @var int
+        * @access private
+        */
+       public $_bucketcount;
+
+       /**
+        * # of total servers we have
+        *
+        * @var int
+        * @access private
+        */
+       public $_active;
+
+       /**
+        * Stream timeout in seconds. Applies for example to fread()
+        *
+        * @var int
+        * @access private
+        */
+       public $_timeout_seconds;
+
+       /**
+        * Stream timeout in microseconds
+        *
+        * @var int
+        * @access private
+        */
+       public $_timeout_microseconds;
+
+       /**
+        * Connect timeout in seconds
+        */
+       public $_connect_timeout;
+
+       /**
+        * Number of connection attempts for each server
+        */
+       public $_connect_attempts;
+
+       /**
+        * @var LoggerInterface
+        */
+       private $_logger;
+
+       // }}}
+       // }}}
+       // {{{ methods
+       // {{{ public functions
+       // {{{ memcached()
+
+       /**
+        * Memcache initializer
+        *
+        * @param array $args Associative array of settings
+        *
+        * @return mixed
+        */
+       public function __construct( $args ) {
+               $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
+               $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false;
+               $this->stats = array();
+               $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0;
+               $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false;
+               $this->_compress_enable = true;
+               $this->_have_zlib = function_exists( 'gzcompress' );
+
+               $this->_cache_sock = array();
+               $this->_host_dead = array();
+
+               $this->_timeout_seconds = 0;
+               $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 500000;
+
+               $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1;
+               $this->_connect_attempts = 2;
+
+               $this->_logger = isset( $args['logger'] ) ? $args['logger'] : new NullLogger();
+       }
+
+       // }}}
+       // {{{ add()
+
+       /**
+        * Adds a key/value to the memcache server if one isn't already set with
+        * that key
+        *
+        * @param string $key Key to set with data
+        * @param mixed $val Value to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of expiration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool
+        */
+       public function add( $key, $val, $exp = 0 ) {
+               return $this->_set( 'add', $key, $val, $exp );
+       }
+
+       // }}}
+       // {{{ decr()
+
+       /**
+        * Decrease a value stored on the memcache server
+        *
+        * @param string $key Key to decrease
+        * @param int $amt (optional) amount to decrease
+        *
+        * @return mixed False on failure, value on success
+        */
+       public function decr( $key, $amt = 1 ) {
+               return $this->_incrdecr( 'decr', $key, $amt );
+       }
+
+       // }}}
+       // {{{ delete()
+
+       /**
+        * Deletes a key from the server, optionally after $time
+        *
+        * @param string $key Key to delete
+        * @param int $time (optional) how long to wait before deleting
+        *
+        * @return bool True on success, false on failure
+        */
+       public function delete( $key, $time = 0 ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+
+               if ( isset( $this->stats['delete'] ) ) {
+                       $this->stats['delete']++;
+               } else {
+                       $this->stats['delete'] = 1;
+               }
+               $cmd = "delete $key $time\r\n";
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return false;
+               }
+               $res = $this->_fgets( $sock );
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) );
+               }
+
+               if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * @param string $key
+        * @param int $timeout
+        * @return bool
+        */
+       public function lock( $key, $timeout = 0 ) {
+               /* stub */
+               return true;
+       }
+
+       /**
+        * @param string $key
+        * @return bool
+        */
+       public function unlock( $key ) {
+               /* stub */
+               return true;
+       }
+
+       // }}}
+       // {{{ disconnect_all()
+
+       /**
+        * Disconnects all connected sockets
+        */
+       public function disconnect_all() {
+               foreach ( $this->_cache_sock as $sock ) {
+                       fclose( $sock );
+               }
+
+               $this->_cache_sock = array();
+       }
+
+       // }}}
+       // {{{ enable_compress()
+
+       /**
+        * Enable / Disable compression
+        *
+        * @param bool $enable True to enable, false to disable
+        */
+       public function enable_compress( $enable ) {
+               $this->_compress_enable = $enable;
+       }
+
+       // }}}
+       // {{{ forget_dead_hosts()
+
+       /**
+        * Forget about all of the dead hosts
+        */
+       public function forget_dead_hosts() {
+               $this->_host_dead = array();
+       }
+
+       // }}}
+       // {{{ get()
+
+       /**
+        * Retrieves the value associated with the key from the memcache server
+        *
+        * @param array|string $key key to retrieve
+        * @param float $casToken [optional]
+        *
+        * @return mixed
+        */
+       public function get( $key, &$casToken = null ) {
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( "get($key)\n" );
+               }
+
+               if ( !is_array( $key ) && strval( $key ) === '' ) {
+                       $this->_debugprint( "Skipping key which equals to an empty string" );
+                       return false;
+               }
+
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+               if ( isset( $this->stats['get'] ) ) {
+                       $this->stats['get']++;
+               } else {
+                       $this->stats['get'] = 1;
+               }
+
+               $cmd = "gets $key\r\n";
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return false;
+               }
+
+               $val = array();
+               $this->_load_items( $sock, $val, $casToken );
+
+               if ( $this->_debug ) {
+                       foreach ( $val as $k => $v ) {
+                               $this->_debugprint( sprintf( "MemCache: sock %s got %s\n", serialize( $sock ), $k ) );
+                       }
+               }
+
+               $value = false;
+               if ( isset( $val[$key] ) ) {
+                       $value = $val[$key];
+               }
+               return $value;
+       }
+
+       // }}}
+       // {{{ get_multi()
+
+       /**
+        * Get multiple keys from the server(s)
+        *
+        * @param array $keys Keys to retrieve
+        *
+        * @return array
+        */
+       public function get_multi( $keys ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               if ( isset( $this->stats['get_multi'] ) ) {
+                       $this->stats['get_multi']++;
+               } else {
+                       $this->stats['get_multi'] = 1;
+               }
+               $sock_keys = array();
+               $socks = array();
+               foreach ( $keys as $key ) {
+                       $sock = $this->get_sock( $key );
+                       if ( !is_resource( $sock ) ) {
+                               continue;
+                       }
+                       $key = is_array( $key ) ? $key[1] : $key;
+                       if ( !isset( $sock_keys[$sock] ) ) {
+                               $sock_keys[intval( $sock )] = array();
+                               $socks[] = $sock;
+                       }
+                       $sock_keys[intval( $sock )][] = $key;
+               }
+
+               $gather = array();
+               // Send out the requests
+               foreach ( $socks as $sock ) {
+                       $cmd = 'gets';
+                       foreach ( $sock_keys[intval( $sock )] as $key ) {
+                               $cmd .= ' ' . $key;
+                       }
+                       $cmd .= "\r\n";
+
+                       if ( $this->_fwrite( $sock, $cmd ) ) {
+                               $gather[] = $sock;
+                       }
+               }
+
+               // Parse responses
+               $val = array();
+               foreach ( $gather as $sock ) {
+                       $this->_load_items( $sock, $val, $casToken );
+               }
+
+               if ( $this->_debug ) {
+                       foreach ( $val as $k => $v ) {
+                               $this->_debugprint( sprintf( "MemCache: got %s\n", $k ) );
+                       }
+               }
+
+               return $val;
+       }
+
+       // }}}
+       // {{{ incr()
+
+       /**
+        * Increments $key (optionally) by $amt
+        *
+        * @param string $key Key to increment
+        * @param int $amt (optional) amount to increment
+        *
+        * @return int|null Null if the key does not exist yet (this does NOT
+        * create new mappings if the key does not exist). If the key does
+        * exist, this returns the new value for that key.
+        */
+       public function incr( $key, $amt = 1 ) {
+               return $this->_incrdecr( 'incr', $key, $amt );
+       }
+
+       // }}}
+       // {{{ replace()
+
+       /**
+        * Overwrites an existing value for key; only works if key is already set
+        *
+        * @param string $key Key to set value as
+        * @param mixed $value Value to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool
+        */
+       public function replace( $key, $value, $exp = 0 ) {
+               return $this->_set( 'replace', $key, $value, $exp );
+       }
+
+       // }}}
+       // {{{ run_command()
+
+       /**
+        * Passes through $cmd to the memcache server connected by $sock; returns
+        * output as an array (null array if no output)
+        *
+        * @param Resource $sock Socket to send command on
+        * @param string $cmd Command to run
+        *
+        * @return array Output array
+        */
+       public function run_command( $sock, $cmd ) {
+               if ( !is_resource( $sock ) ) {
+                       return array();
+               }
+
+               if ( !$this->_fwrite( $sock, $cmd ) ) {
+                       return array();
+               }
+
+               $ret = array();
+               while ( true ) {
+                       $res = $this->_fgets( $sock );
+                       $ret[] = $res;
+                       if ( preg_match( '/^END/', $res ) ) {
+                               break;
+                       }
+                       if ( strlen( $res ) == 0 ) {
+                               break;
+                       }
+               }
+               return $ret;
+       }
+
+       // }}}
+       // {{{ set()
+
+       /**
+        * Unconditionally sets a key to a given value in the memcache.  Returns true
+        * if set successfully.
+        *
+        * @param string $key Key to set value as
+        * @param mixed $value Value to set
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool True on success
+        */
+       public function set( $key, $value, $exp = 0 ) {
+               return $this->_set( 'set', $key, $value, $exp );
+       }
+
+       // }}}
+       // {{{ cas()
+
+       /**
+        * Sets a key to a given value in the memcache if the current value still corresponds
+        * to a known, given value.  Returns true if set successfully.
+        *
+        * @param float $casToken Current known value
+        * @param string $key Key to set value as
+        * @param mixed $value Value to set
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return bool True on success
+        */
+       public function cas( $casToken, $key, $value, $exp = 0 ) {
+               return $this->_set( 'cas', $key, $value, $exp, $casToken );
+       }
+
+       // }}}
+       // {{{ set_compress_threshold()
+
+       /**
+        * Sets the compression threshold
+        *
+        * @param int $thresh Threshold to compress if larger than
+        */
+       public function set_compress_threshold( $thresh ) {
+               $this->_compress_threshold = $thresh;
+       }
+
+       // }}}
+       // {{{ set_debug()
+
+       /**
+        * Sets the debug flag
+        *
+        * @param bool $dbg True for debugging, false otherwise
+        *
+        * @see MWMemcached::__construct
+        */
+       public function set_debug( $dbg ) {
+               $this->_debug = $dbg;
+       }
+
+       // }}}
+       // {{{ set_servers()
+
+       /**
+        * Sets the server list to distribute key gets and puts between
+        *
+        * @param array $list Array of servers to connect to
+        *
+        * @see MWMemcached::__construct()
+        */
+       public function set_servers( $list ) {
+               $this->_servers = $list;
+               $this->_active = count( $list );
+               $this->_buckets = null;
+               $this->_bucketcount = 0;
+
+               $this->_single_sock = null;
+               if ( $this->_active == 1 ) {
+                       $this->_single_sock = $this->_servers[0];
+               }
+       }
+
+       /**
+        * Sets the timeout for new connections
+        *
+        * @param int $seconds Number of seconds
+        * @param int $microseconds Number of microseconds
+        */
+       public function set_timeout( $seconds, $microseconds ) {
+               $this->_timeout_seconds = $seconds;
+               $this->_timeout_microseconds = $microseconds;
+       }
+
+       // }}}
+       // }}}
+       // {{{ private methods
+       // {{{ _close_sock()
+
+       /**
+        * Close the specified socket
+        *
+        * @param string $sock Socket to close
+        *
+        * @access private
+        */
+       function _close_sock( $sock ) {
+               $host = array_search( $sock, $this->_cache_sock );
+               fclose( $this->_cache_sock[$host] );
+               unset( $this->_cache_sock[$host] );
+       }
+
+       // }}}
+       // {{{ _connect_sock()
+
+       /**
+        * Connects $sock to $host, timing out after $timeout
+        *
+        * @param int $sock Socket to connect
+        * @param string $host Host:IP to connect to
+        *
+        * @return bool
+        * @access private
+        */
+       function _connect_sock( &$sock, $host ) {
+               list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
+               $sock = false;
+               $timeout = $this->_connect_timeout;
+               $errno = $errstr = null;
+               for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
+                       MediaWiki\suppressWarnings();
+                       if ( $this->_persistent == 1 ) {
+                               $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
+                       } else {
+                               $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
+                       }
+                       MediaWiki\restoreWarnings();
+               }
+               if ( !$sock ) {
+                       $this->_error_log( "Error connecting to $host: $errstr\n" );
+                       $this->_dead_host( $host );
+                       return false;
+               }
+
+               // Initialise timeout
+               stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
+
+               // If the connection was persistent, flush the read buffer in case there
+               // was a previous incomplete request on this connection
+               if ( $this->_persistent ) {
+                       $this->_flush_read_buffer( $sock );
+               }
+               return true;
+       }
+
+       // }}}
+       // {{{ _dead_sock()
+
+       /**
+        * Marks a host as dead until 30-40 seconds in the future
+        *
+        * @param string $sock Socket to mark as dead
+        *
+        * @access private
+        */
+       function _dead_sock( $sock ) {
+               $host = array_search( $sock, $this->_cache_sock );
+               $this->_dead_host( $host );
+       }
+
+       /**
+        * @param string $host
+        */
+       function _dead_host( $host ) {
+               $parts = explode( ':', $host );
+               $ip = $parts[0];
+               $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
+               $this->_host_dead[$host] = $this->_host_dead[$ip];
+               unset( $this->_cache_sock[$host] );
+       }
+
+       // }}}
+       // {{{ get_sock()
+
+       /**
+        * get_sock
+        *
+        * @param string $key Key to retrieve value for;
+        *
+        * @return Resource|bool Resource on success, false on failure
+        * @access private
+        */
+       function get_sock( $key ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               if ( $this->_single_sock !== null ) {
+                       return $this->sock_to_host( $this->_single_sock );
+               }
+
+               $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
+               if ( $this->_buckets === null ) {
+                       $bu = array();
+                       foreach ( $this->_servers as $v ) {
+                               if ( is_array( $v ) ) {
+                                       for ( $i = 0; $i < $v[1]; $i++ ) {
+                                               $bu[] = $v[0];
+                                       }
+                               } else {
+                                       $bu[] = $v;
+                               }
+                       }
+                       $this->_buckets = $bu;
+                       $this->_bucketcount = count( $bu );
+               }
+
+               $realkey = is_array( $key ) ? $key[1] : $key;
+               for ( $tries = 0; $tries < 20; $tries++ ) {
+                       $host = $this->_buckets[$hv % $this->_bucketcount];
+                       $sock = $this->sock_to_host( $host );
+                       if ( is_resource( $sock ) ) {
+                               return $sock;
+                       }
+                       $hv = $this->_hashfunc( $hv . $realkey );
+               }
+
+               return false;
+       }
+
+       // }}}
+       // {{{ _hashfunc()
+
+       /**
+        * Creates a hash integer based on the $key
+        *
+        * @param string $key Key to hash
+        *
+        * @return int Hash value
+        * @access private
+        */
+       function _hashfunc( $key ) {
+               # Hash function must be in [0,0x7ffffff]
+               # We take the first 31 bits of the MD5 hash, which unlike the hash
+               # function used in a previous version of this client, works
+               return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
+       }
+
+       // }}}
+       // {{{ _incrdecr()
+
+       /**
+        * Perform increment/decriment on $key
+        *
+        * @param string $cmd Command to perform
+        * @param string|array $key Key to perform it on
+        * @param int $amt Amount to adjust
+        *
+        * @return int New value of $key
+        * @access private
+        */
+       function _incrdecr( $cmd, $key, $amt = 1 ) {
+               if ( !$this->_active ) {
+                       return null;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return null;
+               }
+
+               $key = is_array( $key ) ? $key[1] : $key;
+               if ( isset( $this->stats[$cmd] ) ) {
+                       $this->stats[$cmd]++;
+               } else {
+                       $this->stats[$cmd] = 1;
+               }
+               if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
+                       return null;
+               }
+
+               $line = $this->_fgets( $sock );
+               $match = array();
+               if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
+                       return null;
+               }
+               return $match[1];
+       }
+
+       // }}}
+       // {{{ _load_items()
+
+       /**
+        * Load items into $ret from $sock
+        *
+        * @param Resource $sock Socket to read from
+        * @param array $ret returned values
+        * @param float $casToken [optional]
+        * @return bool True for success, false for failure
+        *
+        * @access private
+        */
+       function _load_items( $sock, &$ret, &$casToken = null ) {
+               $results = array();
+
+               while ( 1 ) {
+                       $decl = $this->_fgets( $sock );
+
+                       if ( $decl === false ) {
+                               /*
+                                * If nothing can be read, something is wrong because we know exactly when
+                                * to stop reading (right after "END") and we return right after that.
+                                */
+                               return false;
+                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
+                               /*
+                                * Read all data returned. This can be either one or multiple values.
+                                * Save all that data (in an array) to be processed later: we'll first
+                                * want to continue reading until "END" before doing anything else,
+                                * to make sure that we don't leave our client in a state where it's
+                                * output is not yet fully read.
+                                */
+                               $results[] = array(
+                                       $match[1], // rkey
+                                       $match[2], // flags
+                                       $match[3], // len
+                                       $match[4], // casToken
+                                       $this->_fread( $sock, $match[3] + 2 ), // data
+                               );
+                       } elseif ( $decl == "END" ) {
+                               if ( count( $results ) == 0 ) {
+                                       return false;
+                               }
+
+                               /**
+                                * All data has been read, time to process the data and build
+                                * meaningful return values.
+                                */
+                               foreach ( $results as $vars ) {
+                                       list( $rkey, $flags, $len, $casToken, $data ) = $vars;
+
+                                       if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
+                                               $this->_handle_error( $sock,
+                                                       'line ending missing from data block from $1' );
+                                               return false;
+                                       }
+                                       $data = substr( $data, 0, -2 );
+                                       $ret[$rkey] = $data;
+
+                                       if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
+                                               $ret[$rkey] = gzuncompress( $ret[$rkey] );
+                                       }
+
+                                       /*
+                                        * This unserialize is the exact reason that we only want to
+                                        * process data after having read until "END" (instead of doing
+                                        * this right away): "unserialize" can trigger outside code:
+                                        * in the event that $ret[$rkey] is a serialized object,
+                                        * unserializing it will trigger __wakeup() if present. If that
+                                        * function attempted to read from memcached (while we did not
+                                        * yet read "END"), these 2 calls would collide.
+                                        */
+                                       if ( $flags & self::SERIALIZED ) {
+                                               $ret[$rkey] = unserialize( $ret[$rkey] );
+                                       } elseif ( $flags & self::INTVAL ) {
+                                               $ret[$rkey] = intval( $ret[$rkey] );
+                                       }
+                               }
+
+                               return true;
+                       } else {
+                               $this->_handle_error( $sock, 'Error parsing response from $1' );
+                               return false;
+                       }
+               }
+       }
+
+       // }}}
+       // {{{ _set()
+
+       /**
+        * Performs the requested storage operation to the memcache server
+        *
+        * @param string $cmd Command to perform
+        * @param string $key Key to act on
+        * @param mixed $val What we need to store
+        * @param int $exp (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        * @param float $casToken [optional]
+        *
+        * @return bool
+        * @access private
+        */
+       function _set( $cmd, $key, $val, $exp, $casToken = null ) {
+               if ( !$this->_active ) {
+                       return false;
+               }
+
+               $sock = $this->get_sock( $key );
+               if ( !is_resource( $sock ) ) {
+                       return false;
+               }
+
+               if ( isset( $this->stats[$cmd] ) ) {
+                       $this->stats[$cmd]++;
+               } else {
+                       $this->stats[$cmd] = 1;
+               }
+
+               $flags = 0;
+
+               if ( is_int( $val ) ) {
+                       $flags |= self::INTVAL;
+               } elseif ( !is_scalar( $val ) ) {
+                       $val = serialize( $val );
+                       $flags |= self::SERIALIZED;
+                       if ( $this->_debug ) {
+                               $this->_debugprint( sprintf( "client: serializing data as it is not scalar\n" ) );
+                       }
+               }
+
+               $len = strlen( $val );
+
+               if ( $this->_have_zlib && $this->_compress_enable
+                       && $this->_compress_threshold && $len >= $this->_compress_threshold
+               ) {
+                       $c_val = gzcompress( $val, 9 );
+                       $c_len = strlen( $c_val );
+
+                       if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
+                               if ( $this->_debug ) {
+                                       $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len ) );
+                               }
+                               $val = $c_val;
+                               $len = $c_len;
+                               $flags |= self::COMPRESSED;
+                       }
+               }
+
+               $command = "$cmd $key $flags $exp $len";
+               if ( $casToken ) {
+                       $command .= " $casToken";
+               }
+
+               if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
+                       return false;
+               }
+
+               $line = $this->_fgets( $sock );
+
+               if ( $this->_debug ) {
+                       $this->_debugprint( sprintf( "%s %s (%s)\n", $cmd, $key, $line ) );
+               }
+               if ( $line == "STORED" ) {
+                       return true;
+               }
+               return false;
+       }
+
+       // }}}
+       // {{{ sock_to_host()
+
+       /**
+        * Returns the socket for the host
+        *
+        * @param string $host Host:IP to get socket for
+        *
+        * @return Resource|bool IO Stream or false
+        * @access private
+        */
+       function sock_to_host( $host ) {
+               if ( isset( $this->_cache_sock[$host] ) ) {
+                       return $this->_cache_sock[$host];
+               }
+
+               $sock = null;
+               $now = time();
+               list( $ip, /* $port */) = explode( ':', $host );
+               if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
+                       isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
+               ) {
+                       return null;
+               }
+
+               if ( !$this->_connect_sock( $sock, $host ) ) {
+                       return null;
+               }
+
+               // Do not buffer writes
+               stream_set_write_buffer( $sock, 0 );
+
+               $this->_cache_sock[$host] = $sock;
+
+               return $this->_cache_sock[$host];
+       }
+
+       /**
+        * @param string $text
+        */
+       function _debugprint( $text ) {
+               $this->_logger->debug( $text );
+       }
+
+       /**
+        * @param string $text
+        */
+       function _error_log( $text ) {
+               $this->_logger->error( "Memcached error: $text" );
+       }
+
+       /**
+        * Write to a stream. If there is an error, mark the socket dead.
+        *
+        * @param Resource $sock The socket
+        * @param string $buf The string to write
+        * @return bool True on success, false on failure
+        */
+       function _fwrite( $sock, $buf ) {
+               $bytesWritten = 0;
+               $bufSize = strlen( $buf );
+               while ( $bytesWritten < $bufSize ) {
+                       $result = fwrite( $sock, $buf );
+                       $data = stream_get_meta_data( $sock );
+                       if ( $data['timed_out'] ) {
+                               $this->_handle_error( $sock, 'timeout writing to $1' );
+                               return false;
+                       }
+                       // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
+                       if ( $result === false || $result === 0 ) {
+                               $this->_handle_error( $sock, 'error writing to $1' );
+                               return false;
+                       }
+                       $bytesWritten += $result;
+               }
+
+               return true;
+       }
+
+       /**
+        * Handle an I/O error. Mark the socket dead and log an error.
+        *
+        * @param Resource $sock
+        * @param string $msg
+        */
+       function _handle_error( $sock, $msg ) {
+               $peer = stream_socket_get_name( $sock, true /** remote **/ );
+               if ( strval( $peer ) === '' ) {
+                       $peer = array_search( $sock, $this->_cache_sock );
+                       if ( $peer === false ) {
+                               $peer = '[unknown host]';
+                       }
+               }
+               $msg = str_replace( '$1', $peer, $msg );
+               $this->_error_log( "$msg\n" );
+               $this->_dead_sock( $sock );
+       }
+
+       /**
+        * Read the specified number of bytes from a stream. If there is an error,
+        * mark the socket dead.
+        *
+        * @param Resource $sock The socket
+        * @param int $len The number of bytes to read
+        * @return string|bool The string on success, false on failure.
+        */
+       function _fread( $sock, $len ) {
+               $buf = '';
+               while ( $len > 0 ) {
+                       $result = fread( $sock, $len );
+                       $data = stream_get_meta_data( $sock );
+                       if ( $data['timed_out'] ) {
+                               $this->_handle_error( $sock, 'timeout reading from $1' );
+                               return false;
+                       }
+                       if ( $result === false ) {
+                               $this->_handle_error( $sock, 'error reading buffer from $1' );
+                               return false;
+                       }
+                       if ( $result === '' ) {
+                               // This will happen if the remote end of the socket is shut down
+                               $this->_handle_error( $sock, 'unexpected end of file reading from $1' );
+                               return false;
+                       }
+                       $len -= strlen( $result );
+                       $buf .= $result;
+               }
+               return $buf;
+       }
+
+       /**
+        * Read a line from a stream. If there is an error, mark the socket dead.
+        * The \r\n line ending is stripped from the response.
+        *
+        * @param Resource $sock The socket
+        * @return string|bool The string on success, false on failure
+        */
+       function _fgets( $sock ) {
+               $result = fgets( $sock );
+               // fgets() may return a partial line if there is a select timeout after
+               // a successful recv(), so we have to check for a timeout even if we
+               // got a string response.
+               $data = stream_get_meta_data( $sock );
+               if ( $data['timed_out'] ) {
+                       $this->_handle_error( $sock, 'timeout reading line from $1' );
+                       return false;
+               }
+               if ( $result === false ) {
+                       $this->_handle_error( $sock, 'error reading line from $1' );
+                       return false;
+               }
+               if ( substr( $result, -2 ) === "\r\n" ) {
+                       $result = substr( $result, 0, -2 );
+               } elseif ( substr( $result, -1 ) === "\n" ) {
+                       $result = substr( $result, 0, -1 );
+               } else {
+                       $this->_handle_error( $sock, 'line ending missing in response from $1' );
+                       return false;
+               }
+               return $result;
+       }
+
+       /**
+        * Flush the read buffer of a stream
+        * @param Resource $f
+        */
+       function _flush_read_buffer( $f ) {
+               if ( !is_resource( $f ) ) {
+                       return;
+               }
+               $r = array( $f );
+               $w = null;
+               $e = null;
+               $n = stream_select( $r, $w, $e, 0, 0 );
+               while ( $n == 1 && !feof( $f ) ) {
+                       fread( $f, 1024 );
+                       $r = array( $f );
+                       $w = null;
+                       $e = null;
+                       $n = stream_select( $r, $w, $e, 0, 0 );
+               }
+       }
+
+       // }}}
+       // }}}
+       // }}}
+}
+
+// }}}
+
+class MemCachedClientforWiki extends MWMemcached {
+}
diff --git a/includes/libs/objectcache/MemcachedPhpBagOStuff.php b/includes/libs/objectcache/MemcachedPhpBagOStuff.php
new file mode 100644 (file)
index 0000000..1bb38fa
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Object caching using memcached.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * A wrapper class for the pure-PHP memcached client, exposing a BagOStuff interface.
+ *
+ * @ingroup Cache
+ */
+class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
+       /**
+        * Available parameters are:
+        *   - servers:             The list of IP:port combinations holding the memcached servers.
+        *   - debug:               Whether to set the debug flag in the underlying client.
+        *   - persistent:          Whether to use a persistent connection
+        *   - compress_threshold:  The minimum size an object must be before it is compressed
+        *   - timeout:             The read timeout in microseconds
+        *   - connect_timeout:     The connect timeout in seconds
+        *
+        * @param array $params
+        */
+       function __construct( $params ) {
+               parent::__construct( $params );
+               $params = $this->applyDefaultParams( $params );
+
+               $this->client = new MWMemcached( $params );
+               $this->client->set_servers( $params['servers'] );
+               $this->client->set_debug( $params['debug'] );
+       }
+
+       public function setDebug( $debug ) {
+               $this->client->set_debug( $debug );
+       }
+
+       public function getMulti( array $keys, $flags = 0 ) {
+               foreach ( $keys as $key ) {
+                       $this->validateKeyEncoding( $key );
+               }
+
+               return $this->client->get_multi( $keys );
+       }
+
+       public function incr( $key, $value = 1 ) {
+               $this->validateKeyEncoding( $key );
+
+               return $this->client->incr( $key, $value );
+       }
+
+       public function decr( $key, $value = 1 ) {
+               $this->validateKeyEncoding( $key );
+
+               return $this->client->decr( $key, $value );
+       }
+}
diff --git a/includes/libs/objectcache/MultiWriteBagOStuff.php b/includes/libs/objectcache/MultiWriteBagOStuff.php
new file mode 100644 (file)
index 0000000..73bdabd
--- /dev/null
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Wrapper for object caching in different caches.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * A cache class that replicates all writes to multiple child caches. Reads
+ * are implemented by reading from the caches in the order they are given in
+ * the configuration until a cache gives a positive result.
+ *
+ * @ingroup Cache
+ */
+class MultiWriteBagOStuff extends BagOStuff {
+       /** @var BagOStuff[] */
+       protected $caches;
+       /** @var bool Use async secondary writes */
+       protected $asyncWrites = false;
+       /** @var callback|null */
+       protected $asyncHandler;
+
+       /** Idiom for "write to all backends" */
+       const ALL = INF;
+
+       const UPGRADE_TTL = 3600; // TTL when a key is copied to a higher cache tier
+
+       /**
+        * $params include:
+        *   - caches: A numbered array of either ObjectFactory::getObjectFromSpec
+        *      arrays yeilding BagOStuff objects or direct BagOStuff objects.
+        *      If using the former, the 'args' field *must* be set.
+        *      The first cache is the primary one, being the first to
+        *      be read in the fallback chain. Writes happen to all stores
+        *      in the order they are defined. However, lock()/unlock() calls
+        *      only use the primary store.
+        *   - replication: Either 'sync' or 'async'. This controls whether writes
+        *      to secondary stores are deferred when possible. Async writes
+        *      require setting 'asyncCallback'. HHVM register_postsend_function() function.
+        *      Async writes can increase the chance of some race conditions
+        *      or cause keys to expire seconds later than expected. It is
+        *      safe to use for modules when cached values: are immutable,
+        *      invalidation uses logical TTLs, invalidation uses etag/timestamp
+        *      validation against the DB, or merge() is used to handle races.
+        *   - asyncHandler: callable that takes a callback and runs it after the
+        *      current web request ends. In CLI mode, it should run it immediately.
+        * @param array $params
+        * @throws InvalidArgumentException
+        */
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               if ( empty( $params['caches'] ) || !is_array( $params['caches'] ) ) {
+                       throw new InvalidArgumentException(
+                               __METHOD__ . ': "caches" parameter must be an array of caches'
+                       );
+               }
+
+               $this->caches = array();
+               foreach ( $params['caches'] as $cacheInfo ) {
+                       if ( $cacheInfo instanceof BagOStuff ) {
+                               $this->caches[] = $cacheInfo;
+                       } else {
+                               if ( !isset( $cacheInfo['args'] ) ) {
+                                       // B/C for when $cacheInfo was for ObjectCache::newFromParams().
+                                       // Callers intenting this to be for ObjectFactory::getObjectFromSpec
+                                       // should have set "args" per the docs above. Doings so avoids extra
+                                       // (likely harmless) params (factory/class/calls) ending up in "args".
+                                       $cacheInfo['args'] = array( $cacheInfo );
+                               }
+                               $this->caches[] = ObjectFactory::getObjectFromSpec( $cacheInfo );
+                       }
+               }
+
+               $this->asyncHandler = isset( $params['asyncHandler'] )
+                       ? $params['asyncHandler']
+                       : null;
+               $this->asyncWrites = (
+                       isset( $params['replication'] ) &&
+                       $params['replication'] === 'async' &&
+                       is_callable( $this->asyncHandler )
+               );
+       }
+
+       public function setDebug( $debug ) {
+               foreach ( $this->caches as $cache ) {
+                       $cache->setDebug( $debug );
+               }
+       }
+
+       protected function doGet( $key, $flags = 0 ) {
+               if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) {
+                       // If the latest write was a delete(), we do NOT want to fallback
+                       // to the other tiers and possibly see the old value. Also, this
+                       // is used by mergeViaLock(), which only needs to hit the primary.
+                       return $this->caches[0]->get( $key, $flags );
+               }
+
+               $misses = 0; // number backends checked
+               $value = false;
+               foreach ( $this->caches as $cache ) {
+                       $value = $cache->get( $key, $flags );
+                       if ( $value !== false ) {
+                               break;
+                       }
+                       ++$misses;
+               }
+
+               if ( $value !== false
+                       && $misses > 0
+                       && ( $flags & self::READ_VERIFIED ) == self::READ_VERIFIED
+               ) {
+                       $this->doWrite( $misses, $this->asyncWrites, 'set', $key, $value, self::UPGRADE_TTL );
+               }
+
+               return $value;
+       }
+
+       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               $asyncWrites = ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC )
+                       ? false
+                       : $this->asyncWrites;
+
+               return $this->doWrite( self::ALL, $asyncWrites, 'set', $key, $value, $exptime );
+       }
+
+       public function delete( $key ) {
+               return $this->doWrite( self::ALL, $this->asyncWrites, 'delete', $key );
+       }
+
+       public function add( $key, $value, $exptime = 0 ) {
+               return $this->doWrite( self::ALL, $this->asyncWrites, 'add', $key, $value, $exptime );
+       }
+
+       public function incr( $key, $value = 1 ) {
+               return $this->doWrite( self::ALL, $this->asyncWrites, 'incr', $key, $value );
+       }
+
+       public function decr( $key, $value = 1 ) {
+               return $this->doWrite( self::ALL, $this->asyncWrites, 'decr', $key, $value );
+       }
+
+       public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) {
+               // Only need to lock the first cache; also avoids deadlocks
+               return $this->caches[0]->lock( $key, $timeout, $expiry, $rclass );
+       }
+
+       public function unlock( $key ) {
+               // Only the first cache is locked
+               return $this->caches[0]->unlock( $key );
+       }
+
+       public function getLastError() {
+               return $this->caches[0]->getLastError();
+       }
+
+       public function clearLastError() {
+               $this->caches[0]->clearLastError();
+       }
+
+       /**
+        * Apply a write method to the first $count backing caches
+        *
+        * @param integer $count
+        * @param bool $asyncWrites
+        * @param string $method
+        * @param mixed ...
+        * @return bool
+        */
+       protected function doWrite( $count, $asyncWrites, $method /*, ... */ ) {
+               $ret = true;
+               $args = array_slice( func_get_args(), 3 );
+
+               foreach ( $this->caches as $i => $cache ) {
+                       if ( $i >= $count ) {
+                               break; // ignore the lower tiers
+                       }
+
+                       if ( $i == 0 || !$asyncWrites ) {
+                               // First store or in sync mode: write now and get result
+                               if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
+                                       $ret = false;
+                               }
+                       } else {
+                               // Secondary write in async mode: do not block this HTTP request
+                               $logger = $this->logger;
+                               call_user_func(
+                                       $this->asyncHandler,
+                                       function () use ( $cache, $method, $args, $logger ) {
+                                               if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
+                                                       $logger->warning( "Async $method op failed" );
+                                               }
+                                       }
+                               );
+                       }
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Delete objects expiring before a certain date.
+        *
+        * Succeed if any of the child caches succeed.
+        * @param string $date
+        * @param bool|callable $progressCallback
+        * @return bool
+        */
+       public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
+               $ret = false;
+               foreach ( $this->caches as $cache ) {
+                       if ( $cache->deleteObjectsExpiringBefore( $date, $progressCallback ) ) {
+                               $ret = true;
+                       }
+               }
+
+               return $ret;
+       }
+}
diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php
deleted file mode 100644 (file)
index 95f5c8d..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-<?php
-/**
- * Base class for memcached clients.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * Base class for memcached clients.
- *
- * @ingroup Cache
- */
-class MemcachedBagOStuff extends BagOStuff {
-       /** @var MWMemcached|Memcached */
-       protected $client;
-
-       /**
-        * Fill in the defaults for any parameters missing from $params, using the
-        * backwards-compatible global variables
-        * @param array $params
-        * @return array
-        */
-       protected function applyDefaultParams( $params ) {
-               if ( !isset( $params['servers'] ) ) {
-                       $params['servers'] = $GLOBALS['wgMemCachedServers'];
-               }
-               if ( !isset( $params['debug'] ) ) {
-                       $params['debug'] = $GLOBALS['wgMemCachedDebug'];
-               }
-               if ( !isset( $params['persistent'] ) ) {
-                       $params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
-               }
-               if ( !isset( $params['compress_threshold'] ) ) {
-                       $params['compress_threshold'] = 1500;
-               }
-               if ( !isset( $params['timeout'] ) ) {
-                       $params['timeout'] = $GLOBALS['wgMemCachedTimeout'];
-               }
-               if ( !isset( $params['connect_timeout'] ) ) {
-                       $params['connect_timeout'] = 0.5;
-               }
-               return $params;
-       }
-
-       protected function doGet( $key, $flags = 0 ) {
-               $casToken = null;
-
-               return $this->getWithToken( $key, $casToken, $flags );
-       }
-
-       protected function getWithToken( $key, &$casToken, $flags = 0 ) {
-               return $this->client->get( $this->encodeKey( $key ), $casToken );
-       }
-
-       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
-               return $this->client->set( $this->encodeKey( $key ), $value,
-                       $this->fixExpiry( $exptime ) );
-       }
-
-       protected function cas( $casToken, $key, $value, $exptime = 0 ) {
-               return $this->client->cas( $casToken, $this->encodeKey( $key ),
-                       $value, $this->fixExpiry( $exptime ) );
-       }
-
-       public function delete( $key ) {
-               return $this->client->delete( $this->encodeKey( $key ) );
-       }
-
-       public function add( $key, $value, $exptime = 0 ) {
-               return $this->client->add( $this->encodeKey( $key ), $value,
-                       $this->fixExpiry( $exptime ) );
-       }
-
-       public function merge( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new Exception( "Got invalid callback." );
-               }
-
-               return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
-       }
-
-       /**
-        * Get the underlying client object. This is provided for debugging
-        * purposes.
-        * @return BagOStuff
-        */
-       public function getClient() {
-               return $this->client;
-       }
-
-       /**
-        * Encode a key for use on the wire inside the memcached protocol.
-        *
-        * We encode spaces and line breaks to avoid protocol errors. We encode
-        * the other control characters for compatibility with libmemcached
-        * verify_key. We leave other punctuation alone, to maximise backwards
-        * compatibility.
-        * @param string $key
-        * @return string
-        */
-       public function encodeKey( $key ) {
-               return preg_replace_callback( '/[\x00-\x20\x25\x7f]+/',
-                       array( $this, 'encodeKeyCallback' ), $key );
-       }
-
-       /**
-        * @param array $m
-        * @return string
-        */
-       protected function encodeKeyCallback( $m ) {
-               return rawurlencode( $m[0] );
-       }
-
-       /**
-        * TTLs higher than 30 days will be detected as absolute TTLs
-        * (UNIX timestamps), and will result in the cache entry being
-        * discarded immediately because the expiry is in the past.
-        * Clamp expires >30d at 30d, unless they're >=1e9 in which
-        * case they are likely to really be absolute (1e9 = 2011-09-09)
-        * @param int $expiry
-        * @return int
-        */
-       function fixExpiry( $expiry ) {
-               if ( $expiry > 2592000 && $expiry < 1000000000 ) {
-                       $expiry = 2592000;
-               }
-               return (int)$expiry;
-       }
-
-       /**
-        * Decode a key encoded with encodeKey(). This is provided as a convenience
-        * function for debugging.
-        *
-        * @param string $key
-        *
-        * @return string
-        */
-       public function decodeKey( $key ) {
-               // matches %00-%20, %25, %7F (=decoded alternatives for those encoded in encodeKey)
-               return preg_replace_callback( '/%([0-1][0-9]|20|25|7F)/i', function ( $match ) {
-                       return urldecode( $match[0] );
-               }, $key );
-       }
-
-       /**
-        * Send a debug message to the log
-        * @param string $text
-        */
-       protected function debugLog( $text ) {
-               $this->logger->debug( $text );
-       }
-
-       public function modifySimpleRelayEvent( array $event ) {
-               if ( array_key_exists( 'val', $event ) ) {
-                       $event['flg'] = 0; // data is not serialized nor gzipped (for memcached driver)
-               }
-
-               return $event;
-       }
-}
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
deleted file mode 100644 (file)
index 5010b89..0000000
+++ /dev/null
@@ -1,1276 +0,0 @@
-<?php
-// @codingStandardsIgnoreFile It's an external lib and it isn't. Let's not bother.
-/**
- * Memcached client for PHP.
- *
- * +---------------------------------------------------------------------------+
- * | memcached client, PHP                                                     |
- * +---------------------------------------------------------------------------+
- * | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net>                 |
- * | All rights reserved.                                                      |
- * |                                                                           |
- * | Redistribution and use in source and binary forms, with or without        |
- * | modification, are permitted provided that the following conditions        |
- * | are met:                                                                  |
- * |                                                                           |
- * | 1. Redistributions of source code must retain the above copyright         |
- * |    notice, this list of conditions and the following disclaimer.          |
- * | 2. Redistributions in binary form must reproduce the above copyright      |
- * |    notice, this list of conditions and the following disclaimer in the    |
- * |    documentation and/or other materials provided with the distribution.   |
- * |                                                                           |
- * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
- * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
- * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
- * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
- * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
- * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
- * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
- * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
- * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
- * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
- * +---------------------------------------------------------------------------+
- * | Author: Ryan T. Dean <rtdean@cytherianage.net>                            |
- * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
- * |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
- * |   client logic under 2-clause BSD license.                                |
- * +---------------------------------------------------------------------------+
- *
- * @file
- * $TCAnet$
- */
-
-/**
- * This is the PHP client for memcached - a distributed memory cache daemon.
- * More information is available at http://www.danga.com/memcached/
- *
- * Usage example:
- *
- * require_once 'memcached.php';
- *
- * $mc = new MWMemcached(array(
- *              'servers' => array('127.0.0.1:10000',
- *                                 array('192.0.0.1:10010', 2),
- *                                 '127.0.0.1:10020'),
- *              'debug'   => false,
- *              'compress_threshold' => 10240,
- *              'persistent' => true));
- *
- * $mc->add( 'key', array( 'some', 'array' ) );
- * $mc->replace( 'key', 'some random string' );
- * $val = $mc->get( 'key' );
- *
- * @author  Ryan T. Dean <rtdean@cytherianage.net>
- * @version 0.1.2
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-// {{{ requirements
-// }}}
-
-// {{{ class MWMemcached
-/**
- * memcached client class implemented using (p)fsockopen()
- *
- * @author  Ryan T. Dean <rtdean@cytherianage.net>
- * @ingroup Cache
- */
-class MWMemcached {
-       // {{{ properties
-       // {{{ public
-
-       // {{{ constants
-       // {{{ flags
-
-       /**
-        * Flag: indicates data is serialized
-        */
-       const SERIALIZED = 1;
-
-       /**
-        * Flag: indicates data is compressed
-        */
-       const COMPRESSED = 2;
-
-       /**
-        * Flag: indicates data is an integer
-        */
-       const INTVAL = 4;
-
-       // }}}
-
-       /**
-        * Minimum savings to store data compressed
-        */
-       const COMPRESSION_SAVINGS = 0.20;
-
-       // }}}
-
-       /**
-        * Command statistics
-        *
-        * @var array
-        * @access public
-        */
-       public $stats;
-
-       // }}}
-       // {{{ private
-
-       /**
-        * Cached Sockets that are connected
-        *
-        * @var array
-        * @access private
-        */
-       public $_cache_sock;
-
-       /**
-        * Current debug status; 0 - none to 9 - profiling
-        *
-        * @var bool
-        * @access private
-        */
-       public $_debug;
-
-       /**
-        * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
-        *
-        * @var array
-        * @access private
-        */
-       public $_host_dead;
-
-       /**
-        * Is compression available?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_have_zlib;
-
-       /**
-        * Do we want to use compression?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_compress_enable;
-
-       /**
-        * At how many bytes should we compress?
-        *
-        * @var int
-        * @access private
-        */
-       public $_compress_threshold;
-
-       /**
-        * Are we using persistent links?
-        *
-        * @var bool
-        * @access private
-        */
-       public $_persistent;
-
-       /**
-        * If only using one server; contains ip:port to connect to
-        *
-        * @var string
-        * @access private
-        */
-       public $_single_sock;
-
-       /**
-        * Array containing ip:port or array(ip:port, weight)
-        *
-        * @var array
-        * @access private
-        */
-       public $_servers;
-
-       /**
-        * Our bit buckets
-        *
-        * @var array
-        * @access private
-        */
-       public $_buckets;
-
-       /**
-        * Total # of bit buckets we have
-        *
-        * @var int
-        * @access private
-        */
-       public $_bucketcount;
-
-       /**
-        * # of total servers we have
-        *
-        * @var int
-        * @access private
-        */
-       public $_active;
-
-       /**
-        * Stream timeout in seconds. Applies for example to fread()
-        *
-        * @var int
-        * @access private
-        */
-       public $_timeout_seconds;
-
-       /**
-        * Stream timeout in microseconds
-        *
-        * @var int
-        * @access private
-        */
-       public $_timeout_microseconds;
-
-       /**
-        * Connect timeout in seconds
-        */
-       public $_connect_timeout;
-
-       /**
-        * Number of connection attempts for each server
-        */
-       public $_connect_attempts;
-
-       /**
-        * @var LoggerInterface
-        */
-       private $_logger;
-
-       // }}}
-       // }}}
-       // {{{ methods
-       // {{{ public functions
-       // {{{ memcached()
-
-       /**
-        * Memcache initializer
-        *
-        * @param array $args Associative array of settings
-        *
-        * @return mixed
-        */
-       public function __construct( $args ) {
-               $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
-               $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false;
-               $this->stats = array();
-               $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0;
-               $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false;
-               $this->_compress_enable = true;
-               $this->_have_zlib = function_exists( 'gzcompress' );
-
-               $this->_cache_sock = array();
-               $this->_host_dead = array();
-
-               $this->_timeout_seconds = 0;
-               $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 500000;
-
-               $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1;
-               $this->_connect_attempts = 2;
-
-               $this->_logger = isset( $args['logger'] ) ? $args['logger'] : new NullLogger();
-       }
-
-       // }}}
-       // {{{ add()
-
-       /**
-        * Adds a key/value to the memcache server if one isn't already set with
-        * that key
-        *
-        * @param string $key Key to set with data
-        * @param mixed $val Value to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of expiration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool
-        */
-       public function add( $key, $val, $exp = 0 ) {
-               return $this->_set( 'add', $key, $val, $exp );
-       }
-
-       // }}}
-       // {{{ decr()
-
-       /**
-        * Decrease a value stored on the memcache server
-        *
-        * @param string $key Key to decrease
-        * @param int $amt (optional) amount to decrease
-        *
-        * @return mixed False on failure, value on success
-        */
-       public function decr( $key, $amt = 1 ) {
-               return $this->_incrdecr( 'decr', $key, $amt );
-       }
-
-       // }}}
-       // {{{ delete()
-
-       /**
-        * Deletes a key from the server, optionally after $time
-        *
-        * @param string $key Key to delete
-        * @param int $time (optional) how long to wait before deleting
-        *
-        * @return bool True on success, false on failure
-        */
-       public function delete( $key, $time = 0 ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-
-               if ( isset( $this->stats['delete'] ) ) {
-                       $this->stats['delete']++;
-               } else {
-                       $this->stats['delete'] = 1;
-               }
-               $cmd = "delete $key $time\r\n";
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return false;
-               }
-               $res = $this->_fgets( $sock );
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) );
-               }
-
-               if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * @param string $key
-        * @param int $timeout
-        * @return bool
-        */
-       public function lock( $key, $timeout = 0 ) {
-               /* stub */
-               return true;
-       }
-
-       /**
-        * @param string $key
-        * @return bool
-        */
-       public function unlock( $key ) {
-               /* stub */
-               return true;
-       }
-
-       // }}}
-       // {{{ disconnect_all()
-
-       /**
-        * Disconnects all connected sockets
-        */
-       public function disconnect_all() {
-               foreach ( $this->_cache_sock as $sock ) {
-                       fclose( $sock );
-               }
-
-               $this->_cache_sock = array();
-       }
-
-       // }}}
-       // {{{ enable_compress()
-
-       /**
-        * Enable / Disable compression
-        *
-        * @param bool $enable True to enable, false to disable
-        */
-       public function enable_compress( $enable ) {
-               $this->_compress_enable = $enable;
-       }
-
-       // }}}
-       // {{{ forget_dead_hosts()
-
-       /**
-        * Forget about all of the dead hosts
-        */
-       public function forget_dead_hosts() {
-               $this->_host_dead = array();
-       }
-
-       // }}}
-       // {{{ get()
-
-       /**
-        * Retrieves the value associated with the key from the memcache server
-        *
-        * @param array|string $key key to retrieve
-        * @param float $casToken [optional]
-        *
-        * @return mixed
-        */
-       public function get( $key, &$casToken = null ) {
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( "get($key)\n" );
-               }
-
-               if ( !is_array( $key ) && strval( $key ) === '' ) {
-                       $this->_debugprint( "Skipping key which equals to an empty string" );
-                       return false;
-               }
-
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-               if ( isset( $this->stats['get'] ) ) {
-                       $this->stats['get']++;
-               } else {
-                       $this->stats['get'] = 1;
-               }
-
-               $cmd = "gets $key\r\n";
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return false;
-               }
-
-               $val = array();
-               $this->_load_items( $sock, $val, $casToken );
-
-               if ( $this->_debug ) {
-                       foreach ( $val as $k => $v ) {
-                               $this->_debugprint( sprintf( "MemCache: sock %s got %s\n", serialize( $sock ), $k ) );
-                       }
-               }
-
-               $value = false;
-               if ( isset( $val[$key] ) ) {
-                       $value = $val[$key];
-               }
-               return $value;
-       }
-
-       // }}}
-       // {{{ get_multi()
-
-       /**
-        * Get multiple keys from the server(s)
-        *
-        * @param array $keys Keys to retrieve
-        *
-        * @return array
-        */
-       public function get_multi( $keys ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               if ( isset( $this->stats['get_multi'] ) ) {
-                       $this->stats['get_multi']++;
-               } else {
-                       $this->stats['get_multi'] = 1;
-               }
-               $sock_keys = array();
-               $socks = array();
-               foreach ( $keys as $key ) {
-                       $sock = $this->get_sock( $key );
-                       if ( !is_resource( $sock ) ) {
-                               continue;
-                       }
-                       $key = is_array( $key ) ? $key[1] : $key;
-                       if ( !isset( $sock_keys[$sock] ) ) {
-                               $sock_keys[intval( $sock )] = array();
-                               $socks[] = $sock;
-                       }
-                       $sock_keys[intval( $sock )][] = $key;
-               }
-
-               $gather = array();
-               // Send out the requests
-               foreach ( $socks as $sock ) {
-                       $cmd = 'gets';
-                       foreach ( $sock_keys[intval( $sock )] as $key ) {
-                               $cmd .= ' ' . $key;
-                       }
-                       $cmd .= "\r\n";
-
-                       if ( $this->_fwrite( $sock, $cmd ) ) {
-                               $gather[] = $sock;
-                       }
-               }
-
-               // Parse responses
-               $val = array();
-               foreach ( $gather as $sock ) {
-                       $this->_load_items( $sock, $val, $casToken );
-               }
-
-               if ( $this->_debug ) {
-                       foreach ( $val as $k => $v ) {
-                               $this->_debugprint( sprintf( "MemCache: got %s\n", $k ) );
-                       }
-               }
-
-               return $val;
-       }
-
-       // }}}
-       // {{{ incr()
-
-       /**
-        * Increments $key (optionally) by $amt
-        *
-        * @param string $key Key to increment
-        * @param int $amt (optional) amount to increment
-        *
-        * @return int|null Null if the key does not exist yet (this does NOT
-        * create new mappings if the key does not exist). If the key does
-        * exist, this returns the new value for that key.
-        */
-       public function incr( $key, $amt = 1 ) {
-               return $this->_incrdecr( 'incr', $key, $amt );
-       }
-
-       // }}}
-       // {{{ replace()
-
-       /**
-        * Overwrites an existing value for key; only works if key is already set
-        *
-        * @param string $key Key to set value as
-        * @param mixed $value Value to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool
-        */
-       public function replace( $key, $value, $exp = 0 ) {
-               return $this->_set( 'replace', $key, $value, $exp );
-       }
-
-       // }}}
-       // {{{ run_command()
-
-       /**
-        * Passes through $cmd to the memcache server connected by $sock; returns
-        * output as an array (null array if no output)
-        *
-        * @param Resource $sock Socket to send command on
-        * @param string $cmd Command to run
-        *
-        * @return array Output array
-        */
-       public function run_command( $sock, $cmd ) {
-               if ( !is_resource( $sock ) ) {
-                       return array();
-               }
-
-               if ( !$this->_fwrite( $sock, $cmd ) ) {
-                       return array();
-               }
-
-               $ret = array();
-               while ( true ) {
-                       $res = $this->_fgets( $sock );
-                       $ret[] = $res;
-                       if ( preg_match( '/^END/', $res ) ) {
-                               break;
-                       }
-                       if ( strlen( $res ) == 0 ) {
-                               break;
-                       }
-               }
-               return $ret;
-       }
-
-       // }}}
-       // {{{ set()
-
-       /**
-        * Unconditionally sets a key to a given value in the memcache.  Returns true
-        * if set successfully.
-        *
-        * @param string $key Key to set value as
-        * @param mixed $value Value to set
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool True on success
-        */
-       public function set( $key, $value, $exp = 0 ) {
-               return $this->_set( 'set', $key, $value, $exp );
-       }
-
-       // }}}
-       // {{{ cas()
-
-       /**
-        * Sets a key to a given value in the memcache if the current value still corresponds
-        * to a known, given value.  Returns true if set successfully.
-        *
-        * @param float $casToken Current known value
-        * @param string $key Key to set value as
-        * @param mixed $value Value to set
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        *
-        * @return bool True on success
-        */
-       public function cas( $casToken, $key, $value, $exp = 0 ) {
-               return $this->_set( 'cas', $key, $value, $exp, $casToken );
-       }
-
-       // }}}
-       // {{{ set_compress_threshold()
-
-       /**
-        * Sets the compression threshold
-        *
-        * @param int $thresh Threshold to compress if larger than
-        */
-       public function set_compress_threshold( $thresh ) {
-               $this->_compress_threshold = $thresh;
-       }
-
-       // }}}
-       // {{{ set_debug()
-
-       /**
-        * Sets the debug flag
-        *
-        * @param bool $dbg True for debugging, false otherwise
-        *
-        * @see MWMemcached::__construct
-        */
-       public function set_debug( $dbg ) {
-               $this->_debug = $dbg;
-       }
-
-       // }}}
-       // {{{ set_servers()
-
-       /**
-        * Sets the server list to distribute key gets and puts between
-        *
-        * @param array $list Array of servers to connect to
-        *
-        * @see MWMemcached::__construct()
-        */
-       public function set_servers( $list ) {
-               $this->_servers = $list;
-               $this->_active = count( $list );
-               $this->_buckets = null;
-               $this->_bucketcount = 0;
-
-               $this->_single_sock = null;
-               if ( $this->_active == 1 ) {
-                       $this->_single_sock = $this->_servers[0];
-               }
-       }
-
-       /**
-        * Sets the timeout for new connections
-        *
-        * @param int $seconds Number of seconds
-        * @param int $microseconds Number of microseconds
-        */
-       public function set_timeout( $seconds, $microseconds ) {
-               $this->_timeout_seconds = $seconds;
-               $this->_timeout_microseconds = $microseconds;
-       }
-
-       // }}}
-       // }}}
-       // {{{ private methods
-       // {{{ _close_sock()
-
-       /**
-        * Close the specified socket
-        *
-        * @param string $sock Socket to close
-        *
-        * @access private
-        */
-       function _close_sock( $sock ) {
-               $host = array_search( $sock, $this->_cache_sock );
-               fclose( $this->_cache_sock[$host] );
-               unset( $this->_cache_sock[$host] );
-       }
-
-       // }}}
-       // {{{ _connect_sock()
-
-       /**
-        * Connects $sock to $host, timing out after $timeout
-        *
-        * @param int $sock Socket to connect
-        * @param string $host Host:IP to connect to
-        *
-        * @return bool
-        * @access private
-        */
-       function _connect_sock( &$sock, $host ) {
-               list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
-               $sock = false;
-               $timeout = $this->_connect_timeout;
-               $errno = $errstr = null;
-               for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
-                       MediaWiki\suppressWarnings();
-                       if ( $this->_persistent == 1 ) {
-                               $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
-                       } else {
-                               $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
-                       }
-                       MediaWiki\restoreWarnings();
-               }
-               if ( !$sock ) {
-                       $this->_error_log( "Error connecting to $host: $errstr\n" );
-                       $this->_dead_host( $host );
-                       return false;
-               }
-
-               // Initialise timeout
-               stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
-
-               // If the connection was persistent, flush the read buffer in case there
-               // was a previous incomplete request on this connection
-               if ( $this->_persistent ) {
-                       $this->_flush_read_buffer( $sock );
-               }
-               return true;
-       }
-
-       // }}}
-       // {{{ _dead_sock()
-
-       /**
-        * Marks a host as dead until 30-40 seconds in the future
-        *
-        * @param string $sock Socket to mark as dead
-        *
-        * @access private
-        */
-       function _dead_sock( $sock ) {
-               $host = array_search( $sock, $this->_cache_sock );
-               $this->_dead_host( $host );
-       }
-
-       /**
-        * @param string $host
-        */
-       function _dead_host( $host ) {
-               $parts = explode( ':', $host );
-               $ip = $parts[0];
-               $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
-               $this->_host_dead[$host] = $this->_host_dead[$ip];
-               unset( $this->_cache_sock[$host] );
-       }
-
-       // }}}
-       // {{{ get_sock()
-
-       /**
-        * get_sock
-        *
-        * @param string $key Key to retrieve value for;
-        *
-        * @return Resource|bool Resource on success, false on failure
-        * @access private
-        */
-       function get_sock( $key ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               if ( $this->_single_sock !== null ) {
-                       return $this->sock_to_host( $this->_single_sock );
-               }
-
-               $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
-               if ( $this->_buckets === null ) {
-                       $bu = array();
-                       foreach ( $this->_servers as $v ) {
-                               if ( is_array( $v ) ) {
-                                       for ( $i = 0; $i < $v[1]; $i++ ) {
-                                               $bu[] = $v[0];
-                                       }
-                               } else {
-                                       $bu[] = $v;
-                               }
-                       }
-                       $this->_buckets = $bu;
-                       $this->_bucketcount = count( $bu );
-               }
-
-               $realkey = is_array( $key ) ? $key[1] : $key;
-               for ( $tries = 0; $tries < 20; $tries++ ) {
-                       $host = $this->_buckets[$hv % $this->_bucketcount];
-                       $sock = $this->sock_to_host( $host );
-                       if ( is_resource( $sock ) ) {
-                               return $sock;
-                       }
-                       $hv = $this->_hashfunc( $hv . $realkey );
-               }
-
-               return false;
-       }
-
-       // }}}
-       // {{{ _hashfunc()
-
-       /**
-        * Creates a hash integer based on the $key
-        *
-        * @param string $key Key to hash
-        *
-        * @return int Hash value
-        * @access private
-        */
-       function _hashfunc( $key ) {
-               # Hash function must be in [0,0x7ffffff]
-               # We take the first 31 bits of the MD5 hash, which unlike the hash
-               # function used in a previous version of this client, works
-               return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
-       }
-
-       // }}}
-       // {{{ _incrdecr()
-
-       /**
-        * Perform increment/decriment on $key
-        *
-        * @param string $cmd Command to perform
-        * @param string|array $key Key to perform it on
-        * @param int $amt Amount to adjust
-        *
-        * @return int New value of $key
-        * @access private
-        */
-       function _incrdecr( $cmd, $key, $amt = 1 ) {
-               if ( !$this->_active ) {
-                       return null;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return null;
-               }
-
-               $key = is_array( $key ) ? $key[1] : $key;
-               if ( isset( $this->stats[$cmd] ) ) {
-                       $this->stats[$cmd]++;
-               } else {
-                       $this->stats[$cmd] = 1;
-               }
-               if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
-                       return null;
-               }
-
-               $line = $this->_fgets( $sock );
-               $match = array();
-               if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
-                       return null;
-               }
-               return $match[1];
-       }
-
-       // }}}
-       // {{{ _load_items()
-
-       /**
-        * Load items into $ret from $sock
-        *
-        * @param Resource $sock Socket to read from
-        * @param array $ret returned values
-        * @param float $casToken [optional]
-        * @return bool True for success, false for failure
-        *
-        * @access private
-        */
-       function _load_items( $sock, &$ret, &$casToken = null ) {
-               $results = array();
-
-               while ( 1 ) {
-                       $decl = $this->_fgets( $sock );
-
-                       if ( $decl === false ) {
-                               /*
-                                * If nothing can be read, something is wrong because we know exactly when
-                                * to stop reading (right after "END") and we return right after that.
-                                */
-                               return false;
-                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
-                               /*
-                                * Read all data returned. This can be either one or multiple values.
-                                * Save all that data (in an array) to be processed later: we'll first
-                                * want to continue reading until "END" before doing anything else,
-                                * to make sure that we don't leave our client in a state where it's
-                                * output is not yet fully read.
-                                */
-                               $results[] = array(
-                                       $match[1], // rkey
-                                       $match[2], // flags
-                                       $match[3], // len
-                                       $match[4], // casToken
-                                       $this->_fread( $sock, $match[3] + 2 ), // data
-                               );
-                       } elseif ( $decl == "END" ) {
-                               if ( count( $results ) == 0 ) {
-                                       return false;
-                               }
-
-                               /**
-                                * All data has been read, time to process the data and build
-                                * meaningful return values.
-                                */
-                               foreach ( $results as $vars ) {
-                                       list( $rkey, $flags, $len, $casToken, $data ) = $vars;
-
-                                       if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
-                                               $this->_handle_error( $sock,
-                                                       'line ending missing from data block from $1' );
-                                               return false;
-                                       }
-                                       $data = substr( $data, 0, -2 );
-                                       $ret[$rkey] = $data;
-
-                                       if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
-                                               $ret[$rkey] = gzuncompress( $ret[$rkey] );
-                                       }
-
-                                       /*
-                                        * This unserialize is the exact reason that we only want to
-                                        * process data after having read until "END" (instead of doing
-                                        * this right away): "unserialize" can trigger outside code:
-                                        * in the event that $ret[$rkey] is a serialized object,
-                                        * unserializing it will trigger __wakeup() if present. If that
-                                        * function attempted to read from memcached (while we did not
-                                        * yet read "END"), these 2 calls would collide.
-                                        */
-                                       if ( $flags & self::SERIALIZED ) {
-                                               $ret[$rkey] = unserialize( $ret[$rkey] );
-                                       } elseif ( $flags & self::INTVAL ) {
-                                               $ret[$rkey] = intval( $ret[$rkey] );
-                                       }
-                               }
-
-                               return true;
-                       } else {
-                               $this->_handle_error( $sock, 'Error parsing response from $1' );
-                               return false;
-                       }
-               }
-       }
-
-       // }}}
-       // {{{ _set()
-
-       /**
-        * Performs the requested storage operation to the memcache server
-        *
-        * @param string $cmd Command to perform
-        * @param string $key Key to act on
-        * @param mixed $val What we need to store
-        * @param int $exp (optional) Expiration time. This can be a number of seconds
-        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
-        * longer must be the timestamp of the time at which the mapping should expire. It
-        * is safe to use timestamps in all cases, regardless of exipration
-        * eg: strtotime("+3 hour")
-        * @param float $casToken [optional]
-        *
-        * @return bool
-        * @access private
-        */
-       function _set( $cmd, $key, $val, $exp, $casToken = null ) {
-               if ( !$this->_active ) {
-                       return false;
-               }
-
-               $sock = $this->get_sock( $key );
-               if ( !is_resource( $sock ) ) {
-                       return false;
-               }
-
-               if ( isset( $this->stats[$cmd] ) ) {
-                       $this->stats[$cmd]++;
-               } else {
-                       $this->stats[$cmd] = 1;
-               }
-
-               $flags = 0;
-
-               if ( is_int( $val ) ) {
-                       $flags |= self::INTVAL;
-               } elseif ( !is_scalar( $val ) ) {
-                       $val = serialize( $val );
-                       $flags |= self::SERIALIZED;
-                       if ( $this->_debug ) {
-                               $this->_debugprint( sprintf( "client: serializing data as it is not scalar\n" ) );
-                       }
-               }
-
-               $len = strlen( $val );
-
-               if ( $this->_have_zlib && $this->_compress_enable
-                       && $this->_compress_threshold && $len >= $this->_compress_threshold
-               ) {
-                       $c_val = gzcompress( $val, 9 );
-                       $c_len = strlen( $c_val );
-
-                       if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
-                               if ( $this->_debug ) {
-                                       $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len ) );
-                               }
-                               $val = $c_val;
-                               $len = $c_len;
-                               $flags |= self::COMPRESSED;
-                       }
-               }
-
-               $command = "$cmd $key $flags $exp $len";
-               if ( $casToken ) {
-                       $command .= " $casToken";
-               }
-
-               if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
-                       return false;
-               }
-
-               $line = $this->_fgets( $sock );
-
-               if ( $this->_debug ) {
-                       $this->_debugprint( sprintf( "%s %s (%s)\n", $cmd, $key, $line ) );
-               }
-               if ( $line == "STORED" ) {
-                       return true;
-               }
-               return false;
-       }
-
-       // }}}
-       // {{{ sock_to_host()
-
-       /**
-        * Returns the socket for the host
-        *
-        * @param string $host Host:IP to get socket for
-        *
-        * @return Resource|bool IO Stream or false
-        * @access private
-        */
-       function sock_to_host( $host ) {
-               if ( isset( $this->_cache_sock[$host] ) ) {
-                       return $this->_cache_sock[$host];
-               }
-
-               $sock = null;
-               $now = time();
-               list( $ip, /* $port */) = explode( ':', $host );
-               if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
-                       isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
-               ) {
-                       return null;
-               }
-
-               if ( !$this->_connect_sock( $sock, $host ) ) {
-                       return null;
-               }
-
-               // Do not buffer writes
-               stream_set_write_buffer( $sock, 0 );
-
-               $this->_cache_sock[$host] = $sock;
-
-               return $this->_cache_sock[$host];
-       }
-
-       /**
-        * @param string $text
-        */
-       function _debugprint( $text ) {
-               $this->_logger->debug( $text );
-       }
-
-       /**
-        * @param string $text
-        */
-       function _error_log( $text ) {
-               $this->_logger->error( "Memcached error: $text" );
-       }
-
-       /**
-        * Write to a stream. If there is an error, mark the socket dead.
-        *
-        * @param Resource $sock The socket
-        * @param string $buf The string to write
-        * @return bool True on success, false on failure
-        */
-       function _fwrite( $sock, $buf ) {
-               $bytesWritten = 0;
-               $bufSize = strlen( $buf );
-               while ( $bytesWritten < $bufSize ) {
-                       $result = fwrite( $sock, $buf );
-                       $data = stream_get_meta_data( $sock );
-                       if ( $data['timed_out'] ) {
-                               $this->_handle_error( $sock, 'timeout writing to $1' );
-                               return false;
-                       }
-                       // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
-                       if ( $result === false || $result === 0 ) {
-                               $this->_handle_error( $sock, 'error writing to $1' );
-                               return false;
-                       }
-                       $bytesWritten += $result;
-               }
-
-               return true;
-       }
-
-       /**
-        * Handle an I/O error. Mark the socket dead and log an error.
-        *
-        * @param Resource $sock
-        * @param string $msg
-        */
-       function _handle_error( $sock, $msg ) {
-               $peer = stream_socket_get_name( $sock, true /** remote **/ );
-               if ( strval( $peer ) === '' ) {
-                       $peer = array_search( $sock, $this->_cache_sock );
-                       if ( $peer === false ) {
-                               $peer = '[unknown host]';
-                       }
-               }
-               $msg = str_replace( '$1', $peer, $msg );
-               $this->_error_log( "$msg\n" );
-               $this->_dead_sock( $sock );
-       }
-
-       /**
-        * Read the specified number of bytes from a stream. If there is an error,
-        * mark the socket dead.
-        *
-        * @param Resource $sock The socket
-        * @param int $len The number of bytes to read
-        * @return string|bool The string on success, false on failure.
-        */
-       function _fread( $sock, $len ) {
-               $buf = '';
-               while ( $len > 0 ) {
-                       $result = fread( $sock, $len );
-                       $data = stream_get_meta_data( $sock );
-                       if ( $data['timed_out'] ) {
-                               $this->_handle_error( $sock, 'timeout reading from $1' );
-                               return false;
-                       }
-                       if ( $result === false ) {
-                               $this->_handle_error( $sock, 'error reading buffer from $1' );
-                               return false;
-                       }
-                       if ( $result === '' ) {
-                               // This will happen if the remote end of the socket is shut down
-                               $this->_handle_error( $sock, 'unexpected end of file reading from $1' );
-                               return false;
-                       }
-                       $len -= strlen( $result );
-                       $buf .= $result;
-               }
-               return $buf;
-       }
-
-       /**
-        * Read a line from a stream. If there is an error, mark the socket dead.
-        * The \r\n line ending is stripped from the response.
-        *
-        * @param Resource $sock The socket
-        * @return string|bool The string on success, false on failure
-        */
-       function _fgets( $sock ) {
-               $result = fgets( $sock );
-               // fgets() may return a partial line if there is a select timeout after
-               // a successful recv(), so we have to check for a timeout even if we
-               // got a string response.
-               $data = stream_get_meta_data( $sock );
-               if ( $data['timed_out'] ) {
-                       $this->_handle_error( $sock, 'timeout reading line from $1' );
-                       return false;
-               }
-               if ( $result === false ) {
-                       $this->_handle_error( $sock, 'error reading line from $1' );
-                       return false;
-               }
-               if ( substr( $result, -2 ) === "\r\n" ) {
-                       $result = substr( $result, 0, -2 );
-               } elseif ( substr( $result, -1 ) === "\n" ) {
-                       $result = substr( $result, 0, -1 );
-               } else {
-                       $this->_handle_error( $sock, 'line ending missing in response from $1' );
-                       return false;
-               }
-               return $result;
-       }
-
-       /**
-        * Flush the read buffer of a stream
-        * @param Resource $f
-        */
-       function _flush_read_buffer( $f ) {
-               if ( !is_resource( $f ) ) {
-                       return;
-               }
-               $r = array( $f );
-               $w = null;
-               $e = null;
-               $n = stream_select( $r, $w, $e, 0, 0 );
-               while ( $n == 1 && !feof( $f ) ) {
-                       fread( $f, 1024 );
-                       $r = array( $f );
-                       $w = null;
-                       $e = null;
-                       $n = stream_select( $r, $w, $e, 0, 0 );
-               }
-       }
-
-       // }}}
-       // }}}
-       // }}}
-}
-
-// }}}
-
-class MemCachedClientforWiki extends MWMemcached {
-}
index 365236d..b7d1eaf 100644 (file)
@@ -100,13 +100,17 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
                                break;
                        case 'igbinary':
                                if ( !Memcached::HAVE_IGBINARY ) {
-                                       throw new MWException( __CLASS__ . ': the igbinary extension is not available ' .
-                                               'but igbinary serialization was requested.' );
+                                       throw new InvalidArgumentException(
+                                               __CLASS__ . ': the igbinary extension is not available ' .
+                                               'but igbinary serialization was requested.'
+                                       );
                                }
                                $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
                                break;
                        default:
-                               throw new MWException( __CLASS__ . ': invalid value for serializer parameter' );
+                               throw new InvalidArgumentException(
+                                       __CLASS__ . ': invalid value for serializer parameter'
+                               );
                }
                $servers = array();
                foreach ( $params['servers'] as $host ) {
@@ -117,7 +121,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
 
        protected function getWithToken( $key, &$casToken, $flags = 0 ) {
                $this->debugLog( "get($key)" );
-               $result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
+               $result = $this->client->get( $this->validateKeyEncoding( $key ), null, $casToken );
                $result = $this->checkResult( $key, $result );
                return $result;
        }
@@ -202,14 +206,10 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
 
        public function getMulti( array $keys, $flags = 0 ) {
                $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
-               $callback = array( $this, 'encodeKey' );
-               $encodedResult = $this->client->getMulti( array_map( $callback, $keys ) );
-               $encodedResult = $encodedResult ?: array(); // must be an array
-               $result = array();
-               foreach ( $encodedResult as $key => $value ) {
-                       $key = $this->decodeKey( $key );
-                       $result[$key] = $value;
+               foreach ( $keys as $key ) {
+                       $this->validateKeyEncoding( $key );
                }
+               $result = $this->client->getMulti( $keys ) ?: array();
                return $this->checkResult( false, $result );
        }
 
@@ -219,14 +219,10 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
         * @return bool
         */
        public function setMulti( array $data, $exptime = 0 ) {
-               foreach ( $data as $key => $value ) {
-                       $encKey = $this->encodeKey( $key );
-                       if ( $encKey !== $key ) {
-                               $data[$encKey] = $value;
-                               unset( $data[$key] );
-                       }
-               }
                $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
+               foreach ( array_keys( $data ) as $key ) {
+                       $this->validateKeyEncoding( $key );
+               }
                $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
                return $this->checkResult( false, $result );
        }
diff --git a/includes/objectcache/MemcachedPhpBagOStuff.php b/includes/objectcache/MemcachedPhpBagOStuff.php
deleted file mode 100644 (file)
index 6f0ba58..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-/**
- * Object caching using memcached.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * A wrapper class for the pure-PHP memcached client, exposing a BagOStuff interface.
- *
- * @ingroup Cache
- */
-class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
-
-       /**
-        * Constructor.
-        *
-        * Available parameters are:
-        *   - servers:             The list of IP:port combinations holding the memcached servers.
-        *   - debug:               Whether to set the debug flag in the underlying client.
-        *   - persistent:          Whether to use a persistent connection
-        *   - compress_threshold:  The minimum size an object must be before it is compressed
-        *   - timeout:             The read timeout in microseconds
-        *   - connect_timeout:     The connect timeout in seconds
-        *
-        * @param array $params
-        */
-       function __construct( $params ) {
-               parent::__construct( $params );
-               $params = $this->applyDefaultParams( $params );
-
-               $this->client = new MemCachedClientforWiki( $params );
-               $this->client->set_servers( $params['servers'] );
-               $this->client->set_debug( $params['debug'] );
-       }
-
-       /**
-        * @param bool $debug
-        */
-       public function setDebug( $debug ) {
-               $this->client->set_debug( $debug );
-       }
-
-       public function getMulti( array $keys, $flags = 0 ) {
-               $callback = array( $this, 'encodeKey' );
-               $encodedResult = $this->client->get_multi( array_map( $callback, $keys ) );
-               $result = array();
-               foreach ( $encodedResult as $key => $value ) {
-                       $key = $this->decodeKey( $key );
-                       $result[$key] = $value;
-               }
-               return $result;
-       }
-
-       /**
-        * @param string $key
-        * @param int $value
-        * @return mixed
-        */
-       public function incr( $key, $value = 1 ) {
-               return $this->client->incr( $this->encodeKey( $key ), $value );
-       }
-
-       /**
-        * @param string $key
-        * @param int $value
-        * @return mixed
-        */
-       public function decr( $key, $value = 1 ) {
-               return $this->client->decr( $this->encodeKey( $key ), $value );
-       }
-}
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
deleted file mode 100644 (file)
index 73bdabd..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-<?php
-/**
- * Wrapper for object caching in different caches.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * A cache class that replicates all writes to multiple child caches. Reads
- * are implemented by reading from the caches in the order they are given in
- * the configuration until a cache gives a positive result.
- *
- * @ingroup Cache
- */
-class MultiWriteBagOStuff extends BagOStuff {
-       /** @var BagOStuff[] */
-       protected $caches;
-       /** @var bool Use async secondary writes */
-       protected $asyncWrites = false;
-       /** @var callback|null */
-       protected $asyncHandler;
-
-       /** Idiom for "write to all backends" */
-       const ALL = INF;
-
-       const UPGRADE_TTL = 3600; // TTL when a key is copied to a higher cache tier
-
-       /**
-        * $params include:
-        *   - caches: A numbered array of either ObjectFactory::getObjectFromSpec
-        *      arrays yeilding BagOStuff objects or direct BagOStuff objects.
-        *      If using the former, the 'args' field *must* be set.
-        *      The first cache is the primary one, being the first to
-        *      be read in the fallback chain. Writes happen to all stores
-        *      in the order they are defined. However, lock()/unlock() calls
-        *      only use the primary store.
-        *   - replication: Either 'sync' or 'async'. This controls whether writes
-        *      to secondary stores are deferred when possible. Async writes
-        *      require setting 'asyncCallback'. HHVM register_postsend_function() function.
-        *      Async writes can increase the chance of some race conditions
-        *      or cause keys to expire seconds later than expected. It is
-        *      safe to use for modules when cached values: are immutable,
-        *      invalidation uses logical TTLs, invalidation uses etag/timestamp
-        *      validation against the DB, or merge() is used to handle races.
-        *   - asyncHandler: callable that takes a callback and runs it after the
-        *      current web request ends. In CLI mode, it should run it immediately.
-        * @param array $params
-        * @throws InvalidArgumentException
-        */
-       public function __construct( $params ) {
-               parent::__construct( $params );
-
-               if ( empty( $params['caches'] ) || !is_array( $params['caches'] ) ) {
-                       throw new InvalidArgumentException(
-                               __METHOD__ . ': "caches" parameter must be an array of caches'
-                       );
-               }
-
-               $this->caches = array();
-               foreach ( $params['caches'] as $cacheInfo ) {
-                       if ( $cacheInfo instanceof BagOStuff ) {
-                               $this->caches[] = $cacheInfo;
-                       } else {
-                               if ( !isset( $cacheInfo['args'] ) ) {
-                                       // B/C for when $cacheInfo was for ObjectCache::newFromParams().
-                                       // Callers intenting this to be for ObjectFactory::getObjectFromSpec
-                                       // should have set "args" per the docs above. Doings so avoids extra
-                                       // (likely harmless) params (factory/class/calls) ending up in "args".
-                                       $cacheInfo['args'] = array( $cacheInfo );
-                               }
-                               $this->caches[] = ObjectFactory::getObjectFromSpec( $cacheInfo );
-                       }
-               }
-
-               $this->asyncHandler = isset( $params['asyncHandler'] )
-                       ? $params['asyncHandler']
-                       : null;
-               $this->asyncWrites = (
-                       isset( $params['replication'] ) &&
-                       $params['replication'] === 'async' &&
-                       is_callable( $this->asyncHandler )
-               );
-       }
-
-       public function setDebug( $debug ) {
-               foreach ( $this->caches as $cache ) {
-                       $cache->setDebug( $debug );
-               }
-       }
-
-       protected function doGet( $key, $flags = 0 ) {
-               if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) {
-                       // If the latest write was a delete(), we do NOT want to fallback
-                       // to the other tiers and possibly see the old value. Also, this
-                       // is used by mergeViaLock(), which only needs to hit the primary.
-                       return $this->caches[0]->get( $key, $flags );
-               }
-
-               $misses = 0; // number backends checked
-               $value = false;
-               foreach ( $this->caches as $cache ) {
-                       $value = $cache->get( $key, $flags );
-                       if ( $value !== false ) {
-                               break;
-                       }
-                       ++$misses;
-               }
-
-               if ( $value !== false
-                       && $misses > 0
-                       && ( $flags & self::READ_VERIFIED ) == self::READ_VERIFIED
-               ) {
-                       $this->doWrite( $misses, $this->asyncWrites, 'set', $key, $value, self::UPGRADE_TTL );
-               }
-
-               return $value;
-       }
-
-       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
-               $asyncWrites = ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC )
-                       ? false
-                       : $this->asyncWrites;
-
-               return $this->doWrite( self::ALL, $asyncWrites, 'set', $key, $value, $exptime );
-       }
-
-       public function delete( $key ) {
-               return $this->doWrite( self::ALL, $this->asyncWrites, 'delete', $key );
-       }
-
-       public function add( $key, $value, $exptime = 0 ) {
-               return $this->doWrite( self::ALL, $this->asyncWrites, 'add', $key, $value, $exptime );
-       }
-
-       public function incr( $key, $value = 1 ) {
-               return $this->doWrite( self::ALL, $this->asyncWrites, 'incr', $key, $value );
-       }
-
-       public function decr( $key, $value = 1 ) {
-               return $this->doWrite( self::ALL, $this->asyncWrites, 'decr', $key, $value );
-       }
-
-       public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) {
-               // Only need to lock the first cache; also avoids deadlocks
-               return $this->caches[0]->lock( $key, $timeout, $expiry, $rclass );
-       }
-
-       public function unlock( $key ) {
-               // Only the first cache is locked
-               return $this->caches[0]->unlock( $key );
-       }
-
-       public function getLastError() {
-               return $this->caches[0]->getLastError();
-       }
-
-       public function clearLastError() {
-               $this->caches[0]->clearLastError();
-       }
-
-       /**
-        * Apply a write method to the first $count backing caches
-        *
-        * @param integer $count
-        * @param bool $asyncWrites
-        * @param string $method
-        * @param mixed ...
-        * @return bool
-        */
-       protected function doWrite( $count, $asyncWrites, $method /*, ... */ ) {
-               $ret = true;
-               $args = array_slice( func_get_args(), 3 );
-
-               foreach ( $this->caches as $i => $cache ) {
-                       if ( $i >= $count ) {
-                               break; // ignore the lower tiers
-                       }
-
-                       if ( $i == 0 || !$asyncWrites ) {
-                               // First store or in sync mode: write now and get result
-                               if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
-                                       $ret = false;
-                               }
-                       } else {
-                               // Secondary write in async mode: do not block this HTTP request
-                               $logger = $this->logger;
-                               call_user_func(
-                                       $this->asyncHandler,
-                                       function () use ( $cache, $method, $args, $logger ) {
-                                               if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
-                                                       $logger->warning( "Async $method op failed" );
-                                               }
-                                       }
-                               );
-                       }
-               }
-
-               return $ret;
-       }
-
-       /**
-        * Delete objects expiring before a certain date.
-        *
-        * Succeed if any of the child caches succeed.
-        * @param string $date
-        * @param bool|callable $progressCallback
-        * @return bool
-        */
-       public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
-               $ret = false;
-               foreach ( $this->caches as $cache ) {
-                       if ( $cache->deleteObjectsExpiringBefore( $date, $progressCallback ) ) {
-                               $ret = true;
-                       }
-               }
-
-               return $ret;
-       }
-}
index 151bb06..f21b797 100644 (file)
@@ -178,8 +178,26 @@ class ObjectCache {
                        return call_user_func( $params['factory'], $params );
                } elseif ( isset( $params['class'] ) ) {
                        $class = $params['class'];
-                       if ( $class === 'MultiWriteBagOStuff' && !isset( $params['asyncHandler'] ) ) {
-                               $params['asyncHandler'] = 'DeferredUpdates::addCallableUpdate';
+                       // Automatically set the 'async' update handler
+                       if ( $class === 'MultiWriteBagOStuff' ) {
+                               $params['asyncHandler'] = isset( $params['asyncHandler'] )
+                                       ? $params['asyncHandler']
+                                       : 'DeferredUpdates::addCallableUpdate';
+                       }
+                       // Do b/c logic for MemcachedBagOStuff
+                       if ( is_subclass_of( $class, 'MemcachedBagOStuff' ) ) {
+                               if ( !isset( $params['servers'] ) ) {
+                                       $params['servers'] = $GLOBALS['wgMemCachedServers'];
+                               }
+                               if ( !isset( $params['debug'] ) ) {
+                                       $params['debug'] = $GLOBALS['wgMemCachedDebug'];
+                               }
+                               if ( !isset( $params['persistent'] ) ) {
+                                       $params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
+                               }
+                               if ( !isset( $params['timeout'] ) ) {
+                                       $params['timeout'] = $GLOBALS['wgMemCachedTimeout'];
+                               }
                        }
                        return new $class( $params );
                } else {
index cdaab1a..96b2e27 100644 (file)
@@ -1092,7 +1092,9 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Get a ParserOutput for the given ParserOptions and revision ID.
-        * The parser cache will be used if possible.
+        *
+        * The parser cache will be used if possible. Cache misses that result
+        * in parser runs are debounced with PoolCounter.
         *
         * @since 1.19
         * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
index 7acfe38..2451390 100644 (file)
@@ -93,11 +93,6 @@ class CacheTime {
                if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) {
                        $this->mCacheExpiry = $seconds;
                }
-
-               // hack: set old-style marker for uncacheable entries.
-               if ( $this->mCacheExpiry !== null && $this->mCacheExpiry <= 0 ) {
-                       $this->mCacheTime = -1;
-               }
        }
 
        /**
index efad151..9060756 100644 (file)
@@ -5763,7 +5763,6 @@ class Parser {
                        throw new MWException( __METHOD__ .
                                " can only be called when actually parsing something" );
                }
-               $this->mOutput->setCacheTime( -1 ); // old style, for compatibility
                $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
        }
 
index d601467..3c60ea9 100644 (file)
@@ -147,6 +147,7 @@ class PoolWorkArticleView extends PoolCounterWork {
                        $logger->info( '{time} {title}', array(
                                'time' => number_format( $time, 2 ),
                                'title' => $this->page->getTitle()->getPrefixedDBkey(),
+                               'trigger' => 'view',
                        ) );
                }
 
index 4348b14..0c1a941 100644 (file)
@@ -100,7 +100,6 @@ class SpecialAllPages extends IncludableSpecialPage {
         */
        function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
                $t = $this->getPageTitle();
-
                $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
                $out .= Xml::openElement(
                        'form',
@@ -148,7 +147,6 @@ class SpecialAllPages extends IncludableSpecialPage {
                $out .= Xml::closeElement( 'fieldset' );
                $out .= Xml::closeElement( 'form' );
                $out .= Xml::closeElement( 'div' );
-
                return $out;
        }
 
@@ -273,14 +271,10 @@ class SpecialAllPages extends IncludableSpecialPage {
                        return;
                }
 
+               $navLinks = array();
                $self = $this->getPageTitle();
 
-               $topLinks = array(
-                       Linker::link( $self, $this->msg( 'allpages' )->escaped() )
-               );
-               $bottomLinks = array();
-
-               # Do we put a previous link ?
+               // Generate a "previous page" link if needed
                if ( $prevTitle ) {
                        $query = array( 'from' => $prevTitle->getText() );
 
@@ -292,16 +286,16 @@ class SpecialAllPages extends IncludableSpecialPage {
                                $query['hideredirects'] = $hideredirects;
                        }
 
-                       $prevLink = Linker::linkKnown(
+                       $navLinks[] = Linker::linkKnown(
                                $self,
                                $this->msg( 'prevpage', $prevTitle->getText() )->escaped(),
                                array(),
                                $query
                        );
-                       $topLinks[] = $prevLink;
-                       $bottomLinks[] = $prevLink;
+
                }
 
+               // Generate a "next page" link if needed
                if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
                        # $s is the first link of the next chunk
                        $t = Title::makeTitle( $namespace, $s->page_title );
@@ -315,36 +309,28 @@ class SpecialAllPages extends IncludableSpecialPage {
                                $query['hideredirects'] = $hideredirects;
                        }
 
-                       $nextLink = Linker::linkKnown(
+                       $navLinks[] = Linker::linkKnown(
                                $self,
                                $this->msg( 'nextpage', $t->getText() )->escaped(),
                                array(),
                                $query
                        );
-                       $topLinks[] = $nextLink;
-                       $bottomLinks[] = $nextLink;
                }
 
-               $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
-               $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
-                       '<tr>
-                                               <td>' .
-                       $nsForm .
-                       '</td>
-                                               <td class="mw-allpages-nav">' .
-                       $this->getLanguage()->pipeList( $topLinks ) .
-                       '</td></tr></table>';
-
-               $output->addHTML( $out2 . $out );
-
-               if ( count( $bottomLinks ) ) {
-                       $output->addHTML(
-                               Html::element( 'hr' ) .
-                                       Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
-                                               $this->getLanguage()->pipeList( $bottomLinks )
-                                       )
+               $topOut = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
+
+               if ( count( $navLinks ) ) {
+                       // Add pagination links
+                       $pagination = Html::rawElement( 'div',
+                               array( 'class' => 'mw-allpages-nav' ),
+                               $this->getLanguage()->pipeList( $navLinks )
                        );
+
+                       $topOut .= $pagination;
+                       $out .= Html::element( 'hr' ) . $pagination; // Footer
                }
+
+               $output->addHTML( $topOut . $out );
        }
 
        /**
index bc5dfd0..fbe5ab3 100644 (file)
@@ -201,7 +201,7 @@ class SpecialPrefixindex extends SpecialAllPages {
                                )
                        );
 
-                       # ## @todo FIXME: Side link to previous
+                       // @todo FIXME: Side link to previous
 
                        $n = 0;
                        if ( $res->numRows() > 0 ) {
@@ -239,54 +239,55 @@ class SpecialPrefixindex extends SpecialAllPages {
                        }
                }
 
-               $footer = '';
-               if ( $this->including() ) {
-                       $out2 = '';
-               } else {
-                       $nsForm = $this->namespacePrefixForm( $namespace, $prefix );
-                       $self = $this->getPageTitle();
-                       $out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
-                               '<tr>
-                                       <td>' .
-                               $nsForm .
-                               '</td>
-                               <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
-
-                       if ( $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
-                               $query = array(
-                                       'from' => $s->page_title,
-                                       'prefix' => $prefix,
-                                       'hideredirects' => $this->hideRedirects,
-                                       'stripprefix' => $this->stripPrefix,
-                               );
+               $output = $this->getOutput();
 
-                               if ( $namespace || $prefix == '' ) {
-                                       // Keep the namespace even if it's 0 for empty prefixes.
-                                       // This tells us we're not just a holdover from old links.
-                                       $query['namespace'] = $namespace;
-                               }
+               if ( $this->including() ) {
+                       // We don't show the nav-links and the form when included into other
+                       // pages so let's just finish here.
+                       $output->addHTML( $out );
+                       return;
+               }
 
-                               $nextLink = Linker::linkKnown(
-                                       $self,
-                                       $this->msg( 'nextpage', str_replace( '_', ' ', $s->page_title ) )->escaped(),
-                                       array(),
-                                       $query
-                               );
+               $topOut = $this->namespacePrefixForm( $namespace, $prefix );
 
-                               $out2 .= $nextLink;
+               if ( $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+                       $query = array(
+                               'from' => $s->page_title,
+                               'prefix' => $prefix,
+                               'hideredirects' => $this->hideRedirects,
+                               'stripprefix' => $this->stripPrefix,
+                       );
 
-                               $footer = "\n" . Html::element( 'hr' ) .
-                                       Html::rawElement(
-                                               'div',
-                                               array( 'class' => 'mw-prefixindex-nav' ),
-                                               $nextLink
-                                       );
+                       if ( $namespace || $prefix == '' ) {
+                               // Keep the namespace even if it's 0 for empty prefixes.
+                               // This tells us we're not just a holdover from old links.
+                               $query['namespace'] = $namespace;
                        }
-                       $out2 .= "</td></tr>" .
-                               Xml::closeElement( 'table' );
+
+                       $nextLink = Linker::linkKnown(
+                               $this->getPageTitle(),
+                               $this->msg( 'nextpage', str_replace( '_', ' ', $s->page_title ) )->escaped(),
+                               array(),
+                               $query
+                       );
+
+                       // Link shown at the top of the page below the form
+                       $topOut .= Html::rawElement( 'div',
+                               array( 'class' => 'mw-prefixindex-nav' ),
+                               $nextLink
+                       );
+
+                       // Link shown at the footer
+                       $out .= "\n" . Html::element( 'hr' ) .
+                               Html::rawElement(
+                                       'div',
+                                       array( 'class' => 'mw-prefixindex-nav' ),
+                                       $nextLink
+                               );
+
                }
 
-               $this->getOutput()->addHTML( $out2 . $out . $footer );
+               $output->addHTML( $topOut . $out );
        }
 
        protected function getGroupName() {
index 0da598b..8facb35 100644 (file)
@@ -783,8 +783,10 @@ class LoginForm extends SpecialPage {
                // Give general extensions, such as a captcha, a chance to abort logins
                $abort = self::ABORTED;
                if ( !Hooks::run( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
+                       if ( !in_array( $abort, self::$statusCodes, true ) ) {
+                               throw new Exception( 'Invalid status code returned from AbortLogin hook: ' . $abort );
+                       }
                        $this->mAbortLoginErrorMsg = $msg;
-
                        return $abort;
                }
 
@@ -1385,11 +1387,6 @@ class LoginForm extends SpecialPage {
                ) );
 
                if ( $this->mType == 'signup' ) {
-                       // XXX hack pending RL or JS parse() support for complex content messages
-                       // https://phabricator.wikimedia.org/T27349
-                       $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
-                               $this->msg( 'createacct-imgcaptcha-help' )->parse() );
-
                        // Additional styles and scripts for signup form
                        $out->addModules( array(
                                'mediawiki.special.userlogin.signup.js'
index 67c3b84..785860f 100644 (file)
        "createaccountreason": "السبب:",
        "createacct-reason": "السبب",
        "createacct-reason-ph": "لماذا تقوم بإنشاء حساب آخر",
-       "createacct-captcha": "تحقق أمني",
-       "createacct-imgcaptcha-ph": "أدخل النص الذي تراه في الأعلى",
        "createacct-submit": "افتح الحساب",
        "createacct-another-submit": "أنشئ حسابا",
        "createacct-benefit-heading": "موقع {{SITENAME}} أنشأه أشخاص مثلك.",
index e8f6149..26b4ddd 100644 (file)
        "createaccountreason": "Motivu:",
        "createacct-reason": "Motivu",
        "createacct-reason-ph": "Por qué quier crear otra cuenta",
-       "createacct-captcha": "Comprobación de seguridá",
-       "createacct-imgcaptcha-ph": "Escriba'l testu qu'apaez arriba",
        "createacct-submit": "Crear la cuenta",
        "createacct-another-submit": "Crear una cuenta",
        "createacct-benefit-heading": "{{SITENAME}} failu xente como vusté.",
        "permissionserrors": "Fallu de permisos",
        "permissionserrorstext": "Nun tien permisu pa facer eso {{PLURAL:$1|pol siguiente motivu|polos siguientes motivos}}:",
        "permissionserrorstext-withaction": "Nun tien permisu pa $2 {{PLURAL:$1|pol siguiente motivu|polos siguientes motivos}}:",
+       "contentmodelediterror": "Nun ye posible editar esta revisión porque'l so modelu de conteníu ye <code>$1</code>, mentanto que'l modelu de conteníu actual de la páxina ye <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Avisu: Tas volviendo a crear una páxina que se desanició anteriormente.'''\n\nHabríes considerar si ye afechisco siguir editando esta páxina.\nEquí tienes el rexistru de desanicios y tresllaos d'esta páxina:",
        "moveddeleted-notice": "Esta páxina se desanició.\nComo referencia, embaxo s'ufre'l rexistru de desanicios y tresllaos de la páxina.",
        "moveddeleted-notice-recent": "Esta páxina desanicióse apocayá (dientro de les postreres 24 hores).\nLos rexistros de desaniciu y treslláu de la páxina amuésense de siguío como referencia.",
index 4d226b5..8785392 100644 (file)
        "createaccountreason": "Прычына:",
        "createacct-reason": "Прычына",
        "createacct-reason-ph": "Зь якой мэтай вы ствараеце іншы рахунак",
-       "createacct-captcha": "Праверка бясьпекі",
-       "createacct-imgcaptcha-ph": "Увядзіце тэкст, што бачыце вышэй",
        "createacct-submit": "Стварыць рахунак",
        "createacct-another-submit": "Стварыць рахунак",
        "createacct-benefit-heading": "{{SITENAME}} створаная людзьмі, такімі як вы.",
        "foreign-structured-upload-form-label-own-work-message-local": "Я пацьвярджаю, што загружаю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
        "foreign-structured-upload-form-label-not-own-work-message-local": "Калі вы ня можаце загрузіць файл у адпаведнасьці з правіламі {{GRAMMAR:родны|{{SITENAME}}}}, калі ласка, закрыйце гэтае акно і паспрабуйце іншы мэтад.",
        "foreign-structured-upload-form-label-not-own-work-local-local": "Вы таксама можаце паспрабаваць [[Special:Upload|старонку загрузкі па змоўчаньні]].",
+       "foreign-structured-upload-form-label-own-work-message-default": "Я разумею, што загружаю гэты файл у агульнае сховішча. Я пацьвярджаю, што раблю гэта ў адпаведнасьці з умовамі выкарыстаньня і ліцэнзійнай палітыкай.",
        "backend-fail-stream": "Немагчыма накіраваць файл $1.",
        "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.",
        "backend-fail-notexists": "Файл $1 не існуе.",
index 3b2366e..7f4ed54 100644 (file)
        "createaccountreason": "Причина:",
        "createacct-reason": "Причина",
        "createacct-reason-ph": "Защо създавате друга сметка",
-       "createacct-captcha": "Проверка за сигурност",
-       "createacct-imgcaptcha-ph": "Въведете текста, който виждате по-горе",
        "createacct-submit": "Създаване на сметката",
        "createacct-another-submit": "Създаване на друга сметка",
        "createacct-benefit-heading": "{{SITENAME}} се създава от хора като вас.",
index 92d2a03..0f7a605 100644 (file)
@@ -12,6 +12,7 @@
        "tog-hideminor": "کمین تغیرات شه آخیرین تغیراتانی لڑلیستا چیهر داته بئینت",
        "tog-hidepatrolled": "گشت وارته گین ایڈ\tیٹان شه آخیرین ایڈ\tیٹانی لڑلیستا چیهر داته بئینت",
        "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": "چیهرین تهرئانه نشان دهی",
        "createaccountreason": "دلیل:",
        "createacct-reason": "دلیل",
        "createacct-reason-ph": "پچی شما لوٹیت که دیگرین کار زوروکین حساب جوڑ کنیت؟",
-       "createacct-captcha": "امنیتئ چیک کورتین",
-       "createacct-imgcaptcha-ph": "په بُرزه گین اکسا کی گیندیت متنی داخل کنیت",
        "createacct-submit": "وتئ کار زوروکین حسابا جوڑ کنیت",
        "createacct-another-submit": "دیگرین کار زوروکین حسابئ جوڑ کورتین",
        "createacct-benefit-heading": "ای {{SITENAME}} چو شمیا همیرنگین مردمانی واسطه ئا جوڑ بوته",
        "anonpreviewwarning": "<em>شما لوگین نه بوته ئیت. ذخیره کورتین باعث ئه بیئت که شمی آی پی ادرس بی ای تاکدیمی تاریخچه ئی تا ثبت بیئت.</em>",
        "missingcommenttext": "مهربانی بکنیت جهلگا توضیح دهیت.",
        "summary-preview": "دیم دیست ئی خلاصه:",
-       "subject-preview": "Ù\85Ù\88ضÙ\88 Ø¦Û\8c Ø¯Û\8cÙ\85 Ø¯Û\8cست/ئÙ\86Ù\88اÙ\86:",
+       "subject-preview": "Ù\85Ù\88زÙ\88Û\8cÛ\8c Ø¯Û\8cÙ\85 Ø¯Û\8cست:",
        "previewerrortext": "وختیکه شوما په وتي تغیراني نشان داتینا کوشش ئه کورتێت، یک خطاي مانجینا آت.",
        "blockedtitle": "کار زوروک بسته بوته",
        "blockedtext": "<strong>شمئ کارزورکین حساب یا آی پي ادرس بسته بوته.</strong>\n\nای بسته بوتین شه $1 ئی نیمگاه بوته.\nای نفر وتي دلیلا، ایرنگ گوشته: <em>$2</em>\n\n* بلاک یا بستین ئی شروع: $8\n* پاچ بوتین یا بلاک ئی الاسي: $6\n* هدفین بلاک بوته ئین کارزوروک: $7\n\nشوما توانێت که گۆ $1 یا گۆ [[{{MediaWiki:Grouppage-sysop}}|یک دیگرێن مدیري]] تماس بگیرێت و بئ ائ باره ئا هبر دهیت.\nتوجه کنیت که شما نه توانتیت شه  «ایمیل په ای کارزوروکا» استفاده بکنیت مگه ايکه یک معتبریندایمیل ادرسئ به وتي [[Special:Preferences|کارزوروکین حسابئ]] تا ثبت کورته به ئیت و هم  باید ای قابلیتئ امکان شه شوما گیپته مه بيت.\nشمئ انونین آی پي ادرس $3 و شمئ بلاک بوتینئ شماره $5 اینت.\nمهرباني بکنیت موچین مالوماتانا به وتئ درخواست ئانی تا ذکر بکنیت.",
+       "autoblockedtext": "شمی آی‌پی ادرس یسته بوته، په خاتیریکه ای  آی‌پی ادرس شه یک دیگرین کار زوروکئ نیمگاه استفاده بوته که آ شه $1 ئی نیمگاه بلاک بوته.\nدیم بوته ئین دلیل ایرنگ اینت:\n\n:''$2''\n\n* بلاک یا بستین ئی شروع: $8\n* پاچ بوتین یا بلاک ئی الاسي: $6\n* هدفین بلاک بوته ئین کارزوروک: $7\n\nشوما توانێت که گۆ $1 یا گۆ [[{{MediaWiki:Grouppage-sysop}}|یک دیگرێن مدیري]] تماس بگیرێت و بئ ائ باره ئا حبر دهیت.\nتوجه کنیت که شما نه توانتیت شه  «ایمیل په ای کارزوروکا» استفاده بکنیت مگه ايکه یک معتبرین ایمیل ادرسئ به وتي [[Special:Preferences|کارزوروکین حسابئ]] تا ثبت کورته به ئیت و هم  باید ای قابلیتئ امکان شه شوما گیپته مه بيت.\nشمئ انونین آی پي ادرس $3 و شمئ بلاک بوتینئ شماره $5 اینت.\nمهرباني بکنیت موچین مالوماتانا به وتئ درخواست ئانی تا ذکر بکنیت.",
        "blockednoreason": "دلیلی مشخص نه بوته",
        "whitelistedittext": "په مقاله ئانی ایڈیٹ ئا باید $1.",
+       "confirmedittext": "باید شما،دیم شه تاکدیماني دستکاری کورتینا، وتي ایمیل آدرسا مشخص و تائید بکنیت. مهرباني بکنیت شه [[Special:Preferences|کار زوروکئ تنظماتاني]] نیمگاه ای کارا بکنیت.",
        "nosuchsectiontitle": "ایرنگی بخشئ ودئ نه بوت",
+       "nosuchsectiontext": "شما کوشش کورته ایت که به ای تاکدیمی تا یک بخشیا دستکاری بکنیت که موجود نه اینت.\nمومکن اینت به همي وختا که شما تاکدیما سیل ئه کورتیت ای بخش جابجا یک پاک بوته.",
        "loginreqtitle": "لوگین بوتین ضروری اینت",
        "loginreqlink": "داخل بوتین",
        "loginreqpagetext": "په دیگه تاکدیمانئ دیستینا باید  $1.",
        "editundo": "خنثی‌ کورتین",
        "diff-empty": "(بدون تفاوت)",
        "diff-multi-sameuser": "(ای کار زوروکئ {{PLURAL:$1|یک میانی نخسه|$1 میانی نخسه}}ِ نمایش داته بوته)",
+       "diff-multi-otherusers": "({{PLURAL:$1|۱ میانین نخسه|$1 میانین نخسه}} دستکاری بوتگ شه {{PLURAL:$2|۱ کارزوروک|$2 کارزوروک}}ئ نیمگاه  نشان داته نه بوته)",
+       "diff-multi-manyusers": "({{PLURAL:$1|یک|$1}} میانین دستکاری بوته ئین نخسه گیشتیر شه {{PLURAL:$2|یک|$2}} کارزورکئ نیمگاه نشان داته نه بوته)",
        "searchresults": "گشتین ئی  نتیجه",
        "searchresults-title": "گشتینئ نتایج په «$1»",
        "titlematches": "مقاله ئی ئنوانئ یکی کورتین",
        "searchprofile-everything-tooltip": "گشتین په موچین محتوا ئان (شاملئ گپ ئی تاکدیمان)",
        "searchprofile-advanced-tooltip": "گشتین بی فضای نام دلخواه",
        "search-result-size": "$1 ({{PLURAL:$2|یک کلیمه|$2 کلیمه}})",
+       "search-result-category-size": "{{PLURAL:$1|یک عضو|$1 عضو}} ({{PLURAL:$2|یک گۆنڈ تهر|$2 گۆنڈ تهر}}، {{PLURAL:$3|یک فایل|$3 فایل}})",
        "search-redirect": "($1 یي گردینتین)",
        "search-section": "(بخش $1)",
        "search-category": "(تهر  $1)",
        "search-file-match": "(فایلی محتوایی یکرنگی)",
        "search-suggest": "آیا شمی منظور ایش ات: $1",
+       "search-rewritten": "$1 ئی نتیجه ئانی نمایش. گشتین په $2 جاها.",
        "search-interwiki-caption": "گوارین پروژه ئان",
        "search-interwiki-default": "نتایج شه $1 :",
        "search-interwiki-more": "(گیشتیر)",
        "recentchangescount": "پیش پرزین نشان داته بوته ئین ایڈیٹ ئانی اندازه گ:",
        "prefs-help-recentchangescount": "ای شامیل ئه بیت شه آخیرئین تغیران،که تاکدیمانی تاریخچه و خالیگاه انت.",
        "savedprefs": "شمی تنظیمات ذخیره بوتنت.",
+       "savedrights": "{{GENDER:$1|$1}}ئ کارزوروکین دسترسی ساتیته بوته انت.",
        "timezonelegend": "منطقهٔ زمانی:",
        "localtime": "محلی ئن وخت:",
        "timezoneuseserverdefault": "استفاده شه  پیش‌فرض ئین ویکی ($1)",
        "rcshowhidemine": "$1 نی ایڈیٹان",
        "rcshowhidemine-show": "نشان داتین",
        "rcshowhidemine-hide": "چیهر داتین",
+       "rcshowhidecategorization": "$1 تاکدیمئ تهربندی",
+       "rcshowhidecategorization-show": "نشان داتین",
+       "rcshowhidecategorization-hide": "چیهر داتین",
        "rclinks": "نشان داتین $1 آخیر ئین تغییر بئ $2 اخیرین روچا<br />$3",
        "diff": "تفاوت",
        "hist": "تاریخچه",
        "trackingcategories-msg": "ردیابئ تهر",
        "trackingcategories-name": "پیامی نام",
        "trackingcategories-desc": "تهرئ کنجایش ئی معیاران",
+       "hidden-category-category-desc": "تهر به وتي تاکدیمئ  محتوای تا شمل شه <code><nowiki>__HIDDENCAT__</nowiki></code> اینت، که به اتوماتیکین رقما شه تهرِ لینکاني بکسئ نشان‌داتینا به تاکدیماني تا دیمگیری ئه کنت.",
        "trackingcategories-nodesc": "هیچ توضیحی موجود نه اینت.",
        "trackingcategories-disabled": "تهر غیر پئال بوته",
        "mailnologin": "ادرسئ شه دیم دهۆک ئا موجود نه اینت",
        "deletepage": "تاکدیمی پاک کورتین",
        "confirm": "تأیید کورتین",
        "excontent": "تاکدیمی محتوا ایش ات: «$1»",
-       "excontentauthor": "تاکدیمی محتوا ایش ات: «$1» (و تانا مشارکت کنۆک «[[Special:Contributions/$2|$2]]» ات)",
+       "excontentauthor": "تاکدیمی محتوا ایش ات: «$1» (و تاناین مشارکت کنۆک «[[Special:Contributions/$2|$2]]» ([[User talk:$2|گپ]]) ات)",
        "exbeforeblank": "تاکدیمی محتوا دیم شه خالی کورتینا ایش ات: «$1»",
        "delete-confirm": "پاک کورتین «$1»",
        "delete-legend": "پاک کورتین",
        "tooltip-n-help": "جای په ودی کورتین ئا",
        "tooltip-t-whatlinkshere": "موچین تاکدیمانی لڑ لیست که گۆ ای دیما لینک وارته انت",
        "tooltip-t-recentchangeslinked": "تاکدیمانئ آخیرین تغیران که ای دیم گۆ آوان لینک داریت",
+       "tooltip-feed-rss": "آراس‌اس ئی خبرنامه په ای تاکدیما",
        "tooltip-feed-atom": "اتم ئی حبرنامه په ای دیما",
        "tooltip-t-contributions": "ای کار زوروکئ شراکتانی لڑ لیست",
        "tooltip-t-emailuser": "په ای کار زوروکا ایمیل ئی دیم داتین",
        "tooltip-save": "وتئ تغیرانا ذخیره بکنیت",
        "tooltip-preview": "شمی تغیرانی دیم دیست، مهربانی بکنیت دیما شه تاکدیمی ذخیره کورتینا شه ای کیلی ئا استفاده بکنیت.",
        "tooltip-diff": "شمی تغیرانی نمایش که شما بئ متن ئی تا داته ایت.",
+       "tooltip-compareselectedversions": "ای تاکدمئ دو انتخاب بوته ئین نخسه ئاني فرقاني دیستین",
+       "tooltip-watch": "ای دیما به وتي واچلیست یا پدگیری لیستئ تا اڈڈ بکنیت",
        "tooltip-watchlistedit-normal-submit": "ئنوانانی پاک کورتین",
        "tooltip-watchlistedit-raw-submit": "واچلیست ئی اپڈیٹ",
        "tooltip-recreate": "پداجۆڑ کورتین تاکدیمی نه دیستین ایشیرا که ای تاکدیم دیما پاک بوته",
        "patrol-log-page": "گشتئ سیاه چال",
        "patrol-log-header": "ای سیاهه شه گشت وارته ئین ایڈیٹ ئان است.",
        "log-show-hide-patrol": "$1 گشت جنوکین سیاهه",
+       "log-show-hide-tag": "$1 خالیجای ٹاپه",
        "deletedrevision": "$1 قدیمی پاک بوته ئین نخسه ئی است",
        "filedeleteerror-short": "خطا بئ فایلی پاک کورتین: $1",
        "filedeleteerror-long": "بی پدا  پاک کورتین ئی وختا خطا رخ دات:\n\n$1",
        "exif-gpslatitude-s": "جوگراپیایی جنوبی گ\tۆور",
        "exif-gpslongitude-e": "جوگراپیایی روچ درات ئی تچکی",
        "exif-gpslongitude-w": "جوگراپیایی روچ کپت ئی تچکی",
-       "exif-gpsaltitude-above-sealevel": "$1 {{PLURAL:$1|میتر|میتر}} بُرزتیر شه دریابی روگا",
-       "exif-gpsaltitude-below-sealevel": "$1 {{PLURAL:$1|میتر|میتر}} جهلتیر شه دریابی روگا",
+       "exif-gpsaltitude-above-sealevel": "$1 {{PLURAL:$1|میتر}} بُرزتیر شه دریابی روگا",
+       "exif-gpsaltitude-below-sealevel": "$1 {{PLURAL:$1|میتر}} جهلتیر شه دریابی روگا",
        "exif-gpsstatus-a": "بی اندازه گیری هالا",
        "exif-gpsstatus-v": "اندازه گ ئی موقایسه گیری",
        "exif-gpsmeasuremode-2": "دو بوئدین اندازه گیری",
        "version-libraries": "نصب بوته ئین کتابخانه",
        "version-libraries-library": "کتابجاه",
        "version-libraries-version": "نخسه",
+       "version-libraries-license": "اجازه نامه",
+       "version-libraries-description": "توضیحان",
+       "version-libraries-authors": "نویسوکان",
        "redirect-legend": "گردینتین په یک پایل یا تاکدیمئ",
        "redirect-submit": "برا",
        "redirect-lookup": "گشتین:",
        "tags-deactivate-reason": "دلیل:",
        "tags-deactivate-not-allowed": "«$1» ئی تگی غیر په هال کورتین ممکن نه اینت.",
        "tags-deactivate-submit": "غیرپئال کورتین",
+       "tags-edit-title": "ٹاپه ئی ایڈ\tیٹ کورتین",
+       "tags-edit-manage-link": "ٹاپه ئاني مدیریت کورتین",
+       "tags-edit-revision-selected": "{{PLURAL:$1|انتخاب بووته ئین نخسه|انتخابین نخسه ئان}} شه [[:$2]]:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|انتخاب بوته ئین خالیجا}}:",
        "tags-edit-existing-tags": "موجودین ٹاپه ئان:",
        "tags-edit-existing-tags-none": "\"هیچگوجام\"",
        "tags-edit-new-tags": "نوکین ٹاپه:",
        "revdelete-uname-unhid": "کار زوروکئ ناما سر درا کورت",
        "revdelete-restricted": "مدیر ئانه محدود کورت",
        "revdelete-unrestricted": "مدیرئانی محدودیت ئا پروشت",
+       "logentry-block-reblock": "$1 بلاک ئی {{GENDER:$2|تنظیماتانه}} په {{GENDER:$4|$3}} ئانه به انبلاک گو بلاکئ وختي الاسیا $5 $6 تغییر دات.",
+       "logentry-suppress-block": "$1 {{GENDER:$2|بسته بوت}} {{GENDER:$4|$3}} گو انبلاک کورتینئ وختا به $5 $6",
+       "logentry-suppress-reblock": "$1 بلاک ئی {{GENDER:$2|تنظیماتانه}} په {{GENDER:$4|$3}} ئانه به انبلاک گو بلاکئ وختي الاسیا $5 $6 تغییر دات.",
+       "logentry-import-interwiki": "$1 $3 ئانه شه دیگه ویکیێ {{GENDER:$2|لَڈڈینت}}",
        "logentry-merge-merge": "$1  $3  را بئ  $4 {{GENDER:$2| ادغام کورت}} (نخسه تا  $5)",
        "logentry-move-move": "$1، $3 ئی تاکدیما بئ $4 {{GENDER:$2|جابجا کورت}}",
+       "logentry-move-move-noredirect": "$1  $3 ئی تاکدیما بغیر شه ایشی که گوجام تغیرمسیرئ بجا بیللیت  به $4 {{GENDER:$2|جابجا کورت}}",
        "logentry-newusers-newusers": "$1 ئی کار زوروکئ حساب {{GENDER:$2|جۆڑ بوت}}",
        "logentry-newusers-create": "$1 ئی کار زوروکئ حساب {{GENDER:$2|جۆڑ بوت}}",
        "logentry-newusers-create2": "$3 ئی کار زوروکئ حساب شه $1 ئی نیمگا {{GENDER:$2|جۆڑ بوت}}",
        "logentry-newusers-byemail": "$3 ئی کار زوروکئ حساب شه $1 ئی نیمگا {{GENDER:$2|جۆڑ بوت}} و چیهرگال یا پاسورد گو ایمیلا دیم داته بوت",
        "logentry-newusers-autocreate": "$1 ئی حساب بئ اتوماتیکین رکما {{GENDER:$2|جۆڑ بوت}}",
+       "logentry-protect-move_prot": "$1 قُلپئ تنظیمانا شه $4 به $3 {{GENDER:$2|جابجا کورت}}",
+       "logentry-protect-unprotect": "$1 $3 ئا شه قُپا {{GENDER:$2|دَر کورت}}",
+       "logentry-protect-protect": "$1 $3 ئا {{GENDER:$2|قُلپ کورت}} $4",
+       "logentry-protect-protect-cascade": "$1 $3 $4 {{GENDER:$2|قُلپ کورت}} [آبشاری]",
+       "logentry-protect-modify": "$1 $3 ئ قُلپئ اندازه گا به {{GENDER:$2|تغییر دات}} $4",
+       "logentry-protect-modify-cascade": "$1 $3 $4 ئ قُلپئ اندازه گا په {{GENDER:$2|تغییر دات}}[آپشاری]",
        "logentry-rights-rights": "$1 ، $3 ئی عضویتا شه $4 ئی گروپا بئ $5 {{GENDER:$2|تغییر دات}}",
        "logentry-rights-rights-legacy": "$1 عضویتئ گروپا بئ $3 ئا {{GENDER:$2|تغییر دات}}",
        "logentry-rights-autopromote": "$1 بئ اوتوماتیکین رکما وتر شه $4 بئ $5 {{GENDER:$2|ارتقاء دات}}",
        "logentry-upload-revert": "$1 {{GENDER:$2|بُرز کورت}} $3 ئا",
        "log-name-managetags": "تگ ئی تاریخچه ئی مدیریت",
        "logentry-managetags-create": "$1 تگ «$4» ئا {{GENDER:$2|ج\tۆڑ کورت}}",
+       "log-name-tag": "خالیجای ٹاپه",
        "rightsnone": "(هیچ)",
        "revdelete-summary": "ایڈیتی خاصه",
        "feedback-back": "بیئرگشت",
index 75746dc..9d3e8b1 100644 (file)
@@ -33,6 +33,7 @@
        "tog-hideminor": "অনুল্লেখ্য সম্পাদনাগুলো সাম্প্রতিক পরিবর্তনসমূহে আড়াল করো",
        "tog-hidepatrolled": "পরীক্ষিত সম্পাদনা গুলো সাম্প্রতিক পরিবর্তনসমূহে আড়াল করো",
        "tog-newpageshidepatrolled": "পরীক্ষিত পাতা গুলো নতুন পাতার তালিকায় আড়াল করো",
+       "tog-hidecategorization": "পাতার শ্রেণীবদ্ধকরণ লুকান",
        "tog-extendwatchlist": "শুধু সাম্প্রতিক পরিবর্তনই নয়, সকল পরিবর্তন দেখতে নজর তালিকা সম্প্রসারণ করুন",
        "tog-usenewrc": "সাম্প্রতিক পরিবর্তনসমূহ এবং নজরতালিকা পাতায় পরিবর্তনগুলো একত্রে প্রদর্শন",
        "tog-numberheadings": "শিরোনামগুলোকে স্বয়ংক্রিয়ভাবে ক্রমিক নম্বর দাও",
@@ -62,6 +63,7 @@
        "tog-watchlisthideliu": "নজরতালিকাতে অ্যাকাউন্টে লগ-ইন করা ব্যবহারকারীদের সম্পাদনা আড়ালে রাখা হোক",
        "tog-watchlisthideanons": "নজরতালিকাতে বেনামী ব্যবহারকারীদের সম্পাদনা আড়ালে রাখা হোক",
        "tog-watchlisthidepatrolled": "পরীক্ষিত সম্পাদনা গুলো নজরতালিকায় আড়াল করো",
+       "tog-watchlisthidecategorization": "পাতার শ্রেণীবদ্ধকরণ লুকান",
        "tog-ccmeonemails": "অন্য ব্যবহারকারীর কাছে আমার পাঠানো ইমেইলের একটি অনুলিপি আমাকে পাঠানো হোক",
        "tog-diffonly": "পার্থক্যের নিচে পাতার বিষয়বস্তু না দেখানো হোক",
        "tog-showhiddencats": "লুকায়িত বিষয়শ্রেণীসমূহ দেখাও",
        "createaccountreason": "কারণ:",
        "createacct-reason": "কারণ",
        "createacct-reason-ph": "কেন আপনি আরেকটি অ্যাকাউন্ট তৈরি করছেন",
-       "createacct-captcha": "নিরাপত্তা পরীক্ষা",
-       "createacct-imgcaptcha-ph": "উপরে যে লেখা দেখতে পাচ্ছেন তা লিখুন",
        "createacct-submit": "আপনার অ্যাকাউন্ট তৈরি করুন",
        "createacct-another-submit": "অ্যাকাউন্ট তৈরি করুন",
        "createacct-benefit-heading": "{{SITENAME}} আপনার মত লোকের দ্বারাই তৈরি।",
        "passwordreset-emailtext-ip": "কেউ একজন (সম্ভবত আপনি, $1 আইপি ঠিকানা থেকে) {{SITENAME}} ($4) সাইটের জন্য আপনার\nপাসওয়ার্ড বদলের জন্য অনুরোধ করেছে। নিচের ব্যবহারকারী {{PLURAL:$3|অ্যাকাউন্টটি|অ্যাকাউন্টগুলো}}\nএই ই-মেইল ঠিকানার সাথে সংযুক্ত:\n\n$2\n\n{{PLURAL:$3|এই অস্থায়ী পাসওয়ার্ডটি|এই অস্থায়ী পাসওয়ার্ডগুলো}} আগামী {{PLURAL:$5|এক দিন|$5 দিন}} পর মেয়াদোত্তীর্ণ হয়ে যাবে।\nআপনার অবশ্যই লগ-ইন করে একটি নতুন পাসওয়ার্ড পছন্দ করা উচিত। যদি অন্য কেউ এই অনুরোধ করে থাকে,\nঅথবা আপনি যদি পুরোনো পাসওয়ার্ড মনে করতে পারেন, এবং আপনার সেটি পরিবর্তন করার কোনো ইচ্ছা না থাকে, তবে\nআপনি এই বার্তাটি উপেক্ষা করতে পারে, এবং আপনার পুরোনো পাসওয়ার্ড ব্যবহার করা চালিয়ে যেতে পারেন।",
        "passwordreset-emailtext-user": "ব্যবহারকারী $1 {{SITENAME}} ($4) সাইটের জন্য আপনার পাসওয়ার্ড বদলের জন্য অনুরোধ করেছে। নিচের ব্যবহারকারী {{PLURAL:$3|অ্যাকাউন্টটি|অ্যাকাউন্টগুলো}}\nএই ই-মেইল ঠিকানার সাথে সংযুক্ত:\n\n$2\n\n{{PLURAL:$3|এই অস্থায়ী পাসওয়ার্ডটি|এই অস্থায়ী পাসওয়ার্ডগুলো}} আগামী {{PLURAL:$5|এক দিন|$5 দিন}} পর মেয়াদোত্তীর্ণ হয়ে যাবে।\nআপনার অবশ্যই লগ-ইন করে একটি নতুন পাসওয়ার্ড পছন্দ করা উচিত। যদি অন্য কেউ এই অনুরোধ করে থাকে,\nঅথবা আপনি যদি পুরোনো পাসওয়ার্ড মনে করতে পারেন, এবং আপনার সেটি পরিবর্তন করার কোনো ইচ্ছা না থাকে, তবে\nআপনি এই বার্তাটি উপেক্ষা করতে পারে, এবং আপনার পুরোনো পাসওয়ার্ড ব্যবহার করা চালিয়ে যেতে পারেন।",
        "passwordreset-emailelement": "ব্যবহারকারী নাম: \n$1\n\nঅস্থায়ী পাসওয়ার্ড: \n$2",
-       "passwordreset-emailsent": "পাসà¦\93য়ারà§\8dড à¦¬à¦¦à¦²à§\87র à¦\8fà¦\95à¦\9fি à¦\87-মà§\87à¦\87ল à¦ªà¦¾à¦ à¦¾à¦¨à§\8b à¦¹à¦¯à¦¼à§\87à¦\9bে।",
+       "passwordreset-emailsent": "যদি à¦\86পনার à¦\85à§\8dযাà¦\95াà¦\89নà§\8dà¦\9fà§\87র à¦\9cনà§\8dয à¦\8fà¦\9fি à¦\8fà¦\95à¦\9fি à¦¨à¦¿à¦¬à¦¨à§\8dধিত à¦\87মà§\87ল à¦ à¦¿à¦\95ানা à¦¹à¦¯à¦¼, à¦¤à¦¾à¦¹à¦²à§\87 à¦\8fà¦\95à¦\9fি à¦ªà¦¾à¦¸à¦\93য়ারà§\8dড à¦¬à¦¦à¦²à§\87র à¦\87মà§\87à¦\87ল à¦ªà¦¾à¦ à¦¾à¦¨à§\8b à¦¹à¦¬ে।",
        "passwordreset-emailsent-capture": "স্মরণ করিয়ে দেয়ার জন্য একটি ইমেইল করা হয়েছে, যা নিচে দেখানো হচ্ছে।",
        "passwordreset-emailerror-capture": "স্মরণ করিয়ে দেয়ার জন্য একটি ইমেইল তৈরী করা হয়েছিল, যা নিচে দেখানো হচ্ছে, তবে $1 {{GENDER:$2|ব্যবহারকারীকে}} এটি পাঠানো যায়নি!",
-       "changeemail": "ই-মেইল ঠিকানা পরিবর্তন",
+       "changeemail": "ই-মেইল ঠিকানা পরিবর্তন বা বাতিল",
        "changeemail-header": "অ্যাকাউন্ট ই-মেইল ঠিকানা পরিবর্তন",
        "changeemail-no-info": "এই পাতাটিতে সরাসরি প্রবেশাধিকার পেতে আপনাকে অবশ্যই লগইন করতে হবে।",
        "changeemail-oldemail": "বর্তমান ই-মেইল ঠিকানা:",
        "prefs-watchlist-token": "নজরতালিকা টোকেন:",
        "prefs-misc": "বিবিধ",
        "prefs-resetpass": "পাসওয়ার্ড পরিবর্তন",
-       "prefs-changeemail": "ই-মেইল পরিবর্তন",
+       "prefs-changeemail": "ইমেইল ঠিকানা পরিবর্তন বা বাতিল",
        "prefs-setemail": "একটি ই-মেইল ঠিকানা নির্ধারণ করুন",
        "prefs-email": "ই-মেইল অপশন",
        "prefs-rendering": "অবয়ব",
        "rows": "সারি:",
        "columns": "কলাম:",
        "searchresultshead": "অনুসন্ধান",
-       "stub-threshold": "<a href=\"#\" class=\"stub\">অসম্পূর্ণ নিবন্ধের সংযোগগুলির</a> বিশেষ ফরম্যাটিঙের সীমা (বাইটে):",
+       "stub-threshold": "অসম্পূর্ণ লিঙ্ক বিন্যাসের জন্য প্রান্তিক মাপ ($1):",
        "stub-threshold-sample-link": "নমুনা",
        "stub-threshold-disabled": "নিস্ক্রিয়",
        "recentchangesdays": "সাম্প্রতিক পরিবর্তনে দিনসমূহ দেখানোর জন্য:",
        "rcshowhidemine": "আমার সম্পাদনাগুলো $1",
        "rcshowhidemine-show": "দেখাও",
        "rcshowhidemine-hide": "আড়াল করো",
+       "rcshowhidecategorization": "পাতা শ্রেণীবদ্ধকরণ $1",
+       "rcshowhidecategorization-show": "দেখাও",
+       "rcshowhidecategorization-hide": "আড়াল করো",
        "rclinks": "'''প্রদর্শনের ধরন'''<br />\n* বিগত ($2) দিনের শেষ ($1)টি পরিবর্তন দেখাও\n* $3",
        "diff": "পরিবর্তন",
        "hist": "ইতিহাস",
        "upload-form-label-infoform-description": "বিবরণ",
        "upload-form-label-usage-title": "ব্যবহার",
        "upload-form-label-usage-filename": "ফাইলের নাম",
+       "foreign-structured-upload-form-label-own-work": "এটি আমার নিজের কাজ",
+       "foreign-structured-upload-form-label-infoform-categories": "বিষয়শ্রেণীসমূহ",
+       "foreign-structured-upload-form-label-infoform-date": "তারিখ",
        "backend-fail-stream": "\"$1\" ফাইলের স্ট্রিম দেখানো যাচ্ছে না।",
        "backend-fail-backup": "\"$1\" ফাইলের ব্যাকআপ তৈরী সম্ভব নয়।",
        "backend-fail-notexists": "\"$1\" নামের কোনো ফাইল নেই।",
        "deletepage": "পাতাটি মুছে ফেলা হোক",
        "confirm": "নিশ্চিত করুন",
        "excontent": "বিষয়বস্তু ছিল: '$1'",
-       "excontentauthor": "বিষয়বস্তু ছিল: \"$1\" (এবং একমাত্র অবদানকারী ছিলেন \"[[Special:Contributions/$2|$2]]\")",
+       "excontentauthor": "বিষয়বস্তু ছিল: \"$1\", এবং একমাত্র অবদানকারী ছিলেন \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|আলাপ]])",
        "exbeforeblank": "মুছে ফেলার আগে বিষয়বস্তু ছিল: '$1'",
        "delete-confirm": "\"$1\" অপসারণ",
        "delete-legend": "অপসারণ",
        "cant-move-to-user-page": "আপনার কোনো পাতা ব্যবহারকারী পাতার স্থানান্তরের অনুমতি নেই (ব্যবহারকারী উপপাতা ব্যতিত)।",
        "cant-move-category-page": "আপনার বিষয়শ্রেণী পাতা স্থানান্তরের অনুমতি নেই।",
        "cant-move-to-category-page": "আপনার পাতাটিকে বিষয়শ্রেণী পাতায় স্থানান্তরের অনুমতি নেই।",
-       "newtitle": "à¦\8fà¦\87 à¦¨à¦¤à§\81ন à¦¶à¦¿à¦°à§\8bনামà§\87",
+       "newtitle": "নতà§\81ন à¦¶à¦¿à¦°à§\8bনাম:",
        "move-watch": "এই পাতাটি নজরে রাখুন",
        "movepagebtn": "পাতা স্থানান্তর করুন",
        "pagemovedsub": "সরিয়ে নেওয়া হয়েছে",
index b9afc7c..81fa8c4 100644 (file)
        "createaccountreason": "Razlog:",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zašto pravite još jedan korisnički račun?",
-       "createacct-captcha": "Sigurnosna provjera",
-       "createacct-imgcaptcha-ph": "Unesite tekst koji vidite iznad",
        "createacct-submit": "Napravite svoj korisnički račun",
        "createacct-another-submit": "Napravi još jedan korisnički račun",
        "createacct-benefit-heading": "{{SITENAME}} je napravljena od strane ljudi kao što ste Vi.",
index 1604018..ecd344d 100644 (file)
        "createaccountreason": "Motiu:",
        "createacct-reason": "Motiu",
        "createacct-reason-ph": "Per què creeu un altre compte",
-       "createacct-captcha": "Control de seguretat",
-       "createacct-imgcaptcha-ph": "Introduïu el text que apareix a dalt",
        "createacct-submit": "Crea el meu compte",
        "createacct-another-submit": "Crea un compte",
        "createacct-benefit-heading": "{{SITENAME}} és feta per gent com tu.",
index 987e08e..845338e 100644 (file)
        "createaccountreason": "Důvod:",
        "createacct-reason": "Důvod",
        "createacct-reason-ph": "Proč si vytváříte další účet",
-       "createacct-captcha": "Bezpečnostní kontrola",
-       "createacct-imgcaptcha-ph": "Opište výše zobrazený text",
        "createacct-submit": "Vytvořit účet",
        "createacct-another-submit": "Vytvořit účet",
        "createacct-benefit-heading": "{{grammar:4sg|{{SITENAME}}}} tvoří lidé jako vy.",
index 2e1beb6..952e338 100644 (file)
        "createaccountreason": "Begrundelse:",
        "createacct-reason": "Årsag",
        "createacct-reason-ph": "Hvorfor du vil oprette endnu en konto",
-       "createacct-captcha": "Sikkerhedskontrol",
-       "createacct-imgcaptcha-ph": "Indtast venligst ovenstående tekst",
        "createacct-submit": "Opret din konto",
        "createacct-another-submit": "Opret konto",
        "createacct-benefit-heading": "{{SITENAME}} laves af mennesker som dig.",
index 24f1eed..4414b1b 100644 (file)
        "createaccountreason": "Grund:",
        "createacct-reason": "Begründung",
        "createacct-reason-ph": "Warum erstellst du ein anderes Benutzerkonto?",
-       "createacct-captcha": "Sicherheitsprüfung",
-       "createacct-imgcaptcha-ph": "Gib den Text ein, den du oben siehst.",
        "createacct-submit": "Benutzerkonto erstellen",
        "createacct-another-submit": "Benutzerkonto erstellen",
        "createacct-benefit-heading": "{{SITENAME}} wird von Menschen wie dir geschaffen.",
index 781db89..bc2d65a 100644 (file)
        "createaccountreason": "Sebeb:",
        "createacct-reason": "Sebeb",
        "createacct-reason-ph": "Şımaye çı xo re zewbi hesab vırazeni?",
-       "createacct-captcha": "Qontrolê asayişi",
-       "createacct-imgcaptcha-ph": "Nuşteyo ke cor aseno ey cı ke",
        "createacct-submit": "Hesabê xo vıraze",
        "createacct-another-submit": "Zewbi hesab vıraz",
        "createacct-benefit-heading": "{{SITENAME}} meş de merduman şi",
index d8efcde..c004926 100644 (file)
@@ -50,6 +50,7 @@
        "tog-hideminor": "Απόκρυψη μικροεπεξεργασιών στις πρόσφατες αλλαγές",
        "tog-hidepatrolled": "Απόκρυψη ελεγμένων επεξεργασιών στις πρόσφατες αλλαγές",
        "tog-newpageshidepatrolled": "Απόκρυψη ελεγμένων σελίδων από τον κατάλογο νέων σελίδων",
+       "tog-hidecategorization": "Απόκρυψη κατηγοριοποίησης σελίδων",
        "tog-extendwatchlist": "Επέκταση της λίστας παρακολούθησης ώστε να δείχνει όλες τις αλλαγές, όχι μόνο τις πιο πρόσφατες",
        "tog-usenewrc": "Ομαδοποίηση αλλαγών ανά σελίδα στις πρόσφατες αλλαγές και στη λίστα παρακολούθησης",
        "tog-numberheadings": "Αυτόματη αρίθμηση επικεφαλίδων",
@@ -79,6 +80,7 @@
        "tog-watchlisthideliu": "Απόκρυψη επεξεργασιών συνδεδεμένων χρηστών από τη λίστα παρακολούθησης",
        "tog-watchlisthideanons": "Απόκρυψη επεξεργασιών ανωνύμων χρηστών από τη λίστα παρακολούθησης",
        "tog-watchlisthidepatrolled": "Απόκρυψη ελεγμένων επεξεργασιών από τη λίστα παρακολούθησης",
+       "tog-watchlisthidecategorization": "Απόκρυψη κατηγοριοποίησης σελίδων",
        "tog-ccmeonemails": "Να μου αποστέλλονται αντίγραφα των μηνυμάτων ηλεκτρονικού ταχυδρομείου που στέλνω σε άλλους χρήστες",
        "tog-diffonly": "Να μην εμφανίζεται περιεχόμενο σελίδων κάτω από τις διαφορές των εκδόσεων",
        "tog-showhiddencats": "Εμφάνιση κρυμμένων κατηγοριών",
        "createaccountreason": "Αιτία:",
        "createacct-reason": "Λόγος",
        "createacct-reason-ph": "Γιατί δημιουργείτε έναν άλλο λογαριασμό",
-       "createacct-captcha": "Έλεγχος ασφαλείας",
-       "createacct-imgcaptcha-ph": "Εισαγωγή του κειμένου που βλέπετε παραπάνω",
        "createacct-submit": "Δημιουργία λογαριασμού χρήστη",
        "createacct-another-submit": "Δημιουργήσετε έναν άλλο λογαριασμό",
        "createacct-benefit-heading": "{{SITENAME}} έχει γίνει από ανθρώπους όπως εσύ.",
        "passwordreset-emailerror-capture": "Ένα email επαναφοράς κωδικού έχει δημιουργηθεί, το οποίο φαίνεται πιο κάτω, αλλά απέτυχε η αποστολή του στο  {{GENDER:$2|χρήστη}}: $1",
        "changeemail": "Αλλαγή ή αφαίρεση της διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "changeemail-header": "Αλλαγή λογαριασμού ηλεκτρονικού ταχυδρομείου",
+       "changeemail-passwordrequired": "Θα χρειαστεί να εισάγετε τον κωδικό σας για να επιβεβαιώσετε την αλλαγή αυτή.",
        "changeemail-no-info": "Πρέπει να έχετε συνδεθεί για άμεση πρόσβαση σε αυτήν τη σελίδα.",
        "changeemail-oldemail": "Τρέχουσα διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "changeemail-newemail": "Νέα διεύθυνση ηλεκτρονικού ταχυδρομείου:",
        "changeemail-password": "Ο κωδικός πρόσβασής σας στο εγχείρημα {{SITENAME}}:",
        "changeemail-submit": "Αλλαγή διεύθυνσης ηλεκτρονικού ταχυδρομείου",
        "changeemail-throttled": "Κάνατε πάρα πολλές απόπειρες σύνδεσης.\nΠαρακαλούμε περιμένετε $1 προτού ξαναδοκιμάσετε.",
+       "changeemail-nochange": "Παρακαλώ εισάγετε μια διαφορετική νέα διεύθυνση ηλεκτρονικού ταχυδρομείου.",
        "resettokens": "Επαναφορά των κλειδιών",
        "resettokens-text": "Μπορείτε να επαναφέρετε τα κλειδιά, τα οποία επιτρέπουν την πρόσβαση σε ορισμένα ιδιωτικά δεδομένα που συνδέονται με τον λογαριασμό σας εδώ.\n\nΠρέπει να το κάνετε εάν κατά λάθος τα μοιραστήκατε με κάποιον ή αν ο λογαριασμός σας έχει παραβιαστεί.",
        "resettokens-no-tokens": "Δεν υπάρχουν κλειδιά για επαναφορά.",
        "sig_tip": "Η υπογραφή σας με ώρα και ημερομηνία",
        "hr_tip": "Οριζόντια γραμμή (να χρησιμοποιείται με φειδώ)",
        "summary": "Σύνοψη:",
-       "subject": "Θέμα/επικεφαλίδα:",
+       "subject": "Θέμα:",
        "minoredit": "Αυτή είναι μια μικροαλλαγή",
        "watchthis": "Παρακολούθηση αυτής της σελίδας",
        "savearticle": "Αποθήκευση σελίδας",
        "missingsummary": "'''Υπενθύμιση:''' Δεν έχετε συμπληρώσει τη σύνοψη επεξεργασίας. Αν κάνετε κλικ στο κουμπί Αποθήκευση πάλι, η επεξεργασία σας θα αποθηκευτεί χωρίς σύνοψη.",
        "selfredirect": "<strong>Προσοχή:</strong> Ανακατευθύνετε αυτή τη σελίδα στον εαυτό της. Μπορεί να δώσατε λάθος στόχο για την ανακατεύθυνση, ή μπορεί να επεξεργάζεστε λάθος σελίδα.\nΑν κάνε κλίκ στο \"{{int:savearticle}}\" ξανά, η ανακατεύθυνση θα δημιουργηθεί ούτως ή άλλως.",
        "missingcommenttext": "Παρακαλώ εισάγετε ένα σχόλιο παρακάτω.",
-       "missingcommentheader": "'''Υπενθύμιση:''' Δεν έχετε γράψει ένα θέμα/επικεφαλίδα για αυτό το σχόλιο.\nΑν κάνετε πάλι κλικ στο κουμπί \"{{int:savearticle}}\", η επεξεργασία σας θα αποθηκευτεί χωρίς θέμα ή επικεφαλίδα.",
+       "missingcommentheader": "<strong>Υπενθύμιση:</strong> δεν έχετε δώσει ένα θέμα γι' αυτό το σχόλιο.\nΕάν κάνετε κλικ στο κουμπί \"{{int:savearticle}}\" ξανά, η επεξεργασία σας θα αποθηκευτεί χωρίς αυτό.",
        "summary-preview": "Προεπισκόπηση σύνοψης:",
-       "subject-preview": "Προεπισκόπηση θέματος/επικεφαλίδας:",
+       "subject-preview": "Προεπισκόπηση θέματος:",
        "previewerrortext": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια για να κάνετε προεπισκόπηση των αλλαγών σας.",
        "blockedtitle": "Ο χρήστης έχει υποστεί φραγή.",
        "blockedtext": "'''Το όνομα χρήστη σας ή η διεύθυνση IP σας έχει υποστεί φραγή.'''\n\nΗ φραγή έγινε από τον/την $1.\nΗ αιτιολογία που δόθηκε είναι: ''$2''.\n\n* Έναρξη φραγής: $8\n* Λήξη φραγής: $6\n* Η φραγή προορίζεται για το χρήστη: $7\n\nΜπορείτε να απευθυνθείτε στον/στην $1 ή σε κάποιον άλλον [[{{MediaWiki:Grouppage-sysop}}|διαχειριστή]] για να συζητήσετε τη φραγή.\nΔεν μπορείτε να χρησιμοποιήσετε την δυνατότητα «αποστολή e-mail σε αυτό το χρήστη» εκτός αν μια έγκυρη ηλεκτρονική διεύθυνση έχει οριστεί στις [[Special:Preferences|προτιμήσεις χρήστη]] σας.\nΗ τρέχουσα διεύθυνση IP σας είναι $3, και ο αριθμός αναγνώρισης της φραγής είναι #$5.\nΠαρακαλούμε περιλαμβάνετε οποιοδήποτε ή και τα δύο από αυτά σε οποιαδήποτε ερωτήματα σας.",
        "rcshowhidemine": "$1 των επεξεργασιών μου",
        "rcshowhidemine-show": "Εμφάνιση",
        "rcshowhidemine-hide": "Απόκρυψη",
+       "rcshowhidecategorization": "$1 κατηγοριοποίησης σελίδας",
+       "rcshowhidecategorization-show": "Εμφάνιση",
+       "rcshowhidecategorization-hide": "Απόκρυψη",
        "rclinks": "Εμφάνιση των τελευταίων $1 αλλαγών στο διάστημα των τελευταίων $2 ημερών<br />$3",
        "diff": "διαφορά",
        "hist": "ιστορικό",
        "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": "Αυτόματη αλλαγή MediaWiki",
        "upload": "Ανέβασμα αρχείου",
        "uploadbtn": "Ανέβασμα αρχείου",
        "reuploaddesc": "Επιστροφή στη φόρμα φόρτωσης",
index 3e545d2..853c9ed 100644 (file)
        "createaccountreason": "Reason:",
        "createacct-reason": "Reason",
        "createacct-reason-ph": "Why you are creating another account",
-       "createacct-captcha": "Security check",
        "createacct-imgcaptcha-help": "",
-       "createacct-imgcaptcha-ph": "Enter the text you see above",
        "createacct-submit": "Create your account",
        "createacct-another-submit": "Create account",
        "createacct-benefit-heading": "{{SITENAME}} is made by people like you.",
index d5d538f..3943862 100644 (file)
        "createaccountreason": "Kialo:",
        "createacct-reason": "Kialo",
        "createacct-reason-ph": "Kial vi kreas plian konton",
-       "createacct-captcha": "Sekureca kontrolo",
-       "createacct-imgcaptcha-ph": "Entajpu la supran tekston",
        "createacct-submit": "Krei konton",
        "createacct-another-submit": "Krei alian konton",
        "createacct-benefit-heading": "{{SITENAME}} estas kreata de homoj kiel vi.",
index c8325b9..3b1f548 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por qué estás creando otra cuenta",
-       "createacct-captcha": "Comprobación de seguridad",
-       "createacct-imgcaptcha-ph": "Escribe el texto de arriba",
        "createacct-submit": "Crea tu cuenta",
        "createacct-another-submit": "Crear cuenta",
        "createacct-benefit-heading": "Personas como tú son las que construyen {{SITENAME}}.",
index 1f2a107..5c86d5d 100644 (file)
@@ -33,6 +33,7 @@
        "tog-hideminor": "Peida pisiparandused viimastes muudatustes",
        "tog-hidepatrolled": "Peida kontrollitud redaktsioonid viimastes muudatustes",
        "tog-newpageshidepatrolled": "Peida uute lehtede loendis kontrollitud leheküljed",
+       "tog-hidecategorization": "Peida lehekülgede kategoriseerimine",
        "tog-extendwatchlist": "Laienda jälgimisloendit, et näha kõiki muudatusi, mitte vaid kõige värskemaid",
        "tog-usenewrc": "Rühmita viimased muudatused ja muudatused jälgimisloendis lehekülje järgi",
        "tog-numberheadings": "Pealkirjade automaatnummerdus",
@@ -62,6 +63,7 @@
        "tog-watchlisthideliu": "Peida sisselogitud kasutajate muudatused jälgimisloendist",
        "tog-watchlisthideanons": "Peida anonüümsete kasutajate muudatused jälgimisloendist",
        "tog-watchlisthidepatrolled": "Peida kontrollitud muudatused jälgimisloendist",
+       "tog-watchlisthidecategorization": "Peida lehekülgede kategoriseerimine",
        "tog-ccmeonemails": "Saada mulle koopiad minu läkitatud e-kirjadest",
        "tog-diffonly": "Ära näita erinevuste vaate all lehe sisu",
        "tog-showhiddencats": "Näita peidetud kategooriaid",
        "createaccountreason": "Põhjus:",
        "createacct-reason": "Põhjus",
        "createacct-reason-ph": "Miks lood teist kontot?",
-       "createacct-captcha": "Turvakontroll",
-       "createacct-imgcaptcha-ph": "Sisesta ülalnähtav tekst",
        "createacct-submit": "Loo konto",
        "createacct-another-submit": "Loo konto",
        "createacct-benefit-heading": "{{SITENAME}} on sinusuguste inimeste tehtud.",
        "permissionserrors": "Loatõrge",
        "permissionserrorstext": "Sul pole õigust seda teha {{PLURAL:$1|järgmisel põhjusel|järgmistel põhjustel}}:",
        "permissionserrorstext-withaction": "Sul pole lubatud {{lcfirst:$2}} {{PLURAL:$1|järgmisel põhjusel|järgmistel põhjustel}}:",
+       "contentmodelediterror": "Sa ei saa seda redaktsiooni redigeerida, sest selle sisumudel on <code>$1</code> ning lehekülje praegune sisumudel on <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Hoiatus: Lood uuesti lehekülge, mis on varem kustutatud.'''\n\nKaalu, kas lehekülje uuesti loomine on kohane.\nLehekülje eelnevad kustutamised ja teisaldamised:",
        "moveddeleted-notice": "See lehekülg on kustutatud.\nAllpool on esitatud lehekülje kustutamis- ja teisaldamislogi.",
        "moveddeleted-notice-recent": "Kahjuks on see lehekülg hiljuti kustutatud (viimase 24 tunni jooksul).\nAllpool on ära toodud selle lehekülje sissekanded teisaldamis- ja kustutamislogis.",
        "prefs-help-recentchangescount": "See käib viimaste muudatuste, lehekülgede ajalugude ja logide kohta.",
        "prefs-help-watchlist-token2": "See on sinu jälgimisloendi veebivoo salavõti.\nIgaüks, kes seda teab, saab lugeda sinu jälgimisloendit. Seega ära jaga seda.\n[[Special:ResetTokens|Klõpsa siia, kui sul on vaja see lähtestada]].",
        "savedprefs": "Sinu eelistused on salvestatud.",
+       "savedrights": "Kasutaja $1 õigused on salvestatud.",
        "timezonelegend": "Ajavöönd:",
        "localtime": "Kohalik aeg:",
        "timezoneuseserverdefault": "Kasuta serveri vaikesätet ($1)",
        "rcshowhidemine": "Minu parandused ($1)",
        "rcshowhidemine-show": "näita",
        "rcshowhidemine-hide": "peida",
+       "rcshowhidecategorization": "Kategoriseerimine ($1)",
+       "rcshowhidecategorization-show": "näita",
+       "rcshowhidecategorization-hide": "peida",
        "rclinks": "Näita viimast $1 muudatust viimase $2 päeva jooksul<br />$3",
        "diff": "erin",
        "hist": "ajal",
        "foreign-structured-upload-form-label-not-own-work-local-local": "Võimalik, et soovid kasutada [[Special:Upload|harilikku üleslaadimislehekülge]].",
        "foreign-structured-upload-form-label-own-work-message-default": "Saan aru, et laadin selle faili jagatud varamusse. Kinnitan, et teen seda kooskõlas sealsete kasutustingimuste ja litsentsipõhimõtetega.",
        "foreign-structured-upload-form-label-not-own-work-message-default": "Kui sul pole võimalik laadida seda faili üles kooskõlas jagatud varamu reeglitega, siis palun sule see dialoog ja proovi teisiti toimida.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Võimalik, et soovid [[Special:Upload|laadida selle faili üles saidil {{SITENAME}}]], kui seda on võimalik teha kooskõlas sealsete reeglitega.",
+       "foreign-structured-upload-form-label-not-own-work-local-default": "Võimalik, et soovid [[Special:Upload|laadida selle faili üles saidil {{SITENAME}}]], kui seda on võimalik teha kooskõlas siinsete reeglitega.",
        "foreign-structured-upload-form-label-own-work-message-shared": "Kinnitan, et olen selle faili autoriõiguse valdaja ja nõustun faili avaldamisega Wikimedia Commonsis pöördumatult Creative Commonsi litsentsi \"[https://creativecommons.org/licenses/by-sa/4.0/deed.et Autorile viitamine + jagamine samadel tingimustel 4.0]\" all. Samuti nõustun [https://wikimediafoundation.org/wiki/Terms_of_Use kasutustingimustega].",
        "foreign-structured-upload-form-label-not-own-work-message-shared": "Kui sa pole selle faili autoriõiguse valdaja või kui soovid avaldada seda teise litsentsi all, siis on sul võimalik kasutada [https://commons.wikimedia.org/wiki/Special:UploadWizard Commonsi üleslaadimisviisardit].",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Võimalik, et soovid kasutada [[Special:Upload|saidi {{SITENAME}} üleslaadimislehekülge]], kui seda faili on lubatud üles laadida kooskõlas sealsete reeglitega.",
+       "foreign-structured-upload-form-label-not-own-work-local-shared": "Võimalik, et soovid kasutada [[Special:Upload|saidi {{SITENAME}} üleslaadimislehekülge]], kui seda faili on lubatud üles laadida kooskõlas siinsete reeglitega.",
        "backend-fail-stream": "Faili $1 ei saanud edastada.",
        "backend-fail-backup": "Faili $1 ei saanud varundada.",
        "backend-fail-notexists": "Faili $1 pole olemas.",
index f3701af..473ae02 100644 (file)
@@ -55,6 +55,7 @@
        "tog-hideminor": "تغییرات جزئی از فهرست تغییرات اخیر پنهان شوند",
        "tog-hidepatrolled": "ویرایش‌های گشت‌خورده از فهرست تغییرات اخیر پنهان شوند",
        "tog-newpageshidepatrolled": "صفحه‌های گشت‌خورده از فهرست صفحه‌های تازه پنهان شوند",
+       "tog-hidecategorization": "نهفتن رده‌بندی صفحه‌ها",
        "tog-extendwatchlist": "گسترش فهرست پیگیری‌ها برای نمایش همهٔ تغییرات، نه فقط آخرین‌ها",
        "tog-usenewrc": "گروه‌بندی تغییرات بر پایهٔ صفحات تغییرات اخیر و فهرست پیگیری‌ها",
        "tog-numberheadings": "شماره‌گذاری خودکار عنوان‌ها",
@@ -84,6 +85,7 @@
        "tog-watchlisthideliu": "ویرایش‌های کاربران وارد شده به سامانه در فهرست پی‌گیری‌ها پنهان شود",
        "tog-watchlisthideanons": "ویرایش‌های کاربران ناشناس در فهرست پی‌گیری‌های من پنهان شود",
        "tog-watchlisthidepatrolled": "ویرایش‌های گشت‌خورده در فهرست پی‌گیری‌ها پنهان شود",
+       "tog-watchlisthidecategorization": "نهفتن رده‌بندی صفحه‌ها",
        "tog-ccmeonemails": "رونوشتی از ایمیلی که به دیگران ارسال می‌کنم برای خودم هم فرستاده شود",
        "tog-diffonly": "محتوای صفحه، زیر تفاوت نمایش داده نشود",
        "tog-showhiddencats": "رده‌های پنهان نمایش داده شود",
        "viewsource": "نمایش مبدأ",
        "viewsource-title": "نمایش مبدأ برای $1",
        "actionthrottled": "جلوی عمل شما گرفته شد",
-       "actionthrottledtext": "بÙ\87 Ù\85Ù\86ظÙ\88ر Ø¬Ù\84Ù\88Ú¯Û\8cرÛ\8c Ø§Ø² Ø§Ù\86تشار Ø§Ø³Ù¾Ù\85، اجازه ندارید که چنین عملی را بیش از چند بار در یک مدت زمان کوتاه انجام بدهید.\nلطفاً پس از چند دقیقه دوباره تلاش کنید.",
+       "actionthrottledtext": "بÙ\87 Ù\85Ù\86ظÙ\88ر Ø¬Ù\84Ù\88Ú¯Û\8cرÛ\8c Ø§Ø² Ø§Ù\86تشار Ø®Ø±Ø§Ø¨Ú©Ø§Ø±Û\8c، اجازه ندارید که چنین عملی را بیش از چند بار در یک مدت زمان کوتاه انجام بدهید.\nلطفاً پس از چند دقیقه دوباره تلاش کنید.",
        "protectedpagetext": "این صفحه برای جلوگیری از ویرایش یا فعالیت دیگر محافظت شده‌است.",
        "viewsourcetext": "می‌توانید متن مبدأ این صفحه را مشاهده کنید یا از آن نسخه بردارید:",
        "viewyourtext": "می‌توانید کد مبدأ <strong>ویرایش‌هایتان</strong> در این صفحه را ببینید و کپی کنید.",
        "createaccountreason": "دلیل:",
        "createacct-reason": "دلیل",
        "createacct-reason-ph": "چرا شما حساب دیگری می‌سازید؟",
-       "createacct-captcha": "بررسی امنیتی",
-       "createacct-imgcaptcha-ph": "متن تصویری که در بالا می‌بینید، وارد کنید",
        "createacct-submit": "حسابتان را بسازید",
        "createacct-another-submit": "ایجاد حساب کاربری",
        "createacct-benefit-heading": "{{SITENAME}} توسط افرادی مانند شما ساخته شده است",
        "passwordreset-emailtext-ip": "یک نفر (احتمالاً شما، با نشانی آی‌پی $1) درخواست بازنشانی گذرواژه‌تان در {{SITENAME}} ($4) را کرده‌است. {{PLURAL:$3|حساب|حساب‌های}} کاربری زیر با این آدرس ایمیل مرتبط هستند:\n\n$2\n\n{{PLURAL:$3|این گذرواژهٔ موقت|این گذرواژه‌های موقت}} پس از {{PLURAL:$5|یک روز|$5 روز}} باطل خواهند شد.\nشما باید هم‌اکنون ثبت ورود کنید و گذرواژه‌ای جدید برگزینید. اگر فکر می‌کنید شخص دیگری این درخواست را داده است یا اگر گذرواژهٔ اصلی‌تان را به یاد آوردید و دیگر نمی‌خواهید آن را تغییر دهید، می‌توانید این پیغام را نادیده بگیرید و به استفاده از گذرواژهٔ قبلی‌تان ادامه دهید.",
        "passwordreset-emailtext-user": "کاربر $1 از {{SITENAME}} درخواست بازنشانی گذرواژهٔ شما در {{SITENAME}} ($4) را کرده است. {{PLURAL:$3|حساب|حساب‌های}} کاربری زیر با این آدرس ایمیل مرتبط است:\n\n$2\n\n{{PLURAL:$3|این گذرواژهٔ موقت|این گذرواژه‌های موقت}} تا {{PLURAL:$5|یک روز|$5 روز}} باطل می‌شود.\nشما باید هم‌اکنون وارد شده و یک گذرواژهٔ جدید برگزینید. اگر شخص دیگری این درخواست را داده است، یا اگر گذرواژهٔ اصلی‌تان را به خاطر آوردید و دیگر نمی‌خواهید آن را تغییر دهید، می‌توانید این پیغام را نادیده بگیرید و به استفاده از گذرواژهٔ قبلی‌تان ادامه دهید.",
        "passwordreset-emailelement": "نام کاربری: \n$1\n\nگذرواژهٔ موقت: \n$2",
-       "passwordreset-emailsent": "یک نامهٔ بازنشانی گذرواژه فرستاده شده‌است.",
+       "passwordreset-emailsent": "اگر ایمیلی را برای حساب کاربریتان مشخص کرده باشید، یک نامهٔ بازنشانی گذرواژه فرستاده شده‌است.",
        "passwordreset-emailsent-capture": "یک ایمیل بازنشانی که در پایین نمایش داده شده، فرستاده شده است.",
        "passwordreset-emailerror-capture": "ایمیل بازنشانی، که در زیر نمایش داده شده، ایجاد شد، ولی ارسال آن به {{GENDER:$2|کاربر}} موفقیت‌آمیز نبود: $1",
        "changeemail": "تغییر یا حذف نشانی ایمیل",
-       "changeemail-header": "تغییر آدرس ایمیل حساب کاربری",
+       "changeemail-header": "برای تغییر ایمیلتان این فرم را کامل کنید. برای حذف ایملیتان کافی است بخش ایمیل را خالی رها کنید و فرم را ارسال کنید.",
+       "changeemail-passwordrequired": "برای تائید این تغییر باید گذرواژه‌تان را وارد کنید.",
        "changeemail-no-info": "برای دسترسی مستقیم به این صفحه شما باید به سامانه وارد شده باشید.",
        "changeemail-oldemail": "آدرس ایمیل کنونی:",
        "changeemail-newemail": "آدرس ایمیل جدید:",
+       "changeemail-newemail-help": "برای حذف ایمیل باید این بخش را خالی رها کنید در نتیجه امکان بازگردانی گذرواژه و دریافت ایمیل از ویکی برای شما مقدور نخواهد بود.",
        "changeemail-none": "(هیچ)",
        "changeemail-password": "گذرواژهٔ {{SITENAME}} شما:",
        "changeemail-submit": "تغییر ایمیل",
        "missingsummary": "'''یادآوری:''' شما خلاصهٔ ویرایش ننوشته‌اید.\nاگر دوباره دکمهٔ «{{int:savearticle}}» را فشار دهید ویرایش شما بدون آن ذخیره خواهد شد.",
        "selfredirect": "<strong>هشدار:</strong> شما در حال تغییرمسیر صفحه به خودش هستید.\nامکان دارد هدف اشتباهی را برای تغییرمسیر انتخاب کردید، یا ممکن است صفحهٔ اشتباهی را ویرایش می‌کنید.\n\nاگر بر روی \"{{int:savearticle}}\" دوباره کلیک کنید، تغییرمسیر ساخته خواهد شد.",
        "missingcommenttext": "لطفاً توضیحی در زیر بیفزایید.",
-       "missingcommentheader": "'''یادآوری:''' شما موضوع/عنوان این یادداشت را مشخص نکرده‌اید.\nاگر دوباره دکمهٔ «{{int:savearticle}}» را فشار دهید ویرایش شما بدون آن ذخیره خواهد شد.",
+       "missingcommentheader": "<strong>یادآوری:</strong> شما موضوع/عنوان این یادداشت را مشخص نکرده‌اید.\nاگر دوباره دکمهٔ «{{int:savearticle}}» را فشار دهید ویرایش شما بدون آن ذخیره خواهد شد.",
        "summary-preview": "پیش‌نمایش خلاصه:",
-       "subject-preview": "پیش‌نمایش موضوع/عنوان:",
+       "subject-preview": "پیش‌نمایش موضوع:",
        "previewerrortext": "در زمان تلاش برای نمایش دادن تغییرات شما، خطای رخ داد.",
        "blockedtitle": "کاربر بسته شده‌است",
        "blockedtext": "<strong>دسترسی حساب کاربری یا نشانی آی‌پی شما بسته شده‌است.</strong>\n\nاین قطع دسترسی توسط $1 انجام شده است.\nدلیل ارائه‌شده چنین است: <em>$2</em>\n\n* شروع قطع دسترسی: $8\n* پایان قطع دسترسی: $6\n* کاربری هدف قطع دسترسی: $7\n\nشما می‌توانید با $1 یا [[{{MediaWiki:Grouppage-sysop}}|مدیری]] دیگر تماس بگیرید و در این باره صحبت کنید.\nتوجه کنید که شما نمی‌توانید از قابلیت «ایمیل به این کاربر» استفاده کنید مگر آنکه آدرس ایمیل معتبری در [[Special:Preferences|ترجیحات کاربری]] خودتان ثبت کرده باشید و نیز باید امکان استفاده از این قابلیت برای شما قطع نشده باشد.\nنشانی آی‌پی فعلی شما $3 و شمارهٔ قطع دسترسی شما $5 است.\nلطفاً تمامی جزئیات فوق را در کلیهٔ درخواست‌هایی که در این باره مطرح می‌کنید ذکر کنید.",
        "permissionserrors": "خطای سطح دسترسی",
        "permissionserrorstext": "شما اجازهٔ انجام این کار را به این {{PLURAL:$1|دلیل|دلایل}} ندارید:",
        "permissionserrorstext-withaction": "شما اجازهٔ $2 را به این {{PLURAL:$1|دلیل|دلایل}} ندارید:",
+       "contentmodelediterror": "امکان ویرایش این نسخه برای شما نیست چون نوع محتوای آن <code>$1</code> است و نوع محتوای کنونی صفحه <code>$2</code> است.",
        "recreate-moveddeleted-warn": "<strong>هشدار: شما در حال ایجاد صفحه‌ای هستید که قبلاً حذف شده‌است.</strong>\n\nدر نظر داشته باشید که آیا ادامهٔ ویرایش این صفحه کار درستی‌است یا نه.\nسیاههٔ حذف و انتقال این صفحه در زیر نشان داده شده‌است:",
        "moveddeleted-notice": "این صفحه حذف شده‌است.\nدر زیر سیاههٔ حذف و انتقال این صفحه نمایش داده شده‌است.",
        "moveddeleted-notice-recent": "متاسفانه صفحه قبلا حذف شده‌است (در ۲۴ ساعت اخیر) \nدلیل حذف و سیاههٔ انتقال در پائین موجود است.",
        "prefs-help-recentchangescount": "این گزینه شامل تغییرات اخیر، تاریخچهٔ صفحه‌ها و سیاهه‌ها می‌شود.",
        "prefs-help-watchlist-token2": "این کلید رمز خوراک وب فهرست پی‌گیری‌های شماست.\nهرکس آن را بداند می‌تواند فهرست پی‌گیری‌هایتان را بخواند، بنابراین آن را به اشتراک نگذارید. [[Special:ResetTokens|اگر لازم است آن را تغییر دهید اینجا را کلیک کنید]].",
        "savedprefs": "ترجیحات شما ذخیره شد.",
+       "savedrights": "دسترسی کاربری {{GENDER:$1|$1}} ذخیره شده‌است.",
        "timezonelegend": "منطقهٔ زمانی:",
        "localtime": "زمان محلی:",
        "timezoneuseserverdefault": "استفاده از پیش‌فرض ویکی ($1)",
        "rcshowhidemine": "$1 ویرایش‌های من",
        "rcshowhidemine-show": "نمایش",
        "rcshowhidemine-hide": "نهفتن",
+       "rcshowhidecategorization": "$1 رده‌بندی صفحه‌ها",
+       "rcshowhidecategorization-show": "نمایش",
+       "rcshowhidecategorization-hide": "نهفتن",
        "rclinks": "نمایش آخرین $1 تغییر در $2 روز اخیر<br />$3",
        "diff": "تفاوت",
        "hist": "تاریخچه",
        "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": "بازگشت به فرم بارگذاری",
        "upload-options": "گزینه‌های بارگذاری",
        "watchthisupload": "پی‌گیری این پرونده",
        "filewasdeleted": "پرونده‌ای با همین نام پیشتر بارگذاری و پس از آن پاک شده‌است.\nشما باید $1 را قبل از بارگذاری مجدد آن ببینید.",
+       "filename-thumb-name": "به نظر می رسد که عنوان thumbnail است. لطفاً تصویر thumbnail  از دیگر ویکی‌ها را بارگذاری نکنید. عنوان پرونده را اصلاح کنید به صورتی که پیشوند thumbnail نداشته باشد.",
        "filename-bad-prefix": "نام پرونده‌ای که بارگذاری می‌کنید با '''$1''' آغاز می‌شود که یک پیشوند مخصوص تصاویر ثبت شده توسط دوربین‌های دیجیتال است.\nلطفاً نامی بهتر برای پرونده برگزینید.",
        "upload-success-subj": "بارگذاری با موفقیت انجام شد",
        "upload-success-msg": "بارگذاری شما از [$2] موفق بود. این پرونده در اینجا قابل دسترسی است: [[:{{ns:file}}:$1]]",
        "foreign-structured-upload-form-label-own-work": "این کار خودم است",
        "foreign-structured-upload-form-label-infoform-categories": "رده‌ها",
        "foreign-structured-upload-form-label-infoform-date": "تاریخ",
+       "foreign-structured-upload-form-label-own-work-message-local": "تائید می کنم که این پرونده را تحت مجوزها و سیاست‌های {{SITENAME}} بارگذاری می‌کنم.",
+       "foreign-structured-upload-form-label-not-own-work-message-local": "اگر امکان بارگذاری پرونده تحت سیاست‌های {{SITENAME}} را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "foreign-structured-upload-form-label-not-own-work-local-local": "ممکن بخواهید از [[Special:Upload|پنجرهٔ بارگذاری پیش‌فرض]] استفاده کنید.",
+       "foreign-structured-upload-form-label-own-work-message-default": "متوجهم که این پرونده را بر روی مخزن مشترک بارگذاری می‌کنم و تائید می‌کنم که تحت سیاست‌ها و مجوزهای آنجا عمل می‌کنم.",
+       "foreign-structured-upload-form-label-not-own-work-message-default": "اگر امکان بارگذاری پرونده تحت سیاست‌ها و مجوزهای مخزن اشتراک‌گذاری را ندارید، لطفاً این پنجره را ببندید و از روش‌های دیگر استفاده کنید.",
+       "foreign-structured-upload-form-label-not-own-work-local-default": "در صورتی که نمی‌شود پرونده را تحت سیاست‌ها بارگذاری کنید ممکن است بخواهید از [[Special:Upload|پنجرهٔ بارگذاری در {{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|پنجرهٔ بارگذاری در {{SITENAME}}]] استفاده کنید.",
        "backend-fail-stream": "نمی‌توان پروندهٔ $1 را ارسال کرد.",
        "backend-fail-backup": "نمی‌توان نسخهٔ پشتیبان برای پروندهٔ $1 ایجاد کرد.",
        "backend-fail-notexists": "پروندهٔ $1 وجود ندارد.",
        "nopagetext": "صفحهٔ هدفی که شما مشخص کردید وجود ندارد.",
        "pager-newer-n": "{{PLURAL:$1|یک مورد جدیدتر|$1 مورد جدیدتر}}",
        "pager-older-n": "{{PLURAL:$1|یک مورد قدیمی‌تر|$1 مورد قدیمی‌تر}}",
-       "suppress": "نظارت",
+       "suppress": "سرکوب",
        "querypage-disabled": "این صفحه ویژه به دلایل عملکردی غیرفعال شده‌است.",
        "apihelp": "راهنمای API",
        "apihelp-no-such-module": "پودمان \" $1 \" یافت نشد.",
        "move-page-legend": "انتقال صفحه",
        "movepagetext": "با استفاده از فرم زیر نام صفحه تغییر خواهد کرد، و تمام تاریخچه‌اش به نام جدید منتقل خواهد شد.\nعنوان قدیمی تبدیل به یک صفحهٔ تغییرمسیر به عنوان جدید خواهد شد.\nشما می‌توانید تغییرمسیرهایی که به عنوان اصلی اشاره دارند را به صورت خودکار به‌روزرسانی کنید.\nپیوندهای که به عنوان صفحهٔ قدیمی وجود دارند، تغییر نخواهند کرد؛ حتماً تغییرمسیرهای [[Special:DoubleRedirects|دوتایی]] یا [[Special:BrokenRedirects|خراب]] را بررسی کنید.\n'''شما''' مسئول اطمینان از این هستید که پیوندها هنوز به همان‌جایی که قرار است بروند.\n\nتوجه کنید که اگر از قبل صفحه‌ای در عنوان جدید وجود داشته باشد صفحه منتقل '''نخواهد شد'''،\nمگر این آخرین ویرایش تغییرمسیر باشد و در  تاریخچهٔ ویرایشی نداشته باشد.\nاین یعنی اگر اشتباه کردید می‌توانید صفحه را به همان جایی که از آن منتقل شده بود برگردانید، و این که نمی‌توانید روی صفحات موجود بنویسید.\n\n'''هشدار!'''\nانتقال صفحات به نام جدید ممکن است تغییر اساسی و غیرمنتظره‌ای برای صفحات محبوب باشد؛\nلطفاً مطمئن شوید که قبل از انتقال دادن صفحه، عواقب این کار را درک می‌کنید.",
        "movepagetext-noredirectfixer": "استفاده از فرم زیر سبب تغییر نام یک صفحه و انتقال تمام تاریخچهٔ آن به نام جدید می‌شود.\nعنوان پیشین تغییرمسیری به عنوان جدید خواهد شد.\nبه خاطر داشته باشید که [[Special:DoubleRedirects|تغییرمسیرهای دوتایی]] یا [[Special:BrokenRedirects|تغییرمسیرهای خراب]] را بررسی کنید.\nشما مسئولید که مطمئن شوید پس از انتقال، پیوندها به عنوان پیشین به جایی منتهی می‌شوند که باید.\n\nتوجه کنید که اگر صفحه‌ای تحت عنوان جدید از قبل موجود باشد، انتقال انجام '''نخواهد شد'''، مگر اینکه صفحه خالی و یا تغییرمسیر باشد و تاریخچهٔ ویرایشی دیگری نداشته باشد.\nاین یعنی اگر صفحه را به نامی اشتباه منتقل کردید می‌توانید این تغییر را واگردانی کنید، اما نمی‌توانید یک صفحه را به صفحه‌ای که از قبل موجود است انتقال دهید.\n\n'''هشدار!'''\nانتقال صفحه‌های پربیننده ممکن است عملی غیرمنتظره باشد؛\nلطفاً پیش از انتقال مطمئن شوید از نتیجهٔ کار آگاهید.",
-       "movepagetalktext": "صÙ\81Ø­Ù\87Ù\94 Ø¨Ø­Ø« Ù\85ربÙ\88Ø·Ø\8c Ø§Ú¯Ø± Ù\88جÙ\88د Ø¯Ø§Ø´ØªÙ\87 Ø¨Ø§Ø´Ø¯Ø\8c Ø¨Ø·Ù\88ر Ø®Ù\88دکار Ù\87Ù\85راÙ\87 Ø¨Ø§ Ù\85Ù\82اÙ\84Ù\87Ù\94 Ø§ØµÙ\84Û\8c Ù\85Ù\86تÙ\82Ù\84 Ø®Ù\88اÙ\87د Ø´Ø¯ <strong>Ù\85گر Ø§Û\8cÙ\86Ú©Ù\87:</strong>\n* Ø¯Ø± Ø­Ø§Ù\84 Ø§Ù\86تÙ\82اÙ\84 ØµÙ\81Ø­Ù\87 Ø§Ø² Ø§Û\8cÙ\86 Ù\81ضاÛ\8c Ù\86اÙ\85 Ø¨Ù\87 Ù\81ضاÛ\8c Ù\86اÙ\85 Ø¯Û\8cگرÛ\8c Ø¨Ø§Ø´Û\8cدØ\8c\n* Û\8cÚ© ØµÙ\81Ø­Ù\87Ù\94 Ø¨Ø­Ø« ØºÛ\8cرخاÙ\84Û\8c ØªØ­Øª Ø§Û\8cÙ\86 Ù\86اÙ\85 Ø¬Ø¯Û\8cد Ù\88جÙ\88د Ø¯Ø§Ø´ØªÙ\87 Ø¨Ø§Ø´Ø¯Ø\8c Û\8cا\n* Ø¬Ø¹Ø¨Ù\87Ù\94 Ø²Û\8cر Ø±Ø§ ØªÛ\8cÚ© Ù\86زدÙ\87 Ø¨Ø§Ø´Û\8cد.\n\nدر Ø§Û\8cÙ\86 Ø­Ø§Ù\84ات، باید صفحه را بطور دستی انتقال داده و یا محتویات دو صفحه را با ویرایش ادغام کنید.",
+       "movepagetalktext": "اگر Ø§Û\8cÙ\86 Ú¯Ø²Û\8cÙ\86Ù\87 Ø±Ø§ Ø§Ù\86تخاب Ú©Ù\86Û\8cدØ\8c ØµÙ\81Ø­Ù\87Ù\94 Ø¨Ø­Ø« Ù\85رتبط Ø¨Ù\87 ØµÙ\88رت Ø®Ù\88دکار Ø§Ù\86تÙ\82اÙ\84 Ø¯Ø§Ø¯Ù\87 Ù\85Û\8câ\80\8cØ´Ù\88د Ø¨Ù\87 Ø¹Ù\86Ù\88اÙ\86 Ø¬Ø¯Û\8cد Ù\88 Ø¯Ø± ØµÙ\88رت Ø¹Ø¯Ù\85 Ø§Ù\86تخاب Ú¯Ø²Û\8cÙ\86Ù\87Ø\8c ØµÙ\81Ø­Ù\87Ù\94 Ø¨Ø­Ø« Ø¬Ø¯Û\8cد Û\8cÚ© ØµÙ\81Ø­Ù\87Ù\94 Ø®Ø§Ù\84Û\8c Ø®Ù\88اÙ\87د Ø¨Ù\88د Ù\88 Ø¯Ø± Ø§Û\8cÙ\86 Ø­Ø§Ù\84ت، باید صفحه را بطور دستی انتقال داده و یا محتویات دو صفحه را با ویرایش ادغام کنید.",
        "moveuserpage-warning": "'''هشدار:''' شما در حال انتقال دادن یک صفحهٔ کاربر هستید. توجه داشته باشید که تنها صفحه منتقل می‌شود و نام کاربر تغییر '''نمی‌یابد'''.",
        "movecategorypage-warning": "<strong>هشدار:</strong> شما در حال انتقال صفحه رده هستید. لطفاً توجه داشته باشید که فقط صفحه منتقل خواهد شد و  صفحات در رده قدیمی می‌مانند و به رده جدید <em>نمی‌روند</em>.",
        "movenologintext": "برای انتقال صفحات باید کاربر ثبت‌شده بوده و [[Special:UserLogin|به سامانه وارد شوید]].",
        "svg-long-error": "پرونده SVG نامجاز: $1",
        "show-big-image": "پروندهٔ اصلی",
        "show-big-image-preview": "اندازهٔ این پیش‌نمایش: $1.",
+       "show-big-image-preview-differ": "حجم پیش‌نمایش $3 این $2 file:$1",
        "show-big-image-other": "{{PLURAL:$2|کیفیت|کیفیت‌های}} دیگر: $1.",
        "show-big-image-size": "<span dir=\"ltr\">$1 × $2</span> پیکسل",
        "file-info-gif-looped": "چرخش‌دار",
        "logentry-protect-move_prot": "$1 تنظیمات محافظت را از $4 به $3 {{GENDER:$2|منتقل کرد}}",
        "logentry-protect-unprotect": "$1 $3 را از محافظت {{GENDER:$2|خارج کرد}}",
        "logentry-protect-protect": "$1 $3 را {{GENDER:$2|محافظت کرد}} $4",
+       "logentry-protect-protect-cascade": "$1 $3 $4 {{GENDER:$2|محافظت کرد}} [آبشاری]",
        "logentry-protect-modify": "$1 سطح محافظت $3 را {{GENDER:$2|تغییر داد}} $4",
+       "logentry-protect-modify-cascade": "$1 سطح حفاظت برای $3 $4 را {{GENDER:$2|تغییر داد}}[آبشاری]",
        "logentry-rights-rights": "$1 عضویت $3 را از گروه $4 به $5 {{GENDER:$2|تغییر داد}}",
        "logentry-rights-rights-legacy": "$1 گروه عضویت $3 را {{GENDER:$2|تغییر داد}}",
        "logentry-rights-autopromote": "$1 به طور خودکار از $4 به $5 {{GENDER:$2|ارتقاء داد}}",
index 0c3fb7a..44bb4af 100644 (file)
        "createaccountreason": "Motif :",
        "createacct-reason": "Motif",
        "createacct-reason-ph": "Pourquoi créez-vous un autre compte",
-       "createacct-captcha": "Contrôle de sécurité",
-       "createacct-imgcaptcha-ph": "Entrez le texte que vous voyez ci-dessus",
        "createacct-submit": "Créez votre compte",
        "createacct-another-submit": "Créer le compte",
        "createacct-benefit-heading": "{{SITENAME}} est écrit par des gens comme vous.",
        "hijri-calendar-m12": "Dhou al-hijja",
        "hebrew-calendar-m1": "tichri",
        "hebrew-calendar-m2": "hechvan",
-       "hebrew-calendar-m3": "Kislev",
-       "hebrew-calendar-m4": "Téveth",
+       "hebrew-calendar-m3": "kislev",
+       "hebrew-calendar-m4": "tévet",
        "hebrew-calendar-m5": "Schébat",
        "hebrew-calendar-m6": "Adar",
        "hebrew-calendar-m7": "Nissane",
        "hebrew-calendar-m12": "Éloul",
        "hebrew-calendar-m1-gen": "tichri",
        "hebrew-calendar-m2-gen": "hechvan",
-       "hebrew-calendar-m3-gen": "Kislev",
-       "hebrew-calendar-m4-gen": "Téveth",
+       "hebrew-calendar-m3-gen": "kislev",
+       "hebrew-calendar-m4-gen": "tévet",
        "hebrew-calendar-m5-gen": "Schébat",
        "hebrew-calendar-m6-gen": "Adar",
        "hebrew-calendar-m7-gen": "Nissane",
index 7df66f8..1e6f40f 100644 (file)
        "createaccountreason": "Grund:",
        "createacct-reason": "Grund",
        "createacct-reason-ph": "Wurum Du ne ander Benutzerkonto aaleisch",
-       "createacct-captcha": "Sicherheitspriefig",
-       "createacct-imgcaptcha-ph": "Gib dr Tekscht yy, wu Du obe siisch",
        "createacct-submit": "Dyy Benutzerkonto aalege",
        "createacct-another-submit": "Benutzerkonto aalege",
        "createacct-benefit-heading": "{{SITENAME}} wird vu Mänsche wie Dir gschaffe.",
index 1662718..98e762e 100644 (file)
        "createaccountreason": "סיבה:",
        "createacct-reason": "סיבה",
        "createacct-reason-ph": "סיבה ליצירת חשבון נוסף",
-       "createacct-captcha": "בדיקת אבטחה",
-       "createacct-imgcaptcha-ph": "יש להקליד את הטקסט המופיע למעלה",
        "createacct-submit": "יצירת החשבון שלך",
        "createacct-another-submit": "יצירת חשבון",
        "createacct-benefit-heading": "אנשים כמוך יוצרים את {{SITENAME}}.",
index 8aea5cb..e4f567f 100644 (file)
        "createaccountreason": "कारण:",
        "createacct-reason": "कारण",
        "createacct-reason-ph": "आप एक अन्य खाता क्यों बना रहे हैं",
-       "createacct-captcha": "सुरक्षा जाँच",
-       "createacct-imgcaptcha-ph": "उपरोक्त पाठ लिखें",
        "createacct-submit": "अपना खाता बनाएँ",
        "createacct-another-submit": "खाता बनाएँ",
        "createacct-benefit-heading": "{{SITENAME}} आपके जैसे लोगों द्वारा बनायी गई है।",
index eccf3ea..a8a22b1 100644 (file)
        "createaccountreason": "Razlog:",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zašto stvarate drugi račun",
-       "createacct-captcha": "Sigurnosna provjera",
-       "createacct-imgcaptcha-ph": "Unesite tekst koji vidite iznad",
        "createacct-submit": "Stvorite svoj suradnički račun",
        "createacct-another-submit": "Stvori još jedan račun",
        "createacct-benefit-heading": "{{SITENAME}} su stvorili ljudi poput Vas.",
index c81c306..ae96253 100644 (file)
        "createaccountreason": "Indoklás:",
        "createacct-reason": "Indoklás",
        "createacct-reason-ph": "Miért hozol létre egy másik fiókot",
-       "createacct-captcha": "Biztonsági ellenőrzés",
-       "createacct-imgcaptcha-ph": "Írd be a szöveget, amit fent látsz",
        "createacct-submit": "Felhasználói fiók létrehozása",
        "createacct-another-submit": "Újabb felhasználói fiók létrehozása",
        "createacct-benefit-heading": "A(z) {{SITENAME}}-t hozzád hasonló emberek készítik.",
index bd773d0..ba0af4e 100644 (file)
        "createaccountreason": "Պատճառը՝",
        "createacct-reason": "Պատճառ",
        "createacct-reason-ph": "Ինչո՞ւ եք փորձում ստեղծել մեկ այլ հաշիվ",
-       "createacct-captcha": "Անվտանգության ստուգում",
-       "createacct-imgcaptcha-ph": "Մուտքագրեք վերը բերված գրվածքը",
        "createacct-submit": "Ստեղծել ձեր հաշիվը",
        "createacct-another-submit": "Ստեղծել մեկ այլ հաշիվ",
        "createacct-benefit-heading": "{{SITENAME}}՝ ստեղծվում է ձեր պես մարդկանց կողմից։",
index 0a177eb..4effd6e 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Proque crea tu un altere conto?",
-       "createacct-captcha": "Controlo de securitate",
-       "createacct-imgcaptcha-ph": "Scribe le texto que tu vide hic supra",
        "createacct-submit": "Crear tu conto",
        "createacct-another-submit": "Crear conto",
        "createacct-benefit-heading": "{{SITENAME}} es facite per gente como tu.",
index 4f4a6c3..57bcbb5 100644 (file)
        "createaccountreason": "Alasan:",
        "createacct-reason": "Alasan",
        "createacct-reason-ph": "Mengapa Anda membuat akun lain",
-       "createacct-captcha": "Pemeriksaan keamanan",
-       "createacct-imgcaptcha-ph": "Masukkan teks yang Anda lihat di atas",
        "createacct-submit": "Buat akun Anda",
        "createacct-another-submit": "Buat akun lain",
        "createacct-benefit-heading": "{{SITENAME}} dibuat oleh orang-orang seperti Anda.",
index bb09356..f0b98c9 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Perché stai creando un'altra utenza",
-       "createacct-captcha": "Controllo di sicurezza",
-       "createacct-imgcaptcha-ph": "Inserisci il testo che vedi sopra",
        "createacct-submit": "Crea la tua utenza",
        "createacct-another-submit": "Crea utenza",
        "createacct-benefit-heading": "{{SITENAME}} cresce grazie a persone come te.",
index 5b9f114..05f0f7a 100644 (file)
@@ -74,6 +74,7 @@
        "tog-hideminor": "最近の更新に、細部の編集を表示しない",
        "tog-hidepatrolled": "最近の更新に、巡回済みの編集を表示しない",
        "tog-newpageshidepatrolled": "新しいページの一覧に、巡回済みのページを表示しない",
+       "tog-hidecategorization": "ページのカテゴリ化を隠す",
        "tog-extendwatchlist": "ウォッチリストを拡張し、最新のものだけではなくすべての変更を表示",
        "tog-usenewrc": "最近の更新とウォッチリストで、複数の変更をページごとにまとめる",
        "tog-numberheadings": "見出しに番号を自動的に振る",
        "tog-watchlisthideliu": "ログイン利用者による編集をウォッチリストに表示しない",
        "tog-watchlisthideanons": "匿名利用者による編集をウォッチリストに表示しない",
        "tog-watchlisthidepatrolled": "巡回済みの編集をウォッチリストに表示しない",
+       "tog-watchlisthidecategorization": "ページのカテゴリ化を隠す",
        "tog-ccmeonemails": "他の利用者に送信したメールの控えを自分にも送信",
        "tog-diffonly": "差分の下にページ内容を表示しない",
        "tog-showhiddencats": "隠しカテゴリを表示",
        "createaccountreason": "理由:",
        "createacct-reason": "理由",
        "createacct-reason-ph": "アカウントを作成する理由",
-       "createacct-captcha": "自動作成防止チェック",
-       "createacct-imgcaptcha-ph": "上に表示されている文字列を入力",
        "createacct-submit": "アカウントを作成",
        "createacct-another-submit": "アカウントを作成",
        "createacct-benefit-heading": "{{SITENAME}}は、あなたのような人々が創っています。",
        "svg-long-error": "無効な SVG ファイル: $1",
        "show-big-image": "元のファイル",
        "show-big-image-preview": "このプレビューのサイズ: $1。",
+       "show-big-image-preview-differ": "この $2 ファイルのこの $3 プレビューのサイズ: $1.",
        "show-big-image-other": "その他の{{PLURAL:$2|解像度}}: $1。",
        "show-big-image-size": "$1 × $2 ピクセル",
        "file-info-gif-looped": "ループします",
index 9c082b8..f5afec6 100644 (file)
        "createaccountreason": "Alesan:",
        "createacct-reason": "Alesan",
        "createacct-reason-ph": "Kenapa sampeyan nggawe akun liyane",
-       "createacct-captcha": "Periksa kamanan",
-       "createacct-imgcaptcha-ph": "Lebokna tulisan ing dhuwur",
        "createacct-submit": "Gawé akun",
        "createacct-another-submit": "Gawé akun anyar",
        "createacct-benefit-heading": "{{SITENAME}} digawe karo wong-wong kaya sampeyan.",
index 01e467f..cbc220c 100644 (file)
        "createaccountreason": "მიზეზი:",
        "createacct-reason": "მიზეზი",
        "createacct-reason-ph": "რატომ ქმნით ახალ ანგარიშს?",
-       "createacct-captcha": "უსაფრთხოების შემოწმება",
-       "createacct-imgcaptcha-ph": "შეიყვანეთ ზემოთ მოცემული ტექსტი",
        "createacct-submit": "შექმენით თქვენი ანგარიში",
        "createacct-another-submit": "ანგარიშის შექმნა",
        "createacct-benefit-heading": "{{SITENAME}} შექმნილია თქვენნაირი ადამიანების მიერ.",
index 4a042ed..af5b715 100644 (file)
        "createaccountreason": "មូលហេតុ៖",
        "createacct-reason": "មូលហេតុ",
        "createacct-reason-ph": "មូលហេតុដែលអ្នកចង់បង្កើតគណនីមួយទៀត",
-       "createacct-captcha": "ត្រួតពិនិត្យសុវត្ថិភាព",
-       "createacct-imgcaptcha-ph": "បញ្ចូលឃ្លាដែលអ្នកឃើញខាងលើ",
        "createacct-submit": "បង្កើតគណនីរបស់អ្នក",
        "createacct-another-submit": "បង្កើតគណនី",
        "createacct-benefit-heading": "{{SITENAME}} ត្រូវបង្កើតឡើងដោយបុគ្គលស្ម័គ្រចិត្តដូចជាអ្នកជាដើម។",
index 52aea4a..7b783cc 100644 (file)
        "createaccountreason": "이유:",
        "createacct-reason": "이유",
        "createacct-reason-ph": "왜 다른 계정을 만들어야 합니까",
-       "createacct-captcha": "보안 검사",
-       "createacct-imgcaptcha-ph": "위에 보이는 텍스트를 입력하세요",
        "createacct-submit": "계정 만들기",
        "createacct-another-submit": "계정 만들기",
        "createacct-benefit-heading": "{{SITENAME}} 프로젝트는 여러분과 같은 사람들이 만듭니다.",
index 5958156..b7acea9 100644 (file)
        "createaccountreason": "Jrond:",
        "createacct-reason": "Der Jrond udder Aanlaß",
        "createacct-reason-ph": "Woröm deihs De noch ene Zohjang aanlääje?",
-       "createacct-captcha": "Zor Sescherheit",
-       "createacct-imgcaptcha-ph": "Jiv dä Täx en, dä De heh drövver sühs!",
        "createacct-submit": "Lohß Jonn!",
        "createacct-another-submit": "Donn jäz enne zohsäzlejje Zohjang aanlääje",
        "createacct-benefit-heading": "{{ucfirst:{{GRAMMAR:Nominative|{{ucfirst:{{SITENAME}}}}}}}} weed vun Minsche wi Dir jemaat.",
index d4f0b14..48526c8 100644 (file)
        "createaccountreason": "Grond:",
        "createacct-reason": "Grond",
        "createacct-reason-ph": "Fir wat Dir een anere Benotzerkonnt uleet",
-       "createacct-captcha": "Sécherheets-Check",
-       "createacct-imgcaptcha-ph": "Gitt den Text an deen Dir hei driwwer gesitt",
        "createacct-submit": "Äre Benotzerkont uleeën",
        "createacct-another-submit": "Benotzerkont uleeën",
        "createacct-benefit-heading": "{{SITENAME}} gëtt vu Leit wéi Iech gemaach.",
index 75d2db3..bc0f0c0 100644 (file)
@@ -13,6 +13,7 @@
        "tog-hideminor": "قام کئردئن ڤیرایشتیا کوچئک مین آلئشتیا تازە",
        "tog-hidepatrolled": "قام کئردئن ڤیرایشتیا تیە دیار کئردە مین آلئشتیا تازە",
        "tog-newpageshidepatrolled": "بألگە یا تیە دیار کئردە نە مئن نومگە بألگە یا تازە قام کو",
+       "tog-hidecategorization": "قام کئردئن جأرغە کاری بألگە یا",
        "tog-extendwatchlist": "سئیل بأرگە نە سی نئشوٙ دأئن تأموٙم آلئشتیا ڤا کو نە فأقأط سی بیشتئر تازە باڤیا.",
        "tog-usenewrc": "دأسە بأنی آلئشتیا ڤا بألگە د آلئشتیا تازە و سئیل بأرگ",
        "tog-numberheadings": "سأربألگە خود شمارئشت کو",
@@ -42,6 +43,7 @@
        "tog-watchlisthideliu": "ڤبرایشتیایی نە کئ ڤئ دأس کاریاریا ڤامین ئوٙماە أنجوم گئرئتە د سئیل بأرگ نئهوٙ بأک",
        "tog-watchlisthideanons": "ڤیرایئشتیا کاریاریایی کئ نادیارئن د سئیل بأرگ نئهوٙ بأک",
        "tog-watchlisthidepatrolled": "ڤیرایشتیایی که هان د تیە رأس د سئیل بأرگ قام کو",
+       "tog-watchlisthidecategorization": "قام کئردئن جأرغە کاری بألگە یا",
        "tog-ccmeonemails": "ڤورداشتە یا أنجومانامه یا مئنە کئ سی کاریاریا هأنی کئل می کئم سیم کئل بأک",
        "tog-diffonly": "بألگە یایی کئ د ڤأر گئرتە فأرخیا هارئن دیاریشوٙ بأک",
        "tog-showhiddencats": "دأسە یا نئهوٙ بییأنئ دیاری بأک",
        "createaccountreason": "دألیل:",
        "createacct-reason": "دألیل",
        "createacct-reason-ph": "سی چی شوما داریت یئ گئل حئساڤ هأنی راس میکید",
-       "createacct-captcha": "ڤارئسی أمنیأت دار بییئن",
-       "createacct-imgcaptcha-ph": "نیسئسە یی نە کئ د ڤارو مئینیت ڤارئد بأکیت",
        "createacct-submit": "حئسأڤ خوتوٙنە راس بأکیت",
        "createacct-another-submit": "یئ گئل حئساڤ هأنی راس بأکیت",
        "createacct-benefit-heading": "{{SITENAME}}  ڤئ دأس خألکی چی شوما رأڤأندیاری بییە.",
        "passwordreset-emailsent-capture": "رازینە گوڤاردئن تازە توٙ سی أنجومانامە توٙ کئ ها د هار کئل بییە.",
        "passwordreset-emailerror-capture": "رازینە گوڤاردئن د أنجومانامە د نۊ زئنە کون کئل بییە،و ڤئ د هار دیاری میکە، ڤألی کئل بییئن ڤئ سی {{GENDER:$2|کاریار}} ناخوش سأرنجوم بییە:$1",
        "changeemail": "أنجومانامە توٙنە آلئشت کاری بأکیت",
-       "changeemail-header": "اÛ\8cÙ\85Û\8cÙ\84 Ø­Ø³Ø§Ù\88تÙ\88Ù\86Ù\87 Ø¢Ù\84شت Ø¨Ú©Û\8cد",
+       "changeemail-header": "ئÛ\8c Ù\81Ù\88رÙ\85ئ Ù\86Û\95 Ø³Û\8c Ø¢Ù\84ئشتکارÛ\8c Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 ØªÙ\88Ù\99 Ù¾Ù\88ر Ø¨Ø£Ú©Û\8cت. Ø£Ø± Ù\85Û\8cھاÛ\8cت Ú¾Ø£Ø± Ø¬Ù\88Ù\99ر Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 Û\8cÛ\8c Ù\86Û\95 Ø¯ Ù\85Û\8cÙ\86جا Ø­Ø¦Ø³Ø§Ú¤Ø¦ØªÙ\88Ù\99 Ù¾Ø§Ú©Ø³Ø§ Ø¨Ø£Ú©Û\8cتØ\8c Ø¬Ø§Ú¯Û\95 Ø¯Ø£Ø¦Ù\86 Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 Ù\86ئ Ø¯ Ú\86ئÙ\86Û\8c Ù\81Ù\88رÙ\85Û\8c Ø­Ø§Ù\84Û\8c Ø¨Ø£Ù\86Û\8cت.",
        "changeemail-passwordrequired": "شوما سی پوشت راستکاری ئی آلئشت بایأد یئ گئل رازینە گوڤاردئن بأزئنیت.",
        "changeemail-no-info": "شوما سی یە کئ د ئی بألگە دأسرئسی داشتوٙییت باس بیاییت ڤامین.",
        "changeemail-oldemail": "تیرنئشوٙن أنجومانامە ئیسئنی:",
        "changeemail-newemail": "تیرنئشوٙن أنجومانامە تازە:",
+       "changeemail-newemail-help": "أر شوما میھایت تیرنئشوٙن أنجفونامە توٙنە ڤئرداریت چئنی جاگە یی نە بایأد حالی بأنیت. شوما. ئوٙسئ أر رازینە گورادئنئتوٙ د ڤیرتوٙ رأتە با نئمی توٙنیت د نۊ زئنەش بأکیت و ھأنی ھیچ جوٙر أنجومانامە یی د ئی ڤیکی ڤئ دأس شوما نئمی رئسە.-",
        "changeemail-none": "(هيش كوم)",
        "changeemail-password": "رازینە گوڤاردئن{{SITENAME}} شوما:",
        "changeemail-submit": "آلئشت دأئن أنجومانامە",
        "rcshowhidemine": "ڤیرایئشتیا مئ $1",
        "rcshowhidemine-show": "نئشوٙ دأئن",
        "rcshowhidemine-hide": "قام کئردئن",
+       "rcshowhidecategorization": "جأرغە کاری بألگە $1",
+       "rcshowhidecategorization-show": "نئشوٙ دأئن",
+       "rcshowhidecategorization-hide": "قام کئردئن",
        "rclinks": "آخرین آلشتیا $1 نشو بیه د اخرین روزیا $2",
        "diff": "فأرخ",
        "hist": "ڤیرگار",
        "boteditletter": "ب",
        "unpatrolledletter": "!",
        "number_of_watching_users_pageview": "[$1 دینه {{PLURAL:$1|کاریار|کاریاریا}}]",
-       "rc_categories": "دسÙ\87 Û\8cا Ù\86Ù\87 Ù\85حدÙ\88د Ú©Ù\88\88ا \"|\" Ø¬Ú¯Ø§ Ø¨Ù\88ئÙ\86",
-       "rc_categories_any": "هرکوم",
+       "rc_categories": "جأرغÛ\95 Û\8cا Ù\86Û\95 Ù\85أدÙ\88Ù\99د Ú©Ù\88(ڤا \"|\" Ø¬Ø¦Ú¯Ø§ Ø¨Ø§Ù\86)",
+       "rc_categories_any": "ھأرکوم کئ گولئ ڤورچیە بیینە",
        "rc-change-size": "$1",
        "rc-change-size-new": "$1 {{PLURAL:$1|بایت|بایتیا}} نئها آلئشتکاری",
        "newsectionsummary": "/* $1 */ بهرجا تازه",
        "recentchangeslinked-summary": "ای نوم گه تازه د بلگیایی که وا بلگیا ویجه هوم پیوند بینه آلشت بیه(یا سی اندومیا دسه بنی بیه)\nبلگیا یی که هان [[Special:Watchlist|your 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": "سوار کردن نه انجوم شیو بکیت و د ورئردیت جابلگ سوارکرد",
        "uploaderror": "خأطا د سوڤار کئردئن",
        "upload-recreate-warning": "'''زئنار: جانیایی که وه ای نومه یا پاکسا بیه یا جا وه جا بیه.'''\n\nسی رائتی، نومگه پاکساگری و جا وه جا کردن ای بلگه ها د هار:",
        "uploadtext": "دسی دئین جانیایی که  د دماتر سوار بینه روئیت وه  [[Special:FileList|نوم گه جانیایا]] . د نو سوارکردن  د [[Special:Log/upload|نوم گه سوارکردیا]] و پاکساگری جانیایا د [[Special:Log/delete|deletion log]] جاگئر موئه.\n\nنها یه که یه گل جانیانه سوار کردیت می تونیت د سه شکل هاری وه نه د بلگه یا وه کار بونیت:\n*'''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' سی به کار بسن نسقه کامل جانیا\n*'''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|alt text]]</nowiki></code>''' سی به کار بسن یه گل نسقه 200 پیکسلی  د جانیایی که ها د مینه یه گل جعوه  که ها د لا چپ نیسسه که د وه عبارت alt text چی توضیح وه کار بسه بیه\n*'''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' سی دروس کردن یه گل هوم پیوند سرراست وه جانیا بی یه که جانیا دیاری بکه نوم بلگه هاری سی سوارکرد جانیایا تازه استفاده بکیت.",
-       "upload-permitted": "جورا جانیا صلادار:$1 .",
-       "upload-preferred": "جورا جانیا صلادار:$1.",
+       "upload-permitted": "جوٙرا صئلادار جانیا {{PLURAL:$2|جوٙر|جوٙرا}}:$1 .",
+       "upload-preferred": "جوٙرا حاستئنی جانیا {{PLURAL:$2|جوٙر|جوٙرا}}:$1 .",
        "upload-prohibited": "جورا جانیا صلادار:$1{{PLURAL:$2|.}}",
        "uploadlogpage": "سوارکرد",
        "uploadlogpagetext": "نومگه هاری یه گل نومگه د آخری سوارکرد جانیایا هئ.\nسی د نو سیل کردن[[Special:NewFiles|عسگدونی جانیایا تازه نه]] به ونیت.",
        "upload-too-many-redirects": "ای یو آر ال د ورگیرنه واگردونیا فرئی هئ",
        "upload-http-error": "یه گل خطا اچ تی تی پی پیش اومائه:$1",
        "upload-copy-upload-invalid-domain": "ورداشتن سوارکردیا د ای پوشگئر د دسرس نئ.",
+       "upload-dialog-title": "سوڤار کئردئن جانیا",
+       "upload-dialog-button-cancel": "أنجوم شیڤئسئن",
        "upload-dialog-button-done": "أنجوم بییە",
        "upload-dialog-button-save": "ئمایە کئردئن",
        "upload-dialog-button-upload": "سوڤار کئردئن",
+       "upload-form-label-select-file": "ئنتئخاڤ جانیا",
+       "upload-form-label-infoform-title": "جوزئیات",
        "upload-form-label-infoform-name": "نوم",
        "upload-form-label-infoform-description": "توضی",
        "upload-form-label-usage-title": "ڤئ کار گئرئتئن",
        "upload-form-label-usage-filename": "نوم جانیا",
+       "foreign-structured-upload-form-label-own-work": "یە کار مئنە",
+       "foreign-structured-upload-form-label-infoform-categories": "دأسە یا",
+       "foreign-structured-upload-form-label-infoform-date": "گات",
        "backend-fail-stream": "نبوئه جانیا\"$1\" کل بوئه.",
        "backend-fail-backup": "نبوئه سی \"$1\" پشتجا گرت.",
        "backend-fail-notexists": "جانیا $1 وجود ناره.",
        "randomincategory-nopages": "هیچ بلگه ای د ای  [[:دسه نئ:$1|$1]] دسه.",
        "randomincategory-category": "دأسە:",
        "randomincategory-legend": "بلگه بختی د ای دسه",
+       "randomincategory-submit": "رو",
        "randomredirect": "واگردونی بختکی",
        "randomredirect-nopages": "هیچ واگردونی د نومجا \"$1\" نئ.",
        "statistics": "آماريا",
        "booksources-text": "د هار نومگه ای د هوم پیوندیا د دیارگه یا هنی اومائه که کتاویا نو و دس دوئم می فروشن، و همچنو شایت دونسمنیا بیشتری راجع وه کتاو حاستنی شما داشتوئن:",
        "booksources-invalid-isbn": "شازک که دئه بیه معتور نئ؛ وارسی خطایا د گات ؤرداشتن د سرچشمه اولی وه کار گرته بوئه.",
        "specialloguserlabel": "انجومکار:",
-       "speciallogtitlelabel": "حاستنی(موضوع یا کاریار):",
+       "speciallogtitlelabel": "حاستئنی(داسوٙن یا نوم کاریاری سی کاریار):",
        "log": "پهرستنومه یا",
        "all-logs-page": "همه پهرستنومه یا عمومی",
        "alllogstext": "نماشت یه جا همه پهرستنومه یا که هان د{{SITENAME}}.\nمی تونید وا انتخاو نوع پهرستنومه، نوم کاریاری(حساس وه کؤچکی و گپی حرفیا) و بلگه یا آلشت کرده(حساس و گپی و کؤچکی حرنیا) نمایشت نه دیر د ویرتر بکیت.\n\n{{SITENAME}}.",
        "listgrouprights-members": "(نوم گه اندومیا)",
        "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
        "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
-       "listgrouprights-addgroup": "{{PLURAL:$2|گرویا|گرویا}} اضاف بکیتو: $1",
-       "listgrouprights-removegroup": "{{PLURAL:$2|گرویا|گرویا}} ورداریت: $1",
-       "listgrouprights-addgroup-all": "همه گرویا نه اضاف کو",
-       "listgrouprights-removegroup-all": "همه گرویا نه وردار",
-       "listgrouprights-addgroup-self": " {{PLURAL:$2|گروه|گرویا}} نه د حساو: $1 اضاف کو",
-       "listgrouprights-removegroup-self": "{{PLURAL:$2|گرویا|گرویا}} نه د حساو ورداریت: $1",
+       "listgrouprights-addgroup": "{{PLURAL:$2|جأرغە|جأرغە یا}} نە ئضاف بأکیت: $1",
+       "listgrouprights-removegroup": "{{PLURAL:$2|جأرغە|جأرغە یا}} نە ڤئرداریت: $1",
+       "listgrouprights-addgroup-all": "هأمە جأرغە یا نە ئضاف کو",
+       "listgrouprights-removegroup-all": "ھأمە جأرغە یا نە ڤئردار",
+       "listgrouprights-addgroup-self": " {{PLURAL:$2|جأرغە|جأرغە یا}} نە د حئساڤ: $1 ئضاف کو",
+       "listgrouprights-removegroup-self": " {{PLURAL:$2|جأرغە|جأرغە یا}} نە د حئساڤ: $1 ڤئرداریت",
        "listgrouprights-addgroup-self-all": "همه گرویا نه د حساو خوشو اضاف بکیت",
        "listgrouprights-removegroup-self-all": "همه گرویا نه د حساو خوشو ورداریت",
        "listgrouprights-namespaceprotection-header": "محدودیت نومجا",
        "listgrouprights-namespaceprotection-namespace": "نوم جا",
        "listgrouprights-namespaceprotection-restrictedto": "دسرسیا مجاز کاریار سی ویرایشت",
-       "trackingcategories": "دما گری دسه یا",
+       "trackingcategories": "دئماگئری دأسە یا",
        "trackingcategories-summary": "ای بلگه نومگه دسه یایی دماگری بیه ئه که وه شکل خودانجوم وه دس ویکی وارسگر پر بوئن . نومیا ونو نها آلشت کردن پیغومیا سامونه ای مرتوط د نومجا {{ns:8}} آلشت دئه با.",
-       "trackingcategories-msg": "دماگری دسه",
-       "trackingcategories-name": "نوم پیغوم",
+       "trackingcategories-msg": "دئماگئری دأسە",
+       "trackingcategories-name": "نوم پئیغوم",
        "trackingcategories-desc": "جادیارکنیا گنجایشت دسه",
        "noindex-category-desc": "ای بلگه وا رباتیا نومگه کاری نبیه و سی یه کلیمه یا جادویی <code><nowiki>__NOINDEX__</nowiki></code> د وه یا د جاگه ای که بیرق مجازه دش هئ.",
        "index-category-desc": "ای بلگه<code><nowiki>__INDEX__</nowiki></code> که ها دش(و د نومجایی یه که بیرق دش مجازه)،  سی یه وا رباط ما مشگلی ناره که وه شکل عادی نباید با.",
        "trackingcategories-disabled": "دسه ناکشتگر بیه",
        "mailnologin": "هیپچ نشونی یی کل نبیه",
        "mailnologintext": "سی کل کردن انجومانامه وه کاریاریا هنی واس [[Special:UserLogin|بیایت وامین سامونه]] و تیرنشون انجومانامه معتوری د [[Special:Preferences|ترجیحات]] خوتو داشتوئیت.",
-       "emailuser": "اÛ\8c Ù\83ارÙ\88ر Ù\86Ù\87 Ø§Ù\8aÙ\85Ù\8aÙ\84 Ù\83و",
-       "emailuser-title-target": "اÛ\8cÙ\85Û\8cÙ\84 Ø³Û\8c Ø§Û\8c {{GENDER:$1|کارÙ\88ر}}",
+       "emailuser": "ئÛ\8c Ú©Ø§Ø±Û\8cار Ù\86Û\95 Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 Ú©Ø§Ø±Û\8c Ú©و",
+       "emailuser-title-target": "Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 Ø³Û\8c Ø¦Û\8c {{GENDER:$1|کارÛ\8cار}}",
        "emailuser-title-notarget": "أنجومانامە کاریار",
        "emailpagetext": "شما می تونیت  نوم بلگه هار نه سی کل کردن یه گل انجومانامه وه ای  {{GENDER:$1|کاریار}} وه کار بئیرت.\nتیرنشون انجومانامه یی که د [[Special:Preferences|ترجیحات کاریارتو]] دئیه ته د تیرنشون کلکار انجومانامه میا، سی یه که گیرنه بتونه جواوش بیه.",
        "defemailsubject": "{{نوم سیل جا}} ایمیل د کارور \"$1\"",
        "email-legend": "کل کردن یه گل انجومانامه سی یه گل کاریار هنی د {{SITENAME}}",
        "emailfrom": "د:",
        "emailto": "سی:",
-       "emailsubject": "سرÙ\88ن:",
-       "emailmessage": "پيغوم:",
-       "emailsend": "كل بيه",
+       "emailsubject": "داسÙ\88Ù\99ن:",
+       "emailmessage": "پئیغوٙم",
+       "emailsend": "کئل کئردئن",
        "emailccme": "یه گل وردار د پیغوم مه نه سیم ایمیل کو.",
        "emailccsubject": " پیغومتو سی $1:$2 ورداشته بی",
-       "emailsent": "اÛ\8cÙ\85Û\8cÙ\84 Ú©Ù\84 Ø¨Û\8cÙ\87",
+       "emailsent": "Ø£Ù\86جÙ\88Ù\85اÙ\86اÙ\85Û\95 Ú©Ø¦Ù\84 Ø¨Û\8cÛ\8cÛ\95",
        "emailsenttext": "پیغوم ایمیلی تو کل بیه.",
        "emailuserfooter": "ای انجومانامه وا به کار گرتن د خصوصیت \"کل کردن نومه د ای کاریار\"{{SITENAME}} وه دس $1 به $2 کل بی.",
        "usermessage-summary": "رئتن د سامونه پیغوم",
        "usermessage-editor": "پیغوم فرسن سیستم",
        "usermessage-template": "ویکی وارسگر:پیغوم کاریار",
-       "watchlist": "سیل برگ",
-       "mywatchlist": "سیل برگ",
+       "watchlist": "سئیل بأرگ",
+       "mywatchlist": "سئیل بأرگ",
        "watchlistfor2": "سي $1 $2",
        "nowatchlist": "شما هیچی د سیل برگ خوتو ناریت",
        "watchlistanontext": "لطفن بیایت وامین و ویرایشتیا نه د سیل برگتو سیل بکیت.",
-       "watchnologin": "وارد نبیه",
-       "addwatch": "اضاÙ\81 Ú©Ø±Ø¯Ù\86 Ø¯ Ø³Û\8cÙ\84 Ø¨رگ",
+       "watchnologin": "ھأنی نیوٙماە ڤامین",
+       "addwatch": "ئضاÙ\81 Ú©Ø¦Ø±Ø¯Ø¦Ù\86 Ø¯ Ø³Ø¦Û\8cÙ\84 Ø¨Ø£رگ",
        "addedwatchtext": "بلگه «[[:$1]]» د [[Special:Watchlist|نومگه دماگردی]] شما اضاف بی.\nآلشتیا ای بلگه بلگه چک چنه ری وه ریش د نهاتر د ایچه نومگه کاری بوئه.",
        "addedwatchtext-short": "بلگه \"$1\" وه سیل برگ شما اضاف بیه.",
-       "removewatch": "جا وه جا کردن د سیل برگ",
+       "removewatch": "جا ڤئ جا کئردئن د سئیل بأرگ",
        "removedwatchtext": "بلگه\"[[:$1]]\" د [[Special:سیل برگ|سیل برگ خوتو]] جا وه جا بیه.",
        "removedwatchtext-short": "بلگه \"$1\" د سیل برگ جا وه جا بیه.",
-       "watch": "سيل كردن",
-       "watchthispage": "ديئن ای بلگه",
-       "unwatch": "ديه نبيه",
-       "unwatchthispage": "Ù\88اداشتÙ\86 دیئن",
-       "notanarticle": "مینونه هیچ بلگه ای نئ",
+       "watch": "سئیل کئردئن",
+       "watchthispage": "دیئن ئی بألگە",
+       "unwatch": "دیە نأبییە",
+       "unwatchthispage": "Ù\86ئھاگئرÛ\8c دیئن",
+       "notanarticle": "ھیچ بألگە مینوٙنە داری نی",
        "notvisiblerev": "آخری وانئری که د دس یه کاریار هنی انجوم بیه پاکسا بیه.",
        "watchlist-details": "{{PLURAL:$1|$1 بلگه|$1 بلگیا}} د سیل برگتو هیش بلگه قسه کردن نی.",
-       "wlheader-enotif": "وارسیاری ایمیل فعال بیه.",
+       "wlheader-enotif": "ڤارئسیاری أنجومانامە کونئشتکار بییە.",
        "wlheader-showupdated": "بلگه یایی که د آخرین کرتی که شما دشو دیئن کردیته آلشت بینه د <strong>توپر</strong>نشون دئه بینه",
        "wlnote": "د هار {{PLURAL:$1|آلشت|<strong>$1</strong> آلشتی}} که د {{PLURAL:$2|ساعت|<strong>$2</strong> ساعت}} دماتر انجوم بیه هیئش، ویرگار آخرین واجوری انجام شده موجود است، ویرگار آخری واجوری: $3، $4",
        "wlshowlast": "آخرین$1 ساعتیا $2و روزیا  نشو بیئه",
-       "watchlist-options": "گزÛ\8cÙ\86Û\8cا Ø³Û\8cÙ\84 Ø¨رگ",
-       "watching": "د حال دیئن...",
+       "watchlist-options": "Ú\86Û\8cا Ø³Ø¦Û\8cÙ\84 Ø¨Ø£رگ",
+       "watching": "د حال و بال دیئن...",
        "unwatching": "د حال ندیئن...",
        "watcherrortext": "یه گل اشگال د گات آلشت کردن میزونکاری نومگه سیل برگتو سی «$1» پیش اوما.",
        "enotif_reset": "همه بلگه یا دیئه بینه نشودار بکید",
        "enotif_body_intro_changed": "{{SITENAME}} بلگه $1 د ویرگار $PAGEEDITDATE وه دس {{gender:$2|$2}} آلشت بیه، سی وانئری ایسنی سیل $3 بکیت.",
        "enotif_lastvisited": "همه آلشتیا$1 د اوسه که شما د آخرین بار دیئته بوینیت.",
        "enotif_lastdiff": "سی دیئن ای آلشتیا $1 نه سیل بکیت.",
-       "enotif_anon_editor": "کارو ناشناس$1",
+       "enotif_anon_editor": "کاریار نادیار $1",
        "enotif_body": "$WATCHINGUSERNAME نازار،\n\n$PAGEINTRO $NEWPAGE\n\n\nتوضیح ویراشتکار: $PAGESUMMARY $PAGEMINOREDIT\n\nپیوند گرتن وا ویراشتکار:\nنومه: $PAGEEDITOR_EMAIL\nویکی: $PAGEEDITOR_WIKI\n\nتا گاتی که سر نه دئیته وه بلگه، د حال و بار پیش اومائن ائتمالی کنشتیاری بیشتر، تا گاتی که وا نوم کاریاریتو هایت د سامونه، گوته دیاری سی شما کل نبوئه.\nشما همچنی می تونید د بلگه دماگریا خوتو بیرقیا مربوط وه وارسکاری نه صفر بکیت همچنی می تونیت بیرقیا وارسکاری نه د نو نشوکاری بکیت.\n\nدوسیار شما، سامونه وارسکاری {{SITENAME}}\n\n--\nسی آلشت دئن میزونکاریا نومگه انجومانامه یا گوته دیاری روئیت وه {{canonicalurl:{{#special:EditWatchlist}}}}.\n\nسی آلشت دئن میزونکاری نومگه دماگریاتو روئیت وه {{canonicalurl:{{#special:EditWatchlist}}}}.\n\nسی پاکساکاری بلگه د نومگه دماگریاتو روئیت د $UNWATCHURL.\n\nبازحرد و هومیاری بیشتر:\n$HELPPAGE",
        "created": "دروس بیه",
        "changed": "آلشت بیه",
-       "deletepage": "پاک کردن بلگه",
-       "confirm": "Ù\85ئÙ\83Ù\85 Ù\83ردن",
-       "excontent": "Ù\85Û\8cÙ\86Ù\88Ù\86Ù\87 :\"$1\" بی",
+       "deletepage": "پاکسا کئردئن بألگە",
+       "confirm": "Ù¾Ù\88شت Ø±Ø§Ø³ Ú©Ø§Ø±Û\8c Ú©Ø¦Ø±Ø¯Ø¦ن",
+       "excontent": "Ù\85Û\8cÙ\86Ù\88Ù\99Ù\86Û\95 :\"$1\" بی",
        "excontentauthor": "مینونه بلگه یه بی: «$1» (و تئنا هومیار«[[Special:Contributions/$2|$2]]» بی)",
        "exbeforeblank": "مینونه حالی دمایی:\"$1\" بی",
-       "delete-confirm": "پاکسا Ú©Ø±Ø¯Ù\86\"$1\"",
-       "delete-legend": "پاك كردن",
+       "delete-confirm": "پاکسا Ú©Ø¦Ø±Ø¯Ø¦Ù\86 \"$1\"",
+       "delete-legend": "پاکسا کئردئن",
        "historywarning": "<strong>هشدار:</strong> بلگه یی که شما میهایت پاکساش بکیت دش یه گل ویرگارچه واگرد $1 {{PLURAL:$1|وانئری|وانئریا}} ئه:",
        "confirmdeletetext": "شما د حال و بار پاکسا کردن یه گل بلگه یا عسگ د رسینه جا واگرد همه ویرگارچه ونیت.\nلطف بکیت ای کنشتکاری نه پشت راسکاری بکیت و یه دل بوئیت که سرانجوم ای کار نه دونیت و ای کار نه مطابق وا [[{{MediaWiki:Policy-url}}|سیاستیا]] انجوم دئیته.",
        "actioncomplete": "عملكرد كامل بيه",
        "dellogpagetext": "نومگه هاری یه گل نومگه د آخری چیا پاکسا بیه هئ.",
        "deletionlog": "پهرستنومه پاک بیئن",
        "reverted": "لرسه د نزیکترین وانئری",
-       "deletecomment": "دليل:",
-       "deleteotherreason": "دليليا هنی:",
-       "deletereasonotherlist": "دلیل هنی",
+       "deletecomment": "دألیل:",
+       "deleteotherreason": "دألیلیا/ئضافی ھأنی:",
+       "deletereasonotherlist": "دألیل ھأنی",
        "deletereason-dropdown": "* دلیلیا پاکسا کردن رسم بیه\n** اسپم\n** خراوکاری\n** رعایت نبین کپی رایت\n** درحاست نیسنه\n** نهاورگشت شکست حرده",
        "delete-edit-reasonlist": "دلیلیا پاکسا کردنه نه ویرایشت بکید",
        "delete-toobig": "ای بلگه ویرگارچه ویرایشت فره گپی داره، که د ور گرته بیشتر د یه گل د $1 {{PLURAL:$1|نسقه|نسقه}} ئه.\nسی یه که د اختلال ناحاستنی د {{SITENAME}} نهاگری با پاکسا کردن ای جوراین بلگه یا محدود بیه.c",
        "rollback-success": "ویرایشتیا $1 پاکساگری بی؛\nبلگه وه آخری ویرایشت $2 آلشت بیه.",
        "sessionfailure-title": "شکست حردن نشینگه",
        "sessionfailure": "چنی وه نظری میا که مشکلی ها د نشتجا کاریاری شما؛\nانجومگر حاستنی سی یه که د دزیه بیین دونسمنیاتو د نشسجا کاریاری نهاگری با انجومشیو بیه.\nلطف بکیت د ری دگمه ؤرئشتن بپورنیت و بلگه یی که رسسینه دش د نو واحونی بکیت، اوسه دنو تلاش بکیت.",
+       "changecontentmodel-title-label": "داسوٙن بألگە",
+       "changecontentmodel-reason-label": "دألیل:",
        "protectlogpage": "پر و پیم کاری کردن",
        "protectlogtext": "د هار یه گل نومگه د آلشتیا ریتراز پر و پیم کاری بلگه یا اومائه.\n[[Special:ProtectedPages|نومگه بلگه یا پر و پیم کار بیه]] نه سی دیئن نومگه پر و پیم کاری کارگرا بلگه یا سیل بکیت.",
        "protectedarticle": "حفاظت بيه [[$1]]",
        "protect-norestrictiontypes-text": "امکان پر و پیم کردن ای بلگه سی یه که نوع محدودیتی ناره، وجود ناره.",
        "protect-norestrictiontypes-title": "بلگه بی حامین گر",
        "protect-legend": "پشت راس کردن حامین گری",
-       "protectcomment": "دلیل:",
+       "protectcomment": "دألیل:",
        "protectexpiry": "تموم بين:",
        "protect_expiry_invalid": "گات تموم بیین نامعتوره.",
        "protect_expiry_old": "گات تموم بیین مال دماتره.",
index c384743..1c56659 100644 (file)
        "helppage-top-gethelp": "Pagalba",
        "mainpage": "Pagrindinis puslapis",
        "mainpage-description": "Pagrindinis puslapis",
-       "policy-url": "Projektas:Politika",
+       "policy-url": "Project:Politika",
        "portal": "Bendruomenė",
        "portal-url": "Project:Community portal",
        "privacy": "Privatumo politika",
        "createaccountreason": "Priežastis:",
        "createacct-reason": "Priežastis",
        "createacct-reason-ph": "Kodėl kuriate kitą paskyrą",
-       "createacct-captcha": "Saugumo patikrinimas",
-       "createacct-imgcaptcha-ph": "Įveskite tekstą, kurį matote aukščiau",
        "createacct-submit": "Sukurkite savo paskyrą",
        "createacct-another-submit": "Sukurti paskyrą",
        "createacct-benefit-heading": "{{SITENAME}} sukurtas žmonių kaip jūs.",
index b00c78c..2620562 100644 (file)
        "createaccountreason": "Iemesls:",
        "createacct-reason": "Iemesls",
        "createacct-reason-ph": "Kāpēc jūs veidojat citu kontu",
-       "createacct-captcha": "Drošības pārbaude",
-       "createacct-imgcaptcha-ph": "Ievadiet tekstu, kuru jūs redzat augstāk",
        "createacct-submit": "Izveidot savu kontu",
        "createacct-another-submit": "Izveidot citu dalībnieka kontu",
        "createacct-benefit-heading": "{{SITENAME}} darbojas ar tādu cilvēku kā Tu ieguldījumu.",
index 1112215..1e22c00 100644 (file)
        "createaccountreason": "Причина:",
        "createacct-reason": "Причина",
        "createacct-reason-ph": "Зошто правите друга сметка",
-       "createacct-captcha": "Безбедносна проверка",
-       "createacct-imgcaptcha-ph": "Внесете го гореприкажаниот текст",
        "createacct-submit": "Направи ја",
        "createacct-another-submit": "Создај сметка",
        "createacct-benefit-heading": "{{SITENAME}} е дело на луѓе како вас.",
index 513811e..dfdcc75 100644 (file)
        "createaccountreason": "കാരണം:",
        "createacct-reason": "കാരണം",
        "createacct-reason-ph": "താങ്കൾ എന്തുകൊണ്ടാണ് മറ്റൊരു അംഗത്വം എടുക്കുന്നത്",
-       "createacct-captcha": "സുരക്ഷാ പരിശോധന",
-       "createacct-imgcaptcha-ph": "മുകളിൽ കാണുന്ന എഴുത്ത് ഇവിടെ നൽകുക",
        "createacct-submit": "താങ്കളുടെ അംഗത്വം സൃഷ്ടിക്കുക",
        "createacct-another-submit": "അംഗത്വമെടുക്കുക",
        "createacct-benefit-heading": "താങ്കളെപ്പോലെയുള്ളവരാണ്  {{SITENAME}}  പടുത്തുയർത്തിയിരിക്കുന്നത്.",
index 7248767..cbcc170 100644 (file)
        "createaccountreason": "कारण:",
        "createacct-reason": "कारण",
        "createacct-reason-ph": "आपण दुसरे खाते कां बनवीत आहात",
-       "createacct-captcha": "सुरक्षा तपासणी",
-       "createacct-imgcaptcha-ph": "वर दिसत असलेला मजकूर येथे टाका",
        "createacct-submit": "आपले खाते निर्माण करा",
        "createacct-another-submit": "खाते तयार करा",
        "createacct-benefit-heading": "{{SITENAME}} हे आपल्यासारख्याच लोकांनी बनविलेले आहे.",
index e52f060..04b324e 100644 (file)
        "createaccountreason": "Mutivo:",
        "createacct-reason": "Mutivo",
        "createacct-reason-ph": "Pecché staje crianno n'at'utenza",
-       "createacct-captcha": "Cuntrollo 'e sicurezza",
-       "createacct-imgcaptcha-ph": "Scrivite 'o testo ca vedite ncoppa",
        "createacct-submit": "Cria 'a toja utenza",
        "createacct-another-submit": "Cria nu cunto",
        "createacct-benefit-heading": "{{SITENAME}} è fatta 'e perzone comme te.",
index bcf8572..4adc0c0 100644 (file)
        "createaccountreason": "Årsak:",
        "createacct-reason": "Årsak",
        "createacct-reason-ph": "Hvorfor lager du en annen bruker",
-       "createacct-captcha": "Sikkerhetssjekk",
-       "createacct-imgcaptcha-ph": "Fyll inn teksten du ser ovenfor",
        "createacct-submit": "Opprett konto",
        "createacct-another-submit": "Opprett konto",
        "createacct-benefit-heading": "{{SITENAME}} er laget av folk som deg.",
index d258392..df18827 100644 (file)
        "createaccountreason": "Reden:",
        "createacct-reason": "Reden",
        "createacct-reason-ph": "Waarom u een andere account aanmaakt",
-       "createacct-captcha": "Veiligheidscontrole",
-       "createacct-imgcaptcha-ph": "Geef de tekst in die u hierboven ziet",
        "createacct-submit": "Account aanmaken",
        "createacct-another-submit": "Account aanmaken",
        "createacct-benefit-heading": "{{SITENAME}} wordt gemaakt door mensen zoals u.",
        "watchlistanontext": "Om uw volglijst te bekijken of te bewerken moet u zich aanmelden.",
        "watchnologin": "U bent niet aangemeld",
        "addwatch": "Toevoegen aan volglijst",
-       "addedwatchtext": "\"[[:$1]]\" de overlegpagina zijn toegevoegd aan uw [[Special:Watchlist|volglijst]].",
+       "addedwatchtext": "\"[[:$1]]\" en de bijhorende overlegpagina zijn toegevoegd aan uw [[Special:Watchlist|volglijst]].",
        "addedwatchtext-short": "De pagina \"$1\" is aan uw volglijst toegevoegd.",
        "removewatch": "Verwijderen uit volglijst",
        "removedwatchtext": "\"[[:$1]]\" en de overlegpagina zijn verwijderd van [[Special:Watchlist|uw volglijst]].",
index ebe1506..56ee506 100644 (file)
        "createaccountreason": "Syy:",
        "createacct-reason": "Syy",
        "createacct-reason-ph": "Mindäh olet luadimas tostu käyttäitilii",
-       "createacct-captcha": "Turvallizusvarmistus",
-       "createacct-imgcaptcha-ph": "Kirjuta ylähän olii tekstu",
        "createacct-submit": "Luaji tili",
        "createacct-another-submit": "Luaji tili",
        "createacct-benefit-heading": "Sivun {{SITENAME}} ollah luajittu sinunjyttymät rahvas.",
index dd04a2b..380d8d5 100644 (file)
        "createaccountreason": "Powód:",
        "createacct-reason": "Powód",
        "createacct-reason-ph": "Dlaczego zakładasz kolejne konto",
-       "createacct-captcha": "Kontrola bezpieczeństwa",
-       "createacct-imgcaptcha-ph": "Wpisz tekst widoczny powyżej",
        "createacct-submit": "Utwórz konto",
        "createacct-another-submit": "Utwórz konto",
        "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzą ludzie tacy jak Ty.",
index 147f55f..192b927 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por que você está criando outra conta",
-       "createacct-captcha": "Verificação de segurança",
-       "createacct-imgcaptcha-ph": "Digite o texto acima",
        "createacct-submit": "Crie sua conta",
        "createacct-another-submit": "Criar conta",
        "createacct-benefit-heading": "{{SITENAME}} é feita por pessoas como você.",
index b034a2e..3a30202 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Porque está a criar outra conta",
-       "createacct-captcha": "Verificação de segurança",
-       "createacct-imgcaptcha-ph": "Digite o texto que vê acima",
        "createacct-submit": "Crie a sua conta",
        "createacct-another-submit": "Criar conta",
        "createacct-benefit-heading": "{{SITENAME}} é feito por pessoas como você.",
index 3592562..17d259d 100644 (file)
        "createaccountreason": "Since 1.22 no longer used in core, but may be used by some extensions. DEPRECATED\n\n{{Identical|Reason}}",
        "createacct-reason": "In create account form, label for field to enter reason to create an account when already logged-in.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Reason}}",
        "createacct-reason-ph": "Placeholder in vertical-layout create account form for reason field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
-       "createacct-captcha": "Label in vertical-layout create account form for CAPTCHA input field when repositioned by JavaScript.",
        "createacct-imgcaptcha-help": "{{Optional}} Optional help text in vertical-layout create account form for image CAPTCHA input field when repositioned by JavaScript.\n\nBlank by default.",
-       "createacct-imgcaptcha-ph": "Placehodler text in vertical-layout create account form for image CAPTCHA input field when repositioned by JavaScript.",
        "createacct-submit": "Submit button on vertical-layout create account form.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
        "createacct-another-submit": "Submit button of  [[Special:UserLogin/signup]] ([[Special:CreateAccount]]) when accessed by a registered user.\n\nThe original means \"create an account in addition to the one you already have\"; sometimes, but not always, it means you are going to \"Create the account on behalf of somebody else\" or \"Create account for another\".\n{{Identical|Create another account}}",
        "createacct-benefit-heading": "In vertical-layout create account form, the heading for the section describing the benefits of creating an account. See example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n\nIf in your language you need to know the gender of the name for the wiki (which is the subject of the English sentence), please adapt the sentence as much as you need for your translation to fit.",
index f5c9600..38f16df 100644 (file)
        "createaccountreason": "Mutive:",
        "createacct-reason": "Mutive",
        "createacct-reason-ph": "Purcé tu ste ccreje 'n'otre cunde utende?",
-       "createacct-captcha": "Verifiche de securezze",
-       "createacct-imgcaptcha-ph": "Mitte 'u teste tune aqquà sus",
        "createacct-submit": "Ccreje 'u cunde utende tune",
        "createacct-another-submit": "Ccreje 'nu cunde utende",
        "createacct-benefit-heading": "{{SITENAME}} jè fatte da crestiane cumme a te.",
index bbb58e8..389f63d 100644 (file)
        "createaccountreason": "Причина:",
        "createacct-reason": "Причина",
        "createacct-reason-ph": "Зачем вы создаёте другую учетную запись",
-       "createacct-captcha": "Проверка безопасности",
-       "createacct-imgcaptcha-ph": "Введите текст, который вы видите выше",
        "createacct-submit": "Создать учётную запись",
        "createacct-another-submit": "Создать учётную запись",
        "createacct-benefit-heading": "{{SITENAME}} — совместный труд таких же людей, как вы.",
index bbd3272..e7b82af 100644 (file)
        "createaccountreason": "Төрүөтэ:",
        "createacct-reason": "Төрүөтэ",
        "createacct-reason-ph": "Саҥа аатынан тоҕо киирэҕиний",
-       "createacct-captcha": "Куттал суох буолуутун тургутуу",
-       "createacct-imgcaptcha-ph": "Үөһэ көстөрү хатылаа",
        "createacct-submit": "Бэлиэтэнии",
        "createacct-another-submit": "Бэлиэтэн",
        "createacct-benefit-heading": "{{SITENAME}} ситим-сири эн курдук дьон оҥороллор.",
index 1be5ee8..7cbbda8 100644 (file)
        "createaccountreason": "Raison:",
        "createacct-reason": "Raison",
        "createacct-reason-ph": "Why ar ye creating anither accoont",
-       "createacct-captcha": "Security check.",
-       "createacct-imgcaptcha-ph": "Enter the tex ye see abuin",
        "createacct-submit": "Mak yer accoont",
        "createacct-another-submit": "Mak anither accoont",
        "createacct-benefit-heading": "{{SITENAME}} is makit bi fowk like ye.",
index 09a87de..f8ff64b 100644 (file)
        "createaccountreason": "سبب:",
        "createacct-reason": "سبب",
        "createacct-reason-ph": "توهان ٻيو کاتو ڇو کولي رهيا آهيو",
-       "createacct-captcha": "حفاظتي روڪ",
-       "createacct-imgcaptcha-ph": "مٿي ظاهر ٿيندڙ ٽيڪسٽ ٽائيپ ڪريو",
        "createacct-submit": "پنهنجو کاتو کوليو",
        "createacct-another-submit": "کاتو کوليو",
        "createacct-benefit-heading": "{{SITENAME}} توهان جهڙن سڄڻن ٺاهيو آهي.",
        "summary-preview": "تت تي پيش نگاهہ:",
        "subject-preview": "موضوع پيش نگاهہ:",
        "blockedtitle": "يُوزر بندشيل آهي.",
-       "blockedtext": "'''توهان جي يوزرنانءُ يا آءِ پي کي بندشيو ويو آهي.'''\n\nبندش $1 هنئي. جڏهن تہ ڄاڻايل سبب ''$2'' آهي.\n\n\n* بندش جو آغاز: $8\n* بندش جو انجام: $6\n* بندش جو هدف: $7\n\nاهڙي روڪ تي بحث ڪرڻ لاءِ توهان $1 يا ڪنهن ٻي [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سان رابطو ڪري سگھو ٿا. جيڪڏهن توهان جو درست [[Special:ترجيحات|کاتو ترجيحات]] ۾ درست برق ٽپال پتو درج ٿيل نہ آهي تہ توهان 'هن يوزر کي برق ٽپال ڪريو' وارو فيچر نہ ٿا \nYou cannot use the 'e-mail this user' feature unless a valid e-mail address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nاستعمال ڪري سگھو. توهان جو هاڻوڪو آءِ پي پتو $3 آهي، ۽ بندش سڃاڻپ $5 آهي. مهرباني ڪري ڪنهن به پڇا ڳاڇا يا لهوچڙ لاءِ انهن مان ڪنهن هڪ يا ٻنهي جو حوالو ڏيندا.",
+       "blockedtext": "'''توهان جي يوزرنانءُ يا آءِ پي کي بندشيو ويو آهي.'''\n\nبندش $1 هنئي. جڏهن تہ ڄاڻايل سبب ''$2'' آهي.\n\n\n* بندش جو آغاز: $8\n* بندش جو انجام: $6\n* بندش جو هدف: $7\n\nاهڙي روڪ تي بحث ڪرڻ لاءِ توهان $1 يا ڪنهن ٻي [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سان رابطو ڪري سگھو ٿا. جيڪڏهن توهان جو درست [[Special:ترجيحات|کاتو ترجيحات]] ۾ درست برق ٽپال پتو درج ٿيل نہ آهي تہ توهان 'هن يوزر کي برق ٽپال ڪريو' وارو فيچر نہ ٿا \nYou cannot use the 'e-mail this user' feature unless a valid e-mail address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nاستعمال ڪري سگھو. توهان جو هاڻوڪو آءِ پي پتو $3 آهي، ۽ بندش سڃاڻپ $5 آهي. مهرباني ڪري ڪنهن بہ پڇا ڳاڇا يا لهوچڙ لاءِ انهن مان ڪنهن هڪ يا ٻنهي جو حوالو ڏيندا.",
        "blockednoreason": "سبب اڻڄاڻايل",
        "loginreqtitle": "لاگ اِن گھربل آهي",
        "loginreqlink": "لاگ اِن",
index f988e8c..160d151 100644 (file)
        "createacct-realname": "Tėkros vardos (nie būtėns)",
        "createaccountreason": "Dingstės:",
        "createacct-reason": "Dingstės",
-       "createacct-captcha": "Sargtėis patikrėnėms",
-       "createacct-imgcaptcha-ph": "Ožrašīkėt ženklus, katrus veizėt viršō",
        "createacct-submit": "Padėrbkat savėi paskīra",
        "createacct-another-submit": "Padėrbtė kėta paskīra",
        "createacct-benefit-heading": "{{SITENAME}} īr sokorta prietėliu, tuokiu, kāp Tamsta.",
index f293154..a9336da 100644 (file)
        "createaccountreason": "Razlog:",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zakaj ustvarjate drug račun",
-       "createacct-captcha": "Varnostno preverjanje",
-       "createacct-imgcaptcha-ph": "Vnesite zgornje besedilo",
        "createacct-submit": "Ustvarite svoj račun",
        "createacct-another-submit": "Ustvarite račun",
        "createacct-benefit-heading": "{{GRAMMAR:tožilnik|{{SITENAME}}}} ustvarjajo ljudje, kot ste vi.",
index 77c6418..3e9f302 100644 (file)
        "createaccountreason": "Разлог:",
        "createacct-reason": "Разлог",
        "createacct-reason-ph": "Зашто правите још један налог?",
-       "createacct-captcha": "Сигурносна провера",
-       "createacct-imgcaptcha-ph": "Унесите текст који видите изнад",
        "createacct-submit": "Отвори налог",
        "createacct-another-submit": "Отвори налог",
        "createacct-benefit-heading": "{{SITENAME}} је направљен од стране људи као што сте ви.",
        "right-applychangetags": "примењивање [[Special:Tags|ознака]] на нечије измене",
        "right-changetags": "додавање и уклањање разних [[Special:Tags|ознака]] на појединачним изменама и уносима у дневницима",
        "newuserlogpage": "Дневник нових корисника",
-       "newuserlogpagetext": "Ð\9eво Ñ\98е Ð¸Ñ\81Ñ\82оÑ\80иÑ\98а нових корисника.",
+       "newuserlogpagetext": "Ð\9eво Ñ\98е Ð´Ð½ÐµÐ²Ð½Ð¸Ðº нових корисника.",
        "rightslog": "Дневник корисничких права",
        "rightslogtext": "Ово је дневник измена корисничких права.",
        "action-read": "читање ове странице",
        "log": "Дневници",
        "all-logs-page": "Сви јавни дневници",
        "alllogstext": "Скупни приказ свих доступних историја овог викија.\nМожете сузити приказ одабирући врсту историје, корисничког имена или тражене странице.",
-       "logempty": "Ð\9dема Ð¿Ñ\80онаÑ\92ениÑ\85 Ñ\81Ñ\82авки Ñ\83 Ð¸Ñ\81Ñ\82оÑ\80иÑ\98и.",
+       "logempty": "Ð\9dема Ð¿Ñ\80онаÑ\92ениÑ\85 Ñ\83ноÑ\81а Ñ\83 Ð´Ð½ÐµÐ²Ð½Ð¸ÐºÑ\83.",
        "log-title-wildcard": "Тражи наслове који почињу с овим текстом",
        "showhideselectedlogentries": "Прикажи/сакриј изабране догађаје",
        "log-edit-tags": "Уреди ознаке изабраних уноса у дневницима",
        "logentry-managetags-activate": "$1 је {{GENDER:$2|активирао|активирала}} ознаку „$4“ за употребу од стране корисника и ботова",
        "logentry-managetags-deactivate": "$1 је {{GENDER:$2|деактивирао|деактивирала}} ознаку „$4“ за употребу од стране корисника и ботова",
        "log-name-tag": "Дневник ознака",
+       "log-description-tag": "Овај дневник приказује додавање/уклањање [[Special:Tags|ознака]] на појединачне измене или уносе у дневницима. Овај дневник не приказује означавање када су она део уређивања, брисања или неке друге радње.",
        "rightsnone": "(нема)",
        "revdelete-summary": "опис измене",
        "feedback-adding": "Додајем повратну информацију на страницу…",
        "feedback-bugornote": "Ако сте спремни да детаљно опишете технички проблем, онда [$1 пријавите грешку].\nУ супротном, послужите се једноставним обрасцем испод. Ваш коментар ће стајати на страници „[$3 $2]“, заједно с корисничким именом и прегледачем који користите.",
        "feedback-cancel": "Откажи",
        "feedback-close": "Урађено",
+       "feedback-external-bug-report-button": "Пријави баг",
        "feedback-error-title": "Грешка",
        "feedback-error1": "Грешка: непрепознат резултат од АПИ-ја",
        "feedback-error2": "Грешка: уређивање није успело",
index b9c646c..dc066da 100644 (file)
        "createaccountreason": "Razlog:",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zašto pravite još jedan nalog?",
-       "createacct-captcha": "Sigurnosna provera",
-       "createacct-imgcaptcha-ph": "Unesite tekst koji vidite iznad",
        "createacct-submit": "Otvori nalog",
        "createacct-another-submit": "Otvori nalog",
        "createacct-benefit-heading": "{{SITENAME}} je napravljen od strane ljudi kao što ste vi.",
        "right-passwordreset": "pregledanje poruka za obnavljanje lozinke",
        "right-managechangetags": "pravljenje i/ili brisanje [[Special:Tags|oznaka]] iz baze podataka",
        "newuserlogpage": "Dnevnik novih korisnika",
-       "newuserlogpagetext": "Ovo je istorija novih korisnika.",
+       "newuserlogpagetext": "Ovo je dnevnik novih korisnika.",
        "rightslog": "Dnevnik korisničkih prava",
        "rightslogtext": "Ovo je dnevnik izmena korisničkih prava.",
        "action-read": "čitanje ove stranice",
        "log": "Dnevnici",
        "all-logs-page": "Svi javni dnevnici",
        "alllogstext": "Skupni prikaz svih dostupnih istorija ovog vikija.\nMožete suziti prikaz odabirući vrstu istorije, korisničkog imena ili tražene stranice.",
-       "logempty": "Nema pronađenih stavki u istoriji.",
+       "logempty": "Nema pronađenih unosa u dnevniku.",
        "log-title-wildcard": "Traži naslove koji počinju s ovim tekstom",
        "showhideselectedlogentries": "Prikaži/sakrij izabrane događaje",
        "allpages": "Sve stranice",
index 5c50e95..1692650 100644 (file)
        "createaccountreason": "Orsak:",
        "createacct-reason": "Orsak",
        "createacct-reason-ph": "Varför du skapar ett annat konto",
-       "createacct-captcha": "Säkerhetskontroll",
-       "createacct-imgcaptcha-ph": "Fyll i texten du ser ovan",
        "createacct-submit": "Skapa ditt konto",
        "createacct-another-submit": "Skapa konto",
        "createacct-benefit-heading": "{{SITENAME}} är skapad av människor som dig.",
index 7d8807d..44d65fb 100644 (file)
        "createaccountreason": "Sebep:",
        "createacct-reason": "Gerekçe",
        "createacct-reason-ph": "Neden başka bir hesap oluşturuyorsunuz",
-       "createacct-captcha": "Güvenlik kontrolü",
-       "createacct-imgcaptcha-ph": "Yukarıda gördüğünüz metni girin",
        "createacct-submit": "Hesabınızı oluşturun",
        "createacct-another-submit": "Hesap oluşturun",
        "createacct-benefit-heading": "{{SITENAME}} sizin gibi insanlar tarafından geliştirilir.",
index 4636d0b..60fddad 100644 (file)
        "createacct-email-ph": "Электрон почта юлламагызны языгыз",
        "createaccountmail": "электрон почта аша",
        "createaccountreason": "Сәбәп:",
-       "createacct-captcha": "Саклыкны тикшерү",
-       "createacct-imgcaptcha-ph": "Өстә күрсәтелгән текстны кертегез",
        "createacct-submit": "Хисап язмасы төзү",
        "createacct-benefit-heading": "{{SITENAME}} — сезнең шикелле кешеләрнең хезмәте.",
        "createacct-benefit-body1": "{{PLURAL:$1|төзәтмә}}",
index 07dbf4b..41e132b 100644 (file)
        "createaccountreason": "Rason:",
        "createacct-reason": "Rason",
        "createacct-reason-ph": "Kay ano nahimo ka hin usa pa nga akawnt",
-       "createacct-captcha": "Pagkita han seguridad",
-       "createacct-imgcaptcha-ph": "Igbutang an sinurat nga nakikita mo ha igbaw",
        "createacct-submit": "Ighimo an im akawnt",
        "createacct-another-submit": "Paghimo hin lain nga akant",
        "createacct-benefit-heading": "{{SITENAME}} in ginhimo hin tawo nga sugad ha imo.",
index 2f86d45..c7704ae 100644 (file)
        "createaccountreason": "אורזאַך:",
        "createacct-reason": "אורזאך",
        "createacct-reason-ph": "פֿארוואס שאפֿט איר נאך א קאנטע",
-       "createacct-captcha": "פארזיכערן קאנטראל",
-       "createacct-imgcaptcha-ph": "קלאפט ארײַן דעם טעקסט איר זעט אויבן",
        "createacct-submit": "שאפֿט אײַער קאנטע",
        "createacct-another-submit": "שאַפֿן קאנטע",
        "createacct-benefit-heading": "{{SITENAME}} איז געמאכט דורך מענטשן ווי איר.",
index dca2e5a..b64ff91 100644 (file)
        "createaccountreason": "原因:",
        "createacct-reason": "原因",
        "createacct-reason-ph": "您为什么要创建另一个账户",
-       "createacct-captcha": "安全检查",
-       "createacct-imgcaptcha-ph": "请输入上图中的文字",
        "createacct-submit": "创建您的账户",
        "createacct-another-submit": "创建账户",
        "createacct-benefit-heading": "{{SITENAME}}是由同你一样的人们构筑的。",
index 6d0175e..125b1b3 100644 (file)
@@ -8,7 +8,7 @@
         * @param {jQuery.Event} e
         */
        function doLivePreview( e ) {
-               var isDiff, api, request, postData, copySelectors, section,
+               var isDiff, api, parseRequest, diffRequest, postData, copySelectors, section,
                        $wikiPreview, $wikiDiff, $editform, $textbox, $summary, $copyElements, $spinner, $errorBox;
 
                isDiff = ( e.target.name === 'wpDiff' );
                api = new mw.Api();
                postData = {
                        action: 'parse',
-                       uselang: mw.config.get( 'wgUserLanguage' ),
                        title: mw.config.get( 'wgPageName' ),
-                       text: $textbox.textSelection( 'getContents' ),
                        summary: $summary.textSelection( 'getContents' ),
-                       sectionpreview: section !== ''
+                       prop: ''
                };
 
-               if ( section === 'new' ) {
-                       postData.section = 'new';
-                       postData.sectiontitle = postData.summary;
-               }
-
                if ( isDiff ) {
                        $wikiPreview.hide();
 
-                       // First PST the input, then diff it
-                       postData.onlypst = true;
-                       request = api.post( postData );
-                       request.done( function ( response ) {
-                               api.post( {
-                                       action: 'query',
-                                       indexpageids: true,
-                                       prop: 'revisions',
-                                       titles: mw.config.get( 'wgPageName' ),
-                                       rvdifftotext: response.parse.text[ '*' ],
-                                       rvprop: [],
-                                       rvsection: section === '' ? undefined : section
-                               } ).done( function ( response ) {
-                                       try {
-                                               var diffHtml = response.query.pages[ response.query.pageids[ 0 ] ]
-                                                       .revisions[ 0 ].diff[ '*' ];
-                                               $wikiDiff.find( 'table.diff tbody' ).html( diffHtml );
-                                       } catch ( e ) {
-                                               // "result.blah is undefined" error, ignore
-                                               mw.log.warn( e );
-                                       }
-                                       $wikiDiff.show();
-                               } );
+                       if ( postData.summary ) {
+                               parseRequest = api.post( postData );
+                       }
+
+                       diffRequest = api.post( {
+                               action: 'query',
+                               indexpageids: true,
+                               prop: 'revisions',
+                               titles: mw.config.get( 'wgPageName' ),
+                               rvdifftotext: $textbox.textSelection( 'getContents' ),
+                               rvdifftotextpst: true,
+                               rvprop: '',
+                               rvsection: section === '' ? undefined : section
+                       } );
+
+                       // Wait for the summary before showing the diff so the page doesn't jump twice
+                       $.when( diffRequest, parseRequest ).done( function ( response ) {
+                               var diffHtml,
+                                       query = response[ 0 ].query;
+                               try {
+                                       diffHtml = query.pages[ query.pageids[ 0 ] ]
+                                               .revisions[ 0 ].diff[ '*' ];
+                                       $wikiDiff.find( 'table.diff tbody' ).html( diffHtml );
+                               } catch ( e ) {
+                                       // "result.blah is undefined" error, ignore
+                                       mw.log.warn( e );
+                               }
+                               $wikiDiff.show();
                        } );
                } else {
                        $wikiDiff.hide();
+
                        $.extend( postData, {
+                               prop: 'text|displaytitle|modules|jsconfigvars|categorieshtml|templates|langlinks|limitreporthtml',
+                               text: $textbox.textSelection( 'getContents' ),
                                pst: true,
                                preview: true,
-                               prop: 'text|displaytitle|modules|jsconfigvars|categorieshtml|templates|langlinks|limitreporthtml',
-                               disableeditsection: true
+                               sectionpreview: section !== '',
+                               disableeditsection: true,
+                               uselang: mw.config.get( 'wgUserLanguage' )
                        } );
-                       request = api.post( postData );
-                       request.done( function ( response ) {
+                       if ( section === 'new' ) {
+                               postData.section = 'new';
+                               postData.sectiontitle = postData.summary;
+                       }
+
+                       parseRequest = api.post( postData );
+                       parseRequest.done( function ( response ) {
                                var li, newList, $displaytitle, $content, $parent, $list;
                                if ( response.parse.jsconfigvars ) {
                                        mw.config.set( response.parse.jsconfigvars );
                                        $wikiPreview.append( $content );
 
                                        $wikiPreview.show();
-
                                }
                        } );
                }
-               request.done( function ( response ) {
-                       var isSubject = ( section === 'new' ),
+               $.when( parseRequest, diffRequest ).done( function ( parseResp ) {
+                       var parse = parseResp && parseResp[ 0 ].parse,
+                               isSubject = ( section === 'new' ),
                                summaryMsg = isSubject ? 'subject-preview' : 'summary-preview',
                                $summaryPreview = $editform.find( '.mw-summary-preview' ).empty();
-                       if ( response.parse.parsedsummary && response.parse.parsedsummary[ '*' ] !== '' ) {
+                       if ( parse && parse.parsedsummary && parse.parsedsummary[ '*' ] !== '' ) {
                                $summaryPreview.append(
                                        mw.message( summaryMsg ).parse(),
                                        ' ',
                                        $( '<span>' ).addClass( 'comment' ).html(
                                                // There is no equivalent to rawParams
                                                mw.message( 'parentheses' ).escaped()
-                                                       .replace( '$1', response.parse.parsedsummary[ '*' ] )
+                                                       .replace( '$1', parse.parsedsummary[ '*' ] )
                                        )
                                );
                        }
                        mw.hook( 'wikipage.editform' ).fire( $editform );
-               } );
-               request.always( function () {
+               } ).always( function () {
                        $spinner.hide();
                        $copyElements.animate( {
                                opacity: 1
                        }, 'fast' );
-               } );
-               request.fail( function ( code, result ) {
-                       var errorMsg = 'API error: ' +  code;
+               } ).fail( function ( code, result ) {
+                       // This just shows the error for whatever request failed first
+                       var errorMsg = 'API error: ' + code;
                        if ( code === 'http' ) {
                                errorMsg = 'HTTP error: ';
                                if ( result.exception ) {
index 2b028ae..15f4e4d 100644 (file)
        font-style: italic;
 }
 
-/* Special:Allpages */
-.mw-allpages-nav {
-       text-align: right;
-       margin-bottom: 1em;
-}
-table.mw-allpages-table-form {
-       width: 100%;
-}
-table.mw-allpages-table-form tr {
-       vertical-align: top;
-}
-
-/* Special:Prefixindex */
-.mw-prefixindex-nav {
-       text-align: right;
-}
-table#mw-prefixindex-nav-table {
-       width: 100%;
-}
-td#mw-prefixindex-nav-form {
-       margin-bottom: 1em;
-       vertical-align: top;
-}
-
 /* Special:Block */
 p.mw-ipb-conveniencelinks {
        font-size: 90%;
index 30f000b..831ba8c 100644 (file)
@@ -38,7 +38,7 @@ section.mw-form-header {
        margin-top: 6px;
 }
 
-.mw-createacct-captcha-container {
+.fancycaptcha-captcha-container {
        background-color: #f8f8f8;
        border: 1px solid #c9c9c9;
        padding: 10px;
@@ -52,7 +52,7 @@ section.mw-form-header {
 }
 
 /* Put a border around the fancycaptcha-image-container. */
-.mw-createacct-captcha-and-reload {
+.fancycaptcha-captcha-and-reload {
        border: 1px solid #c9c9c9;
        /* Other display formats end up too wide */
        display: table-cell;
index 82ccd4f..188af6a 100644 (file)
@@ -5,6 +5,8 @@
        width: 20em;
        line-height: 1.35;
        z-index: 10000;
+       /* Avoid horizontal scrollbar on fade in and on fade out */
+       overflow: hidden;
 }
 
 .mw-notification {
index 577dc3c..622fce2 100644 (file)
@@ -79,6 +79,35 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
                $this->assertInternalType( 'string', $obj->setterArgs[0] );
                $this->assertSame( 'unwrapped', $obj->setterArgs[0] );
        }
+
+       /**
+        * @covers ObjectFactory::constructClassInstance
+        * @dataProvider provideConstructClassInstance
+        */
+       public function testConstructClassInstance( $args ) {
+               $obj = ObjectFactory::constructClassInstance(
+                       'ObjectFactoryTestFixture', $args
+               );
+               $this->assertSame( $args, $obj->args );
+       }
+
+       public function provideConstructClassInstance() {
+               // These args go to 11. I thought about making 10 one louder, but 11!
+               return array(
+                       '0 args' => array( array() ),
+                       '1 args' => array( array( 1, ) ),
+                       '2 args' => array( array( 1, 2, ) ),
+                       '3 args' => array( array( 1, 2, 3, ) ),
+                       '4 args' => array( array( 1, 2, 3, 4, ) ),
+                       '5 args' => array( array( 1, 2, 3, 4, 5, ) ),
+                       '6 args' => array( array( 1, 2, 3, 4, 5, 6, ) ),
+                       '7 args' => array( array( 1, 2, 3, 4, 5, 6, 7, ) ),
+                       '8 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, ) ),
+                       '9 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, ) ),
+                       '10 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ),
+                       '11 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ) ),
+               );
+       }
 }
 
 class ObjectFactoryTestFixture {
diff --git a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
new file mode 100644 (file)
index 0000000..94b74cb
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+/**
+ * @author Matthias Mullie <mmullie@wikimedia.org>
+ * @group BagOStuff
+ */
+class BagOStuffTest extends MediaWikiTestCase {
+       /** @var BagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+
+               // type defined through parameter
+               if ( $this->getCliArg( 'use-bagostuff' ) ) {
+                       $name = $this->getCliArg( 'use-bagostuff' );
+
+                       $this->cache = ObjectCache::newFromId( $name );
+               } else {
+                       // no type defined - use simple hash
+                       $this->cache = new HashBagOStuff;
+               }
+
+               $this->cache->delete( wfMemcKey( 'test' ) );
+       }
+
+       /**
+        * @covers BagOStuff::makeGlobalKey
+        * @covers BagOStuff::makeKeyInternal
+        */
+       public function testMakeKey() {
+               $cache = ObjectCache::newFromId( 'hash' );
+
+               $localKey = $cache->makeKey( 'first', 'second', 'third' );
+               $globalKey = $cache->makeGlobalKey( 'first', 'second', 'third' );
+
+               $this->assertStringMatchesFormat(
+                       '%Sfirst%Ssecond%Sthird%S',
+                       $localKey,
+                       'Local key interpolates parameters'
+               );
+
+               $this->assertStringMatchesFormat(
+                       'global%Sfirst%Ssecond%Sthird%S',
+                       $globalKey,
+                       'Global key interpolates parameters and contains global prefix'
+               );
+
+               $this->assertNotEquals(
+                       $localKey,
+                       $globalKey,
+                       'Local key and global key with same parameters should not be equal'
+               );
+
+               $this->assertNotEquals(
+                       $cache->makeKeyInternal( 'prefix', array( 'a', 'bc:', 'de' ) ),
+                       $cache->makeKeyInternal( 'prefix', array( 'a', 'bc', ':de' ) )
+               );
+       }
+
+       /**
+        * @covers BagOStuff::merge
+        * @covers BagOStuff::mergeViaLock
+        */
+       public function testMerge() {
+               $key = wfMemcKey( 'test' );
+
+               $usleep = 0;
+
+               /**
+                * Callback method: append "merged" to whatever is in cache.
+                *
+                * @param BagOStuff $cache
+                * @param string $key
+                * @param int $existingValue
+                * @use int $usleep
+                * @return int
+                */
+               $callback = function ( BagOStuff $cache, $key, $existingValue ) use ( &$usleep ) {
+                       // let's pretend this is an expensive callback to test concurrent merge attempts
+                       usleep( $usleep );
+
+                       if ( $existingValue === false ) {
+                               return 'merged';
+                       }
+
+                       return $existingValue . 'merged';
+               };
+
+               // merge on non-existing value
+               $merged = $this->cache->merge( $key, $callback, 0 );
+               $this->assertTrue( $merged );
+               $this->assertEquals( $this->cache->get( $key ), 'merged' );
+
+               // merge on existing value
+               $merged = $this->cache->merge( $key, $callback, 0 );
+               $this->assertTrue( $merged );
+               $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' );
+
+               /*
+                * Test concurrent merges by forking this process, if:
+                * - not manually called with --use-bagostuff
+                * - pcntl_fork is supported by the system
+                * - cache type will correctly support calls over forks
+                */
+               $fork = (bool)$this->getCliArg( 'use-bagostuff' );
+               $fork &= function_exists( 'pcntl_fork' );
+               $fork &= !$this->cache instanceof HashBagOStuff;
+               $fork &= !$this->cache instanceof EmptyBagOStuff;
+               $fork &= !$this->cache instanceof MultiWriteBagOStuff;
+               if ( $fork ) {
+                       // callback should take awhile now so that we can test concurrent merge attempts
+                       $pid = pcntl_fork();
+                       if ( $pid == -1 ) {
+                               // can't fork, ignore this test...
+                       } elseif ( $pid ) {
+                               // wait a little, making sure that the child process is calling merge
+                               usleep( 3000 );
+
+                               // attempt a merge - this should fail
+                               $merged = $this->cache->merge( $key, $callback, 0, 1 );
+
+                               // merge has failed because child process was merging (and we only attempted once)
+                               $this->assertFalse( $merged );
+
+                               // make sure the child's merge is completed and verify
+                               usleep( 3000 );
+                               $this->assertEquals( $this->cache->get( $key ), 'mergedmergedmerged' );
+                       } else {
+                               $this->cache->merge( $key, $callback, 0, 1 );
+
+                               // Note: I'm not even going to check if the merge worked, I'll
+                               // compare values in the parent process to test if this merge worked.
+                               // I'm just going to exit this child process, since I don't want the
+                               // child to output any test results (would be rather confusing to
+                               // have test output twice)
+                               exit;
+                       }
+               }
+       }
+
+       /**
+        * @covers BagOStuff::add
+        */
+       public function testAdd() {
+               $key = wfMemcKey( 'test' );
+               $this->assertTrue( $this->cache->add( $key, 'test' ) );
+       }
+
+       public function testGet() {
+               $value = array( 'this' => 'is', 'a' => 'test' );
+
+               $key = wfMemcKey( 'test' );
+               $this->cache->add( $key, $value );
+               $this->assertEquals( $this->cache->get( $key ), $value );
+       }
+
+       /**
+        * @covers BagOStuff::getWithSetCallback
+        */
+       public function testGetWithSetCallback() {
+               $key = wfMemcKey( 'test' );
+               $value = $this->cache->getWithSetCallback(
+                       $key,
+                       30,
+                       function () {
+                               return 'hello kitty';
+                       }
+               );
+
+               $this->assertEquals( 'hello kitty', $value );
+               $this->assertEquals( $value, $this->cache->get( $key ) );
+       }
+
+       /**
+        * @covers BagOStuff::incr
+        */
+       public function testIncr() {
+               $key = wfMemcKey( 'test' );
+               $this->cache->add( $key, 0 );
+               $this->cache->incr( $key );
+               $expectedValue = 1;
+               $actualValue = $this->cache->get( $key );
+               $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' );
+       }
+
+       /**
+        * @covers BagOStuff::getMulti
+        */
+       public function testGetMulti() {
+               $value1 = array( 'this' => 'is', 'a' => 'test' );
+               $value2 = array( 'this' => 'is', 'another' => 'test' );
+               $value3 = array( 'testing a key that may be encoded when sent to cache backend' );
+               $value4 = array( 'another test where chars in key will be encoded' );
+
+               $key1 = wfMemcKey( 'test1' );
+               $key2 = wfMemcKey( 'test2' );
+               // internally, MemcachedBagOStuffs will encode to will-%25-encode
+               $key3 = wfMemcKey( 'will-%-encode' );
+               $key4 = wfMemcKey(
+                       'flowdb:flow_ref:wiki:by-source:v3:Parser\'s_"broken"_+_(page)_&_grill:testwiki:1:4.7'
+               );
+
+               $this->cache->add( $key1, $value1 );
+               $this->cache->add( $key2, $value2 );
+               $this->cache->add( $key3, $value3 );
+               $this->cache->add( $key4, $value4 );
+
+               $this->assertEquals(
+                       array( $key1 => $value1, $key2 => $value2, $key3 => $value3, $key4 => $value4 ),
+                       $this->cache->getMulti( array( $key1, $key2, $key3, $key4 ) )
+               );
+
+               // cleanup
+               $this->cache->delete( $key1 );
+               $this->cache->delete( $key2 );
+               $this->cache->delete( $key3 );
+               $this->cache->delete( $key4 );
+       }
+
+       /**
+        * @covers BagOStuff::getScopedLock
+        */
+       public function testGetScopedLock() {
+               $key = wfMemcKey( 'test' );
+               $value1 = $this->cache->getScopedLock( $key, 0 );
+               $value2 = $this->cache->getScopedLock( $key, 0 );
+
+               $this->assertType( 'ScopedCallback', $value1, 'First call returned lock' );
+               $this->assertNull( $value2, 'Duplicate call returned no lock' );
+
+               unset( $value1 );
+
+               $value3 = $this->cache->getScopedLock( $key, 0 );
+               $this->assertType( 'ScopedCallback', $value3, 'Lock returned callback after release' );
+               unset( $value3 );
+
+               $value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
+               $value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
+
+               $this->assertType( 'ScopedCallback', $value1, 'First reentrant call returned lock' );
+               $this->assertType( 'ScopedCallback', $value1, 'Second reentrant call returned lock' );
+       }
+}
diff --git a/tests/phpunit/includes/libs/objectcache/MultiWriteBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/MultiWriteBagOStuffTest.php
new file mode 100644 (file)
index 0000000..1d8f43a
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @group Database
+ */
+class MultiWriteBagOStuffTest extends MediaWikiTestCase {
+       /** @var HashBagOStuff */
+       private $cache1;
+       /** @var HashBagOStuff */
+       private $cache2;
+       /** @var MultiWriteBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->cache1 = new HashBagOStuff();
+               $this->cache2 = new HashBagOStuff();
+               $this->cache = new MultiWriteBagOStuff( array(
+                       'caches' => array( $this->cache1, $this->cache2 ),
+                       'replication' => 'async',
+                       'asyncHandler' => 'DeferredUpdates::addCallableUpdate'
+               ) );
+       }
+
+       public function testSetImmediate() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $this->cache->set( $key, $value );
+
+               // Set in tier 1
+               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
+               // Set in tier 2
+               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
+       }
+
+       public function testSyncMerge() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $func = function () use ( $value ) {
+                       return $value;
+               };
+
+               // XXX: DeferredUpdates bound to transactions in CLI mode
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->begin();
+               $this->cache->merge( $key, $func );
+
+               // Set in tier 1
+               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
+               // Not yet set in tier 2
+               $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' );
+
+               $dbw->commit();
+
+               // Set in tier 2
+               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
+
+               $key = wfRandomString();
+
+               $dbw->begin();
+               $this->cache->merge( $key, $func, 0, 1, BagOStuff::WRITE_SYNC );
+
+               // Set in tier 1
+               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
+               // Also set in tier 2
+               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
+
+               $dbw->commit();
+       }
+
+       public function testSetDelayed() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+
+               // XXX: DeferredUpdates bound to transactions in CLI mode
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->begin();
+               $this->cache->set( $key, $value );
+
+               // Set in tier 1
+               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
+               // Not yet set in tier 2
+               $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' );
+
+               $dbw->commit();
+
+               // Set in tier 2
+               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
+       }
+}
diff --git a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php
new file mode 100644 (file)
index 0000000..a419f5b
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+class ReplicatedBagOStuffTest extends MediaWikiTestCase {
+       /** @var HashBagOStuff */
+       private $writeCache;
+       /** @var HashBagOStuff */
+       private $readCache;
+       /** @var ReplicatedBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->writeCache = new HashBagOStuff();
+               $this->readCache = new HashBagOStuff();
+               $this->cache = new ReplicatedBagOStuff( array(
+                       'writeFactory' => $this->writeCache,
+                       'readFactory' => $this->readCache,
+               ) );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::set
+        */
+       public function testSet() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $this->cache->set( $key, $value );
+
+               // Write to master.
+               $this->assertEquals( $this->writeCache->get( $key ), $value );
+               // Don't write to slave. Replication is deferred to backend.
+               $this->assertEquals( $this->readCache->get( $key ), false );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::get
+        */
+       public function testGet() {
+               $key = wfRandomString();
+
+               $write = wfRandomString();
+               $this->writeCache->set( $key, $write );
+               $read = wfRandomString();
+               $this->readCache->set( $key, $read );
+
+               // Read from slave.
+               $this->assertEquals( $this->cache->get( $key ), $read );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::get
+        */
+       public function testGetAbsent() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $this->writeCache->set( $key, $value );
+
+               // Don't read from master. No failover if value is absent.
+               $this->assertEquals( $this->cache->get( $key ), false );
+       }
+}
diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
new file mode 100644 (file)
index 0000000..c3702c5
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+
+class WANObjectCacheTest extends MediaWikiTestCase {
+       /** @var WANObjectCache */
+       private $cache;
+       /**@var BagOStuff */
+       private $internalCache;
+
+       protected function setUp() {
+               parent::setUp();
+
+               if ( $this->getCliArg( 'use-wanobjectcache' ) ) {
+                       $name = $this->getCliArg( 'use-wanobjectcache' );
+
+                       $this->cache = ObjectCache::getWANInstance( $name );
+               } else {
+                       $this->cache = new WANObjectCache( array(
+                               'cache' => new HashBagOStuff(),
+                               'pool' => 'testcache-hash',
+                               'relayer' => new EventRelayerNull( array() )
+                       ) );
+               }
+
+               $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
+               $this->internalCache = $wanCache->cache;
+       }
+
+       /**
+        * @dataProvider provider_testSetAndGet
+        * @covers WANObjectCache::set()
+        * @covers WANObjectCache::get()
+        * @param mixed $value
+        * @param integer $ttl
+        */
+       public function testSetAndGet( $value, $ttl ) {
+               $key = wfRandomString();
+               $this->cache->set( $key, $value, $ttl );
+
+               $curTTL = null;
+               $this->assertEquals( $value, $this->cache->get( $key, $curTTL ) );
+               if ( is_infinite( $ttl ) || $ttl == 0 ) {
+                       $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
+               } else {
+                       $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
+                       $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
+               }
+       }
+
+       public static function provider_testSetAndGet() {
+               return array(
+                       array( 14141, 3 ),
+                       array( 3535.666, 3 ),
+                       array( array(), 3 ),
+                       array( null, 3 ),
+                       array( '0', 3 ),
+                       array( (object)array( 'meow' ), 3 ),
+                       array( INF, 3 ),
+                       array( '', 3 ),
+                       array( 'pizzacat', INF ),
+               );
+       }
+
+       public function testGetNotExists() {
+               $key = wfRandomString();
+               $curTTL = null;
+               $value = $this->cache->get( $key, $curTTL );
+
+               $this->assertFalse( $value, "Non-existing key has false value" );
+               $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
+       }
+
+       public function testSetOver() {
+               $key = wfRandomString();
+               for ( $i = 0; $i < 3; ++$i ) {
+                       $value = wfRandomString();
+                       $this->cache->set( $key, $value, 3 );
+
+                       $this->assertEquals( $this->cache->get( $key ), $value );
+               }
+       }
+
+       public function testStaleSet() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $this->cache->set( $key, $value, 3, array( 'since' => microtime( true ) - 30 ) );
+
+               $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
+       }
+
+       /**
+        * @covers WANObjectCache::getWithSetCallback()
+        */
+       public function testGetWithSetCallback() {
+               $cache = $this->cache;
+
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $cKey1 = wfRandomString();
+               $cKey2 = wfRandomString();
+
+               $wasSet = 0;
+               $func = function( $old, &$ttl ) use ( &$wasSet, $value ) {
+                       ++$wasSet;
+                       $ttl = 20; // override with another value
+                       return $value;
+               };
+
+               $wasSet = 0;
+               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'lockTSE' => 5 ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $this->assertEquals( 1, $wasSet, "Value regenerated" );
+
+               $curTTL = null;
+               $cache->get( $key, $curTTL );
+               $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
+               $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
+
+               $wasSet = 0;
+               $v = $cache->getWithSetCallback( $key, 30, $func, array(
+                       'lowTTL' => 0,
+                       'lockTSE' => 5,
+               ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $this->assertEquals( 0, $wasSet, "Value not regenerated" );
+
+               $priorTime = microtime( true );
+               usleep( 1 );
+               $wasSet = 0;
+               $v = $cache->getWithSetCallback( $key, 30, $func,
+                       array( 'checkKeys' => array( $cKey1, $cKey2 ) ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
+               $t1 = $cache->getCheckKeyTime( $cKey1 );
+               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
+               $t2 = $cache->getCheckKeyTime( $cKey2 );
+               $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
+
+               $priorTime = microtime( true );
+               $wasSet = 0;
+               $v = $cache->getWithSetCallback( $key, 30, $func,
+                       array( 'checkKeys' => array( $cKey1, $cKey2 ) ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
+               $t1 = $cache->getCheckKeyTime( $cKey1 );
+               $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
+               $t2 = $cache->getCheckKeyTime( $cKey2 );
+               $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
+
+               $curTTL = null;
+               $v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
+
+               $wasSet = 0;
+               $key = wfRandomString();
+               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'pcTTL' => 5 ) );
+               $this->assertEquals( $value, $v, "Value returned" );
+               $cache->delete( $key );
+               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'pcTTL' => 5 ) );
+               $this->assertEquals( $value, $v, "Value still returned after deleted" );
+               $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
+       }
+
+       /**
+        * @covers WANObjectCache::getWithSetCallback()
+        */
+       public function testLockTSE() {
+               $cache = $this->cache;
+               $key = wfRandomString();
+               $value = wfRandomString();
+
+               $calls = 0;
+               $func = function() use ( &$calls, $value ) {
+                       ++$calls;
+                       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' );
+
+               // 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()
+        */
+       public function testGetMulti() {
+               $cache = $this->cache;
+
+               $value1 = array( 'this' => 'is', 'a' => 'test' );
+               $value2 = array( 'this' => 'is', 'another' => 'test' );
+
+               $key1 = wfRandomString();
+               $key2 = wfRandomString();
+               $key3 = wfRandomString();
+
+               $cache->set( $key1, $value1, 5 );
+               $cache->set( $key2, $value2, 10 );
+
+               $curTTLs = array();
+               $this->assertEquals(
+                       array( $key1 => $value1, $key2 => $value2 ),
+                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs )
+               );
+
+               $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
+               $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
+               $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
+
+               $cKey1 = wfRandomString();
+               $cKey2 = wfRandomString();
+               $curTTLs = array();
+               $this->assertEquals(
+                       array( $key1 => $value1, $key2 => $value2 ),
+                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ),
+                       'Result array populated'
+               );
+
+               $priorTime = microtime( true );
+               usleep( 1 );
+               $curTTLs = array();
+               $this->assertEquals(
+                       array( $key1 => $value1, $key2 => $value2 ),
+                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ),
+                       "Result array populated even with new check keys"
+               );
+               $t1 = $cache->getCheckKeyTime( $cKey1 );
+               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
+               $t2 = $cache->getCheckKeyTime( $cKey2 );
+               $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
+               $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
+               $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
+               $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
+
+               usleep( 1 );
+               $curTTLs = array();
+               $this->assertEquals(
+                       array( $key1 => $value1, $key2 => $value2 ),
+                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ),
+                       "Result array still populated even with new check keys"
+               );
+               $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
+               $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
+               $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
+       }
+
+       /**
+        * @covers WANObjectCache::delete()
+        */
+       public function testDelete() {
+               $key = wfRandomString();
+               $value = wfRandomString();
+               $this->cache->set( $key, $value );
+
+               $curTTL = null;
+               $v = $this->cache->get( $key, $curTTL );
+               $this->assertEquals( $value, $v, "Key was created with value" );
+               $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
+
+               $this->cache->delete( $key );
+
+               $curTTL = null;
+               $v = $this->cache->get( $key, $curTTL );
+               $this->assertFalse( $v, "Deleted key has false value" );
+               $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
+
+               $this->cache->set( $key, $value . 'more' );
+               $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
+               $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
+       }
+
+       /**
+        * @covers WANObjectCache::touchCheckKey()
+        * @covers WANObjectCache::resetCheckKey()
+        * @covers WANObjectCache::getCheckKeyTime()
+        */
+       public function testTouchKeys() {
+               $key = wfRandomString();
+
+               $priorTime = microtime( true );
+               usleep( 100 );
+               $t0 = $this->cache->getCheckKeyTime( $key );
+               $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
+
+               $priorTime = microtime( true );
+               usleep( 100 );
+               $this->cache->touchCheckKey( $key );
+               $t1 = $this->cache->getCheckKeyTime( $key );
+               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
+
+               $t2 = $this->cache->getCheckKeyTime( $key );
+               $this->assertEquals( $t1, $t2, 'Check key time did not change' );
+
+               usleep( 100 );
+               $this->cache->touchCheckKey( $key );
+               $t3 = $this->cache->getCheckKeyTime( $key );
+               $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
+
+               $t4 = $this->cache->getCheckKeyTime( $key );
+               $this->assertEquals( $t3, $t4, 'Check key time did not change' );
+
+               usleep( 100 );
+               $this->cache->resetCheckKey( $key );
+               $t5 = $this->cache->getCheckKeyTime( $key );
+               $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
+
+               $t6 = $this->cache->getCheckKeyTime( $key );
+               $this->assertEquals( $t5, $t6, 'Check key time did not change' );
+       }
+}
diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php
deleted file mode 100644 (file)
index 466b9f5..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-<?php
-/**
- * @author Matthias Mullie <mmullie@wikimedia.org>
- * @group BagOStuff
- */
-class BagOStuffTest extends MediaWikiTestCase {
-       /** @var BagOStuff */
-       private $cache;
-
-       protected function setUp() {
-               parent::setUp();
-
-               // type defined through parameter
-               if ( $this->getCliArg( 'use-bagostuff' ) ) {
-                       $name = $this->getCliArg( 'use-bagostuff' );
-
-                       $this->cache = ObjectCache::newFromId( $name );
-               } else {
-                       // no type defined - use simple hash
-                       $this->cache = new HashBagOStuff;
-               }
-
-               $this->cache->delete( wfMemcKey( 'test' ) );
-       }
-
-       /**
-        * @covers BagOStuff::makeGlobalKey
-        * @covers BagOStuff::makeKeyInternal
-        */
-       public function testMakeKey() {
-               $cache = ObjectCache::newFromId( 'hash' );
-
-               $localKey = $cache->makeKey( 'first', 'second', 'third' );
-               $globalKey = $cache->makeGlobalKey( 'first', 'second', 'third' );
-
-               $this->assertStringMatchesFormat(
-                       '%Sfirst%Ssecond%Sthird%S',
-                       $localKey,
-                       'Local key interpolates parameters'
-               );
-
-               $this->assertStringMatchesFormat(
-                       'global%Sfirst%Ssecond%Sthird%S',
-                       $globalKey,
-                       'Global key interpolates parameters and contains global prefix'
-               );
-
-               $this->assertNotEquals(
-                       $localKey,
-                       $globalKey,
-                       'Local key and global key with same parameters should not be equal'
-               );
-       }
-
-       /**
-        * @covers BagOStuff::merge
-        * @covers BagOStuff::mergeViaLock
-        */
-       public function testMerge() {
-               $key = wfMemcKey( 'test' );
-
-               $usleep = 0;
-
-               /**
-                * Callback method: append "merged" to whatever is in cache.
-                *
-                * @param BagOStuff $cache
-                * @param string $key
-                * @param int $existingValue
-                * @use int $usleep
-                * @return int
-                */
-               $callback = function ( BagOStuff $cache, $key, $existingValue ) use ( &$usleep ) {
-                       // let's pretend this is an expensive callback to test concurrent merge attempts
-                       usleep( $usleep );
-
-                       if ( $existingValue === false ) {
-                               return 'merged';
-                       }
-
-                       return $existingValue . 'merged';
-               };
-
-               // merge on non-existing value
-               $merged = $this->cache->merge( $key, $callback, 0 );
-               $this->assertTrue( $merged );
-               $this->assertEquals( $this->cache->get( $key ), 'merged' );
-
-               // merge on existing value
-               $merged = $this->cache->merge( $key, $callback, 0 );
-               $this->assertTrue( $merged );
-               $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' );
-
-               /*
-                * Test concurrent merges by forking this process, if:
-                * - not manually called with --use-bagostuff
-                * - pcntl_fork is supported by the system
-                * - cache type will correctly support calls over forks
-                */
-               $fork = (bool)$this->getCliArg( 'use-bagostuff' );
-               $fork &= function_exists( 'pcntl_fork' );
-               $fork &= !$this->cache instanceof HashBagOStuff;
-               $fork &= !$this->cache instanceof EmptyBagOStuff;
-               $fork &= !$this->cache instanceof MultiWriteBagOStuff;
-               if ( $fork ) {
-                       // callback should take awhile now so that we can test concurrent merge attempts
-                       $pid = pcntl_fork();
-                       if ( $pid == -1 ) {
-                               // can't fork, ignore this test...
-                       } elseif ( $pid ) {
-                               // wait a little, making sure that the child process is calling merge
-                               usleep( 3000 );
-
-                               // attempt a merge - this should fail
-                               $merged = $this->cache->merge( $key, $callback, 0, 1 );
-
-                               // merge has failed because child process was merging (and we only attempted once)
-                               $this->assertFalse( $merged );
-
-                               // make sure the child's merge is completed and verify
-                               usleep( 3000 );
-                               $this->assertEquals( $this->cache->get( $key ), 'mergedmergedmerged' );
-                       } else {
-                               $this->cache->merge( $key, $callback, 0, 1 );
-
-                               // Note: I'm not even going to check if the merge worked, I'll
-                               // compare values in the parent process to test if this merge worked.
-                               // I'm just going to exit this child process, since I don't want the
-                               // child to output any test results (would be rather confusing to
-                               // have test output twice)
-                               exit;
-                       }
-               }
-       }
-
-       /**
-        * @covers BagOStuff::add
-        */
-       public function testAdd() {
-               $key = wfMemcKey( 'test' );
-               $this->assertTrue( $this->cache->add( $key, 'test' ) );
-       }
-
-       public function testGet() {
-               $value = array( 'this' => 'is', 'a' => 'test' );
-
-               $key = wfMemcKey( 'test' );
-               $this->cache->add( $key, $value );
-               $this->assertEquals( $this->cache->get( $key ), $value );
-       }
-
-       /**
-        * @covers BagOStuff::getWithSetCallback
-        */
-       public function testGetWithSetCallback() {
-               $key = wfMemcKey( 'test' );
-               $value = $this->cache->getWithSetCallback(
-                       $key,
-                       30,
-                       function () {
-                               return 'hello kitty';
-                       }
-               );
-
-               $this->assertEquals( 'hello kitty', $value );
-               $this->assertEquals( $value, $this->cache->get( $key ) );
-       }
-
-       /**
-        * @covers BagOStuff::incr
-        */
-       public function testIncr() {
-               $key = wfMemcKey( 'test' );
-               $this->cache->add( $key, 0 );
-               $this->cache->incr( $key );
-               $expectedValue = 1;
-               $actualValue = $this->cache->get( $key );
-               $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' );
-       }
-
-       /**
-        * @covers BagOStuff::getMulti
-        */
-       public function testGetMulti() {
-               $value1 = array( 'this' => 'is', 'a' => 'test' );
-               $value2 = array( 'this' => 'is', 'another' => 'test' );
-               $value3 = array( 'testing a key that may be encoded when sent to cache backend' );
-               $value4 = array( 'another test where chars in key will be encoded' );
-
-               $key1 = wfMemcKey( 'test1' );
-               $key2 = wfMemcKey( 'test2' );
-               // internally, MemcachedBagOStuffs will encode to will-%25-encode
-               $key3 = wfMemcKey( 'will-%-encode' );
-               $key4 = wfMemcKey(
-                       'flowdb:flow_ref:wiki:by-source:v3:Parser\'s_"broken"_+_(page)_&_grill:testwiki:1:4.7'
-               );
-
-               $this->cache->add( $key1, $value1 );
-               $this->cache->add( $key2, $value2 );
-               $this->cache->add( $key3, $value3 );
-               $this->cache->add( $key4, $value4 );
-
-               $this->assertEquals(
-                       array( $key1 => $value1, $key2 => $value2, $key3 => $value3, $key4 => $value4 ),
-                       $this->cache->getMulti( array( $key1, $key2, $key3, $key4 ) )
-               );
-
-               // cleanup
-               $this->cache->delete( $key1 );
-               $this->cache->delete( $key2 );
-               $this->cache->delete( $key3 );
-               $this->cache->delete( $key4 );
-       }
-
-       /**
-        * @covers BagOStuff::getScopedLock
-        */
-       public function testGetScopedLock() {
-               $key = wfMemcKey( 'test' );
-               $value1 = $this->cache->getScopedLock( $key, 0 );
-               $value2 = $this->cache->getScopedLock( $key, 0 );
-
-               $this->assertType( 'ScopedCallback', $value1, 'First call returned lock' );
-               $this->assertNull( $value2, 'Duplicate call returned no lock' );
-
-               unset( $value1 );
-
-               $value3 = $this->cache->getScopedLock( $key, 0 );
-               $this->assertType( 'ScopedCallback', $value3, 'Lock returned callback after release' );
-               unset( $value3 );
-
-               $value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
-               $value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
-
-               $this->assertType( 'ScopedCallback', $value1, 'First reentrant call returned lock' );
-               $this->assertType( 'ScopedCallback', $value1, 'Second reentrant call returned lock' );
-       }
-}
diff --git a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php
new file mode 100644 (file)
index 0000000..b0c9e4f
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @group BagOStuff
+ */
+class MemcachedBagOStuffTest extends MediaWikiTestCase {
+       /** @var MemcachedBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->cache = new MemcachedBagOStuff( array( 'keyspace' => 'test' ) );
+       }
+
+       /**
+        * @covers MemcachedBagOStuff::makeKeyInternal
+        */
+       public function testKeyNormalization() {
+               $this->assertEquals(
+                       'test:vanilla',
+                       $this->cache->makeKey( 'vanilla' )
+               );
+
+               $this->assertEquals(
+                       'test:punctuation_marks_are_ok:!@$^&*()',
+                       $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:but_spaces:hashes%23:and%0Anewlines:are_not',
+                       $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' )
+               );
+
+               $this->assertEquals(
+                       'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' .
+                               'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters',
+                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' )
+               );
+
+               $this->assertEquals(
+                       'test:this:key:contains:#c118f92685a635cb843039de50014c9c',
+                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' )
+               );
+
+               $this->assertEquals(
+                       'test:##5820ad1d105aa4dc698585c39df73e19',
+                       $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
+                               '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
+               );
+
+               $this->assertEquals(
+                       'test:%23%235820ad1d105aa4dc698585c39df73e19',
+                       $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' )
+               );
+
+               $this->assertEquals(
+                       'test:percent_is_escaped:!@$%25^&*()',
+                       $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:colon_is_escaped:!@$%3A^&*()',
+                       $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac',
+                       $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) )
+               );
+       }
+}
diff --git a/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php
deleted file mode 100644 (file)
index 1d8f43a..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * @group Database
- */
-class MultiWriteBagOStuffTest extends MediaWikiTestCase {
-       /** @var HashBagOStuff */
-       private $cache1;
-       /** @var HashBagOStuff */
-       private $cache2;
-       /** @var MultiWriteBagOStuff */
-       private $cache;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->cache1 = new HashBagOStuff();
-               $this->cache2 = new HashBagOStuff();
-               $this->cache = new MultiWriteBagOStuff( array(
-                       'caches' => array( $this->cache1, $this->cache2 ),
-                       'replication' => 'async',
-                       'asyncHandler' => 'DeferredUpdates::addCallableUpdate'
-               ) );
-       }
-
-       public function testSetImmediate() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $this->cache->set( $key, $value );
-
-               // Set in tier 1
-               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
-               // Set in tier 2
-               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
-       }
-
-       public function testSyncMerge() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $func = function () use ( $value ) {
-                       return $value;
-               };
-
-               // XXX: DeferredUpdates bound to transactions in CLI mode
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->begin();
-               $this->cache->merge( $key, $func );
-
-               // Set in tier 1
-               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
-               // Not yet set in tier 2
-               $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' );
-
-               $dbw->commit();
-
-               // Set in tier 2
-               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
-
-               $key = wfRandomString();
-
-               $dbw->begin();
-               $this->cache->merge( $key, $func, 0, 1, BagOStuff::WRITE_SYNC );
-
-               // Set in tier 1
-               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
-               // Also set in tier 2
-               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
-
-               $dbw->commit();
-       }
-
-       public function testSetDelayed() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-
-               // XXX: DeferredUpdates bound to transactions in CLI mode
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->begin();
-               $this->cache->set( $key, $value );
-
-               // Set in tier 1
-               $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' );
-               // Not yet set in tier 2
-               $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' );
-
-               $dbw->commit();
-
-               // Set in tier 2
-               $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
-       }
-}
diff --git a/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php
deleted file mode 100644 (file)
index a419f5b..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-class ReplicatedBagOStuffTest extends MediaWikiTestCase {
-       /** @var HashBagOStuff */
-       private $writeCache;
-       /** @var HashBagOStuff */
-       private $readCache;
-       /** @var ReplicatedBagOStuff */
-       private $cache;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->writeCache = new HashBagOStuff();
-               $this->readCache = new HashBagOStuff();
-               $this->cache = new ReplicatedBagOStuff( array(
-                       'writeFactory' => $this->writeCache,
-                       'readFactory' => $this->readCache,
-               ) );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::set
-        */
-       public function testSet() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $this->cache->set( $key, $value );
-
-               // Write to master.
-               $this->assertEquals( $this->writeCache->get( $key ), $value );
-               // Don't write to slave. Replication is deferred to backend.
-               $this->assertEquals( $this->readCache->get( $key ), false );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::get
-        */
-       public function testGet() {
-               $key = wfRandomString();
-
-               $write = wfRandomString();
-               $this->writeCache->set( $key, $write );
-               $read = wfRandomString();
-               $this->readCache->set( $key, $read );
-
-               // Read from slave.
-               $this->assertEquals( $this->cache->get( $key ), $read );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::get
-        */
-       public function testGetAbsent() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $this->writeCache->set( $key, $value );
-
-               // Don't read from master. No failover if value is absent.
-               $this->assertEquals( $this->cache->get( $key ), false );
-       }
-}
diff --git a/tests/phpunit/includes/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php
deleted file mode 100644 (file)
index c3702c5..0000000
+++ /dev/null
@@ -1,316 +0,0 @@
-<?php
-
-class WANObjectCacheTest extends MediaWikiTestCase {
-       /** @var WANObjectCache */
-       private $cache;
-       /**@var BagOStuff */
-       private $internalCache;
-
-       protected function setUp() {
-               parent::setUp();
-
-               if ( $this->getCliArg( 'use-wanobjectcache' ) ) {
-                       $name = $this->getCliArg( 'use-wanobjectcache' );
-
-                       $this->cache = ObjectCache::getWANInstance( $name );
-               } else {
-                       $this->cache = new WANObjectCache( array(
-                               'cache' => new HashBagOStuff(),
-                               'pool' => 'testcache-hash',
-                               'relayer' => new EventRelayerNull( array() )
-                       ) );
-               }
-
-               $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
-               $this->internalCache = $wanCache->cache;
-       }
-
-       /**
-        * @dataProvider provider_testSetAndGet
-        * @covers WANObjectCache::set()
-        * @covers WANObjectCache::get()
-        * @param mixed $value
-        * @param integer $ttl
-        */
-       public function testSetAndGet( $value, $ttl ) {
-               $key = wfRandomString();
-               $this->cache->set( $key, $value, $ttl );
-
-               $curTTL = null;
-               $this->assertEquals( $value, $this->cache->get( $key, $curTTL ) );
-               if ( is_infinite( $ttl ) || $ttl == 0 ) {
-                       $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
-               } else {
-                       $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
-                       $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
-               }
-       }
-
-       public static function provider_testSetAndGet() {
-               return array(
-                       array( 14141, 3 ),
-                       array( 3535.666, 3 ),
-                       array( array(), 3 ),
-                       array( null, 3 ),
-                       array( '0', 3 ),
-                       array( (object)array( 'meow' ), 3 ),
-                       array( INF, 3 ),
-                       array( '', 3 ),
-                       array( 'pizzacat', INF ),
-               );
-       }
-
-       public function testGetNotExists() {
-               $key = wfRandomString();
-               $curTTL = null;
-               $value = $this->cache->get( $key, $curTTL );
-
-               $this->assertFalse( $value, "Non-existing key has false value" );
-               $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
-       }
-
-       public function testSetOver() {
-               $key = wfRandomString();
-               for ( $i = 0; $i < 3; ++$i ) {
-                       $value = wfRandomString();
-                       $this->cache->set( $key, $value, 3 );
-
-                       $this->assertEquals( $this->cache->get( $key ), $value );
-               }
-       }
-
-       public function testStaleSet() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $this->cache->set( $key, $value, 3, array( 'since' => microtime( true ) - 30 ) );
-
-               $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
-       }
-
-       /**
-        * @covers WANObjectCache::getWithSetCallback()
-        */
-       public function testGetWithSetCallback() {
-               $cache = $this->cache;
-
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $cKey1 = wfRandomString();
-               $cKey2 = wfRandomString();
-
-               $wasSet = 0;
-               $func = function( $old, &$ttl ) use ( &$wasSet, $value ) {
-                       ++$wasSet;
-                       $ttl = 20; // override with another value
-                       return $value;
-               };
-
-               $wasSet = 0;
-               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'lockTSE' => 5 ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $this->assertEquals( 1, $wasSet, "Value regenerated" );
-
-               $curTTL = null;
-               $cache->get( $key, $curTTL );
-               $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
-               $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
-
-               $wasSet = 0;
-               $v = $cache->getWithSetCallback( $key, 30, $func, array(
-                       'lowTTL' => 0,
-                       'lockTSE' => 5,
-               ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $this->assertEquals( 0, $wasSet, "Value not regenerated" );
-
-               $priorTime = microtime( true );
-               usleep( 1 );
-               $wasSet = 0;
-               $v = $cache->getWithSetCallback( $key, 30, $func,
-                       array( 'checkKeys' => array( $cKey1, $cKey2 ) ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
-               $t1 = $cache->getCheckKeyTime( $cKey1 );
-               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
-               $t2 = $cache->getCheckKeyTime( $cKey2 );
-               $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
-
-               $priorTime = microtime( true );
-               $wasSet = 0;
-               $v = $cache->getWithSetCallback( $key, 30, $func,
-                       array( 'checkKeys' => array( $cKey1, $cKey2 ) ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
-               $t1 = $cache->getCheckKeyTime( $cKey1 );
-               $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
-               $t2 = $cache->getCheckKeyTime( $cKey2 );
-               $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
-
-               $curTTL = null;
-               $v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
-
-               $wasSet = 0;
-               $key = wfRandomString();
-               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'pcTTL' => 5 ) );
-               $this->assertEquals( $value, $v, "Value returned" );
-               $cache->delete( $key );
-               $v = $cache->getWithSetCallback( $key, 30, $func, array( 'pcTTL' => 5 ) );
-               $this->assertEquals( $value, $v, "Value still returned after deleted" );
-               $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
-       }
-
-       /**
-        * @covers WANObjectCache::getWithSetCallback()
-        */
-       public function testLockTSE() {
-               $cache = $this->cache;
-               $key = wfRandomString();
-               $value = wfRandomString();
-
-               $calls = 0;
-               $func = function() use ( &$calls, $value ) {
-                       ++$calls;
-                       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' );
-
-               // 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()
-        */
-       public function testGetMulti() {
-               $cache = $this->cache;
-
-               $value1 = array( 'this' => 'is', 'a' => 'test' );
-               $value2 = array( 'this' => 'is', 'another' => 'test' );
-
-               $key1 = wfRandomString();
-               $key2 = wfRandomString();
-               $key3 = wfRandomString();
-
-               $cache->set( $key1, $value1, 5 );
-               $cache->set( $key2, $value2, 10 );
-
-               $curTTLs = array();
-               $this->assertEquals(
-                       array( $key1 => $value1, $key2 => $value2 ),
-                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs )
-               );
-
-               $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
-               $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
-               $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
-
-               $cKey1 = wfRandomString();
-               $cKey2 = wfRandomString();
-               $curTTLs = array();
-               $this->assertEquals(
-                       array( $key1 => $value1, $key2 => $value2 ),
-                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ),
-                       'Result array populated'
-               );
-
-               $priorTime = microtime( true );
-               usleep( 1 );
-               $curTTLs = array();
-               $this->assertEquals(
-                       array( $key1 => $value1, $key2 => $value2 ),
-                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ),
-                       "Result array populated even with new check keys"
-               );
-               $t1 = $cache->getCheckKeyTime( $cKey1 );
-               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
-               $t2 = $cache->getCheckKeyTime( $cKey2 );
-               $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
-               $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
-               $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
-               $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
-
-               usleep( 1 );
-               $curTTLs = array();
-               $this->assertEquals(
-                       array( $key1 => $value1, $key2 => $value2 ),
-                       $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ),
-                       "Result array still populated even with new check keys"
-               );
-               $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
-               $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
-               $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
-       }
-
-       /**
-        * @covers WANObjectCache::delete()
-        */
-       public function testDelete() {
-               $key = wfRandomString();
-               $value = wfRandomString();
-               $this->cache->set( $key, $value );
-
-               $curTTL = null;
-               $v = $this->cache->get( $key, $curTTL );
-               $this->assertEquals( $value, $v, "Key was created with value" );
-               $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
-
-               $this->cache->delete( $key );
-
-               $curTTL = null;
-               $v = $this->cache->get( $key, $curTTL );
-               $this->assertFalse( $v, "Deleted key has false value" );
-               $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
-
-               $this->cache->set( $key, $value . 'more' );
-               $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
-               $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
-       }
-
-       /**
-        * @covers WANObjectCache::touchCheckKey()
-        * @covers WANObjectCache::resetCheckKey()
-        * @covers WANObjectCache::getCheckKeyTime()
-        */
-       public function testTouchKeys() {
-               $key = wfRandomString();
-
-               $priorTime = microtime( true );
-               usleep( 100 );
-               $t0 = $this->cache->getCheckKeyTime( $key );
-               $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
-
-               $priorTime = microtime( true );
-               usleep( 100 );
-               $this->cache->touchCheckKey( $key );
-               $t1 = $this->cache->getCheckKeyTime( $key );
-               $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
-
-               $t2 = $this->cache->getCheckKeyTime( $key );
-               $this->assertEquals( $t1, $t2, 'Check key time did not change' );
-
-               usleep( 100 );
-               $this->cache->touchCheckKey( $key );
-               $t3 = $this->cache->getCheckKeyTime( $key );
-               $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
-
-               $t4 = $this->cache->getCheckKeyTime( $key );
-               $this->assertEquals( $t3, $t4, 'Check key time did not change' );
-
-               usleep( 100 );
-               $this->cache->resetCheckKey( $key );
-               $t5 = $this->cache->getCheckKeyTime( $key );
-               $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
-
-               $t6 = $this->cache->getCheckKeyTime( $key );
-               $this->assertEquals( $t5, $t6, 'Check key time did not change' );
-       }
-}