Merge "Make LinksUpdate no longer extend EnqueueableDataUpdate"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 5 Aug 2019 17:36:51 +0000 (17:36 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 5 Aug 2019 17:36:51 +0000 (17:36 +0000)
102 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/DefaultSettings.php
includes/OutputPage.php
includes/Revision/RenderedRevision.php
includes/Revision/RevisionRenderer.php
includes/Storage/DerivedPageDataUpdater.php
includes/actions/RawAction.php
includes/api/ApiHelp.php
includes/api/ApiMain.php
includes/api/ApiQueryQueryPage.php
includes/api/i18n/ar.json
includes/api/i18n/fr.json
includes/api/i18n/he.json
includes/api/i18n/pt-br.json
includes/api/i18n/pt.json
includes/api/i18n/uk.json
includes/api/i18n/zh-hant.json
includes/changes/ChangesList.php
includes/context/RequestContext.php
includes/deferred/SearchUpdate.php
includes/installer/CliInstaller.php
includes/installer/InstallException.php [new file with mode: 0644]
includes/installer/Installer.php
includes/installer/i18n/ast.json
includes/installer/i18n/he.json
includes/installer/i18n/ja.json
includes/libs/lockmanager/DBLockManager.php
includes/libs/objectcache/WANObjectCache.php [deleted file]
includes/libs/objectcache/WANObjectCacheReaper.php [deleted file]
includes/libs/objectcache/wancache/WANObjectCache.php [new file with mode: 0644]
includes/libs/objectcache/wancache/WANObjectCacheReaper.php [new file with mode: 0644]
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/parser/ParserOutput.php
includes/resourceloader/MessageBlobStore.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/search/SearchDatabase.php
includes/search/SearchEngine.php
includes/search/SearchHighlighter.php
includes/search/SearchMssql.php
includes/search/SearchMySQL.php
includes/search/SearchOracle.php
includes/search/SearchResult.php
includes/search/SearchSqlite.php
includes/search/SqlSearchResult.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialMovepage.php
includes/user/UserGroupMembership.php
languages/i18n/ar.json
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/bcl.json
languages/i18n/bn.json
languages/i18n/br.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/exif/nl.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/hyw.json
languages/i18n/io.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/ku-latn.json
languages/i18n/lb.json
languages/i18n/lrc.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mni.json
languages/i18n/ms.json
languages/i18n/qqq.json
maintenance/Maintenance.php
maintenance/includes/BackupDumper.php
maintenance/includes/TextPassDumper.php
maintenance/install.php
maintenance/shell.php
maintenance/sql.php
maintenance/storage/orphanStats.php
maintenance/storage/recompressTracked.php
maintenance/storage/trackBlobs.php
resources/Resources.php
resources/src/mediawiki.apihelp.css [deleted file]
resources/src/mediawiki.apipretty.css [deleted file]
resources/src/mediawiki.apipretty/apihelp.css [new file with mode: 0644]
resources/src/mediawiki.apipretty/apipretty.css [new file with mode: 0644]
resources/src/mediawiki.inspect.js
resources/src/startup/mediawiki.js
tests/phpunit/includes/deferred/SearchUpdateTest.php
tests/phpunit/structure/ResourcesTest.php
tests/selenium/wdio.conf.js

index 7cc0434..8ecc469 100644 (file)
@@ -424,6 +424,17 @@ because of Phabricator reports.
 * The following public properties on DatabaseBlock are deprecated: $mAuto,
   $mParentBlockId. To check for an autoblock use DatabaseBlock::getType; to
   check for the parent ID, use DatabaseBlock::getParentBlockId.
+* SearchEngine::userHighlightPrefs() is deprecated, simply stop passing
+  $contextlines and $contextchars to the SearchHighlighter methods, they will
+  use proper defaults defined in SearchHighlighter::DEFAULT_CONTEXT_LINES and
+  DEFAULT_CONTEXT_CHARS.
+* SearchUpdate constructor: passing a string as the title param and or a boolean
+  or a string as the content will produce a deprecation warning.
+* SearchEngine::getTextFromContent() is deprecated, use getTextForSearchIndex()
+  directly from the Content object.
+* SearchEngine::textAlreadyUpdatedForIndex() is deprecated, given the
+  deprecation above this method is no longer needed/called and should not be
+  implemented by SearchEngine implementation.
 
 === Other changes in 1.34 ===
 * …
index 3525b48..0208a6d 100644 (file)
@@ -885,6 +885,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
        'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
        'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
+       'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
        'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ . '/includes/interwiki/ClassicInterwikiLookup.php',
        'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . '/includes/interwiki/InterwikiLookup.php',
        'MediaWiki\\Interwiki\\InterwikiLookupAdapter' => __DIR__ . '/includes/interwiki/InterwikiLookupAdapter.php',
@@ -1592,8 +1593,8 @@ $wgAutoloadLocalClasses = [
        'VirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTService.php',
        'VirtualRESTServiceClient' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTServiceClient.php',
        'WANCacheReapUpdate' => __DIR__ . '/includes/deferred/WANCacheReapUpdate.php',
-       'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/WANObjectCache.php',
-       'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/WANObjectCacheReaper.php',
+       'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCache.php',
+       'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCacheReaper.php',
        'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
        'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
        'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
index 608ef6a..a3772b9 100644 (file)
@@ -738,10 +738,11 @@ $wgUploadDialog = [
  *
  * This is an array of file backend configuration arrays.
  * Each backend configuration has the following parameters:
- *  - 'name'         : A unique name for the backend
- *  - 'class'        : The file backend class to use
- *  - 'wikiId'       : A unique string that identifies the wiki (container prefix)
- *  - 'lockManager'  : The name of a lock manager (see $wgLockManagers)
+ *  - name        : A unique name for the backend
+ *  - class       : The file backend class to use
+ *  - wikiId      : A unique string that identifies the wiki (container prefix)
+ *  - lockManager : The name of a lock manager (see $wgLockManagers) [optional]
+ *  - fileJournal : File journal configuration for FileJournal::__construct() [optional]
  *
  * See FileBackend::__construct() for more details.
  * Additional parameters are specific to the file backend class used.
@@ -774,8 +775,8 @@ $wgFileBackends = [];
 /**
  * Array of configuration arrays for each lock manager.
  * Each backend configuration has the following parameters:
- *  - 'name'        : A unique name for the lock manager
- *  - 'class'       : The lock manger class to use
+ *  - name  : A unique name for the lock manager
+ *  - class : The lock manger class to use
  *
  * See LockManager::__construct() for more details.
  * Additional parameters are specific to the lock manager class used.
index e78cd7b..2c7292c 100644 (file)
@@ -3047,8 +3047,8 @@ class OutputPage extends ContextSource {
 
                // This library is intended to run on older browsers that MediaWiki no longer
                // supports as Grade A. For these Grade C browsers, we provide an experience
-               // using only HTML and CSS. Where standards-compliant browsers are able to style
-               // unknown HTML elements without issue, old IE ignores these styles.
+               // using only HTML and CSS. But, where standards-compliant browsers are able to
+               // style unknown HTML elements without issue, old IE ignores these styles.
                // The html5shiv library fixes that.
                // Use an IE conditional comment to serve the script only to old IE
                $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
index a913244..3bc8dda 100644 (file)
@@ -454,7 +454,7 @@ class RenderedRevision implements SlotRenderingProvider {
                ) {
                        // If a self-transclusion used the proposed page text, it must match the final
                        // page content after PST transformations and automatically merged edit conflicts
-                       $logger->info( "$varyMsg (vary-revision-sha1 with wrong SHA-1)" );
+                       $logger->info( "$varyMsg (vary-revision-sha1 with wrong SHA-1)", $context );
                        return true;
                }
 
index bbda0e5..ca4bb73 100644 (file)
@@ -191,7 +191,7 @@ class RevisionRenderer {
                        ? 0
                        : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
 
-               $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->wikiId, $flags );
+               $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
 
                return 1 + (int)$db->selectField(
                        'page',
index 5d847b6..68814ef 100644 (file)
@@ -1484,7 +1484,6 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface {
 
                $id = $this->getPageId();
                $title = $this->getTitle();
-               $dbKey = $title->getPrefixedDBkey();
                $shortTitle = $title->getDBkey();
 
                if ( !$title->exists() ) {
@@ -1522,7 +1521,7 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface {
                // TODO: make search infrastructure aware of slots!
                $mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
                if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
-                       DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
+                       DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $mainSlot->getContent() ) );
                }
 
                // If this is another user's talk page, update newtalk.
index f6c4472..abb8ff5 100644 (file)
@@ -269,9 +269,7 @@ class RawAction extends FormlessAction {
         * @return string
         */
        public function getContentType() {
-               // Use getRawVal instead of getVal because we only
-               // need to match against known strings, there is no
-               // storing of localised content or other user input.
+               // Optimisation: Avoid slow getVal(), this isn't user-generated content.
                $ctype = $this->getRequest()->getRawVal( 'ctype' );
 
                if ( $ctype == '' ) {
index cc96f90..988957b 100644 (file)
@@ -100,7 +100,7 @@ class ApiHelp extends ApiBase {
                $out = $context->getOutput();
                $out->addModuleStyles( [
                        'mediawiki.hlist',
-                       'mediawiki.apihelp',
+                       'mediawiki.apipretty',
                ] );
                if ( !empty( $options['toc'] ) ) {
                        $out->addModuleStyles( 'mediawiki.toc.styles' );
index 8389b24..554ab6a 100644 (file)
@@ -238,7 +238,8 @@ class ApiMain extends ApiBase {
 
                // Setup uselang. This doesn't use $this->getParameter()
                // because we're not ready to handle errors yet.
-               $uselang = $request->getVal( 'uselang', self::API_DEFAULT_USELANG );
+               // Optimisation: Avoid slow getVal(), this isn't user-generated content.
+               $uselang = $request->getRawVal( 'uselang', self::API_DEFAULT_USELANG );
                if ( $uselang === 'user' ) {
                        // Assume the parent context is going to return the user language
                        // for uselang=user (see T85635).
@@ -257,8 +258,9 @@ class ApiMain extends ApiBase {
 
                // Set up the error formatter. This doesn't use $this->getParameter()
                // because we're not ready to handle errors yet.
-               $errorFormat = $request->getVal( 'errorformat', 'bc' );
-               $errorLangCode = $request->getVal( 'errorlang', 'uselang' );
+               // Optimisation: Avoid slow getVal(), this isn't user-generated content.
+               $errorFormat = $request->getRawVal( 'errorformat', 'bc' );
+               $errorLangCode = $request->getRawVal( 'errorlang', 'uselang' );
                $errorsUseDB = $request->getCheck( 'errorsuselocal' );
                if ( in_array( $errorFormat, [ 'plaintext', 'wikitext', 'html', 'raw', 'none' ], true ) ) {
                        if ( $errorLangCode === 'uselang' ) {
index ea20664..26c17c5 100644 (file)
@@ -122,9 +122,12 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
 
                        $title = Title::makeTitle( $row->namespace, $row->title );
                        if ( is_null( $resultPageSet ) ) {
-                               $data = [ 'value' => $row->value ];
-                               if ( $qp->usesTimestamps() ) {
-                                       $data['timestamp'] = wfTimestamp( TS_ISO_8601, $row->value );
+                               $data = [];
+                               if ( isset( $row->value ) ) {
+                                       $data['value'] = $row->value;
+                                       if ( $qp->usesTimestamps() ) {
+                                               $data['timestamp'] = wfTimestamp( TS_ISO_8601, $row->value );
+                                       }
                                }
                                self::addTitleInfo( $data, $title );
 
index 70515eb..af97236 100644 (file)
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "رمز لغة BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "اتجاه كتابة اللغة (إما <code>ltr</code> أو <code>rtl</code>).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "الاسم الداخلي للغة، أي: الاسم بتلك اللغة.",
-       "apihelp-query+languageinfo-paramvalue-prop-name": "اسم اللغة ياللغة المحددة بواسطة الوسيط <var>lilang</var>، مع تطبيق احتياطات اللغة إذا لزم الأمر.",
+       "apihelp-query+languageinfo-paramvalue-prop-name": "اسم اللغة ياللغة المحددة بواسطة الوسيط <var>uselang</var>، مع تطبيق احتياطات اللغة إذا لزم الأمر.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "رموز لغة اللغات الاحتياطية التي تم تكوينها لهذه اللغة، لا يتم تضمين الإرجاع النهائي الضمني إلى \"en\" (ولكن قد ترجع بعض اللغات إلى \"en\" بشكل صريح).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "رموز اللغات للمتغيرات التي تدعمها هذه اللغة.",
        "apihelp-query+languageinfo-param-code": "رموز اللغات التي يجب إرجاعها، أو <code>*</code> لجميع اللغات.",
index b04ad1b..591bf31 100644 (file)
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "Le code de langue BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "La direction d’écriture de la langue (<code>ltr</code> ou <code>rtl</code>).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d’une langue, c’est-à-dire son nom dans cette langue.",
-       "apihelp-query+languageinfo-paramvalue-prop-name": "Le nom de la langue dans la langue spécifiée par le paramètre <var>lilang</var>, avec application des langues de secours si besoin.",
+       "apihelp-query+languageinfo-paramvalue-prop-name": "Le nom de la langue dans la langue spécifiée par le paramètre <var>uselang</var>, avec l'application des langues de repli si besoin.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "Les codes de langue des langues de secours configurées pour cette langue. Le secours implicite final en 'en' n’est pas inclus (mais certaines langues peuvent avoir 'en' en secours explicitement).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "Les codes de langue des variantes supportées par cette langue.",
        "apihelp-query+languageinfo-param-code": "Codes de langue des langues qui doivent être renvoyées, ou <code>*</code> pour toutes les langues.",
index d0d5aa2..e2c5fe7 100644 (file)
@@ -16,7 +16,8 @@
                        "שמזן",
                        "Or",
                        "Umherirrender",
-                       "Strayblues"
+                       "Strayblues",
+                       "Steeve815"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|תיעוד]]\n* [[mw:Special:MyLanguage/API:FAQ|שאלות נפוצות]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api רשימת דיוור]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce הודעות על API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R באגים ובקשות]\n</div>\n<strong>מצב:</strong> ה־API של מדיה־ויקי הוא ממשק ותיק ויציב שנתמך ומשתפר באופן סדיר. למרות שאנחנו משתדלים להימנע מכך, לעתים עלינו לבצע שינויים שעלולים לשבש דברים בפונקציונליות הזו; באפשרותך לעשות מינוי ל[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ רשימת הדיוור mediawiki-api-announce] כדי לקבל הודעות על עדכונים.\n\n<strong>בקשות שגויות:</strong> כשבקשות שגויות נשלחות ל־API, תישלח כותרת HTTP עם המפתח \"MediaWiki-API-Error\", ואז גם הערך של הכותרת וגם קוד השגיאה יוגדרו לאותו ערך. למידע נוסף, אפשר לעיין בדף [[mw:Special:MyLanguage/API:Errors_and_warnings|API: שגיאות ואזהרות]].\n\n<p class=\"mw-apisandbox-link\"><strong>בדיקה:</strong> לבדיקה קלה יותר של בקשות, אפשר להשתמש ב[[Special:ApiSandbox|ארגז החול של API]].</p>",
        "apihelp-query+langlinks-param-inlanguagecode": "קוד שפה בשביל שמות שפות מתורגמות.",
        "apihelp-query+langlinks-example-simple": "קבלת קישורים בין־לשוניים מהדף <kbd>Main Page</kbd>.",
        "apihelp-query+languageinfo-summary": "מחזירה מידע על שפות זמינות.",
+       "apihelp-query+languageinfo-param-prop": "איזה מידע לקבל עבור כל שפה.",
+       "apihelp-query+languageinfo-paramvalue-prop-dir": "כיוון הכתיבה של השפה (<code>ltr</code> או <code>rtl</code>).",
+       "apihelp-query+languageinfo-example-simple": "קבלת קודי שפה של כל השפות הנתמכות.",
        "apihelp-query+links-summary": "החזרת כל הקישורים מהדפים שצוינו.",
        "apihelp-query+links-param-namespace": "להציג קישורים רק במרחבי השם האלה.",
        "apihelp-query+links-param-limit": "כמה קישורים להחזיר.",
index af1596d..0ad7687 100644 (file)
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "O código do idioma BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "A direção de escrita do idioma (<code>ltr</code>, da esquerda para a direita, ou <code>rtl</code>, da direita para a esquerda).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "O autônimo do idioma, isto é, o seu nome nesse idioma.",
-       "apihelp-query+languageinfo-paramvalue-prop-name": "O nome do idioma no idioma especificado pelo parâmetro <var>lilang</var>, com a aplicação de idiomas de recurso se necessário.",
+       "apihelp-query+languageinfo-paramvalue-prop-name": "O nome do idioma no idioma especificado pelo parâmetro <var>uselang</var>, com a aplicação de idiomas de recurso se necessário.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "Os códigos de idioma das idiomas de recurso configuradas para esta língua. O recurso final implícito para 'en' não é incluído (mas algum idiomas podem especificar 'en' como último recurso explicitamente).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "Os códigos de idioma das variantes suportadas por esse idioma.",
        "apihelp-query+languageinfo-param-code": "Códigos de idioma dos idiomas que devem ser devolvidas, ou <code>*</code> para todos os idiomas.",
index 029a6dc..6c30749 100644 (file)
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "O código de língua BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "A direção de escrita da língua (<code>ltr</code>, da esquerda para a direita, ou <code>rtl</code>, da direita para a esquerda).",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "O autónimo da língua, isto é, o seu nome nessa língua.",
-       "apihelp-query+languageinfo-paramvalue-prop-name": "O nome da língua na língua especificada pelo parâmetro <var>lilang</var>, com a aplicação de línguas de recurso se necessário.",
+       "apihelp-query+languageinfo-paramvalue-prop-name": "O nome da língua na língua especificada pelo parâmetro <var>uselang</var>, com a aplicação de línguas de recurso se necessário.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "Os códigos de língua das línguas de recurso configuradas para esta língua. O recurso final implícito para 'en' não é incluído (mas algumas línguas podem especificar 'en' como último recurso explicitamente).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "Os códigos de língua das variantes suportadas por esta língua.",
        "apihelp-query+languageinfo-param-code": "Códigos de língua das línguas que devem ser devolvidas, ou <code>*</code> para todas as línguas.",
index 3ca00ee..d62f1ad 100644 (file)
        "api-help-param-templated-var-first": "<var>&#x7B;$1&#x7D;</var> у назві параметра слід замінити значеннями <var>$2</var>",
        "api-help-param-templated-var": "<var>&#x7B;$1&#x7D;</var> — значеннями <var>$2</var>",
        "api-help-datatypes-header": "Типи даних",
-       "api-help-datatypes": "Вхідні дані у MediaWiki мають бути в NFC-нормалізованому UTF-8. MediaWiki може спробувати конвертувати вхідні дані іншого вигляду, але від цього можуть постраждати деякі операції (як [[Special:ApiHelp/edit|редагування]] з перевіркою MD5).\n\nДеякі типи параметрів у запитах API потребують ширшого пояснення:\n;boolean\n:Логічні параметри працюють як галочки HTML: якщо параметр вказано, не залежно від значення, він вважається істинним. Щоб значення було хибним, пропустіть параметр зовсім.\n;timestamp\n:Часові мітки можуть бути вказані у кількох форматах. Рекомендується час і дата в ISO 8601. Усі значення часу в UTC, будь-які часові пояси ігноруються.\n:* Дата і час ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (пунктуація і <kbd>Z</kbd> необов'язокві)\n:* Дата і час ISO 8601 з (ігнорованими) частками секунди, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (дефіси, двокрапки та <kbd>Z</kbd> необов'язкові)\n:* Формат MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Загальний числовий формат, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (необов'язковий часовий пояс <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> або <kbd>-<var>##</var></kbd> ігнорується)\n:* Формат EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Формат RFC 2822 (часовий пояс може бути опущений), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Формат RFC 850 (часовий пояс може бути опущений), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Формат C ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Секунди від 1970-01-01T00:00:00Z у вигляді цілого числа від 1 до 13 цифр (без <kbd>0</kbd>)\n:* Рядок <kbd>now</kbd>\n;альтернативний роздільник багатьох значень\n:Параметри, що приймають багато значень, зазвичай подаються зі значеннями, розділеними вертикальною рискою, наприклад, <kbd>param=value1|value2</kbd> або <kbd>param=value1%7Cvalue2</kbd>. Якщо значення повинне містити вертикальну риску, використовуйте як роздільник U+001F (роздільник одиниць) ''та'' поставте U+001F перед значенням, наприклад, <kbd>param=%1Fvalue1%1Fvalue2</kbd>.",
+       "api-help-datatypes": "Вхідні дані у MediaWiki мають бути в NFC-нормалізованому UTF-8. MediaWiki може спробувати конвертувати вхідні дані іншого вигляду, але від цього можуть постраждати деякі операції (як [[Special:ApiHelp/edit|редагування]] з перевіркою MD5).\n\nДеякі типи параметрів у запитах API потребують ширшого пояснення:\n;boolean\n:Логічні параметри працюють як галочки HTML: якщо параметр вказано, не залежно від значення, він вважається істинним. Щоб значення було хибним, пропустіть параметр зовсім.\n;timestamp\n:Часові мітки можуть бути вказані у кількох форматах. Рекомендується час і дата в ISO 8601, див. детальніше [[mw:Special:MyLanguage/Timestamp|про формати введення бібліотеки часових міток на mediawiki.org]]. Рекомендована дата і час у форматі ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. Додатково, рядок <kbd>now</kbd> можна використовувати, щоб вказати поточну часову мітку.\n;альтернативний роздільник багатьох значень\n:Параметри, що приймають багато значень, зазвичай подаються зі значеннями, розділеними вертикальною рискою, наприклад, <kbd>param=value1|value2</kbd> або <kbd>param=value1%7Cvalue2</kbd>. Якщо значення повинне містити вертикальну риску, використовуйте як роздільник U+001F (роздільник одиниць) ''та'' поставте U+001F перед значенням, наприклад, <kbd>param=%1Fvalue1%1Fvalue2</kbd>.",
        "api-help-templatedparams-header": "Шаблонні параметри",
        "api-help-templatedparams": "Шаблонні параметри підтримують випадки, в яких модулю API необхідне значення для кожного значення якогось іншого параметра. Наприклад, якби був модуль API для запитів фруктів, у нього був би параметр <var>fruits</var> для зазначення, запит на які саме фрукти надсилається і шаблонний параметр <var>{fruit}-quantity</var> для зазначення, скільки саме кожного різновиду фруктів потрібно. Клієнт API, якому потрібні 1 яблуко, 5 бананів і 20 полуниць, таким чином, міг би надіслати запит у формі <kbd>fruits=apples|bananas|strawberries&apples-quantity=1&bananas-quantity=5&strawberries-quantity=20</kbd>.",
        "api-help-param-type-limit": "Тип: ціле число або <kbd>max</kbd>",
index c434a5b..b7c60ed 100644 (file)
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "BCP-47 語言代碼。",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "語言的書寫方向(<code>ltr</code> 或 <code>rtl</code>)。",
        "apihelp-query+languageinfo-paramvalue-prop-autonym": "語言的本語稱呼,也就是該語言用自己語言本身寫出的名稱。",
-       "apihelp-query+languageinfo-paramvalue-prop-name": "在由 <var>lilang</var> 參數所指定語言裡的語言名稱,如有需要可套用語言遞補。",
+       "apihelp-query+languageinfo-paramvalue-prop-name": "在由 <var>uselang</var> 參數所指定語言裡的語言名稱,如有需要可套用語言遞補。",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "替此語言設置的遞補語言之語言代碼。「en」不包括在內含的最後遞補(但一些語言可明確地指定「en」為最後遞補)。",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "由此語言所支援的變體語言代碼。",
        "apihelp-query+languageinfo-param-code": "所應要回傳的語言該語言代碼,或是以 <code>*</code> 來表示為全部語言。",
index d97abca..31b4443 100644 (file)
@@ -662,15 +662,20 @@ class ChangesList extends ContextSource {
         * field of this revision, if it's marked as deleted.
         * @param RCCacheEntry|RecentChange $rc
         * @param int $field
-        * @param User|null $user User object to check, or null to use $wgUser
+        * @param User|null $user User object to check against. If null, the global RequestContext's
+        * User is assumed instead.
         * @return bool
         */
        public static function userCan( $rc, $field, User $user = null ) {
+               if ( $user === null ) {
+                       $user = RequestContext::getMain()->getUser();
+               }
+
                if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
                        return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
-               } else {
-                       return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
                }
+
+               return RevisionRecord::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
        }
 
        /**
index afc7045..6eeac1c 100644 (file)
@@ -332,7 +332,8 @@ class RequestContext implements IContextSource, MutableContext {
                                $request = $this->getRequest();
                                $user = $this->getUser();
 
-                               $code = $request->getVal( 'uselang', 'user' );
+                               // Optimisation: Avoid slow getVal(), this isn't user-generated content.
+                               $code = $request->getRawVal( 'uselang', 'user' );
                                if ( $code === 'user' ) {
                                        $code = $user->getOption( 'language' );
                                }
@@ -383,7 +384,8 @@ class RequestContext implements IContextSource, MutableContext {
                                // No hook override, go through normal processing
                                if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
                                        $userSkin = $this->getUser()->getOption( 'skin' );
-                                       $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin );
+                                       // Optimisation: Avoid slow getVal(), this isn't user-generated content.
+                                       $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
                                } else {
                                        $userSkin = $this->getConfig()->get( 'DefaultSkin' );
                                }
index 3625476..611469c 100644 (file)
@@ -37,7 +37,7 @@ class SearchUpdate implements DeferrableUpdate {
        /** @var Title Title we're updating */
        private $title;
 
-       /** @var Content|bool Content of the page (not text) */
+       /** @var Content|null Content of the page (not text) */
        private $content;
 
        /** @var WikiPage **/
@@ -45,30 +45,30 @@ class SearchUpdate implements DeferrableUpdate {
 
        /**
         * @param int $id Page id to update
-        * @param Title|string $title Title of page to update
-        * @param Content|string|bool $c Content of the page to update. Default: false.
-        *  If a Content object, text will be gotten from it. String is for back-compat.
-        *  Passing false tells the backend to just update the title, not the content
+        * @param Title $title Title of page to update
+        * @param Content|null $c Content of the page to update.
         */
-       public function __construct( $id, $title, $c = false ) {
+       public function __construct( $id, $title, $c = null ) {
                if ( is_string( $title ) ) {
-                       $nt = Title::newFromText( $title );
+                       wfDeprecated( __METHOD__ . " with a string for the title", 1.34 );
+                       $this->title = Title::newFromText( $title );
+                       if ( $this->title === null ) {
+                               throw new InvalidArgumentException( "Cannot construct the title: $title" );
+                       }
                } else {
-                       $nt = $title;
+                       $this->title = $title;
                }
 
-               if ( $nt ) {
-                       $this->id = $id;
-                       // is_string() check is back-compat for ApprovedRevs
-                       if ( is_string( $c ) ) {
-                               $this->content = new TextContent( $c );
-                       } else {
-                               $this->content = $c ?: false;
-                       }
-                       $this->title = $nt;
-               } else {
-                       wfDebug( "SearchUpdate object created with invalid title '$title'\n" );
+               $this->id = $id;
+               // is_string() check is back-compat for ApprovedRevs
+               if ( is_string( $c ) ) {
+                       wfDeprecated( __METHOD__ . " with a string for the content", 1.34 );
+                       $c = new TextContent( $c );
+               } elseif ( is_bool( $c ) ) {
+                       wfDeprecated( __METHOD__ . " with a boolean for the content", 1.34 );
+                       $c = null;
                }
+               $this->content = $c;
        }
 
        /**
@@ -94,15 +94,13 @@ class SearchUpdate implements DeferrableUpdate {
                        if ( $this->getLatestPage() === null ) {
                                $search->delete( $this->id, $normalTitle );
                                continue;
-                       } elseif ( $this->content === false ) {
+                       } elseif ( $this->content === null ) {
                                $search->updateTitle( $this->id, $normalTitle );
                                continue;
                        }
 
-                       $text = $search->getTextFromContent( $this->title, $this->content );
-                       if ( !$search->textAlreadyUpdatedForIndex() ) {
-                               $text = $this->updateText( $text, $search );
-                       }
+                       $text = $this->content !== null ? $this->content->getTextForSearchIndex() : '';
+                       $text = $this->updateText( $text, $search );
 
                        # Perform the actual update
                        $search->update( $this->id, $normalTitle, $search->normalizeText( $text ) );
index 567fb10..99d594d 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup Deployment
  */
 
+use MediaWiki\Installer\InstallException;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -51,6 +52,7 @@ class CliInstaller extends Installer {
         * @param string $siteName
         * @param string|null $admin
         * @param array $options
+        * @throws InstallException
         */
        function __construct( $siteName, $admin = null, array $options = [] ) {
                global $wgContLang;
@@ -114,7 +116,7 @@ class CliInstaller extends Installer {
                        $status = $this->validateExtensions(
                                'extension', 'extensions', $options['extensions'] );
                        if ( !$status->isOK() ) {
-                               $this->showStatusMessage( $status );
+                               throw new InstallException( $status );
                        }
                        $this->setVar( '_Extensions', $status->value );
                } elseif ( isset( $options['with-extensions'] ) ) {
@@ -125,7 +127,7 @@ class CliInstaller extends Installer {
                if ( isset( $options['skins'] ) ) {
                        $status = $this->validateExtensions( 'skin', 'skins', $options['skins'] );
                        if ( !$status->isOK() ) {
-                               $this->showStatusMessage( $status );
+                               throw new InstallException( $status );
                        }
                        $skins = $status->value;
                } else {
@@ -176,15 +178,23 @@ class CliInstaller extends Installer {
 
                $vars = Installer::getExistingLocalSettings();
                if ( $vars ) {
-                       $this->showStatusMessage(
-                               Status::newFatal( "config-localsettings-cli-upgrade" )
-                       );
+                       $status = Status::newFatal( "config-localsettings-cli-upgrade" );
+                       $this->showStatusMessage( $status );
+                       return $status;
                }
 
-               $this->performInstallation(
+               $result = $this->performInstallation(
                        [ $this, 'startStage' ],
                        [ $this, 'endStage' ]
                );
+               // PerformInstallation bails on a fatal, so make sure the last item
+               // completed before giving 'next.' Likewise, only provide back on failure
+               $lastStepStatus = end( $result );
+               if ( $lastStepStatus->isOk() ) {
+                       return Status::newGood();
+               } else {
+                       return $lastStepStatus;
+               }
        }
 
        /**
@@ -248,11 +258,6 @@ class CliInstaller extends Installer {
                                $this->showMessage( ...$w );
                        }
                }
-
-               if ( !$status->isOK() ) {
-                       echo "\n";
-                       exit( 1 );
-               }
        }
 
        public function envCheckPath() {
diff --git a/includes/installer/InstallException.php b/includes/installer/InstallException.php
new file mode 100644 (file)
index 0000000..a03a5aa
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Installer;
+
+use Throwable;
+
+/**
+ * Exception thrown if an error occur which installation
+ * @ingroup Exception
+ */
+class InstallException extends \MWException {
+       /**
+        * @var \Status State when an exception occurs
+        */
+       private $status;
+
+       /**
+        * InstallException constructor.
+        * @param \Status $status State when an exception occurs
+        * @param string $message The Exception message to throw
+        * @param int $code The Exception code
+        * @param Throwable|null $previous The previous throwable used for the exception chaining
+        */
+       public function __construct( \Status $status, $message = '', $code = 0,
+               Throwable $previous = null ) {
+               parent::__construct( $message, $code, $previous );
+               $this->status = $status;
+       }
+
+       public function getStatus() : \Status {
+               return $this->status;
+       }
+}
index f6a5c41..7452b7c 100644 (file)
@@ -756,6 +756,8 @@ abstract class Installer {
         */
        protected function envCheckDB() {
                global $wgLang;
+               /** @var string|null $dbType The user-specified database type */
+               $dbType = $this->getVar( 'wgDBtype' );
 
                $allNames = [];
 
@@ -768,25 +770,27 @@ abstract class Installer {
                $databases = $this->getCompiledDBs();
 
                $databases = array_flip( $databases );
+               $ok = true;
                foreach ( array_keys( $databases ) as $db ) {
                        $installer = $this->getDBInstaller( $db );
                        $status = $installer->checkPrerequisites();
                        if ( !$status->isGood() ) {
+                               if ( !$this instanceof WebInstaller && $db === $dbType ) {
+                                       // Strictly check the key database type instead of just outputting message
+                                       // Note: No perform this check run from the web installer, since this method always called by
+                                       // the welcome page under web installation, so $dbType will always be 'mysql'
+                                       $ok = false;
+                               }
                                $this->showStatusMessage( $status );
-                       }
-                       if ( !$status->isOK() ) {
                                unset( $databases[$db] );
                        }
                }
                $databases = array_flip( $databases );
                if ( !$databases ) {
                        $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
-
-                       // @todo FIXME: This only works for the web installer!
                        return false;
                }
-
-               return true;
+               return $ok;
        }
 
        /**
@@ -1586,7 +1590,7 @@ abstract class Installer {
         * @param callable $startCB A callback array for the beginning of each step
         * @param callable $endCB A callback array for the end of each step
         *
-        * @return array Array of Status objects
+        * @return Status[] Array of Status objects
         */
        public function performInstallation( $startCB, $endCB ) {
                $installResults = [];
index 6b2024f..26d2ea7 100644 (file)
        "config-restart": "Sí, reanicialu",
        "config-welcome": "=== Comprobaciones del entornu ===\nAgora van facese unes comprobaciones básiques para ver si l'entornu ye afayadizu pa la instalación de MediaWiki.\nAlcuérdese d'incluir esta información si necesita encontu pa completar la instalación.",
        "config-welcome-section-copyright": "=== Drechos d'autor y condiciones d'usu ===\n\n$1\n\nEsti programa ye software llibre; puedes redistribuilu y/o camudalu baxo les condiciones de la llicencia pública xeneral GNU tal como la publica la Free Software Foundation; versión 2 o (como prefieras) cualquier versión posterior.\n\nEsti programa distribúese cola esperanza de que pueda ser útil, pero <strong>ensin garantía denguna</strong>; nin siquiera la garantía implícita de <strong>comercialidá</strong> o \n<strong>adautación a un fin determináu</strong>.\nVer la Llicencia pública xeneral GNU pa más detalles.\n\nHabríes de tener recibío [$2 una copia de la llicencia pública xeneral GNU] xunto con esti programa; sinón, escribi a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [https://www.gnu.org/copyleft/gpl.html lléila en llinia].",
-       "config-sidebar": "* [https://www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuariu]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía del alministrador]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Entrugues frecuentes]\n----\n* <doclink href=Readme>Lléame</doclink>\n* <doclink href=ReleaseNotes>Notes de llanzamientu</doclink>\n* <doclink href=Copying>Copia</doclink>\n* <doclink href=UpgradeDoc>Anovamientu</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org Páxina principal de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guía del usuariu]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guía p'alministradores]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Entrugues frecuentes]",
+       "config-sidebar-readme": "Llei-me",
+       "config-sidebar-relnotes": "Notes de la versión",
+       "config-sidebar-license": "Copiar",
+       "config-sidebar-upgrade": "Anovar",
        "config-env-good": "Comprobóse l'entornu.\nPues instalar MediaWiki.",
        "config-env-bad": "Comprobóse l'entornu.\nNun pues instalar MediaWiki.",
        "config-env-php": "PHP $1 ta instaláu.",
        "config-env-hhvm": "HHVM $1 ta instaláu.",
-       "config-unicode-using-intl": "Usando la [https://pecl.php.net/intl estensión intl PECL] pa la normalización Unicode.",
-       "config-unicode-pure-php-warning": "'''Avisu:''' La [https://pecl.php.net/intl estensión intl PECL] nun ta disponible pa xestionar la normalización Unicode; volviendo a la implementación lenta en PHP puru.\nSi xestiona un sitiu con un tráficu altu, tendría de lleer una migaya sobro la [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
+       "config-unicode-using-intl": "Usando la [https://php.net/manual/en/book.intl.php estensión intl de PHP] pa la normalización Unicode.",
+       "config-unicode-pure-php-warning": "<strong>Avisu:</strong> La [https://php.net/manual/en/book.intl.php estensión intl de PHP] nun ta disponible pa xestionar la normalización Unicode; volviendo a la implementación lenta en PHP puru.\nSi alministres un sitiu con un tráficu altu, tendríes de lleer sobro la [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
        "config-unicode-update-warning": "'''Avisu:''' La versión instalada del envoltoriu de normalización Unicode usa una versión antigua de la biblioteca [http://site.icu-project.org/ de los proyeutos ICU].\nTendría [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations d'anovala] si ye importante pa vusté usar Unicode.",
        "config-no-db": "¡Nun pudo alcontrase un controlador de base de datos afayadizu! Necesites instalar un controlador de base de datos pa PHP.\n{{PLURAL:$2|Tien sofitu el tipu de base de datos siguiente|Tienen sofitu los tipos de base de datos siguientes}}: $1.\n\nSi compilasti PHP tu mesmu, reconfigúralu con un cliente de base de datos activáu, por exemplu, usando <code>./configure --with-mysqli</code>.\nSi instalasti PHP dende un paquete de Debian o Ubuntu, necesites instalar tamién,por exemplu, el paquete <code>php-mysql</code>.",
-       "config-outdated-sqlite": "'''Avisu:''' tien SQLite $1, que ye inferior a la versión mínima necesaria $2. SQLite nun tará disponible.",
+       "config-outdated-sqlite": "<strong>Avisu:</strong> tienes SQLite $1, que ye menor que la versión mínima necesaria $2. SQLite nun tará disponible.",
        "config-no-fts3": "'''Avisu:''' SQLite ta compiláu ensin el [//sqlite.org/fts3.html módulu FTS3]; les funciones de gueta nun tarán disponibles nesti sistema.",
        "config-pcre-old": "<strong>Fatal:</strong> Ríquese PCRE $1 o posterior.\nEl binariu de PHP ta enllazáu con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Más información].",
        "config-pcre-no-utf8": "<strong>Erru fatal:</strong> Paez que'l módulu PCRE de PHP foi compiláu ensin el soporte PCRE_UTF8.\nMediaWiki requier compatibilidá con UTF_8 pa furrular correutamente.",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki ye compatible colos siguientes sistemes de bases de datos:\n\n$1\n\nSi nun atopes na llista el sistema de base de datos que tas intentando utilizar, sigue les instrucciones enllazaes enriba p'activar la compatibilidá.",
+       "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] ye la base de datos primaria pa MediaWiki y la que tien mayor encontu. MediaWiki tamién funciona con [{{int:version-db-myslql-url}} MySQL] y [{{int:version-db-percona-url}} Percona Server], que son compatibles con MariaDB. ([https://www.php.net/manual/en/mysqli.installation.php Cómo compilar PHP con compatibilidá MySQL])",
+       "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ye un sistema popular de base de datos de códigu abiertu, como alternativa a MySQL. ([https://www.php.net/manual/en/pgsql.installation.php Cómo compilar PHP con compatibilidá PostgreSQL]).",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] ye un sistema de base de datos llixeru que tien encontu perbonu. ([https://https://www.php.net/manual/en/pdo.installation.php Cómo compilar PHP con encontu pa SQLite], usa PDO)",
+       "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ye una base de datos comercial a nivel empresarial. ([https://www.php.net/manual/en/oci8.installation.php Cómo compilar PHP con encontu pa OCI8])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ye un sistema comercial de base de datos empresariales pa Windows. ([https://www.php.net/manual/en/sqlsrv.installation.php Cómo compilar PHP con compatibilidá pa SQLSRV])",
        "config-header-mysql": "Axustes de MariaDB/MySQL",
        "config-header-postgres": "Axustes de PostgreSQL",
        "config-sqlite-readonly": "El ficheru <code>$1</code> nun puede escribise.",
        "config-sqlite-cant-create-db": "Nun pudo crease'l ficheru de la base de datos <code>$1</code>.",
        "config-sqlite-fts3-downgrade": "PHP nun tien compatibilidá pa FTS3, baxando a una versión anterior les tables.",
+       "config-can-upgrade": "Esta base de datos contien tables de MediaWiki.\nP'anovales a MediaWiki $1, pulsia <strong>Siguir</strong>.",
+       "config-upgrade-error": "Hebo un error al anovar les tables de MediaWiki de la base de datos.\n\nPa más información, consulta'l rexistru de más arriba; pa tentalo nuevamente, pulsia <strong>Siguir</strong>.",
+       "config-upgrade-done": "Anovamientu completáu.\n\nYá puedes [$1 principiar a usar la wiki].\n\nSi quies rexenerar el ficheru <code>LocalSettings.php</code>, pulsia nel botón d'abaxo.\nEsto <strong>nun s'encamienta</strong> sacante que teas teniendo problemes cola wiki.",
+       "config-upgrade-done-no-regenerate": "Anovamientu completáu.\n\nYá puedes [$1 principiar a usar la wiki].",
        "config-regenerate": "Rexenerar LocalSettings.php →",
        "config-show-table-status": "¡Falló la consulta <code>SHOW TABLE STATUS</code>!",
        "config-unknown-collation": "<strong>Avisu:</strong> La base de datos utiliza un orde alfabéticu ensin reconocer.",
        "config-admin-help": "Escribe equí'l nome d'usuariu que desees, como por casu \"Nel Bloggs\".\nEsti ye'l nome que vas usar pa entrar na wiki.",
        "config-admin-name-blank": "Escribe'l nome d'usuariu d'un alministrador.",
        "config-admin-password-mismatch": "Les dos contraseñes qu'introduxesti nun concasen.",
+       "config-admin-email": "Direición de corréu electrónicu:",
+       "config-admin-email-help": "Escribi equí una dirección de corréu electrónicu pa que te permita recibir mensaxes d'otros usuarios de la wiki, reaniciar la contraseña y recibir avisos de cambeos nes páxines de la to llista de siguimientu. Puedes dexar esti campu vacíu.",
+       "config-admin-error-user": "Error internu al crear un alministrador col nome «<nowiki>$1</nowiki>»",
+       "config-admin-error-password": "Error internu al configurar una contraseña pal alministrador «<nowiki>$1</nowiki>»: <pre>$2</pre>",
+       "config-admin-error-bademail": "Escribisti una dirección de corréu electrónicu inválida.",
        "config-optional-skip": "Yá toi aburríu, namái instala la wiki.",
        "config-profile-wiki": "Wiki públicu",
        "config-profile-no-anon": "Ríquese crear una cuenta",
        "config-install-tables-failed": "<strong>Error:</strong> La creación de tables falló col error siguiente: $1",
        "config-install-interwiki": "Enllenando la tabla d'interwiki predeterminada",
        "config-install-interwiki-list": "Nun pudo lleese'l ficheru <code>interwiki.list</code>.",
+       "config-install-interwiki-exists": "<strong>Avisu:</strong> Paez que la tabla d'interwikis yá contien entraes.\nSaltando la llista predeterminada.",
+       "config-install-stats": "Aniciando les estadístiques",
+       "config-install-keys": "Xenerando les claves secretes",
+       "config-install-updates": "Torgar que s'executen anovamientos innecesarios",
        "config-download-localsettings": "Descargar <code>LocalSettings.php</code>",
        "config-help": "ayuda",
+       "config-help-tooltip": "pulsia p'ampliar",
        "config-nofile": "Nun pudo atopase'l ficheru \"$1\". ¿Desaniciose?",
        "config-skins-screenshots": "$1 (imaxes de pantalla: $2)",
        "config-skins-screenshot": "$1 ($2)",
index 7d361bc..3a869cf 100644 (file)
@@ -11,7 +11,8 @@
                        "Macofe",
                        "Guycn2",
                        "שמזן",
-                       "חיים"
+                       "חיים",
+                       "Steeve815"
                ]
        },
        "config-desc": "תכנית ההתקנה של מדיה־ויקי",
        "config-welcome": "=== בדיקות סביבה ===\nבדיקות בסיסיות תתבצענה עכשיו כדי לראות אם הסביבה הזאת מתאימה להתקנת מדיה־ויקי.\nנא לזכור לכלול את המידע הזה בעת בקשת תמיכה עם השלמת ההתקנה.",
        "config-welcome-section-copyright": "=== זכויות יוצרים ותנאים ===\n\n$1\n\nתכנית זו היא תכנה חופשית; באפשרותך להפיצה מחדש ו/או לשנות אותה על פי תנאי הרישיון הציבורי הכללי של GNU כפי שפורסם על ידי קרן התכנה החופשית; בין אם גרסה 2 של הרישיון, ובין אם (לפי בחירתך) כל גרסה מאוחרת שלו.\n\nתכנית זו מופצת בתקווה שתהיה מועילה, אבל '''בלא אחריות כלשהי'''; ואפילו ללא האחריות המשתמעת בדבר '''מסחריותה''' או '''התאמתה למטרה '''מסוימת'''. לפרטים נוספים, ניתן לעיין ברישיון הציבורי הכללי של GNU.\n\nלתכנית זו אמור היה להיות מצורף [$2 עותק של הרישיון הציבורי הכללי של GNU]; אם לא קיבלת אותו, אפשר לכתוב ל־Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA או [https://www.gnu.org/copyleft/gpl.html לקרוא אותו דרך האינטרנט].",
        "config-sidebar": "* [https://www.mediawiki.org אתר הבית של מדיה־ויקי]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents המדריך למשתמש]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents המדריך למנהל]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ שו״ת]\n----\n* <doclink href=Readme>קרא אותי</doclink>\n* <doclink href=ReleaseNotes>הערות גרסה</doclink>\n* <doclink href=Copying>העתקה</doclink>\n* <doclink href=UpgradeDoc>שדרוג</doclink>",
+       "config-sidebar-readme": "קרא־אותי",
+       "config-sidebar-relnotes": "הערות גרסה",
+       "config-sidebar-license": "העתקה",
+       "config-sidebar-upgrade": "שדרוג",
        "config-env-good": "הסביבה שלכם נבדקה.\nאפשר להתקין מדיה־ויקי.",
        "config-env-bad": "הסביבה שלכם נבדקה.\nאי־אפשר להתקין מדיה־ויקי.",
        "config-env-php": "מותקנת <span dir=\"ltr\">PHP $1</span>.",
        "config-env-hhvm": "מותקנת <span dir=\"ltr\">HHVM $1</span>.",
-       "config-unicode-using-intl": "משתמש ב[https://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד.",
+       "config-unicode-using-intl": "משתמש ב[https://php.net/manual/en/book.intl.php הרחבת PHP intl] לנרמול יוניקוד.",
        "config-unicode-pure-php-warning": "<strong>אזהרה:</strong> [https://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.\nאם זהו אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
        "config-unicode-update-warning": "'''אזהרה''': הגרסה המותקנת של מעטפת נרמול יוניקוד משתמשת בגרסה ישנה של הספרייה של [http://site.icu-project.org/ פרויקט ICU].\nכדאי [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations לעדכן] אם הטיפול ביוניקוד חשוב לך.",
        "config-no-db": "לא נמצא דרייבר מסד נתונים מתאים. יש להתקין דרייבר מסד נתונים ל־PHP.\n{{PLURAL:$2|נתמך הסוג הבא של מסד נתונים|נתמכים הסוגים הבאים של מסדי נתונים}}: $1.\n\nאם קִמפלת את PHP בעצמך, יש להגדיר אותו מחדש ולהפעיל את לקוח מסד נתונים, למשל באמצעות <code dir=\"ltr\">./configure --with-mysqli</code>.\nאם התקנת את PHP מחבילה של דביאן או של אובונטו, יש להתקין, למשל, גם את המודול <code dir=\"ltr\">php-mysql</code>.",
        "config-install-done": "<strong>מזל טוב!</strong>\nהתקנת את תוכנת מדיה־ויקי.\n\nתוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.\nהוא מכיל את כל ההגדרות שלך.\n\nיש להוריד אותו ולהכניס אותו לתיקיית הבסיס שבה הותקן הוויקי שלך (אותה התיקייה שבה נמצא הקובץ index.php). ההורדה אמורה להתחיל באופן אוטומטי.\n\nאם ההורדה לא התחילה, או אם ביטלת אותה, אפשר להתחיל אותה מחדש באמצעות לחיצה על הקישור הבא:\n\n$3\n\n<strong>לתשומת לבך:</strong> אם ההורדה לא תבוצע כעת, קובץ ההגדרות <strong>לא</strong> יהיה זמין מאוחר יותר אם תוכנת ההתקנה תיסגר לפני שהקובץ יורד.\n\nלאחר שביצעת את הפעולות שלהלן, באפשרותך <strong>[$2 להיכנס לאתר הוויקי שלך]</strong>.",
        "config-install-done-path": "<strong>מזל טוב!</strong>\nהתקנת את תוכנת מדיה־ויקי.\n\nתוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.\nהוא מכיל את כל ההגדרות שלך.\n\nיש להוריד אותו ולהכניס אותו לתיקייה <code>$4</code>. ההורדה אמורה להתחיל באופן אוטומטי.\n\nאם ההורדה לא התחילה, או אם ביטלת אותה, אפשר להתחיל אותה מחדש באמצעות לחיצה על הקישור הבא:\n\n$3\n\n<strong>לתשומת לבך:</strong> אם ההורדה לא תבוצע כעת, קובץ ההגדרות <strong>לא</strong> יהיה זמין מאוחר יותר אם תוכנת ההתקנה תיסגר לפני שהקובץ יורד.\n\nלאחר שביצעת את הפעולות שלהלן, באפשרותך <strong>[$2 להיכנס לאתר הוויקי שלך]</strong>.",
        "config-install-success": "מדיה־ויקי הותקנה בהצלחה. עכשיו אפשר\nלבקר בכתובת <$1$2> כדי לצפות בוויקי שלך.\nאם יש לך שאלות, ר' את רשימת השאלות הנפוצות שלנו:\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> ואפשר גם להשתמש\nבאתרי התמיכה שקישורים אליהם מופיעים באותו הדף.",
+       "config-install-db-success": "בסיס הנתונים הוגדר בהצלחה",
        "config-download-localsettings": "הורדת <code>LocalSettings.php</code>",
        "config-help": "עזרה",
        "config-help-tooltip": "להרחיב",
index 9516433..55e8d78 100644 (file)
        "config-restart": "はい、再起動します",
        "config-welcome": "=== 環境の確認 ===\n基本的な確認では、現在の環境が MediaWiki のインストールに適しているかを確認します。\nインストール方法について助けが必要になった場合は、必ずこの確認結果を添えてください。",
        "config-welcome-section-copyright": "=== 著作権および規約 ===\n$1\n\nこの作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行する GNU 一般公衆利用許諾書 (GNU General Public License) (バージョン 2、またはそれ以降のライセンス) の規約に基づき、このライブラリを再配布および改変できます。\n\nこの作品は、有用であることを期待して配布されていますが、<strong>商用または特定の目的に適するかどうか</strong>も含めて、暗黙的にも、<strong>一切保証されません</strong>。\n詳しくは、 GNU 一般公衆利用許諾書をご覧ください。\n\nあなたはこのプログラムと共に、[$2 GNU 一般公衆利用許諾契約書の複製]を受け取ったはずです。受け取っていない場合は、フリーソフトウェア財団 (宛先は the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA) まで請求するか、または[https://www.gnu.org/copyleft/gpl.html オンラインでお読みください]。",
-       "config-sidebar": "* [https://www.mediawiki.org MediaWikiのホーム]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 利用者向け案内]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents 管理者向け案内]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>お読みください</doclink>\n* <doclink href=ReleaseNotes>リリースノート</doclink>\n* <doclink href=Copying>コピー</doclink>\n* <doclink href=UpgradeDoc>アップグレード</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org MediaWikiのホーム]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 利用者向け案内]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents 管理者向け案内]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]",
+       "config-sidebar-readme": "お読みください",
+       "config-sidebar-relnotes": "リリースノート",
+       "config-sidebar-license": "複製",
+       "config-sidebar-upgrade": "アップグレード",
        "config-env-good": "環境を確認しました。\nMediaWiki をインストールできます。",
        "config-env-bad": "環境を確認しました。\nMediaWiki のインストールはできません。",
        "config-env-php": "PHP $1がインストールされています。",
index 5e8c22b..37d53e2 100644 (file)
@@ -150,12 +150,12 @@ abstract class DBLockManager extends QuorumLockManager {
                        } elseif ( is_array( $this->dbServers[$lockDb] ) ) {
                                // Parameters to construct a new database connection
                                $config = $this->dbServers[$lockDb];
+                               $config['flags'] = ( $config['flags'] ?? 0 );
+                               $config['flags'] &= ~( IDatabase::DBO_TRX | IDatabase::DBO_DEFAULT );
                                $db = Database::factory( $config['type'], $config );
                        } else {
                                throw new UnexpectedValueException( "No server called '$lockDb'." );
                        }
-
-                       $db->clearFlag( DBO_TRX );
                        # If the connection drops, try to avoid letting the DB rollback
                        # and release the locks before the file operations are finished.
                        # This won't handle the case of DB server restarts however.
diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php
deleted file mode 100644 (file)
index 1852685..0000000
+++ /dev/null
@@ -1,2633 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-/**
- * Multi-datacenter aware caching interface
- *
- * ### Using WANObjectCache
- *
- * All operations go to the local datacenter cache, except for delete(),
- * touchCheckKey(), and resetCheckKey(), which broadcast to all datacenters.
- *
- * This class is intended for caching data from primary stores.
- * If the get() method does not return a value, then the caller
- * should query the new value and backfill the cache using set().
- * The preferred way to do this logic is through getWithSetCallback().
- * When querying the store on cache miss, the closest DB replica
- * should be used. Try to avoid heavyweight DB master or quorum reads.
- *
- * To ensure consumers of the cache see new values in a timely manner,
- * you either need to follow either the validation strategy, or the
- * purge strategy.
- *
- * The validation strategy refers to the natural avoidance of stale data
- * by one of the following means:
- *
- *   - A) The cached value is immutable.
- *        If the consumer has access to an identifier that uniquely describes a value,
- *        cached value need not change. Instead, the key can change. This also allows
- *        all servers to access their perceived current version. This is important
- *        in context of multiple deployed versions of your application and/or cross-dc
- *        database replication, to ensure deterministic values without oscillation.
- *   - B) Validity is checked against the source after get().
- *        This is the inverse of A. The unique identifier is embedded inside the value
- *        and validated after on retreival. If outdated, the value is recomputed.
- *   - C) The value is cached with a modest TTL (without validation).
- *        If value recomputation is reasonably performant, and the value is allowed to
- *        be stale, one should consider using TTL only – using the value's age as
- *        method of validation.
- *
- * The purge strategy refers to the the approach whereby your application knows that
- * source data has changed and can react by purging the relevant cache keys.
- * As purges are expensive, this strategy should be avoided if possible.
- * The simplest purge method is delete().
- *
- * No matter which strategy you choose, callers must not rely on updates or purges
- * being immediately visible to other servers. It should be treated similarly as
- * one would a database replica.
- *
- * The need for immediate updates should be avoided. If needed, solutions must be
- * sought outside WANObjectCache.
- *
- * ### Deploying WANObjectCache
- *
- * There are two supported ways to set up broadcasted operations:
- *
- *   - A) Set up mcrouter as the underlying cache backend, using a memcached BagOStuff class
- *        for the 'cache' parameter. The 'region' and 'cluster' parameters must be provided
- *        and 'mcrouterAware' must be set to `true`.
- *        Configure mcrouter as follows:
- *          - 1) Use Route Prefixing based on region (datacenter) and cache cluster.
- *               See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
- *               https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup.
- *          - 2) To increase the consistency of delete() and touchCheckKey() during cache
- *               server membership changes, you can use the OperationSelectorRoute to
- *               configure 'set' and 'delete' operations to go to all servers in the cache
- *               cluster, instead of just one server determined by hashing.
- *               See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles.
- *   - B) Set up dynomite as a cache middleware between the web servers and either memcached
- *        or redis and use it as the underlying cache backend, using a memcached BagOStuff
- *        class for the 'cache' parameter. This will broadcast all key setting operations,
- *        not just purges, which can be useful for cache warming. Writes are eventually
- *        consistent via the Dynamo replication model. See https://github.com/Netflix/dynomite.
- *
- * Broadcasted operations like delete() and touchCheckKey() are intended to run
- * immediately in the local datacenter and asynchronously in remote datacenters.
- *
- * This means that callers in all datacenters may see older values for however many
- * milliseconds that the purge took to reach that datacenter. As with any cache, this
- * should not be relied on for cases where reads are used to determine writes to source
- * (e.g. non-cache) data stores, except when reading immutable data.
- *
- * All values are wrapped in metadata arrays. Keys use a "WANCache:" prefix
- * to avoid collisions with keys that are not wrapped as metadata arrays. The
- * prefixes are as follows:
- *   - a) "WANCache:v" : used for regular value keys
- *   - b) "WANCache:i" : used for temporarily storing values of tombstoned keys
- *   - c) "WANCache:t" : used for storing timestamp "check" keys
- *   - d) "WANCache:m" : used for temporary mutex keys to avoid cache stampedes
- *
- * @ingroup Cache
- * @since 1.26
- */
-class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInterface {
-       /** @var BagOStuff The local datacenter cache */
-       protected $cache;
-       /** @var MapCacheLRU[] Map of group PHP instance caches */
-       protected $processCaches = [];
-       /** @var LoggerInterface */
-       protected $logger;
-       /** @var StatsdDataFactoryInterface */
-       protected $stats;
-       /** @var callable|null Function that takes a WAN cache callback and runs it later */
-       protected $asyncHandler;
-
-       /** @bar bool Whether to use mcrouter key prefixing for routing */
-       protected $mcrouterAware;
-       /** @var string Physical region for mcrouter use */
-       protected $region;
-       /** @var string Cache cluster name for mcrouter use */
-       protected $cluster;
-       /** @var bool Whether to use "interim" caching while keys are tombstoned */
-       protected $useInterimHoldOffCaching = true;
-       /** @var float Unix timestamp of the oldest possible valid values */
-       protected $epoch;
-       /** @var string Stable secret used for hasing long strings into key components */
-       protected $secret;
-
-       /** @var int Callback stack depth for getWithSetCallback() */
-       private $callbackDepth = 0;
-       /** @var mixed[] Temporary warm-up cache */
-       private $warmupCache = [];
-       /** @var int Key fetched */
-       private $warmupKeyMisses = 0;
-
-       /** @var float|null */
-       private $wallClockOverride;
-
-       /** @var int Max expected seconds to pass between delete() and DB commit finishing */
-       const MAX_COMMIT_DELAY = 3;
-       /** @var int Max expected seconds of combined lag from replication and view snapshots */
-       const MAX_READ_LAG = 7;
-       /** @var int Seconds to tombstone keys on delete() and treat as volatile after invalidation */
-       const HOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
-
-       /** @var int Idiom for getWithSetCallback() meaning "do not store the callback result" */
-       const TTL_UNCACHEABLE = -1;
-
-       /** @var int Consider regeneration if the key will expire within this many seconds */
-       const LOW_TTL = 30;
-       /** @var int Max TTL, in seconds, to store keys when a data sourced is lagged */
-       const TTL_LAGGED = 30;
-
-       /** @var int Expected time-till-refresh, in seconds, if the key is accessed once per second */
-       const HOT_TTR = 900;
-       /** @var int Minimum key age, in seconds, for expected time-till-refresh to be considered */
-       const AGE_NEW = 60;
-
-       /** @var int Idiom for getWithSetCallback() meaning "no cache stampede mutex required" */
-       const TSE_NONE = -1;
-
-       /** @var int Idiom for set()/getWithSetCallback() meaning "no post-expiration persistence" */
-       const STALE_TTL_NONE = 0;
-       /** @var int Idiom for set()/getWithSetCallback() meaning "no post-expiration grace period" */
-       const GRACE_TTL_NONE = 0;
-       /** @var int Idiom for delete()/touchCheckKey() meaning "no hold-off period" */
-       const HOLDOFF_TTL_NONE = 0;
-       /** @var int Alias for HOLDOFF_TTL_NONE (b/c) (deprecated since 1.34) */
-       const HOLDOFF_NONE = self::HOLDOFF_TTL_NONE;
-
-       /** @var float Idiom for getWithSetCallback() meaning "no minimum required as-of timestamp" */
-       const MIN_TIMESTAMP_NONE = 0.0;
-
-       /** @var string Default process cache name and max key count */
-       const PC_PRIMARY = 'primary:1000';
-
-       /** @var int Idion for get()/getMulti() to return extra information by reference */
-       const PASS_BY_REF = -1;
-
-       /** @var int Seconds to keep dependency purge keys around */
-       private static $CHECK_KEY_TTL = self::TTL_YEAR;
-       /** @var int Seconds to keep interim value keys for tombstoned keys around */
-       private static $INTERIM_KEY_TTL = 1;
-
-       /** @var int Seconds to keep lock keys around */
-       private static $LOCK_TTL = 10;
-       /** @var int Seconds to no-op key set() calls to avoid large blob I/O stampedes */
-       private static $COOLOFF_TTL = 1;
-       /** @var int Seconds to ramp up the chance of regeneration due to expected time-till-refresh */
-       private static $RAMPUP_TTL = 30;
-
-       /** @var float Tiny negative float to use when CTL comes up >= 0 due to clock skew */
-       private static $TINY_NEGATIVE = -0.000001;
-       /** @var float Tiny positive float to use when using "minTime" to assert an inequality */
-       private static $TINY_POSTIVE = 0.000001;
-
-       /** @var int Milliseconds of key fetch/validate/regenerate delay prone to set() stampedes */
-       private static $SET_DELAY_HIGH_MS = 50;
-       /** @var int Min millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL) */
-       private static $RECENT_SET_LOW_MS = 50;
-       /** @var int Max millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL) */
-       private static $RECENT_SET_HIGH_MS = 100;
-
-       /** @var int Consider value generation slow if it takes more than this many seconds */
-       private static $GENERATION_SLOW_SEC = 3;
-
-       /** @var int Key to the tombstone entry timestamp */
-       private static $PURGE_TIME = 0;
-       /** @var int Key to the tombstone entry hold-off TTL */
-       private static $PURGE_HOLDOFF = 1;
-
-       /** @var int Cache format version number */
-       private static $VERSION = 1;
-
-       /** @var int Key to WAN cache version number */
-       private static $FLD_FORMAT_VERSION = 0;
-       /** @var int Key to the cached value */
-       private static $FLD_VALUE = 1;
-       /** @var int Key to the original TTL */
-       private static $FLD_TTL = 2;
-       /** @var int Key to the cache timestamp */
-       private static $FLD_TIME = 3;
-       /** @var int Key to the flags bit field (reserved number) */
-       private static /** @noinspection PhpUnusedPrivateFieldInspection */ $FLD_FLAGS = 4;
-       /** @var int Key to collection cache version number */
-       private static $FLD_VALUE_VERSION = 5;
-       /** @var int Key to how long it took to generate the value */
-       private static $FLD_GENERATION_TIME = 6;
-
-       private static $VALUE_KEY_PREFIX = 'WANCache:v:';
-       private static $INTERIM_KEY_PREFIX = 'WANCache:i:';
-       private static $TIME_KEY_PREFIX = 'WANCache:t:';
-       private static $MUTEX_KEY_PREFIX = 'WANCache:m:';
-       private static $COOLOFF_KEY_PREFIX = 'WANCache:c:';
-
-       private static $PURGE_VAL_PREFIX = 'PURGED:';
-
-       /**
-        * @param array $params
-        *   - cache    : BagOStuff object for a persistent cache
-        *   - logger   : LoggerInterface object
-        *   - stats    : StatsdDataFactoryInterface object
-        *   - asyncHandler : A function that takes a callback and runs it later. If supplied,
-        *       whenever a preemptive refresh would be triggered in getWithSetCallback(), the
-        *       current cache value is still used instead. However, the async-handler function
-        *       receives a WAN cache callback that, when run, will execute the value generation
-        *       callback supplied by the getWithSetCallback() caller. The result will be saved
-        *       as normal. The handler is expected to call the WAN cache callback at an opportune
-        *       time (e.g. HTTP post-send), though generally within a few 100ms. [optional]
-        *   - region: the current physical region. This is required when using mcrouter as the
-        *       backing store proxy. [optional]
-        *   - cluster: name of the cache cluster used by this WAN cache. The name must be the
-        *       same in all datacenters; the ("region","cluster") tuple is what distinguishes
-        *       the counterpart cache clusters among all the datacenter. The contents of
-        *       https://github.com/facebook/mcrouter/wiki/Config-Files give background on this.
-        *       This is required when using mcrouter as the backing store proxy. [optional]
-        *   - mcrouterAware: set as true if mcrouter is the backing store proxy and mcrouter
-        *       is configured to interpret /<region>/<cluster>/ key prefixes as routes. This
-        *       requires that "region" and "cluster" are both set above. [optional]
-        *   - epoch: lowest UNIX timestamp a value/tombstone must have to be valid. [optional]
-        *   - secret: stable secret used for hashing long strings into key components. [optional]
-        */
-       public function __construct( array $params ) {
-               $this->cache = $params['cache'];
-               $this->region = $params['region'] ?? 'main';
-               $this->cluster = $params['cluster'] ?? 'wan-main';
-               $this->mcrouterAware = !empty( $params['mcrouterAware'] );
-               $this->epoch = $params['epoch'] ?? 0;
-               $this->secret = $params['secret'] ?? (string)$this->epoch;
-
-               $this->setLogger( $params['logger'] ?? new NullLogger() );
-               $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
-               $this->asyncHandler = $params['asyncHandler'] ?? null;
-       }
-
-       /**
-        * @param LoggerInterface $logger
-        */
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-
-       /**
-        * Get an instance that wraps EmptyBagOStuff
-        *
-        * @return WANObjectCache
-        */
-       public static function newEmpty() {
-               return new static( [ 'cache' => new EmptyBagOStuff() ] );
-       }
-
-       /**
-        * Fetch the value of a key from cache
-        *
-        * If supplied, $curTTL is set to the remaining TTL (current time left):
-        *   - a) INF; if $key exists, has no TTL, and is not invalidated by $checkKeys
-        *   - b) float (>=0); if $key exists, has a TTL, and is not invalidated by $checkKeys
-        *   - c) float (<0); if $key is tombstoned, stale, or existing but invalidated by $checkKeys
-        *   - d) null; if $key does not exist and is not tombstoned
-        *
-        * If a key is tombstoned, $curTTL will reflect the time since delete().
-        *
-        * The timestamp of $key will be checked against the last-purge timestamp
-        * of each of $checkKeys. Those $checkKeys not in cache will have the last-purge
-        * initialized to the current timestamp. If any of $checkKeys have a timestamp
-        * greater than that of $key, then $curTTL will reflect how long ago $key
-        * became invalid. Callers can use $curTTL to know when the value is stale.
-        * The $checkKeys parameter allow mass invalidations by updating a single key:
-        *   - a) Each "check" key represents "last purged" of some source data
-        *   - b) Callers pass in relevant "check" keys as $checkKeys in get()
-        *   - c) When the source data that "check" keys represent changes,
-        *        the touchCheckKey() method is called on them
-        *
-        * Source data entities might exists in a DB that uses snapshot isolation
-        * (e.g. the default REPEATABLE-READ in innoDB). Even for mutable data, that
-        * isolation can largely be maintained by doing the following:
-        *   - a) Calling delete() on entity change *and* creation, before DB commit
-        *   - b) Keeping transaction duration shorter than the delete() hold-off TTL
-        *   - c) Disabling interim key caching via useInterimHoldOffCaching() before get() calls
-        *
-        * However, pre-snapshot values might still be seen if an update was made
-        * in a remote datacenter but the purge from delete() didn't relay yet.
-        *
-        * Consider using getWithSetCallback() instead of get() and set() cycles.
-        * That method has cache slam avoiding features for hot/expensive keys.
-        *
-        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a cache key metadata map.
-        * This map includes the following metadata:
-        *   - asOf: UNIX timestamp of the value or null if the key is nonexistant
-        *   - tombAsOf: UNIX timestamp of the tombstone or null if the key is not tombstoned
-        *   - lastCKPurge: UNIX timestamp of the highest check key or null if none provided
-        *   - version: cached value version number or null if the key is nonexistant
-        *
-        * Otherwise, $info will transform into the cached value timestamp.
-        *
-        * @param string $key Cache key made from makeKey() or makeGlobalKey()
-        * @param mixed|null &$curTTL Approximate TTL left on the key if present/tombstoned [returned]
-        * @param string[] $checkKeys The "check" keys used to validate the value
-        * @param mixed|null &$info Key info if WANObjectCache::PASS_BY_REF [returned]
-        * @return mixed Value of cache key or false on failure
-        */
-       final public function get(
-               $key, &$curTTL = null, array $checkKeys = [], &$info = null
-       ) {
-               $curTTLs = self::PASS_BY_REF;
-               $infoByKey = self::PASS_BY_REF;
-               $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $infoByKey );
-               $curTTL = $curTTLs[$key] ?? null;
-               if ( $info === self::PASS_BY_REF ) {
-                       $info = [
-                               'asOf' => $infoByKey[$key]['asOf'] ?? null,
-                               'tombAsOf' => $infoByKey[$key]['tombAsOf'] ?? null,
-                               'lastCKPurge' => $infoByKey[$key]['lastCKPurge'] ?? null,
-                               'version' => $infoByKey[$key]['version'] ?? null
-                       ];
-               } else {
-                       $info = $infoByKey[$key]['asOf'] ?? null; // b/c
-               }
-
-               return $values[$key] ?? false;
-       }
-
-       /**
-        * Fetch the value of several keys from cache
-        *
-        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a map of cache keys
-        * to cache key metadata maps, each having the same style as those of WANObjectCache::get().
-        * All the cache keys listed in $keys will have an entry.
-        *
-        * Othwerwise, $info will transform into a map of (cache key => cached value timestamp).
-        * Only the cache keys listed in $keys that exists or are tombstoned will have an entry.
-        *
-        * $checkKeys holds the "check" keys used to validate values of applicable keys. The integer
-        * indexes hold "check" keys that apply to all of $keys while the string indexes hold "check"
-        * keys that only apply to the cache key with that name.
-        *
-        * @see WANObjectCache::get()
-        *
-        * @param string[] $keys List of cache keys made from makeKey() or makeGlobalKey()
-        * @param mixed|null &$curTTLs Map of (key => TTL left) for existing/tombstoned keys [returned]
-        * @param string[]|string[][] $checkKeys Map of (integer or cache key => "check" key(s))
-        * @param mixed|null &$info Map of (key => info) if WANObjectCache::PASS_BY_REF [returned]
-        * @return mixed[] Map of (key => value) for existing values; order of $keys is preserved
-        */
-       final public function getMulti(
-               array $keys,
-               &$curTTLs = [],
-               array $checkKeys = [],
-               &$info = null
-       ) {
-               $result = [];
-               $curTTLs = [];
-               $infoByKey = [];
-
-               $vPrefixLen = strlen( self::$VALUE_KEY_PREFIX );
-               $valueKeys = self::prefixCacheKeys( $keys, self::$VALUE_KEY_PREFIX );
-
-               $checkKeysForAll = [];
-               $checkKeysByKey = [];
-               $checkKeysFlat = [];
-               foreach ( $checkKeys as $i => $checkKeyGroup ) {
-                       $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::$TIME_KEY_PREFIX );
-                       $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
-                       // Are these check keys for a specific cache key, or for all keys being fetched?
-                       if ( is_int( $i ) ) {
-                               $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed );
-                       } else {
-                               $checkKeysByKey[$i] = $prefixed;
-                       }
-               }
-
-               // Fetch all of the raw values
-               $keysGet = array_merge( $valueKeys, $checkKeysFlat );
-               if ( $this->warmupCache ) {
-                       $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) );
-                       $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch
-                       $this->warmupKeyMisses += count( $keysGet );
-               } else {
-                       $wrappedValues = [];
-               }
-               if ( $keysGet ) {
-                       $wrappedValues += $this->cache->getMulti( $keysGet );
-               }
-               // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
-               $now = $this->getCurrentTime();
-
-               // Collect timestamps from all "check" keys
-               $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
-               $purgeValuesByKey = [];
-               foreach ( $checkKeysByKey as $cacheKey => $checks ) {
-                       $purgeValuesByKey[$cacheKey] =
-                               $this->processCheckKeys( $checks, $wrappedValues, $now );
-               }
-
-               // Get the main cache value for each key and validate them
-               foreach ( $valueKeys as $vKey ) {
-                       $key = substr( $vKey, $vPrefixLen ); // unprefix
-                       list( $value, $keyInfo ) = $this->unwrap( $wrappedValues[$vKey] ?? false, $now );
-                       // Force dependent keys to be seen as stale for a while after purging
-                       // to reduce race conditions involving stale data getting cached
-                       $purgeValues = $purgeValuesForAll;
-                       if ( isset( $purgeValuesByKey[$key] ) ) {
-                               $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
-                       }
-
-                       $lastCKPurge = null; // timestamp of the highest check key
-                       foreach ( $purgeValues as $purge ) {
-                               $lastCKPurge = max( $purge[self::$PURGE_TIME], $lastCKPurge );
-                               $safeTimestamp = $purge[self::$PURGE_TIME] + $purge[self::$PURGE_HOLDOFF];
-                               if ( $value !== false && $safeTimestamp >= $keyInfo['asOf'] ) {
-                                       // How long ago this value was invalidated by *this* check key
-                                       $ago = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
-                                       // How long ago this value was invalidated by *any* known check key
-                                       $keyInfo['curTTL'] = min( $keyInfo['curTTL'], $ago );
-                               }
-                       }
-                       $keyInfo[ 'lastCKPurge'] = $lastCKPurge;
-
-                       if ( $value !== false ) {
-                               $result[$key] = $value;
-                       }
-                       if ( $keyInfo['curTTL'] !== null ) {
-                               $curTTLs[$key] = $keyInfo['curTTL'];
-                       }
-
-                       $infoByKey[$key] = ( $info === self::PASS_BY_REF )
-                               ? $keyInfo
-                               : $keyInfo['asOf']; // b/c
-               }
-
-               $info = $infoByKey;
-
-               return $result;
-       }
-
-       /**
-        * @since 1.27
-        * @param string[] $timeKeys List of prefixed time check keys
-        * @param mixed[] $wrappedValues
-        * @param float $now
-        * @return array[] List of purge value arrays
-        */
-       private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
-               $purgeValues = [];
-               foreach ( $timeKeys as $timeKey ) {
-                       $purge = isset( $wrappedValues[$timeKey] )
-                               ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
-                               : false;
-                       if ( $purge === false ) {
-                               // Key is not set or malformed; regenerate
-                               $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
-                               $this->cache->add( $timeKey, $newVal, self::$CHECK_KEY_TTL );
-                               $purge = $this->parsePurgeValue( $newVal );
-                       }
-                       $purgeValues[] = $purge;
-               }
-               return $purgeValues;
-       }
-
-       /**
-        * Set the value of a key in cache
-        *
-        * Simply calling this method when source data changes is not valid because
-        * the changes do not replicate to the other WAN sites. In that case, delete()
-        * should be used instead. This method is intended for use on cache misses.
-        *
-        * If the data was read from a snapshot-isolated transactions (e.g. the default
-        * REPEATABLE-READ in innoDB), use 'since' to avoid the following race condition:
-        *   - a) T1 starts
-        *   - b) T2 updates a row, calls delete(), and commits
-        *   - c) The HOLDOFF_TTL passes, expiring the delete() tombstone
-        *   - d) T1 reads the row and calls set() due to a cache miss
-        *   - e) Stale value is stuck in cache
-        *
-        * Setting 'lag' and 'since' help avoids keys getting stuck in stale states.
-        *
-        * Be aware that this does not update the process cache for getWithSetCallback()
-        * callers. Keys accessed via that method are not generally meant to also be set
-        * using this primitive method.
-        *
-        * Do not use this method on versioned keys accessed via getWithSetCallback().
-        *
-        * Example usage:
-        * @code
-        *     $dbr = wfGetDB( DB_REPLICA );
-        *     $setOpts = Database::getCacheSetOptions( $dbr );
-        *     // Fetch the row from the DB
-        *     $row = $dbr->selectRow( ... );
-        *     $key = $cache->makeKey( 'building', $buildingId );
-        *     $cache->set( $key, $row, $cache::TTL_DAY, $setOpts );
-        * @endcode
-        *
-        * @param string $key Cache key
-        * @param mixed $value
-        * @param int $ttl Seconds to live. Special values are:
-        *   - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
-        *   - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted)
-        * @param array $opts Options map:
-        *   - lag: Seconds of replica DB lag. Typically, this is either the replica DB lag
-        *      before the data was read or, if applicable, the replica DB lag before
-        *      the snapshot-isolated transaction the data was read from started.
-        *      Use false to indicate that replication is not running.
-        *      Default: 0 seconds
-        *   - since: UNIX timestamp of the data in $value. Typically, this is either
-        *      the current time the data was read or (if applicable) the time when
-        *      the snapshot-isolated transaction the data was read from started.
-        *      Default: 0 seconds
-        *   - pending: Whether this data is possibly from an uncommitted write transaction.
-        *      Generally, other threads should not see values from the future and
-        *      they certainly should not see ones that ended up getting rolled back.
-        *      Default: false
-        *   - lockTSE: If excessive replication/snapshot lag is detected, then store the value
-        *      with this TTL and flag it as stale. This is only useful if the reads for this key
-        *      use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
-        *      then it will still add on to this TTL in the excessive lag scenario.
-        *      Default: WANObjectCache::TSE_NONE
-        *   - staleTTL: Seconds to keep the key around if it is stale. The get()/getMulti()
-        *      methods return such stale values with a $curTTL of 0, and getWithSetCallback()
-        *      will call the regeneration callback in such cases, passing in the old value
-        *      and its as-of time to the callback. This is useful if adaptiveTTL() is used
-        *      on the old value's as-of time when it is verified as still being correct.
-        *      Default: WANObjectCache::STALE_TTL_NONE
-        *   - creating: Optimize for the case where the key does not already exist.
-        *      Default: false
-        *   - version: Integer version number signifiying the format of the value.
-        *      Default: null
-        *   - walltime: How long the value took to generate in seconds. Default: 0.0
-        * @note Options added in 1.28: staleTTL
-        * @note Options added in 1.33: creating
-        * @note Options added in 1.34: version, walltime
-        * @return bool Success
-        */
-       final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
-               $now = $this->getCurrentTime();
-               $lag = $opts['lag'] ?? 0;
-               $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
-               $pending = $opts['pending'] ?? false;
-               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
-               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
-               $creating = $opts['creating'] ?? false;
-               $version = $opts['version'] ?? null;
-               $walltime = $opts['walltime'] ?? 0.0;
-
-               if ( $ttl < 0 ) {
-                       return true;
-               }
-
-               // Do not cache potentially uncommitted data as it might get rolled back
-               if ( $pending ) {
-                       $this->logger->info(
-                               'Rejected set() for {cachekey} due to pending writes.',
-                               [ 'cachekey' => $key ]
-                       );
-
-                       return true; // no-op the write for being unsafe
-               }
-
-               $logicalTTL = null; // logical TTL override
-               // Check if there's a risk of writing stale data after the purge tombstone expired
-               if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
-                       // Case A: any long-running transaction
-                       if ( $age > self::MAX_READ_LAG ) {
-                               if ( $lockTSE >= 0 ) {
-                                       // Store value as *almost* stale to avoid cache and mutex stampedes
-                                       $logicalTTL = self::TTL_SECOND;
-                                       $this->logger->info(
-                                               'Lowered set() TTL for {cachekey} due to snapshot lag.',
-                                               [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
-                                       );
-                               } else {
-                                       $this->logger->info(
-                                               'Rejected set() for {cachekey} due to snapshot lag.',
-                                               [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
-                                       );
-
-                                       return true; // no-op the write for being unsafe
-                               }
-                       // Case B: high replication lag; lower TTL instead of ignoring all set()s
-                       } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
-                               if ( $lockTSE >= 0 ) {
-                                       $logicalTTL = min( $ttl ?: INF, self::TTL_LAGGED );
-                               } else {
-                                       $ttl = min( $ttl ?: INF, self::TTL_LAGGED );
-                               }
-                               $this->logger->warning(
-                                       'Lowered set() TTL for {cachekey} due to replication lag.',
-                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
-                               );
-                       // Case C: medium length request with medium replication lag
-                       } elseif ( $lockTSE >= 0 ) {
-                               // Store value as *almost* stale to avoid cache and mutex stampedes
-                               $logicalTTL = self::TTL_SECOND;
-                               $this->logger->info(
-                                       'Lowered set() TTL for {cachekey} due to high read lag.',
-                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
-                               );
-                       } else {
-                               $this->logger->info(
-                                       'Rejected set() for {cachekey} due to high read lag.',
-                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
-                               );
-
-                               return true; // no-op the write for being unsafe
-                       }
-               }
-
-               // Wrap that value with time/TTL/version metadata
-               $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now, $walltime );
-               $storeTTL = $ttl + $staleTTL;
-
-               if ( $creating ) {
-                       $ok = $this->cache->add( self::$VALUE_KEY_PREFIX . $key, $wrapped, $storeTTL );
-               } else {
-                       $ok = $this->cache->merge(
-                               self::$VALUE_KEY_PREFIX . $key,
-                               function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
-                                       // A string value means that it is a tombstone; do nothing in that case
-                                       return ( is_string( $cWrapped ) ) ? false : $wrapped;
-                               },
-                               $storeTTL,
-                               1 // 1 attempt
-                       );
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Purge a key from all datacenters
-        *
-        * This should only be called when the underlying data (being cached)
-        * changes in a significant way. This deletes the key and starts a hold-off
-        * period where the key cannot be written to for a few seconds (HOLDOFF_TTL).
-        * This is done to avoid the following race condition:
-        *   - a) Some DB data changes and delete() is called on a corresponding key
-        *   - b) A request refills the key with a stale value from a lagged DB
-        *   - c) The stale value is stuck there until the key is expired/evicted
-        *
-        * This is implemented by storing a special "tombstone" value at the cache
-        * key that this class recognizes; get() calls will return false for the key
-        * and any set() calls will refuse to replace tombstone values at the key.
-        * For this to always avoid stale value writes, the following must hold:
-        *   - a) Replication lag is bounded to being less than HOLDOFF_TTL; or
-        *   - b) If lag is higher, the DB will have gone into read-only mode already
-        *
-        * Note that set() can also be lag-aware and lower the TTL if it's high.
-        *
-        * Be aware that this does not clear the process cache. Even if it did, callbacks
-        * used by getWithSetCallback() might still return stale data in the case of either
-        * uncommitted or not-yet-replicated changes (callback generally use replica DBs).
-        *
-        * When using potentially long-running ACID transactions, a good pattern is
-        * to use a pre-commit hook to issue the delete. This means that immediately
-        * after commit, callers will see the tombstone in cache upon purge relay.
-        * It also avoids the following race condition:
-        *   - a) T1 begins, changes a row, and calls delete()
-        *   - b) The HOLDOFF_TTL passes, expiring the delete() tombstone
-        *   - c) T2 starts, reads the row and calls set() due to a cache miss
-        *   - d) T1 finally commits
-        *   - e) Stale value is stuck in cache
-        *
-        * Example usage:
-        * @code
-        *     $dbw->startAtomic( __METHOD__ ); // start of request
-        *     ... <execute some stuff> ...
-        *     // Update the row in the DB
-        *     $dbw->update( ... );
-        *     $key = $cache->makeKey( 'homes', $homeId );
-        *     // Purge the corresponding cache entry just before committing
-        *     $dbw->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) {
-        *         $cache->delete( $key );
-        *     } );
-        *     ... <execute some stuff> ...
-        *     $dbw->endAtomic( __METHOD__ ); // end of request
-        * @endcode
-        *
-        * The $ttl parameter can be used when purging values that have not actually changed
-        * recently. For example, a cleanup script to purge cache entries does not really need
-        * a hold-off period, so it can use HOLDOFF_TTL_NONE. Likewise for user-requested purge.
-        * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback().
-        *
-        * If called twice on the same key, then the last hold-off TTL takes precedence. For
-        * idempotence, the $ttl should not vary for different delete() calls on the same key.
-        *
-        * @param string $key Cache key
-        * @param int $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL
-        * @return bool True if the item was purged or not found, false on failure
-        */
-       final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
-               if ( $ttl <= 0 ) {
-                       // Publish the purge to all datacenters
-                       $ok = $this->relayDelete( self::$VALUE_KEY_PREFIX . $key );
-               } else {
-                       // Publish the purge to all datacenters
-                       $ok = $this->relayPurge( self::$VALUE_KEY_PREFIX . $key, $ttl, self::HOLDOFF_TTL_NONE );
-               }
-
-               $kClass = $this->determineKeyClassForStats( $key );
-               $this->stats->increment( "wanobjectcache.$kClass.delete." . ( $ok ? 'ok' : 'error' ) );
-
-               return $ok;
-       }
-
-       /**
-        * Fetch the value of a timestamp "check" key
-        *
-        * The key will be *initialized* to the current time if not set,
-        * so only call this method if this behavior is actually desired
-        *
-        * The timestamp can be used to check whether a cached value is valid.
-        * Callers should not assume that this returns the same timestamp in
-        * all datacenters due to relay delays.
-        *
-        * The level of staleness can roughly be estimated from this key, but
-        * if the key was evicted from cache, such calculations may show the
-        * time since expiry as ~0 seconds.
-        *
-        * Note that "check" keys won't collide with other regular keys.
-        *
-        * @param string $key
-        * @return float UNIX timestamp
-        */
-       final public function getCheckKeyTime( $key ) {
-               return $this->getMultiCheckKeyTime( [ $key ] )[$key];
-       }
-
-       /**
-        * Fetch the values of each timestamp "check" key
-        *
-        * This works like getCheckKeyTime() except it takes a list of keys
-        * and returns a map of timestamps instead of just that of one key
-        *
-        * This might be useful if both:
-        *   - a) a class of entities each depend on hundreds of other entities
-        *   - b) these other entities are depended upon by millions of entities
-        *
-        * The later entities can each use a "check" key to invalidate their dependee entities.
-        * However, it is expensive for the former entities to verify against all of the relevant
-        * "check" keys during each getWithSetCallback() call. A less expensive approach is to do
-        * these verifications only after a "time-till-verify" (TTV) has passed. This is a middle
-        * ground between using blind TTLs and using constant verification. The adaptiveTTL() method
-        * can be used to dynamically adjust the TTV. Also, the initial TTV can make use of the
-        * last-modified times of the dependant entities (either from the DB or the "check" keys).
-        *
-        * Example usage:
-        * @code
-        *     $value = $cache->getWithSetCallback(
-        *         $cache->makeGlobalKey( 'wikibase-item', $id ),
-        *         self::INITIAL_TTV, // initial time-till-verify
-        *         function ( $oldValue, &$ttv, &$setOpts, $oldAsOf ) use ( $checkKeys, $cache ) {
-        *             $now = microtime( true );
-        *             // Use $oldValue if it passes max ultimate age and "check" key comparisons
-        *             if ( $oldValue &&
-        *                 $oldAsOf > max( $cache->getMultiCheckKeyTime( $checkKeys ) ) &&
-        *                 ( $now - $oldValue['ctime'] ) <= self::MAX_CACHE_AGE
-        *             ) {
-        *                 // Increase time-till-verify by 50% of last time to reduce overhead
-        *                 $ttv = $cache->adaptiveTTL( $oldAsOf, self::MAX_TTV, self::MIN_TTV, 1.5 );
-        *                 // Unlike $oldAsOf, "ctime" is the ultimate age of the cached data
-        *                 return $oldValue;
-        *             }
-        *
-        *             $mtimes = []; // dependency last-modified times; passed by reference
-        *             $value = [ 'data' => $this->fetchEntityData( $mtimes ), 'ctime' => $now ];
-        *             // Guess time-till-change among the dependencies, e.g. 1/(total change rate)
-        *             $ttc = 1 / array_sum( array_map(
-        *                 function ( $mtime ) use ( $now ) {
-        *                     return 1 / ( $mtime ? ( $now - $mtime ) : 900 );
-        *                 },
-        *                 $mtimes
-        *             ) );
-        *             // The time-to-verify should not be overly pessimistic nor optimistic
-        *             $ttv = min( max( $ttc, self::MIN_TTV ), self::MAX_TTV );
-        *
-        *             return $value;
-        *         },
-        *         [ 'staleTTL' => $cache::TTL_DAY ] // keep around to verify and re-save
-        *     );
-        * @endcode
-        *
-        * @see WANObjectCache::getCheckKeyTime()
-        * @see WANObjectCache::getWithSetCallback()
-        *
-        * @param string[] $keys
-        * @return float[] Map of (key => UNIX timestamp)
-        * @since 1.31
-        */
-       final public function getMultiCheckKeyTime( array $keys ) {
-               $rawKeys = [];
-               foreach ( $keys as $key ) {
-                       $rawKeys[$key] = self::$TIME_KEY_PREFIX . $key;
-               }
-
-               $rawValues = $this->cache->getMulti( $rawKeys );
-               $rawValues += array_fill_keys( $rawKeys, false );
-
-               $times = [];
-               foreach ( $rawKeys as $key => $rawKey ) {
-                       $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
-                       if ( $purge !== false ) {
-                               $time = $purge[self::$PURGE_TIME];
-                       } else {
-                               // Casting assures identical floats for the next getCheckKeyTime() calls
-                               $now = (string)$this->getCurrentTime();
-                               $this->cache->add(
-                                       $rawKey,
-                                       $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
-                                       self::$CHECK_KEY_TTL
-                               );
-                               $time = (float)$now;
-                       }
-
-                       $times[$key] = $time;
-               }
-
-               return $times;
-       }
-
-       /**
-        * Purge a "check" key from all datacenters, invalidating keys that use it
-        *
-        * This should only be called when the underlying data (being cached)
-        * changes in a significant way, and it is impractical to call delete()
-        * on all keys that should be changed. When get() is called on those
-        * keys, the relevant "check" keys must be supplied for this to work.
-        *
-        * The "check" key essentially represents a last-modified time of an entity.
-        * When the key is touched, the timestamp will be updated to the current time.
-        * Keys using the "check" key via get(), getMulti(), or getWithSetCallback() will
-        * be invalidated. This approach is useful if many keys depend on a single entity.
-        *
-        * The timestamp of the "check" key is treated as being HOLDOFF_TTL seconds in the
-        * future by get*() methods in order to avoid race conditions where keys are updated
-        * with stale values (e.g. from a lagged replica DB). A high TTL is set on the "check"
-        * key, making it possible to know the timestamp of the last change to the corresponding
-        * entities in most cases. This might use more cache space than resetCheckKey().
-        *
-        * When a few important keys get a large number of hits, a high cache time is usually
-        * desired as well as "lockTSE" logic. The resetCheckKey() method is less appropriate
-        * in such cases since the "time since expiry" cannot be inferred, causing any get()
-        * after the reset to treat the key as being "hot", resulting in more stale value usage.
-        *
-        * Note that "check" keys won't collide with other regular keys.
-        *
-        * @see WANObjectCache::get()
-        * @see WANObjectCache::getWithSetCallback()
-        * @see WANObjectCache::resetCheckKey()
-        *
-        * @param string $key Cache key
-        * @param int $holdoff HOLDOFF_TTL or HOLDOFF_TTL_NONE constant
-        * @return bool True if the item was purged or not found, false on failure
-        */
-       final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
-               // Publish the purge to all datacenters
-               $ok = $this->relayPurge( self::$TIME_KEY_PREFIX . $key, self::$CHECK_KEY_TTL, $holdoff );
-
-               $kClass = $this->determineKeyClassForStats( $key );
-               $this->stats->increment( "wanobjectcache.$kClass.ck_touch." . ( $ok ? 'ok' : 'error' ) );
-
-               return $ok;
-       }
-
-       /**
-        * Delete a "check" key from all datacenters, invalidating keys that use it
-        *
-        * This is similar to touchCheckKey() in that keys using it via get(), getMulti(),
-        * or getWithSetCallback() will be invalidated. The differences are:
-        *   - a) The "check" key will be deleted from all caches and lazily
-        *        re-initialized when accessed (rather than set everywhere)
-        *   - b) Thus, dependent keys will be known to be stale, but not
-        *        for how long (they are treated as "just" purged), which
-        *        effects any lockTSE logic in getWithSetCallback()
-        *   - c) Since "check" keys are initialized only on the server the key hashes
-        *        to, any temporary ejection of that server will cause the value to be
-        *        seen as purged as a new server will initialize the "check" key.
-        *
-        * The advantage here is that the "check" keys, which have high TTLs, will only
-        * be created when a get*() method actually uses that key. This is better when
-        * a large number of "check" keys are invalided in a short period of time.
-        *
-        * Note that "check" keys won't collide with other regular keys.
-        *
-        * @see WANObjectCache::get()
-        * @see WANObjectCache::getWithSetCallback()
-        * @see WANObjectCache::touchCheckKey()
-        *
-        * @param string $key Cache key
-        * @return bool True if the item was purged or not found, false on failure
-        */
-       final public function resetCheckKey( $key ) {
-               // Publish the purge to all datacenters
-               $ok = $this->relayDelete( self::$TIME_KEY_PREFIX . $key );
-
-               $kClass = $this->determineKeyClassForStats( $key );
-               $this->stats->increment( "wanobjectcache.$kClass.ck_reset." . ( $ok ? 'ok' : 'error' ) );
-
-               return $ok;
-       }
-
-       /**
-        * Method to fetch/regenerate cache keys
-        *
-        * On cache miss, the key will be set to the callback result via set()
-        * (unless the callback returns false) and that result will be returned.
-        * The arguments supplied to the callback are:
-        *   - $oldValue : current cache value or false if not present
-        *   - &$ttl : a reference to the TTL which can be altered
-        *   - &$setOpts : a reference to options for set() which can be altered
-        *   - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present (since 1.28)
-        *
-        * It is strongly recommended to set the 'lag' and 'since' fields to avoid race conditions
-        * that can cause stale values to get stuck at keys. Usually, callbacks ignore the current
-        * value, but it can be used to maintain "most recent X" values that come from time or
-        * sequence based source data, provided that the "as of" id/time is tracked. Note that
-        * preemptive regeneration and $checkKeys can result in a non-false current value.
-        *
-        * Usage of $checkKeys is similar to get() and getMulti(). However, rather than the caller
-        * having to inspect a "current time left" variable (e.g. $curTTL, $curTTLs), a cache
-        * regeneration will automatically be triggered using the callback.
-        *
-        * The $ttl argument and "hotTTR" option (in $opts) use time-dependant randomization
-        * to avoid stampedes. Keys that are slow to regenerate and either heavily used
-        * or subject to explicit (unpredictable) purges, may need additional mechanisms.
-        * The simplest way to avoid stampedes for such keys is to use 'lockTSE' (in $opts).
-        * If explicit purges are needed, also:
-        *   - a) Pass $key into $checkKeys
-        *   - b) Use touchCheckKey( $key ) instead of delete( $key )
-        *
-        * Example usage (typical key):
-        * @code
-        *     $catInfo = $cache->getWithSetCallback(
-        *         // Key to store the cached value under
-        *         $cache->makeKey( 'cat-attributes', $catId ),
-        *         // Time-to-live (in seconds)
-        *         $cache::TTL_MINUTE,
-        *         // Function that derives the new key value
-        *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             return $dbr->selectRow( ... );
-        *        }
-        *     );
-        * @endcode
-        *
-        * Example usage (key that is expensive and hot):
-        * @code
-        *     $catConfig = $cache->getWithSetCallback(
-        *         // Key to store the cached value under
-        *         $cache->makeKey( 'site-cat-config' ),
-        *         // Time-to-live (in seconds)
-        *         $cache::TTL_DAY,
-        *         // Function that derives the new key value
-        *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             return CatConfig::newFromRow( $dbr->selectRow( ... ) );
-        *         },
-        *         [
-        *             // Calling touchCheckKey() on this key invalidates the cache
-        *             'checkKeys' => [ $cache->makeKey( 'site-cat-config' ) ],
-        *             // Try to only let one datacenter thread manage cache updates at a time
-        *             'lockTSE' => 30,
-        *             // Avoid querying cache servers multiple times in a web request
-        *             'pcTTL' => $cache::TTL_PROC_LONG
-        *         ]
-        *     );
-        * @endcode
-        *
-        * Example usage (key with dynamic dependencies):
-        * @code
-        *     $catState = $cache->getWithSetCallback(
-        *         // Key to store the cached value under
-        *         $cache->makeKey( 'cat-state', $cat->getId() ),
-        *         // Time-to-live (seconds)
-        *         $cache::TTL_HOUR,
-        *         // Function that derives the new key value
-        *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             // Determine new value from the DB
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             return CatState::newFromResults( $dbr->select( ... ) );
-        *         },
-        *         [
-        *              // The "check" keys that represent things the value depends on;
-        *              // Calling touchCheckKey() on any of them invalidates the cache
-        *             'checkKeys' => [
-        *                 $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ),
-        *                 $cache->makeKey( 'people-present', $cat->getHouseId() ),
-        *                 $cache->makeKey( 'cat-laws', $cat->getCityId() ),
-        *             ]
-        *         ]
-        *     );
-        * @endcode
-        *
-        * Example usage (key that is expensive with too many DB dependencies for "check keys"):
-        * @code
-        *     $catToys = $cache->getWithSetCallback(
-        *         // Key to store the cached value under
-        *         $cache->makeKey( 'cat-toys', $catId ),
-        *         // Time-to-live (seconds)
-        *         $cache::TTL_HOUR,
-        *         // Function that derives the new key value
-        *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             // Determine new value from the DB
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             return CatToys::newFromResults( $dbr->select( ... ) );
-        *         },
-        *         [
-        *              // Get the highest timestamp of any of the cat's toys
-        *             'touchedCallback' => function ( $value ) use ( $catId ) {
-        *                 $dbr = wfGetDB( DB_REPLICA );
-        *                 $ts = $dbr->selectField( 'cat_toys', 'MAX(ct_touched)', ... );
-        *
-        *                 return wfTimestampOrNull( TS_UNIX, $ts );
-        *             },
-        *             // Avoid DB queries for repeated access
-        *             'pcTTL' => $cache::TTL_PROC_SHORT
-        *         ]
-        *     );
-        * @endcode
-        *
-        * Example usage (hot key holding most recent 100 events):
-        * @code
-        *     $lastCatActions = $cache->getWithSetCallback(
-        *         // Key to store the cached value under
-        *         $cache->makeKey( 'cat-last-actions', 100 ),
-        *         // Time-to-live (in seconds)
-        *         10,
-        *         // Function that derives the new key value
-        *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             // Start off with the last cached list
-        *             $list = $oldValue ?: [];
-        *             // Fetch the last 100 relevant rows in descending order;
-        *             // only fetch rows newer than $list[0] to reduce scanning
-        *             $rows = iterator_to_array( $dbr->select( ... ) );
-        *             // Merge them and get the new "last 100" rows
-        *             return array_slice( array_merge( $new, $list ), 0, 100 );
-        *        },
-        *        [
-        *             // Try to only let one datacenter thread manage cache updates at a time
-        *             'lockTSE' => 30,
-        *             // Use a magic value when no cache value is ready rather than stampeding
-        *             'busyValue' => 'computing'
-        *        ]
-        *     );
-        * @endcode
-        *
-        * Example usage (key holding an LRU subkey:value map; this can avoid flooding cache with
-        * keys for an unlimited set of (constraint,situation) pairs, thereby avoiding elevated
-        * cache evictions and wasted memory):
-        * @code
-        *     $catSituationTolerabilityCache = $this->cache->getWithSetCallback(
-        *         // Group by constraint ID/hash, cat family ID/hash, or something else useful
-        *         $this->cache->makeKey( 'cat-situation-tolerability-checks', $groupKey ),
-        *         WANObjectCache::TTL_DAY, // rarely used groups should fade away
-        *         // The $scenarioKey format is $constraintId:<ID/hash of $situation>
-        *         function ( $cacheMap ) use ( $scenarioKey, $constraintId, $situation ) {
-        *             $lruCache = MapCacheLRU::newFromArray( $cacheMap ?: [], self::CACHE_SIZE );
-        *             $result = $lruCache->get( $scenarioKey ); // triggers LRU bump if present
-        *             if ( $result === null || $this->isScenarioResultExpired( $result ) ) {
-        *                 $result = $this->checkScenarioTolerability( $constraintId, $situation );
-        *                 $lruCache->set( $scenarioKey, $result, 3 / 8 );
-        *             }
-        *             // Save the new LRU cache map and reset the map's TTL
-        *             return $lruCache->toArray();
-        *         },
-        *         [
-        *             // Once map is > 1 sec old, consider refreshing
-        *             'ageNew' => 1,
-        *             // Update within 5 seconds after "ageNew" given a 1hz cache check rate
-        *             'hotTTR' => 5,
-        *             // Avoid querying cache servers multiple times in a request; this also means
-        *             // that a request can only alter the value of any given constraint key once
-        *             'pcTTL' => WANObjectCache::TTL_PROC_LONG
-        *         ]
-        *     );
-        *     $tolerability = isset( $catSituationTolerabilityCache[$scenarioKey] )
-        *         ? $catSituationTolerabilityCache[$scenarioKey]
-        *         : $this->checkScenarioTolerability( $constraintId, $situation );
-        * @endcode
-        *
-        * @see WANObjectCache::get()
-        * @see WANObjectCache::set()
-        *
-        * @param string $key Cache key made from makeKey() or makeGlobalKey()
-        * @param int $ttl Seconds to live for key updates. Special values are:
-        *   - WANObjectCache::TTL_INDEFINITE: Cache forever (subject to LRU-style evictions)
-        *   - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted)
-        * @param callable $callback Value generation function
-        * @param array $opts Options map:
-        *   - checkKeys: List of "check" keys. The key at $key will be seen as stale when either
-        *      touchCheckKey() or resetCheckKey() is called on any of the keys in this list. This
-        *      is useful if thousands or millions of keys depend on the same entity. The entity can
-        *      simply have its "check" key updated whenever the entity is modified.
-        *      Default: [].
-        *   - graceTTL: If the key is invalidated (by "checkKeys"/"touchedCallback") less than this
-        *      many seconds ago, consider reusing the stale value. The odds of a refresh becomes
-        *      more likely over time, becoming certain once the grace period is reached. This can
-        *      reduce traffic spikes when millions of keys are compared to the same "check" key and
-        *      touchCheckKey() or resetCheckKey() is called on that "check" key. This option is not
-        *      useful for avoiding traffic spikes in the case of the key simply expiring on account
-        *      of its TTL (use "lowTTL" instead).
-        *      Default: WANObjectCache::GRACE_TTL_NONE.
-        *   - lockTSE: If the key is tombstoned or invalidated (by "checkKeys"/"touchedCallback")
-        *      less than this many seconds ago, try to have a single thread handle cache regeneration
-        *      at any given time. Other threads will use stale values if possible. If, on miss,
-        *      the time since expiration is low, the assumption is that the key is hot and that a
-        *      stampede is worth avoiding. Note that if the key falls out of cache then concurrent
-        *      threads will all run the callback on cache miss until the value is saved in cache.
-        *      The only stampede protection in that case is from duplicate cache sets when the
-        *      callback takes longer than WANObjectCache::SET_DELAY_HIGH_MS milliseconds; consider
-        *      using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
-        *      set, the higher the worst-case staleness of returned values can be. Also note that
-        *      this option does not by itself handle the case of the key simply expiring on account
-        *      of its TTL, so make sure that "lowTTL" is not disabled when using this option. Avoid
-        *      combining this option with delete() as it can always cause a stampede due to their
-        *      being no stale value available until after a thread completes the callback.
-        *      Use WANObjectCache::TSE_NONE to disable this logic.
-        *      Default: WANObjectCache::TSE_NONE.
-        *   - busyValue: Specify a placeholder value to use when no value exists and another thread
-        *      is currently regenerating it. This assures that cache stampedes cannot happen if the
-        *      value falls out of cache. This also mitigates stampedes when value regeneration
-        *      becomes very slow (greater than $ttl/"lowTTL"). If this is a closure, then it will
-        *      be invoked to get the placeholder when needed.
-        *      Default: null.
-        *   - pcTTL: Process cache the value in this PHP instance for this many seconds. This avoids
-        *      network I/O when a key is read several times. This will not cache when the callback
-        *      returns false, however. Note that any purges will not be seen while process cached;
-        *      since the callback should use replica DBs and they may be lagged or have snapshot
-        *      isolation anyway, this should not typically matter.
-        *      Default: WANObjectCache::TTL_UNCACHEABLE.
-        *   - pcGroup: Process cache group to use instead of the primary one. If set, this must be
-        *      of the format ALPHANUMERIC_NAME:MAX_KEY_SIZE, e.g. "mydata:10". Use this for storing
-        *      large values, small yet numerous values, or some values with a high cost of eviction.
-        *      It is generally preferable to use a class constant when setting this value.
-        *      This has no effect unless pcTTL is used.
-        *      Default: WANObjectCache::PC_PRIMARY.
-        *   - version: Integer version number. This lets callers make breaking changes to the format
-        *      of cached values without causing problems for sites that use non-instantaneous code
-        *      deployments. Old and new code will recognize incompatible versions and purges from
-        *      both old and new code will been seen by each other. When this method encounters an
-        *      incompatibly versioned value at the provided key, a "variant key" will be used for
-        *      reading from and saving to cache. The variant key is specific to the key and version
-        *      number provided to this method. If the variant key value is older than that of the
-        *      provided key, or the provided key is non-existant, then the variant key will be seen
-        *      as non-existant. Therefore, delete() calls invalidate the provided key's variant keys.
-        *      The "checkKeys" and "touchedCallback" options still apply to variant keys as usual.
-        *      Avoid storing class objects, as this reduces compatibility (due to serialization).
-        *      Default: null.
-        *   - minAsOf: Reject values if they were generated before this UNIX timestamp.
-        *      This is useful if the source of a key is suspected of having possibly changed
-        *      recently, and the caller wants any such changes to be reflected.
-        *      Default: WANObjectCache::MIN_TIMESTAMP_NONE.
-        *   - hotTTR: Expected time-till-refresh (TTR) in seconds for keys that average ~1 hit per
-        *      second (e.g. 1Hz). Keys with a hit rate higher than 1Hz will refresh sooner than this
-        *      TTR and vise versa. Such refreshes won't happen until keys are "ageNew" seconds old.
-        *      This uses randomization to avoid triggering cache stampedes. The TTR is useful at
-        *      reducing the impact of missed cache purges, since the effect of a heavily referenced
-        *      key being stale is worse than that of a rarely referenced key. Unlike simply lowering
-        *      $ttl, seldomly used keys are largely unaffected by this option, which makes it
-        *      possible to have a high hit rate for the "long-tail" of less-used keys.
-        *      Default: WANObjectCache::HOT_TTR.
-        *   - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less
-        *      than this. It becomes more likely over time, becoming certain once the key is expired.
-        *      This helps avoid cache stampedes that might be triggered due to the key expiring.
-        *      Default: WANObjectCache::LOW_TTL.
-        *   - ageNew: Consider popularity refreshes only once a key reaches this age in seconds.
-        *      Default: WANObjectCache::AGE_NEW.
-        *   - staleTTL: Seconds to keep the key around if it is stale. This means that on cache
-        *      miss the callback may get $oldValue/$oldAsOf values for keys that have already been
-        *      expired for this specified time. This is useful if adaptiveTTL() is used on the old
-        *      value's as-of time when it is verified as still being correct.
-        *      Default: WANObjectCache::STALE_TTL_NONE
-        *   - touchedCallback: A callback that takes the current value and returns a UNIX timestamp
-        *      indicating the last time a dynamic dependency changed. Null can be returned if there
-        *      are no relevant dependency changes to check. This can be used to check against things
-        *      like last-modified times of files or DB timestamp fields. This should generally not be
-        *      used for small and easily queried values in a DB if the callback itself ends up doing
-        *      a similarly expensive DB query to check a timestamp. Usages of this option makes the
-        *      most sense for values that are moderately to highly expensive to regenerate and easy
-        *      to query for dependency timestamps. The use of "pcTTL" reduces timestamp queries.
-        *      Default: null.
-        * @return mixed Value found or written to the key
-        * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf
-        * @note Options added in 1.31: staleTTL, graceTTL
-        * @note Options added in 1.33: touchedCallback
-        * @note Callable type hints are not used to avoid class-autoloading
-        */
-       final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
-               $version = $opts['version'] ?? null;
-               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
-               $pCache = ( $pcTTL >= 0 )
-                       ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
-                       : null;
-
-               // Use the process cache if requested as long as no outer cache callback is running.
-               // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
-               // process cached values are more lagged than persistent ones as they are not purged.
-               if ( $pCache && $this->callbackDepth == 0 ) {
-                       $cached = $pCache->get( $this->getProcessCacheKey( $key, $version ), INF, false );
-                       if ( $cached !== false ) {
-                               return $cached;
-                       }
-               }
-
-               $res = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
-               list( $value, $valueVersion, $curAsOf ) = $res;
-               if ( $valueVersion !== $version ) {
-                       // Current value has a different version; use the variant key for this version.
-                       // Regenerate the variant value if it is not newer than the main value at $key
-                       // so that purges to the main key propagate to the variant value.
-                       list( $value ) = $this->fetchOrRegenerate(
-                               $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
-                               $ttl,
-                               $callback,
-                               [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts
-                       );
-               }
-
-               // Update the process cache if enabled
-               if ( $pCache && $value !== false ) {
-                       $pCache->set( $this->getProcessCacheKey( $key, $version ), $value );
-               }
-
-               return $value;
-       }
-
-       /**
-        * Do the actual I/O for getWithSetCallback() when needed
-        *
-        * @see WANObjectCache::getWithSetCallback()
-        *
-        * @param string $key
-        * @param int $ttl
-        * @param callable $callback
-        * @param array $opts Options map for getWithSetCallback()
-        * @return array Ordered list of the following:
-        *   - Cached or regenerated value
-        *   - Cached or regenerated value version number or null if not versioned
-        *   - Timestamp of the current cached value at the key or null if there is no value
-        * @note Callable type hints are not used to avoid class-autoloading
-        */
-       private function fetchOrRegenerate( $key, $ttl, $callback, array $opts ) {
-               $checkKeys = $opts['checkKeys'] ?? [];
-               $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
-               $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
-               $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
-               $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
-               $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
-               $touchedCb = $opts['touchedCallback'] ?? null;
-               $initialTime = $this->getCurrentTime();
-
-               $kClass = $this->determineKeyClassForStats( $key );
-
-               // Get the current key value and its metadata
-               $curTTL = self::PASS_BY_REF;
-               $curInfo = self::PASS_BY_REF; /** @var array $curInfo */
-               $curValue = $this->get( $key, $curTTL, $checkKeys, $curInfo );
-               // Apply any $touchedCb invalidation timestamp to get the "last purge timestamp"
-               list( $curTTL, $LPT ) = $this->resolveCTL( $curValue, $curTTL, $curInfo, $touchedCb );
-               // Use the cached value if it exists and is not due for synchronous regeneration
-               if (
-                       $this->isValid( $curValue, $curInfo['asOf'], $minAsOf ) &&
-                       $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
-               ) {
-                       $preemptiveRefresh = (
-                               $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
-                               $this->worthRefreshPopular( $curInfo['asOf'], $ageNew, $hotTTR, $initialTime )
-                       );
-                       if ( !$preemptiveRefresh ) {
-                               $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
-
-                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
-                       } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) ) {
-                               $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
-
-                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
-                       }
-               }
-
-               // Determine if there is stale or volatile cached value that is still usable
-               $isKeyTombstoned = ( $curInfo['tombAsOf'] !== null );
-               if ( $isKeyTombstoned ) {
-                       // Key is write-holed; use the (volatile) interim key as an alternative
-                       list( $possValue, $possInfo ) = $this->getInterimValue( $key, $minAsOf );
-                       // Update the "last purge time" since the $touchedCb timestamp depends on $value
-                       $LPT = $this->resolveTouched( $possValue, $LPT, $touchedCb );
-               } else {
-                       $possValue = $curValue;
-                       $possInfo = $curInfo;
-               }
-
-               // Avoid overhead from callback runs, regeneration locks, and cache sets during
-               // hold-off periods for the key by reusing very recently generated cached values
-               if (
-                       $this->isValid( $possValue, $possInfo['asOf'], $minAsOf, $LPT ) &&
-                       $this->isVolatileValueAgeNegligible( $initialTime - $possInfo['asOf'] )
-               ) {
-                       $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
-
-                       return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
-               }
-
-               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
-               $busyValue = $opts['busyValue'] ?? null;
-               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
-               $version = $opts['version'] ?? null;
-
-               // Determine whether one thread per datacenter should handle regeneration at a time
-               $useRegenerationLock =
-                       // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
-                       // deduce the key hotness because |$curTTL| will always keep increasing until the
-                       // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
-                       // is not set, constant regeneration of a key for the tombstone lifetime might be
-                       // very expensive. Assume tombstoned keys are possibly hot in order to reduce
-                       // the risk of high regeneration load after the delete() method is called.
-                       $isKeyTombstoned ||
-                       // Assume a key is hot if requested soon ($lockTSE seconds) after invalidation.
-                       // This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
-                       ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ) ||
-                       // Assume a key is hot if there is no value and a busy fallback is given.
-                       // This avoids stampedes on eviction or preemptive regeneration taking too long.
-                       ( $busyValue !== null && $possValue === false );
-
-               // If a regeneration lock is required, threads that do not get the lock will try to use
-               // the stale value, the interim value, or the $busyValue placeholder, in that order. If
-               // none of those are set then all threads will bypass the lock and regenerate the value.
-               $hasLock = $useRegenerationLock && $this->claimStampedeLock( $key );
-               if ( $useRegenerationLock && !$hasLock ) {
-                       if ( $this->isValid( $possValue, $possInfo['asOf'], $minAsOf ) ) {
-                               $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
-
-                               return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
-                       } elseif ( $busyValue !== null ) {
-                               $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
-                               $this->stats->increment( "wanobjectcache.$kClass.$miss.busy" );
-
-                               return [ $this->resolveBusyValue( $busyValue ), $version, $curInfo['asOf'] ];
-                       }
-               }
-
-               // Generate the new value given any prior value with a matching version
-               $setOpts = [];
-               $preCallbackTime = $this->getCurrentTime();
-               ++$this->callbackDepth;
-               try {
-                       $value = $callback(
-                               ( $curInfo['version'] === $version ) ? $curValue : false,
-                               $ttl,
-                               $setOpts,
-                               ( $curInfo['version'] === $version ) ? $curInfo['asOf'] : null
-                       );
-               } finally {
-                       --$this->callbackDepth;
-               }
-               $postCallbackTime = $this->getCurrentTime();
-
-               // How long it took to fetch, validate, and generate the value
-               $elapsed = max( $postCallbackTime - $initialTime, 0.0 );
-
-               // Attempt to save the newly generated value if applicable
-               if (
-                       // Callback yielded a cacheable value
-                       ( $value !== false && $ttl >= 0 ) &&
-                       // Current thread was not raced out of a regeneration lock or key is tombstoned
-                       ( !$useRegenerationLock || $hasLock || $isKeyTombstoned ) &&
-                       // Key does not appear to be undergoing a set() stampede
-                       $this->checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock )
-               ) {
-                       // How long it took to generate the value
-                       $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
-                       $this->stats->timing( "wanobjectcache.$kClass.regen_walltime", 1e3 * $walltime );
-                       // If the key is write-holed then use the (volatile) interim key as an alternative
-                       if ( $isKeyTombstoned ) {
-                               $this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
-                       } else {
-                               $finalSetOpts = [
-                                       'since' => $setOpts['since'] ?? $preCallbackTime,
-                                       'version' => $version,
-                                       'staleTTL' => $staleTTL,
-                                       'lockTSE' => $lockTSE, // informs lag vs performance trade-offs
-                                       'creating' => ( $curValue === false ), // optimization
-                                       'walltime' => $walltime
-                               ] + $setOpts;
-                               $this->set( $key, $value, $ttl, $finalSetOpts );
-                       }
-               }
-
-               $this->yieldStampedeLock( $key, $hasLock );
-
-               $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
-               $this->stats->increment( "wanobjectcache.$kClass.$miss.compute" );
-
-               return [ $value, $version, $curInfo['asOf'] ];
-       }
-
-       /**
-        * @param string $key
-        * @return bool Success
-        */
-       private function claimStampedeLock( $key ) {
-               // Note that locking is not bypassed due to I/O errors; this avoids stampedes
-               return $this->cache->add( self::$MUTEX_KEY_PREFIX . $key, 1, self::$LOCK_TTL );
-       }
-
-       /**
-        * @param string $key
-        * @param bool $hasLock
-        */
-       private function yieldStampedeLock( $key, $hasLock ) {
-               if ( $hasLock ) {
-                       // The backend might be a mcrouter proxy set to broadcast DELETE to *all* the local
-                       // datacenter cache servers via OperationSelectorRoute (for increased consistency).
-                       // Since that would be excessive for these locks, use TOUCH to expire the key.
-                       $this->cache->changeTTL( self::$MUTEX_KEY_PREFIX . $key, $this->getCurrentTime() - 60 );
-               }
-       }
-
-       /**
-        * @param float $age Age of volatile/interim key in seconds
-        * @return bool Whether the age of a volatile value is negligible
-        */
-       private function isVolatileValueAgeNegligible( $age ) {
-               return ( $age < mt_rand( self::$RECENT_SET_LOW_MS, self::$RECENT_SET_HIGH_MS ) / 1e3 );
-       }
-
-       /**
-        * @param string $key
-        * @param string $kClass
-        * @param float $elapsed Seconds spent regenerating the value
-        * @param float $lockTSE
-        * @param bool $hasLock
-        * @return bool Whether it is OK to proceed with a key set operation
-        */
-       private function checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock ) {
-               $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $elapsed );
-
-               // If $lockTSE is set, the lock was bypassed because there was no stale/interim value,
-               // and $elapsed indicates that regeration is slow, then there is a risk of set()
-               // stampedes with large blobs. With a typical scale-out infrastructure, CPU and query
-               // load from $callback invocations is distributed among appservers and replica DBs,
-               // but cache operations for a given key route to a single cache server (e.g. striped
-               // consistent hashing).
-               if ( $lockTSE < 0 || $hasLock ) {
-                       return true; // either not a priori hot or thread has the lock
-               } elseif ( $elapsed <= self::$SET_DELAY_HIGH_MS * 1e3 ) {
-                       return true; // not enough time for threads to pile up
-               }
-
-               $this->cache->clearLastError();
-               if (
-                       !$this->cache->add( self::$COOLOFF_KEY_PREFIX . $key, 1, self::$COOLOFF_TTL ) &&
-                       // Don't treat failures due to I/O errors as the key being in cooloff
-                       $this->cache->getLastError() === BagOStuff::ERR_NONE
-               ) {
-                       $this->stats->increment( "wanobjectcache.$kClass.cooloff_bounce" );
-
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * @param mixed $value
-        * @param float|null $curTTL
-        * @param array $curInfo
-        * @param callable|null $touchedCallback
-        * @return array (current time left or null, UNIX timestamp of last purge or null)
-        * @note Callable type hints are not used to avoid class-autoloading
-        */
-       private function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
-               if ( $touchedCallback === null || $value === false ) {
-                       return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'] ) ];
-               }
-
-               $touched = $touchedCallback( $value );
-               if ( $touched !== null && $touched >= $curInfo['asOf'] ) {
-                       $curTTL = min( $curTTL, self::$TINY_NEGATIVE, $curInfo['asOf'] - $touched );
-               }
-
-               return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'], $touched ) ];
-       }
-
-       /**
-        * @param mixed $value
-        * @param float|null $lastPurge
-        * @param callable|null $touchedCallback
-        * @return float|null UNIX timestamp of last purge or null
-        * @note Callable type hints are not used to avoid class-autoloading
-        */
-       private function resolveTouched( $value, $lastPurge, $touchedCallback ) {
-               return ( $touchedCallback === null || $value === false )
-                       ? $lastPurge // nothing to derive the "touched timestamp" from
-                       : max( $touchedCallback( $value ), $lastPurge );
-       }
-
-       /**
-        * @param string $key
-        * @param float $minAsOf Minimum acceptable "as of" timestamp
-        * @return array (cached value or false, cache key metadata map)
-        */
-       private function getInterimValue( $key, $minAsOf ) {
-               $now = $this->getCurrentTime();
-
-               if ( $this->useInterimHoldOffCaching ) {
-                       $wrapped = $this->cache->get( self::$INTERIM_KEY_PREFIX . $key );
-
-                       list( $value, $keyInfo ) = $this->unwrap( $wrapped, $now );
-                       if ( $this->isValid( $value, $keyInfo['asOf'], $minAsOf ) ) {
-                               return [ $value, $keyInfo ];
-                       }
-               }
-
-               return $this->unwrap( false, $now );
-       }
-
-       /**
-        * @param string $key
-        * @param mixed $value
-        * @param int $ttl
-        * @param int|null $version Value version number
-        * @param float $walltime How long it took to generate the value in seconds
-        */
-       private function setInterimValue( $key, $value, $ttl, $version, $walltime ) {
-               $ttl = max( self::$INTERIM_KEY_TTL, (int)$ttl );
-
-               $wrapped = $this->wrap( $value, $ttl, $version, $this->getCurrentTime(), $walltime );
-               $this->cache->merge(
-                       self::$INTERIM_KEY_PREFIX . $key,
-                       function () use ( $wrapped ) {
-                               return $wrapped;
-                       },
-                       $ttl,
-                       1
-               );
-       }
-
-       /**
-        * @param mixed $busyValue
-        * @return mixed
-        */
-       private function resolveBusyValue( $busyValue ) {
-               return ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
-       }
-
-       /**
-        * Method to fetch multiple cache keys at once with regeneration
-        *
-        * This works the same as getWithSetCallback() except:
-        *   - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys()
-        *   - b) The $callback argument expects a callback taking the following arguments:
-        *         - $id: ID of an entity to query
-        *         - $oldValue : the prior cache value or false if none was present
-        *         - &$ttl : a reference to the new value TTL in seconds
-        *         - &$setOpts : a reference to options for set() which can be altered
-        *         - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present
-        *        Aside from the additional $id argument, the other arguments function the same
-        *        way they do in getWithSetCallback().
-        *   - c) The return value is a map of (cache key => value) in the order of $keyedIds
-        *
-        * @see WANObjectCache::getWithSetCallback()
-        * @see WANObjectCache::getMultiWithUnionSetCallback()
-        *
-        * Example usage:
-        * @code
-        *     $rows = $cache->getMultiWithSetCallback(
-        *         // Map of cache keys to entity IDs
-        *         $cache->makeMultiKeys(
-        *             $this->fileVersionIds(),
-        *             function ( $id ) use ( $cache ) {
-        *                 return $cache->makeKey( 'file-version', $id );
-        *             }
-        *         ),
-        *         // Time-to-live (in seconds)
-        *         $cache::TTL_DAY,
-        *         // Function that derives the new key value
-        *         function ( $id, $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             // Load the row for this file
-        *             $queryInfo = File::getQueryInfo();
-        *             $row = $dbr->selectRow(
-        *                 $queryInfo['tables'],
-        *                 $queryInfo['fields'],
-        *                 [ 'id' => $id ],
-        *                 __METHOD__,
-        *                 [],
-        *                 $queryInfo['joins']
-        *             );
-        *
-        *             return $row ? (array)$row : false;
-        *         },
-        *         [
-        *             // Process cache for 30 seconds
-        *             'pcTTL' => 30,
-        *             // Use a dedicated 500 item cache (initialized on-the-fly)
-        *             'pcGroup' => 'file-versions:500'
-        *         ]
-        *     );
-        *     $files = array_map( [ __CLASS__, 'newFromRow' ], $rows );
-        * @endcode
-        *
-        * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys()
-        * @param int $ttl Seconds to live for key updates
-        * @param callable $callback Callback the yields entity regeneration callbacks
-        * @param array $opts Options map
-        * @return mixed[] Map of (cache key => value) in the same order as $keyedIds
-        * @since 1.28
-        */
-       final public function getMultiWithSetCallback(
-               ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
-       ) {
-               // Load required keys into process cache in one go
-               $this->warmupCache = $this->getRawKeysForWarmup(
-                       $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
-                       $opts['checkKeys'] ?? []
-               );
-               $this->warmupKeyMisses = 0;
-
-               // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
-               $id = null; // current entity ID
-               $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) {
-                       return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf );
-               };
-
-               $values = [];
-               foreach ( $keyedIds as $key => $id ) { // preserve order
-                       $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
-               }
-
-               $this->warmupCache = [];
-
-               return $values;
-       }
-
-       /**
-        * Method to fetch/regenerate multiple cache keys at once
-        *
-        * This works the same as getWithSetCallback() except:
-        *   - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys()
-        *   - b) The $callback argument expects a callback returning a map of (ID => new value)
-        *        for all entity IDs in $ids and it takes the following arguments:
-        *          - $ids: a list of entity IDs that require cache regeneration
-        *          - &$ttls: a reference to the (entity ID => new TTL) map
-        *          - &$setOpts: a reference to options for set() which can be altered
-        *   - c) The return value is a map of (cache key => value) in the order of $keyedIds
-        *   - d) The "lockTSE" and "busyValue" options are ignored
-        *
-        * @see WANObjectCache::getWithSetCallback()
-        * @see WANObjectCache::getMultiWithSetCallback()
-        *
-        * Example usage:
-        * @code
-        *     $rows = $cache->getMultiWithUnionSetCallback(
-        *         // Map of cache keys to entity IDs
-        *         $cache->makeMultiKeys(
-        *             $this->fileVersionIds(),
-        *             function ( $id ) use ( $cache ) {
-        *                 return $cache->makeKey( 'file-version', $id );
-        *             }
-        *         ),
-        *         // Time-to-live (in seconds)
-        *         $cache::TTL_DAY,
-        *         // Function that derives the new key value
-        *         function ( array $ids, array &$ttls, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_REPLICA );
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += Database::getCacheSetOptions( $dbr );
-        *
-        *             // Load the rows for these files
-        *             $rows = [];
-        *             $queryInfo = File::getQueryInfo();
-        *             $res = $dbr->select(
-        *                 $queryInfo['tables'],
-        *                 $queryInfo['fields'],
-        *                 [ 'id' => $ids ],
-        *                 __METHOD__,
-        *                 [],
-        *                 $queryInfo['joins']
-        *             );
-        *             foreach ( $res as $row ) {
-        *                 $rows[$row->id] = $row;
-        *                 $mtime = wfTimestamp( TS_UNIX, $row->timestamp );
-        *                 $ttls[$row->id] = $this->adaptiveTTL( $mtime, $ttls[$row->id] );
-        *             }
-        *
-        *             return $rows;
-        *         },
-        *         ]
-        *     );
-        *     $files = array_map( [ __CLASS__, 'newFromRow' ], $rows );
-        * @endcode
-        *
-        * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys()
-        * @param int $ttl Seconds to live for key updates
-        * @param callable $callback Callback the yields entity regeneration callbacks
-        * @param array $opts Options map
-        * @return mixed[] Map of (cache key => value) in the same order as $keyedIds
-        * @since 1.30
-        */
-       final public function getMultiWithUnionSetCallback(
-               ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
-       ) {
-               $checkKeys = $opts['checkKeys'] ?? [];
-               unset( $opts['lockTSE'] ); // incompatible
-               unset( $opts['busyValue'] ); // incompatible
-
-               // Load required keys into process cache in one go
-               $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
-               $this->warmupCache = $this->getRawKeysForWarmup( $keysByIdGet, $checkKeys );
-               $this->warmupKeyMisses = 0;
-
-               // IDs of entities known to be in need of regeneration
-               $idsRegen = [];
-
-               // Find out which keys are missing/deleted/stale
-               $curTTLs = [];
-               $asOfs = [];
-               $curByKey = $this->getMulti( $keysByIdGet, $curTTLs, $checkKeys, $asOfs );
-               foreach ( $keysByIdGet as $id => $key ) {
-                       if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
-                               $idsRegen[] = $id;
-                       }
-               }
-
-               // Run the callback to populate the regeneration value map for all required IDs
-               $newSetOpts = [];
-               $newTTLsById = array_fill_keys( $idsRegen, $ttl );
-               $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
-
-               // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
-               $id = null; // current entity ID
-               $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
-                       use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts )
-               {
-                       if ( array_key_exists( $id, $newValsById ) ) {
-                               // Value was already regerated as expected, so use the value in $newValsById
-                               $newValue = $newValsById[$id];
-                               $ttl = $newTTLsById[$id];
-                               $setOpts = $newSetOpts;
-                       } else {
-                               // Pre-emptive/popularity refresh and version mismatch cases are not detected
-                               // above and thus $newValsById has no entry. Run $callback on this single entity.
-                               $ttls = [ $id => $ttl ];
-                               $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
-                               $ttl = $ttls[$id];
-                       }
-
-                       return $newValue;
-               };
-
-               // Run the cache-aside logic using warmupCache instead of persistent cache queries
-               $values = [];
-               foreach ( $keyedIds as $key => $id ) { // preserve order
-                       $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
-               }
-
-               $this->warmupCache = [];
-
-               return $values;
-       }
-
-       /**
-        * Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp
-        *
-        * This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
-        * broadcasting in mcrouter setups and also avoids races with new tombstones.
-        *
-        * @param string $key Cache key
-        * @param int $purgeTimestamp UNIX timestamp of purge
-        * @param bool &$isStale Whether the key is stale
-        * @return bool Success
-        * @since 1.28
-        */
-       final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
-               $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
-               $wrapped = $this->cache->get( self::$VALUE_KEY_PREFIX . $key );
-               if ( is_array( $wrapped ) && $wrapped[self::$FLD_TIME] < $minAsOf ) {
-                       $isStale = true;
-                       $this->logger->warning( "Reaping stale value key '$key'." );
-                       $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
-                       $ok = $this->cache->changeTTL( self::$VALUE_KEY_PREFIX . $key, $ttlReap );
-                       if ( !$ok ) {
-                               $this->logger->error( "Could not complete reap of key '$key'." );
-                       }
-
-                       return $ok;
-               }
-
-               $isStale = false;
-
-               return true;
-       }
-
-       /**
-        * Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp
-        *
-        * @param string $key Cache key
-        * @param int $purgeTimestamp UNIX timestamp of purge
-        * @param bool &$isStale Whether the key is stale
-        * @return bool Success
-        * @since 1.28
-        */
-       final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
-               $purge = $this->parsePurgeValue( $this->cache->get( self::$TIME_KEY_PREFIX . $key ) );
-               if ( $purge && $purge[self::$PURGE_TIME] < $purgeTimestamp ) {
-                       $isStale = true;
-                       $this->logger->warning( "Reaping stale check key '$key'." );
-                       $ok = $this->cache->changeTTL( self::$TIME_KEY_PREFIX . $key, self::TTL_SECOND );
-                       if ( !$ok ) {
-                               $this->logger->error( "Could not complete reap of check key '$key'." );
-                       }
-
-                       return $ok;
-               }
-
-               $isStale = false;
-
-               return false;
-       }
-
-       /**
-        * @see BagOStuff::makeKey()
-        * @param string $class Key class
-        * @param string ...$components Key components (starting with a key collection name)
-        * @return string Colon-delimited list of $keyspace followed by escaped components
-        * @since 1.27
-        */
-       public function makeKey( $class, ...$components ) {
-               return $this->cache->makeKey( ...func_get_args() );
-       }
-
-       /**
-        * @see BagOStuff::makeGlobalKey()
-        * @param string $class Key class
-        * @param string ...$components Key components (starting with a key collection name)
-        * @return string Colon-delimited list of $keyspace followed by escaped components
-        * @since 1.27
-        */
-       public function makeGlobalKey( $class, ...$components ) {
-               return $this->cache->makeGlobalKey( ...func_get_args() );
-       }
-
-       /**
-        * Hash a possibly long string into a suitable component for makeKey()/makeGlobalKey()
-        *
-        * @param string $component A raw component used in building a cache key
-        * @return string 64 character HMAC using a stable secret for public collision resistance
-        * @since 1.34
-        */
-       public function hash256( $component ) {
-               return hash_hmac( 'sha256', $component, $this->secret );
-       }
-
-       /**
-        * Get an iterator of (cache key => entity ID) for a list of entity IDs
-        *
-        * The callback takes an ID string and returns a key via makeKey()/makeGlobalKey().
-        * There should be no network nor filesystem I/O used in the callback. The entity
-        * ID/key mapping must be 1:1 or an exception will be thrown. If hashing is needed,
-        * then use the hash256() method.
-        *
-        * Example usage for the default keyspace:
-        * @code
-        *     $keyedIds = $cache->makeMultiKeys(
-        *         $modules,
-        *         function ( $module ) use ( $cache ) {
-        *             return $cache->makeKey( 'module-info', $module );
-        *         }
-        *     );
-        * @endcode
-        *
-        * Example usage for mixed default and global keyspace:
-        * @code
-        *     $keyedIds = $cache->makeMultiKeys(
-        *         $filters,
-        *         function ( $filter ) use ( $cache ) {
-        *             return ( strpos( $filter, 'central:' ) === 0 )
-        *                 ? $cache->makeGlobalKey( 'regex-filter', $filter )
-        *                 : $cache->makeKey( 'regex-filter', $filter )
-        *         }
-        *     );
-        * @endcode
-        *
-        * Example usage with hashing:
-        * @code
-        *     $keyedIds = $cache->makeMultiKeys(
-        *         $urls,
-        *         function ( $url ) use ( $cache ) {
-        *             return $cache->makeKey( 'url-info', $cache->hash256( $url ) );
-        *         }
-        *     );
-        * @endcode
-        *
-        * @see WANObjectCache::makeKey()
-        * @see WANObjectCache::makeGlobalKey()
-        * @see WANObjectCache::hash256()
-        *
-        * @param string[]|int[] $ids List of entity IDs
-        * @param callable $keyCallback Function returning makeKey()/makeGlobalKey() on the input ID
-        * @return ArrayIterator Iterator of (cache key => ID); order of $ids is preserved
-        * @throws UnexpectedValueException
-        * @since 1.28
-        */
-       final public function makeMultiKeys( array $ids, $keyCallback ) {
-               $idByKey = [];
-               foreach ( $ids as $id ) {
-                       // Discourage triggering of automatic makeKey() hashing in some backends
-                       if ( strlen( $id ) > 64 ) {
-                               $this->logger->warning( __METHOD__ . ": long ID '$id'; use hash256()" );
-                       }
-                       $key = $keyCallback( $id, $this );
-                       // Edge case: ignore key collisions due to duplicate $ids like "42" and 42
-                       if ( !isset( $idByKey[$key] ) ) {
-                               $idByKey[$key] = $id;
-                       } elseif ( (string)$id !== (string)$idByKey[$key] ) {
-                               throw new UnexpectedValueException(
-                                       "Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
-                               );
-                       }
-               }
-
-               return new ArrayIterator( $idByKey );
-       }
-
-       /**
-        * Get an (ID => value) map from (i) a non-unique list of entity IDs, and (ii) the list
-        * of corresponding entity values by first appearance of each ID in the entity ID list
-        *
-        * For use with getMultiWithSetCallback() and getMultiWithUnionSetCallback().
-        *
-        * *Only* use this method if the entity ID/key mapping is trivially 1:1 without exception.
-        * Key generation method must utitilize the *full* entity ID in the key (not a hash of it).
-        *
-        * Example usage:
-        * @code
-        *     $poems = $cache->getMultiWithSetCallback(
-        *         $cache->makeMultiKeys(
-        *             $uuids,
-        *             function ( $uuid ) use ( $cache ) {
-        *                 return $cache->makeKey( 'poem', $uuid );
-        *             }
-        *         ),
-        *         $cache::TTL_DAY,
-        *         function ( $uuid ) use ( $url ) {
-        *             return $this->http->run( [ 'method' => 'GET', 'url' => "$url/$uuid" ] );
-        *         }
-        *     );
-        *     $poemsByUUID = $cache->multiRemap( $uuids, $poems );
-        * @endcode
-        *
-        * @see WANObjectCache::makeMultiKeys()
-        * @see WANObjectCache::getMultiWithSetCallback()
-        * @see WANObjectCache::getMultiWithUnionSetCallback()
-        *
-        * @param string[]|int[] $ids Entity ID list makeMultiKeys()
-        * @param mixed[] $res Result of getMultiWithSetCallback()/getMultiWithUnionSetCallback()
-        * @return mixed[] Map of (ID => value); order of $ids is preserved
-        * @since 1.34
-        */
-       final public function multiRemap( array $ids, array $res ) {
-               if ( count( $ids ) !== count( $res ) ) {
-                       // If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
-                       // ArrayIterator will have less entries due to "first appearance" de-duplication
-                       $ids = array_keys( array_flip( $ids ) );
-                       if ( count( $ids ) !== count( $res ) ) {
-                               throw new UnexpectedValueException( "Multi-key result does not match ID list" );
-                       }
-               }
-
-               return array_combine( $ids, $res );
-       }
-
-       /**
-        * Get the "last error" registered; clearLastError() should be called manually
-        * @return int ERR_* class constant for the "last error" registry
-        */
-       final public function getLastError() {
-               $code = $this->cache->getLastError();
-               switch ( $code ) {
-                       case BagOStuff::ERR_NONE:
-                               return self::ERR_NONE;
-                       case BagOStuff::ERR_NO_RESPONSE:
-                               return self::ERR_NO_RESPONSE;
-                       case BagOStuff::ERR_UNREACHABLE:
-                               return self::ERR_UNREACHABLE;
-                       default:
-                               return self::ERR_UNEXPECTED;
-               }
-       }
-
-       /**
-        * Clear the "last error" registry
-        */
-       final public function clearLastError() {
-               $this->cache->clearLastError();
-       }
-
-       /**
-        * Clear the in-process caches; useful for testing
-        *
-        * @since 1.27
-        */
-       public function clearProcessCache() {
-               $this->processCaches = [];
-       }
-
-       /**
-        * Enable or disable the use of brief caching for tombstoned keys
-        *
-        * When a key is purged via delete(), there normally is a period where caching
-        * is hold-off limited to an extremely short time. This method will disable that
-        * caching, forcing the callback to run for any of:
-        *   - WANObjectCache::getWithSetCallback()
-        *   - WANObjectCache::getMultiWithSetCallback()
-        *   - WANObjectCache::getMultiWithUnionSetCallback()
-        *
-        * This is useful when both:
-        *   - a) the database used by the callback is known to be up-to-date enough
-        *        for some particular purpose (e.g. replica DB has applied transaction X)
-        *   - b) the caller needs to exploit that fact, and therefore needs to avoid the
-        *        use of inherently volatile and possibly stale interim keys
-        *
-        * @see WANObjectCache::delete()
-        * @param bool $enabled Whether to enable interim caching
-        * @since 1.31
-        */
-       final public function useInterimHoldOffCaching( $enabled ) {
-               $this->useInterimHoldOffCaching = $enabled;
-       }
-
-       /**
-        * @param int $flag ATTR_* class constant
-        * @return int QOS_* class constant
-        * @since 1.28
-        */
-       public function getQoS( $flag ) {
-               return $this->cache->getQoS( $flag );
-       }
-
-       /**
-        * Get a TTL that is higher for objects that have not changed recently
-        *
-        * This is useful for keys that get explicit purges and DB or purge relay
-        * lag is a potential concern (especially how it interacts with CDN cache)
-        *
-        * Example usage:
-        * @code
-        *     // Last-modified time of page
-        *     $mtime = wfTimestamp( TS_UNIX, $page->getTimestamp() );
-        *     // Get adjusted TTL. If $mtime is 3600 seconds ago and $minTTL/$factor left at
-        *     // defaults, then $ttl is 3600 * .2 = 720. If $minTTL was greater than 720, then
-        *     // $ttl would be $minTTL. If $maxTTL was smaller than 720, $ttl would be $maxTTL.
-        *     $ttl = $cache->adaptiveTTL( $mtime, $cache::TTL_DAY );
-        * @endcode
-        *
-        * Another use case is when there are no applicable "last modified" fields in the DB,
-        * and there are too many dependencies for explicit purges to be viable, and the rate of
-        * change to relevant content is unstable, and it is highly valued to have the cached value
-        * be as up-to-date as possible.
-        *
-        * Example usage:
-        * @code
-        *     $query = "<some complex query>";
-        *     $idListFromComplexQuery = $cache->getWithSetCallback(
-        *         $cache->makeKey( 'complex-graph-query', $hashOfQuery ),
-        *         GraphQueryClass::STARTING_TTL,
-        *         function ( $oldValue, &$ttl, array &$setOpts, $oldAsOf ) use ( $query, $cache ) {
-        *             $gdb = $this->getReplicaGraphDbConnection();
-        *             // Account for any snapshot/replica DB lag
-        *             $setOpts += GraphDatabase::getCacheSetOptions( $gdb );
-        *
-        *             $newList = iterator_to_array( $gdb->query( $query ) );
-        *             sort( $newList, SORT_NUMERIC ); // normalize
-        *
-        *             $minTTL = GraphQueryClass::MIN_TTL;
-        *             $maxTTL = GraphQueryClass::MAX_TTL;
-        *             if ( $oldValue !== false ) {
-        *                 // Note that $oldAsOf is the last time this callback ran
-        *                 $ttl = ( $newList === $oldValue )
-        *                     // No change: cache for 150% of the age of $oldValue
-        *                     ? $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, 1.5 )
-        *                     // Changed: cache for 50% of the age of $oldValue
-        *                     : $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, .5 );
-        *             }
-        *
-        *             return $newList;
-        *        },
-        *        [
-        *             // Keep stale values around for doing comparisons for TTL calculations.
-        *             // High values improve long-tail keys hit-rates, though might waste space.
-        *             'staleTTL' => GraphQueryClass::GRACE_TTL
-        *        ]
-        *     );
-        * @endcode
-        *
-        * @param int|float $mtime UNIX timestamp
-        * @param int $maxTTL Maximum TTL (seconds)
-        * @param int $minTTL Minimum TTL (seconds); Default: 30
-        * @param float $factor Value in the range (0,1); Default: .2
-        * @return int Adaptive TTL
-        * @since 1.28
-        */
-       public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
-               if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
-                       $mtime = (int)$mtime; // handle fractional seconds and string integers
-               }
-
-               if ( !is_int( $mtime ) || $mtime <= 0 ) {
-                       return $minTTL; // no last-modified time provided
-               }
-
-               $age = $this->getCurrentTime() - $mtime;
-
-               return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
-       }
-
-       /**
-        * @return int Number of warmup key cache misses last round
-        * @since 1.30
-        */
-       final public function getWarmupKeyMisses() {
-               return $this->warmupKeyMisses;
-       }
-
-       /**
-        * Do the actual async bus purge of a key
-        *
-        * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>"
-        *
-        * @param string $key Cache key
-        * @param int $ttl Seconds to keep the tombstone around
-        * @param int $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key
-        * @return bool Success
-        */
-       protected function relayPurge( $key, $ttl, $holdoff ) {
-               if ( $this->mcrouterAware ) {
-                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
-                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
-                       $ok = $this->cache->set(
-                               "/*/{$this->cluster}/{$key}",
-                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
-                               $ttl
-                       );
-               } else {
-                       // This handles the mcrouter and the single-DC case
-                       $ok = $this->cache->set(
-                               $key,
-                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
-                               $ttl
-                       );
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Do the actual async bus delete of a key
-        *
-        * @param string $key Cache key
-        * @return bool Success
-        */
-       protected function relayDelete( $key ) {
-               if ( $this->mcrouterAware ) {
-                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
-                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
-                       $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
-               } else {
-                       // Some other proxy handles broadcasting or there is only one datacenter
-                       $ok = $this->cache->delete( $key );
-               }
-
-               return $ok;
-       }
-
-       /**
-        * @param string $key
-        * @param int $ttl Seconds to live
-        * @param callable $callback
-        * @param array $opts
-        * @return bool Success
-        * @note Callable type hints are not used to avoid class-autoloading
-        */
-       private function scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) {
-               if ( !$this->asyncHandler ) {
-                       return false;
-               }
-               // Update the cache value later, such during post-send of an HTTP request
-               $func = $this->asyncHandler;
-               $func( function () use ( $key, $ttl, $callback, $opts ) {
-                       $opts['minAsOf'] = INF; // force a refresh
-                       $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
-               } );
-
-               return true;
-       }
-
-       /**
-        * Check if a key is fresh or in the grace window and thus due for randomized reuse
-        *
-        * If $curTTL > 0 (e.g. not expired) this returns true. Otherwise, the chance of returning
-        * true decrease steadily from 100% to 0% as the |$curTTL| moves from 0 to $graceTTL seconds.
-        * This handles widely varying levels of cache access traffic.
-        *
-        * If $curTTL <= -$graceTTL (e.g. already expired), then this returns false.
-        *
-        * @param float $curTTL Approximate TTL left on the key if present
-        * @param int $graceTTL Consider using stale values if $curTTL is greater than this
-        * @return bool
-        */
-       private function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
-               if ( $curTTL > 0 ) {
-                       return true;
-               } elseif ( $graceTTL <= 0 ) {
-                       return false;
-               }
-
-               $ageStale = abs( $curTTL ); // seconds of staleness
-               $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
-               if ( $curGTTL <= 0 ) {
-                       return false; //  already out of grace period
-               }
-
-               // Chance of using a stale value is the complement of the chance of refreshing it
-               return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
-       }
-
-       /**
-        * Check if a key is nearing expiration and thus due for randomized regeneration
-        *
-        * This returns false if $curTTL >= $lowTTL. Otherwise, the chance of returning true
-        * increases steadily from 0% to 100% as the $curTTL moves from $lowTTL to 0 seconds.
-        * This handles widely varying levels of cache access traffic.
-        *
-        * If $curTTL <= 0 (e.g. already expired), then this returns false.
-        *
-        * @param float $curTTL Approximate TTL left on the key if present
-        * @param float $lowTTL Consider a refresh when $curTTL is less than this
-        * @return bool
-        */
-       protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
-               if ( $lowTTL <= 0 ) {
-                       return false;
-               } elseif ( $curTTL >= $lowTTL ) {
-                       return false;
-               } elseif ( $curTTL <= 0 ) {
-                       return false;
-               }
-
-               $chance = ( 1 - $curTTL / $lowTTL );
-
-               return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
-       }
-
-       /**
-        * Check if a key is due for randomized regeneration due to its popularity
-        *
-        * This is used so that popular keys can preemptively refresh themselves for higher
-        * consistency (especially in the case of purge loss/delay). Unpopular keys can remain
-        * in cache with their high nominal TTL. This means popular keys keep good consistency,
-        * whether the data changes frequently or not, and long-tail keys get to stay in cache
-        * and get hits too. Similar to worthRefreshExpiring(), randomization is used.
-        *
-        * @param float $asOf UNIX timestamp of the value
-        * @param int $ageNew Age of key when this might recommend refreshing (seconds)
-        * @param int $timeTillRefresh Age of key when it should be refreshed if popular (seconds)
-        * @param float $now The current UNIX timestamp
-        * @return bool
-        */
-       protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
-               if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
-                       return false;
-               }
-
-               $age = $now - $asOf;
-               $timeOld = $age - $ageNew;
-               if ( $timeOld <= 0 ) {
-                       return false;
-               }
-
-               $popularHitsPerSec = 1;
-               // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
-               // Note that the "expected # of refreshes" for the ramp-up time range is half
-               // of what it would be if P(refresh) was at its full value during that time range.
-               $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::$RAMPUP_TTL / 2, 1 );
-               // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
-               // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
-               // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
-               $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
-
-               // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
-               $chance *= ( $timeOld <= self::$RAMPUP_TTL ) ? $timeOld / self::$RAMPUP_TTL : 1;
-
-               return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
-       }
-
-       /**
-        * Check if $value is not false, versioned (if needed), and not older than $minTime (if set)
-        *
-        * @param array|bool $value
-        * @param float $asOf The time $value was generated
-        * @param float $minAsOf Minimum acceptable "as of" timestamp
-        * @param float|null $purgeTime The last time the value was invalidated
-        * @return bool
-        */
-       protected function isValid( $value, $asOf, $minAsOf, $purgeTime = null ) {
-               // Avoid reading any key not generated after the latest delete() or touch
-               $safeMinAsOf = max( $minAsOf, $purgeTime + self::$TINY_POSTIVE );
-
-               if ( $value === false ) {
-                       return false;
-               } elseif ( $safeMinAsOf > 0 && $asOf < $minAsOf ) {
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * @param mixed $value
-        * @param int $ttl Seconds to live or zero for "indefinite"
-        * @param int|null $version Value version number or null if not versioned
-        * @param float $now Unix Current timestamp just before calling set()
-        * @param float $walltime How long it took to generate the value in seconds
-        * @return array
-        */
-       private function wrap( $value, $ttl, $version, $now, $walltime ) {
-               // Returns keys in ascending integer order for PHP7 array packing:
-               // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
-               $wrapped = [
-                       self::$FLD_FORMAT_VERSION => self::$VERSION,
-                       self::$FLD_VALUE => $value,
-                       self::$FLD_TTL => $ttl,
-                       self::$FLD_TIME => $now
-               ];
-               if ( $version !== null ) {
-                       $wrapped[self::$FLD_VALUE_VERSION] = $version;
-               }
-               if ( $walltime >= self::$GENERATION_SLOW_SEC ) {
-                       $wrapped[self::$FLD_GENERATION_TIME] = $walltime;
-               }
-
-               return $wrapped;
-       }
-
-       /**
-        * @param array|string|bool $wrapped The entry at a cache key
-        * @param float $now Unix Current timestamp (preferrably pre-query)
-        * @return array (value or false if absent/tombstoned/malformed, value metadata map).
-        * The cache key metadata includes the following metadata:
-        *   - asOf: UNIX timestamp of the value or null if there is no value
-        *   - curTTL: remaining time-to-live (negative if tombstoned) or null if there is no value
-        *   - version: value version number or null if the if there is no value
-        *   - tombAsOf: UNIX timestamp of the tombstone or null if there is no tombstone
-        */
-       private function unwrap( $wrapped, $now ) {
-               $value = false;
-               $info = [ 'asOf' => null, 'curTTL' => null, 'version' => null, 'tombAsOf' => null ];
-
-               if ( is_array( $wrapped ) ) {
-                       // Entry expected to be a cached value; validate it
-                       if (
-                               ( $wrapped[self::$FLD_FORMAT_VERSION] ?? null ) === self::$VERSION &&
-                               $wrapped[self::$FLD_TIME] >= $this->epoch
-                       ) {
-                               if ( $wrapped[self::$FLD_TTL] > 0 ) {
-                                       // Get the approximate time left on the key
-                                       $age = $now - $wrapped[self::$FLD_TIME];
-                                       $curTTL = max( $wrapped[self::$FLD_TTL] - $age, 0.0 );
-                               } else {
-                                       // Key had no TTL, so the time left is unbounded
-                                       $curTTL = INF;
-                               }
-                               $value = $wrapped[self::$FLD_VALUE];
-                               $info['version'] = $wrapped[self::$FLD_VALUE_VERSION] ?? null;
-                               $info['asOf'] = $wrapped[self::$FLD_TIME];
-                               $info['curTTL'] = $curTTL;
-                       }
-               } else {
-                       // Entry expected to be a tombstone; parse it
-                       $purge = $this->parsePurgeValue( $wrapped );
-                       if ( $purge !== false ) {
-                               // Tombstoned keys should always have a negative current $ttl
-                               $info['curTTL'] = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
-                               $info['tombAsOf'] = $purge[self::$PURGE_TIME];
-                       }
-               }
-
-               return [ $value, $info ];
-       }
-
-       /**
-        * @param string[] $keys
-        * @param string $prefix
-        * @return string[] Prefix keys; the order of $keys is preserved
-        */
-       protected static function prefixCacheKeys( array $keys, $prefix ) {
-               $res = [];
-               foreach ( $keys as $key ) {
-                       $res[] = $prefix . $key;
-               }
-
-               return $res;
-       }
-
-       /**
-        * @param string $key String of the format <scope>:<class>[:<class or variable>]...
-        * @return string A collection name to describe this class of key
-        */
-       private function determineKeyClassForStats( $key ) {
-               $parts = explode( ':', $key, 3 );
-
-               return $parts[1] ?? $parts[0]; // sanity
-       }
-
-       /**
-        * @param string|array|bool $value Possible string of the form "PURGED:<timestamp>:<holdoff>"
-        * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer),
-        *  or false if value isn't a valid purge value
-        */
-       private function parsePurgeValue( $value ) {
-               if ( !is_string( $value ) ) {
-                       return false;
-               }
-
-               $segments = explode( ':', $value, 3 );
-               if (
-                       !isset( $segments[0] ) ||
-                       !isset( $segments[1] ) ||
-                       "{$segments[0]}:" !== self::$PURGE_VAL_PREFIX
-               ) {
-                       return false;
-               }
-
-               if ( !isset( $segments[2] ) ) {
-                       // Back-compat with old purge values without holdoff
-                       $segments[2] = self::HOLDOFF_TTL;
-               }
-
-               if ( $segments[1] < $this->epoch ) {
-                       // Values this old are ignored
-                       return false;
-               }
-
-               return [
-                       self::$PURGE_TIME => (float)$segments[1],
-                       self::$PURGE_HOLDOFF => (int)$segments[2],
-               ];
-       }
-
-       /**
-        * @param float $timestamp
-        * @param int $holdoff In seconds
-        * @return string Wrapped purge value
-        */
-       private function makePurgeValue( $timestamp, $holdoff ) {
-               return self::$PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
-       }
-
-       /**
-        * @param string $group
-        * @return MapCacheLRU
-        */
-       private function getProcessCache( $group ) {
-               if ( !isset( $this->processCaches[$group] ) ) {
-                       list( , $size ) = explode( ':', $group );
-                       $this->processCaches[$group] = new MapCacheLRU( (int)$size );
-               }
-
-               return $this->processCaches[$group];
-       }
-
-       /**
-        * @param string $key
-        * @param int $version
-        * @return string
-        */
-       private function getProcessCacheKey( $key, $version ) {
-               return $key . ' ' . (int)$version;
-       }
-
-       /**
-        * @param ArrayIterator $keys
-        * @param array $opts
-        * @return string[] Map of (ID => cache key)
-        */
-       private function getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
-               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
-
-               $keysMissing = [];
-               if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
-                       $version = $opts['version'] ?? null;
-                       $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
-                       foreach ( $keys as $key => $id ) {
-                               if ( !$pCache->has( $this->getProcessCacheKey( $key, $version ), $pcTTL ) ) {
-                                       $keysMissing[$id] = $key;
-                               }
-                       }
-               }
-
-               return $keysMissing;
-       }
-
-       /**
-        * @param string[] $keys
-        * @param string[]|string[][] $checkKeys
-        * @return string[] List of cache keys
-        */
-       private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
-               if ( !$keys ) {
-                       return [];
-               }
-
-               $keysWarmUp = [];
-               // Get all the value keys to fetch...
-               foreach ( $keys as $key ) {
-                       $keysWarmUp[] = self::$VALUE_KEY_PREFIX . $key;
-               }
-               // Get all the check keys to fetch...
-               foreach ( $checkKeys as $i => $checkKeyOrKeys ) {
-                       if ( is_int( $i ) ) {
-                               // Single check key that applies to all value keys
-                               $keysWarmUp[] = self::$TIME_KEY_PREFIX . $checkKeyOrKeys;
-                       } else {
-                               // List of check keys that apply to value key $i
-                               $keysWarmUp = array_merge(
-                                       $keysWarmUp,
-                                       self::prefixCacheKeys( $checkKeyOrKeys, self::$TIME_KEY_PREFIX )
-                               );
-                       }
-               }
-
-               $warmupCache = $this->cache->getMulti( $keysWarmUp );
-               $warmupCache += array_fill_keys( $keysWarmUp, false );
-
-               return $warmupCache;
-       }
-
-       /**
-        * @return float UNIX timestamp
-        * @codeCoverageIgnore
-        */
-       protected function getCurrentTime() {
-               if ( $this->wallClockOverride ) {
-                       return $this->wallClockOverride;
-               }
-
-               $clockTime = (float)time(); // call this first
-               // microtime() uses an initial gettimeofday() call added to usage clocks.
-               // This can severely drift from time() and the microtime() value of other threads
-               // due to undercounting of the amount of time elapsed. Instead of seeing the current
-               // time as being in the past, use the value of time(). This avoids setting cache values
-               // that will immediately be seen as expired and possibly cause stampedes.
-               return max( microtime( true ), $clockTime );
-       }
-
-       /**
-        * @param float|null &$time Mock UNIX timestamp for testing
-        * @codeCoverageIgnore
-        */
-       public function setMockTime( &$time ) {
-               $this->wallClockOverride =& $time;
-               $this->cache->setMockTime( $time );
-       }
-}
diff --git a/includes/libs/objectcache/WANObjectCacheReaper.php b/includes/libs/objectcache/WANObjectCacheReaper.php
deleted file mode 100644 (file)
index fb8a754..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Wikimedia\ScopedCallback;
-
-/**
- * Class for scanning through chronological, log-structured data or change logs
- * and locally purging cache keys related to entities that appear in this data.
- *
- * This is useful for repairing cache when purges are missed by using a reliable
- * stream, such as Kafka or a replicated MySQL table. Purge loss between datacenters
- * is expected to be more common than within them.
- *
- * @since 1.28
- */
-class WANObjectCacheReaper implements LoggerAwareInterface {
-       /** @var WANObjectCache */
-       protected $cache;
-       /** @var BagOStuff */
-       protected $store;
-       /** @var callable */
-       protected $logChunkCallback;
-       /** @var callable */
-       protected $keyListCallback;
-       /** @var LoggerInterface */
-       protected $logger;
-
-       /** @var string */
-       protected $channel;
-       /** @var int */
-       protected $initialStartWindow;
-
-       /**
-        * @param WANObjectCache $cache Cache to reap bad keys from
-        * @param BagOStuff $store Cache to store positions use for locking
-        * @param callable $logCallback Callback taking arguments:
-        *          - The starting position as a UNIX timestamp
-        *          - The starting unique ID used for breaking timestamp collisions or null
-        *          - The ending position as a UNIX timestamp
-        *          - The maximum number of results to return
-        *        It returns a list of maps of (key: cache key, pos: UNIX timestamp, id: unique ID)
-        *        for each key affected, with the corrosponding event timestamp/ID information.
-        *        The events should be in ascending order, by (timestamp,id).
-        * @param callable $keyCallback Callback taking arguments:
-        *          - The WANObjectCache instance
-        *          - An object from the event log
-        *        It should return a list of WAN cache keys.
-        *        The callback must fully duck-type test the object, since can be any model class.
-        * @param array $params Additional options:
-        *          - channel: the name of the update event stream.
-        *          - initialStartWindow: seconds back in time to start if the position is lost.
-        *            Default: 1 hour.
-        *          - logger: an SPL monolog instance [optional]
-        */
-       public function __construct(
-               WANObjectCache $cache,
-               BagOStuff $store,
-               callable $logCallback,
-               callable $keyCallback,
-               array $params
-       ) {
-               $this->cache = $cache;
-               $this->store = $store;
-
-               $this->logChunkCallback = $logCallback;
-               $this->keyListCallback = $keyCallback;
-               if ( isset( $params['channel'] ) ) {
-                       $this->channel = $params['channel'];
-               } else {
-                       throw new UnexpectedValueException( "No channel specified." );
-               }
-
-               $this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
-               $this->logger = $params['logger'] ?? new NullLogger();
-       }
-
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-
-       /**
-        * Check and reap stale keys based on a chunk of events
-        *
-        * @param int $n Number of events
-        * @return int Number of keys checked
-        */
-       final public function invoke( $n = 100 ) {
-               $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
-               $scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
-               if ( !$scopeLock ) {
-                       return 0;
-               }
-
-               $now = time();
-               $status = $this->store->get( $posKey );
-               if ( !$status ) {
-                       $status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
-               }
-
-               // Get events for entities who's keys tombstones/hold-off should have expired by now
-               $events = call_user_func_array(
-                       $this->logChunkCallback,
-                       [ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
-               );
-
-               $event = null;
-               $keyEvents = [];
-               foreach ( $events as $event ) {
-                       $keys = call_user_func_array(
-                               $this->keyListCallback,
-                               [ $this->cache, $event['item'] ]
-                       );
-                       foreach ( $keys as $key ) {
-                               unset( $keyEvents[$key] ); // use only the latest per key
-                               $keyEvents[$key] = [
-                                       'pos' => $event['pos'],
-                                       'id' => $event['id']
-                               ];
-                       }
-               }
-
-               $purgeCount = 0;
-               $lastOkEvent = null;
-               foreach ( $keyEvents as $key => $keyEvent ) {
-                       if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
-                               break;
-                       }
-                       ++$purgeCount;
-                       $lastOkEvent = $event;
-               }
-
-               if ( $lastOkEvent ) {
-                       $ok = $this->store->merge(
-                               $posKey,
-                               function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
-                                       if ( !$curValue ) {
-                                               // Use new position
-                                       } else {
-                                               $curCoord = [ $curValue['pos'], $curValue['id'] ];
-                                               $newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
-                                               if ( $newCoord < $curCoord ) {
-                                                       // Keep prior position instead of rolling it back
-                                                       return $curValue;
-                                               }
-                                       }
-
-                                       return [
-                                               'pos' => $lastOkEvent['pos'],
-                                               'id' => $lastOkEvent['id'],
-                                               'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
-                                       ];
-                               },
-                               IExpiringStore::TTL_INDEFINITE
-                       );
-
-                       $pos = $lastOkEvent['pos'];
-                       $id = $lastOkEvent['id'];
-                       if ( $ok ) {
-                               $this->logger->info( "Updated cache reap position ($pos, $id)." );
-                       } else {
-                               $this->logger->error( "Could not update cache reap position ($pos, $id)." );
-                       }
-               }
-
-               ScopedCallback::consume( $scopeLock );
-
-               return $purgeCount;
-       }
-
-       /**
-        * @return array|bool Returns (pos, id) map or false if not set
-        */
-       public function getState() {
-               $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
-
-               return $this->store->get( $posKey );
-       }
-}
diff --git a/includes/libs/objectcache/wancache/WANObjectCache.php b/includes/libs/objectcache/wancache/WANObjectCache.php
new file mode 100644 (file)
index 0000000..1852685
--- /dev/null
@@ -0,0 +1,2633 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * Multi-datacenter aware caching interface
+ *
+ * ### Using WANObjectCache
+ *
+ * All operations go to the local datacenter cache, except for delete(),
+ * touchCheckKey(), and resetCheckKey(), which broadcast to all datacenters.
+ *
+ * This class is intended for caching data from primary stores.
+ * If the get() method does not return a value, then the caller
+ * should query the new value and backfill the cache using set().
+ * The preferred way to do this logic is through getWithSetCallback().
+ * When querying the store on cache miss, the closest DB replica
+ * should be used. Try to avoid heavyweight DB master or quorum reads.
+ *
+ * To ensure consumers of the cache see new values in a timely manner,
+ * you either need to follow either the validation strategy, or the
+ * purge strategy.
+ *
+ * The validation strategy refers to the natural avoidance of stale data
+ * by one of the following means:
+ *
+ *   - A) The cached value is immutable.
+ *        If the consumer has access to an identifier that uniquely describes a value,
+ *        cached value need not change. Instead, the key can change. This also allows
+ *        all servers to access their perceived current version. This is important
+ *        in context of multiple deployed versions of your application and/or cross-dc
+ *        database replication, to ensure deterministic values without oscillation.
+ *   - B) Validity is checked against the source after get().
+ *        This is the inverse of A. The unique identifier is embedded inside the value
+ *        and validated after on retreival. If outdated, the value is recomputed.
+ *   - C) The value is cached with a modest TTL (without validation).
+ *        If value recomputation is reasonably performant, and the value is allowed to
+ *        be stale, one should consider using TTL only – using the value's age as
+ *        method of validation.
+ *
+ * The purge strategy refers to the the approach whereby your application knows that
+ * source data has changed and can react by purging the relevant cache keys.
+ * As purges are expensive, this strategy should be avoided if possible.
+ * The simplest purge method is delete().
+ *
+ * No matter which strategy you choose, callers must not rely on updates or purges
+ * being immediately visible to other servers. It should be treated similarly as
+ * one would a database replica.
+ *
+ * The need for immediate updates should be avoided. If needed, solutions must be
+ * sought outside WANObjectCache.
+ *
+ * ### Deploying WANObjectCache
+ *
+ * There are two supported ways to set up broadcasted operations:
+ *
+ *   - A) Set up mcrouter as the underlying cache backend, using a memcached BagOStuff class
+ *        for the 'cache' parameter. The 'region' and 'cluster' parameters must be provided
+ *        and 'mcrouterAware' must be set to `true`.
+ *        Configure mcrouter as follows:
+ *          - 1) Use Route Prefixing based on region (datacenter) and cache cluster.
+ *               See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
+ *               https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup.
+ *          - 2) To increase the consistency of delete() and touchCheckKey() during cache
+ *               server membership changes, you can use the OperationSelectorRoute to
+ *               configure 'set' and 'delete' operations to go to all servers in the cache
+ *               cluster, instead of just one server determined by hashing.
+ *               See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles.
+ *   - B) Set up dynomite as a cache middleware between the web servers and either memcached
+ *        or redis and use it as the underlying cache backend, using a memcached BagOStuff
+ *        class for the 'cache' parameter. This will broadcast all key setting operations,
+ *        not just purges, which can be useful for cache warming. Writes are eventually
+ *        consistent via the Dynamo replication model. See https://github.com/Netflix/dynomite.
+ *
+ * Broadcasted operations like delete() and touchCheckKey() are intended to run
+ * immediately in the local datacenter and asynchronously in remote datacenters.
+ *
+ * This means that callers in all datacenters may see older values for however many
+ * milliseconds that the purge took to reach that datacenter. As with any cache, this
+ * should not be relied on for cases where reads are used to determine writes to source
+ * (e.g. non-cache) data stores, except when reading immutable data.
+ *
+ * All values are wrapped in metadata arrays. Keys use a "WANCache:" prefix
+ * to avoid collisions with keys that are not wrapped as metadata arrays. The
+ * prefixes are as follows:
+ *   - a) "WANCache:v" : used for regular value keys
+ *   - b) "WANCache:i" : used for temporarily storing values of tombstoned keys
+ *   - c) "WANCache:t" : used for storing timestamp "check" keys
+ *   - d) "WANCache:m" : used for temporary mutex keys to avoid cache stampedes
+ *
+ * @ingroup Cache
+ * @since 1.26
+ */
+class WANObjectCache implements IExpiringStore, IStoreKeyEncoder, LoggerAwareInterface {
+       /** @var BagOStuff The local datacenter cache */
+       protected $cache;
+       /** @var MapCacheLRU[] Map of group PHP instance caches */
+       protected $processCaches = [];
+       /** @var LoggerInterface */
+       protected $logger;
+       /** @var StatsdDataFactoryInterface */
+       protected $stats;
+       /** @var callable|null Function that takes a WAN cache callback and runs it later */
+       protected $asyncHandler;
+
+       /** @bar bool Whether to use mcrouter key prefixing for routing */
+       protected $mcrouterAware;
+       /** @var string Physical region for mcrouter use */
+       protected $region;
+       /** @var string Cache cluster name for mcrouter use */
+       protected $cluster;
+       /** @var bool Whether to use "interim" caching while keys are tombstoned */
+       protected $useInterimHoldOffCaching = true;
+       /** @var float Unix timestamp of the oldest possible valid values */
+       protected $epoch;
+       /** @var string Stable secret used for hasing long strings into key components */
+       protected $secret;
+
+       /** @var int Callback stack depth for getWithSetCallback() */
+       private $callbackDepth = 0;
+       /** @var mixed[] Temporary warm-up cache */
+       private $warmupCache = [];
+       /** @var int Key fetched */
+       private $warmupKeyMisses = 0;
+
+       /** @var float|null */
+       private $wallClockOverride;
+
+       /** @var int Max expected seconds to pass between delete() and DB commit finishing */
+       const MAX_COMMIT_DELAY = 3;
+       /** @var int Max expected seconds of combined lag from replication and view snapshots */
+       const MAX_READ_LAG = 7;
+       /** @var int Seconds to tombstone keys on delete() and treat as volatile after invalidation */
+       const HOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
+
+       /** @var int Idiom for getWithSetCallback() meaning "do not store the callback result" */
+       const TTL_UNCACHEABLE = -1;
+
+       /** @var int Consider regeneration if the key will expire within this many seconds */
+       const LOW_TTL = 30;
+       /** @var int Max TTL, in seconds, to store keys when a data sourced is lagged */
+       const TTL_LAGGED = 30;
+
+       /** @var int Expected time-till-refresh, in seconds, if the key is accessed once per second */
+       const HOT_TTR = 900;
+       /** @var int Minimum key age, in seconds, for expected time-till-refresh to be considered */
+       const AGE_NEW = 60;
+
+       /** @var int Idiom for getWithSetCallback() meaning "no cache stampede mutex required" */
+       const TSE_NONE = -1;
+
+       /** @var int Idiom for set()/getWithSetCallback() meaning "no post-expiration persistence" */
+       const STALE_TTL_NONE = 0;
+       /** @var int Idiom for set()/getWithSetCallback() meaning "no post-expiration grace period" */
+       const GRACE_TTL_NONE = 0;
+       /** @var int Idiom for delete()/touchCheckKey() meaning "no hold-off period" */
+       const HOLDOFF_TTL_NONE = 0;
+       /** @var int Alias for HOLDOFF_TTL_NONE (b/c) (deprecated since 1.34) */
+       const HOLDOFF_NONE = self::HOLDOFF_TTL_NONE;
+
+       /** @var float Idiom for getWithSetCallback() meaning "no minimum required as-of timestamp" */
+       const MIN_TIMESTAMP_NONE = 0.0;
+
+       /** @var string Default process cache name and max key count */
+       const PC_PRIMARY = 'primary:1000';
+
+       /** @var int Idion for get()/getMulti() to return extra information by reference */
+       const PASS_BY_REF = -1;
+
+       /** @var int Seconds to keep dependency purge keys around */
+       private static $CHECK_KEY_TTL = self::TTL_YEAR;
+       /** @var int Seconds to keep interim value keys for tombstoned keys around */
+       private static $INTERIM_KEY_TTL = 1;
+
+       /** @var int Seconds to keep lock keys around */
+       private static $LOCK_TTL = 10;
+       /** @var int Seconds to no-op key set() calls to avoid large blob I/O stampedes */
+       private static $COOLOFF_TTL = 1;
+       /** @var int Seconds to ramp up the chance of regeneration due to expected time-till-refresh */
+       private static $RAMPUP_TTL = 30;
+
+       /** @var float Tiny negative float to use when CTL comes up >= 0 due to clock skew */
+       private static $TINY_NEGATIVE = -0.000001;
+       /** @var float Tiny positive float to use when using "minTime" to assert an inequality */
+       private static $TINY_POSTIVE = 0.000001;
+
+       /** @var int Milliseconds of key fetch/validate/regenerate delay prone to set() stampedes */
+       private static $SET_DELAY_HIGH_MS = 50;
+       /** @var int Min millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL) */
+       private static $RECENT_SET_LOW_MS = 50;
+       /** @var int Max millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL) */
+       private static $RECENT_SET_HIGH_MS = 100;
+
+       /** @var int Consider value generation slow if it takes more than this many seconds */
+       private static $GENERATION_SLOW_SEC = 3;
+
+       /** @var int Key to the tombstone entry timestamp */
+       private static $PURGE_TIME = 0;
+       /** @var int Key to the tombstone entry hold-off TTL */
+       private static $PURGE_HOLDOFF = 1;
+
+       /** @var int Cache format version number */
+       private static $VERSION = 1;
+
+       /** @var int Key to WAN cache version number */
+       private static $FLD_FORMAT_VERSION = 0;
+       /** @var int Key to the cached value */
+       private static $FLD_VALUE = 1;
+       /** @var int Key to the original TTL */
+       private static $FLD_TTL = 2;
+       /** @var int Key to the cache timestamp */
+       private static $FLD_TIME = 3;
+       /** @var int Key to the flags bit field (reserved number) */
+       private static /** @noinspection PhpUnusedPrivateFieldInspection */ $FLD_FLAGS = 4;
+       /** @var int Key to collection cache version number */
+       private static $FLD_VALUE_VERSION = 5;
+       /** @var int Key to how long it took to generate the value */
+       private static $FLD_GENERATION_TIME = 6;
+
+       private static $VALUE_KEY_PREFIX = 'WANCache:v:';
+       private static $INTERIM_KEY_PREFIX = 'WANCache:i:';
+       private static $TIME_KEY_PREFIX = 'WANCache:t:';
+       private static $MUTEX_KEY_PREFIX = 'WANCache:m:';
+       private static $COOLOFF_KEY_PREFIX = 'WANCache:c:';
+
+       private static $PURGE_VAL_PREFIX = 'PURGED:';
+
+       /**
+        * @param array $params
+        *   - cache    : BagOStuff object for a persistent cache
+        *   - logger   : LoggerInterface object
+        *   - stats    : StatsdDataFactoryInterface object
+        *   - asyncHandler : A function that takes a callback and runs it later. If supplied,
+        *       whenever a preemptive refresh would be triggered in getWithSetCallback(), the
+        *       current cache value is still used instead. However, the async-handler function
+        *       receives a WAN cache callback that, when run, will execute the value generation
+        *       callback supplied by the getWithSetCallback() caller. The result will be saved
+        *       as normal. The handler is expected to call the WAN cache callback at an opportune
+        *       time (e.g. HTTP post-send), though generally within a few 100ms. [optional]
+        *   - region: the current physical region. This is required when using mcrouter as the
+        *       backing store proxy. [optional]
+        *   - cluster: name of the cache cluster used by this WAN cache. The name must be the
+        *       same in all datacenters; the ("region","cluster") tuple is what distinguishes
+        *       the counterpart cache clusters among all the datacenter. The contents of
+        *       https://github.com/facebook/mcrouter/wiki/Config-Files give background on this.
+        *       This is required when using mcrouter as the backing store proxy. [optional]
+        *   - mcrouterAware: set as true if mcrouter is the backing store proxy and mcrouter
+        *       is configured to interpret /<region>/<cluster>/ key prefixes as routes. This
+        *       requires that "region" and "cluster" are both set above. [optional]
+        *   - epoch: lowest UNIX timestamp a value/tombstone must have to be valid. [optional]
+        *   - secret: stable secret used for hashing long strings into key components. [optional]
+        */
+       public function __construct( array $params ) {
+               $this->cache = $params['cache'];
+               $this->region = $params['region'] ?? 'main';
+               $this->cluster = $params['cluster'] ?? 'wan-main';
+               $this->mcrouterAware = !empty( $params['mcrouterAware'] );
+               $this->epoch = $params['epoch'] ?? 0;
+               $this->secret = $params['secret'] ?? (string)$this->epoch;
+
+               $this->setLogger( $params['logger'] ?? new NullLogger() );
+               $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
+               $this->asyncHandler = $params['asyncHandler'] ?? null;
+       }
+
+       /**
+        * @param LoggerInterface $logger
+        */
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * Get an instance that wraps EmptyBagOStuff
+        *
+        * @return WANObjectCache
+        */
+       public static function newEmpty() {
+               return new static( [ 'cache' => new EmptyBagOStuff() ] );
+       }
+
+       /**
+        * Fetch the value of a key from cache
+        *
+        * If supplied, $curTTL is set to the remaining TTL (current time left):
+        *   - a) INF; if $key exists, has no TTL, and is not invalidated by $checkKeys
+        *   - b) float (>=0); if $key exists, has a TTL, and is not invalidated by $checkKeys
+        *   - c) float (<0); if $key is tombstoned, stale, or existing but invalidated by $checkKeys
+        *   - d) null; if $key does not exist and is not tombstoned
+        *
+        * If a key is tombstoned, $curTTL will reflect the time since delete().
+        *
+        * The timestamp of $key will be checked against the last-purge timestamp
+        * of each of $checkKeys. Those $checkKeys not in cache will have the last-purge
+        * initialized to the current timestamp. If any of $checkKeys have a timestamp
+        * greater than that of $key, then $curTTL will reflect how long ago $key
+        * became invalid. Callers can use $curTTL to know when the value is stale.
+        * The $checkKeys parameter allow mass invalidations by updating a single key:
+        *   - a) Each "check" key represents "last purged" of some source data
+        *   - b) Callers pass in relevant "check" keys as $checkKeys in get()
+        *   - c) When the source data that "check" keys represent changes,
+        *        the touchCheckKey() method is called on them
+        *
+        * Source data entities might exists in a DB that uses snapshot isolation
+        * (e.g. the default REPEATABLE-READ in innoDB). Even for mutable data, that
+        * isolation can largely be maintained by doing the following:
+        *   - a) Calling delete() on entity change *and* creation, before DB commit
+        *   - b) Keeping transaction duration shorter than the delete() hold-off TTL
+        *   - c) Disabling interim key caching via useInterimHoldOffCaching() before get() calls
+        *
+        * However, pre-snapshot values might still be seen if an update was made
+        * in a remote datacenter but the purge from delete() didn't relay yet.
+        *
+        * Consider using getWithSetCallback() instead of get() and set() cycles.
+        * That method has cache slam avoiding features for hot/expensive keys.
+        *
+        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a cache key metadata map.
+        * This map includes the following metadata:
+        *   - asOf: UNIX timestamp of the value or null if the key is nonexistant
+        *   - tombAsOf: UNIX timestamp of the tombstone or null if the key is not tombstoned
+        *   - lastCKPurge: UNIX timestamp of the highest check key or null if none provided
+        *   - version: cached value version number or null if the key is nonexistant
+        *
+        * Otherwise, $info will transform into the cached value timestamp.
+        *
+        * @param string $key Cache key made from makeKey() or makeGlobalKey()
+        * @param mixed|null &$curTTL Approximate TTL left on the key if present/tombstoned [returned]
+        * @param string[] $checkKeys The "check" keys used to validate the value
+        * @param mixed|null &$info Key info if WANObjectCache::PASS_BY_REF [returned]
+        * @return mixed Value of cache key or false on failure
+        */
+       final public function get(
+               $key, &$curTTL = null, array $checkKeys = [], &$info = null
+       ) {
+               $curTTLs = self::PASS_BY_REF;
+               $infoByKey = self::PASS_BY_REF;
+               $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $infoByKey );
+               $curTTL = $curTTLs[$key] ?? null;
+               if ( $info === self::PASS_BY_REF ) {
+                       $info = [
+                               'asOf' => $infoByKey[$key]['asOf'] ?? null,
+                               'tombAsOf' => $infoByKey[$key]['tombAsOf'] ?? null,
+                               'lastCKPurge' => $infoByKey[$key]['lastCKPurge'] ?? null,
+                               'version' => $infoByKey[$key]['version'] ?? null
+                       ];
+               } else {
+                       $info = $infoByKey[$key]['asOf'] ?? null; // b/c
+               }
+
+               return $values[$key] ?? false;
+       }
+
+       /**
+        * Fetch the value of several keys from cache
+        *
+        * Pass $info as WANObjectCache::PASS_BY_REF to transform it into a map of cache keys
+        * to cache key metadata maps, each having the same style as those of WANObjectCache::get().
+        * All the cache keys listed in $keys will have an entry.
+        *
+        * Othwerwise, $info will transform into a map of (cache key => cached value timestamp).
+        * Only the cache keys listed in $keys that exists or are tombstoned will have an entry.
+        *
+        * $checkKeys holds the "check" keys used to validate values of applicable keys. The integer
+        * indexes hold "check" keys that apply to all of $keys while the string indexes hold "check"
+        * keys that only apply to the cache key with that name.
+        *
+        * @see WANObjectCache::get()
+        *
+        * @param string[] $keys List of cache keys made from makeKey() or makeGlobalKey()
+        * @param mixed|null &$curTTLs Map of (key => TTL left) for existing/tombstoned keys [returned]
+        * @param string[]|string[][] $checkKeys Map of (integer or cache key => "check" key(s))
+        * @param mixed|null &$info Map of (key => info) if WANObjectCache::PASS_BY_REF [returned]
+        * @return mixed[] Map of (key => value) for existing values; order of $keys is preserved
+        */
+       final public function getMulti(
+               array $keys,
+               &$curTTLs = [],
+               array $checkKeys = [],
+               &$info = null
+       ) {
+               $result = [];
+               $curTTLs = [];
+               $infoByKey = [];
+
+               $vPrefixLen = strlen( self::$VALUE_KEY_PREFIX );
+               $valueKeys = self::prefixCacheKeys( $keys, self::$VALUE_KEY_PREFIX );
+
+               $checkKeysForAll = [];
+               $checkKeysByKey = [];
+               $checkKeysFlat = [];
+               foreach ( $checkKeys as $i => $checkKeyGroup ) {
+                       $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::$TIME_KEY_PREFIX );
+                       $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
+                       // Are these check keys for a specific cache key, or for all keys being fetched?
+                       if ( is_int( $i ) ) {
+                               $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed );
+                       } else {
+                               $checkKeysByKey[$i] = $prefixed;
+                       }
+               }
+
+               // Fetch all of the raw values
+               $keysGet = array_merge( $valueKeys, $checkKeysFlat );
+               if ( $this->warmupCache ) {
+                       $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) );
+                       $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch
+                       $this->warmupKeyMisses += count( $keysGet );
+               } else {
+                       $wrappedValues = [];
+               }
+               if ( $keysGet ) {
+                       $wrappedValues += $this->cache->getMulti( $keysGet );
+               }
+               // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
+               $now = $this->getCurrentTime();
+
+               // Collect timestamps from all "check" keys
+               $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
+               $purgeValuesByKey = [];
+               foreach ( $checkKeysByKey as $cacheKey => $checks ) {
+                       $purgeValuesByKey[$cacheKey] =
+                               $this->processCheckKeys( $checks, $wrappedValues, $now );
+               }
+
+               // Get the main cache value for each key and validate them
+               foreach ( $valueKeys as $vKey ) {
+                       $key = substr( $vKey, $vPrefixLen ); // unprefix
+                       list( $value, $keyInfo ) = $this->unwrap( $wrappedValues[$vKey] ?? false, $now );
+                       // Force dependent keys to be seen as stale for a while after purging
+                       // to reduce race conditions involving stale data getting cached
+                       $purgeValues = $purgeValuesForAll;
+                       if ( isset( $purgeValuesByKey[$key] ) ) {
+                               $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
+                       }
+
+                       $lastCKPurge = null; // timestamp of the highest check key
+                       foreach ( $purgeValues as $purge ) {
+                               $lastCKPurge = max( $purge[self::$PURGE_TIME], $lastCKPurge );
+                               $safeTimestamp = $purge[self::$PURGE_TIME] + $purge[self::$PURGE_HOLDOFF];
+                               if ( $value !== false && $safeTimestamp >= $keyInfo['asOf'] ) {
+                                       // How long ago this value was invalidated by *this* check key
+                                       $ago = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
+                                       // How long ago this value was invalidated by *any* known check key
+                                       $keyInfo['curTTL'] = min( $keyInfo['curTTL'], $ago );
+                               }
+                       }
+                       $keyInfo[ 'lastCKPurge'] = $lastCKPurge;
+
+                       if ( $value !== false ) {
+                               $result[$key] = $value;
+                       }
+                       if ( $keyInfo['curTTL'] !== null ) {
+                               $curTTLs[$key] = $keyInfo['curTTL'];
+                       }
+
+                       $infoByKey[$key] = ( $info === self::PASS_BY_REF )
+                               ? $keyInfo
+                               : $keyInfo['asOf']; // b/c
+               }
+
+               $info = $infoByKey;
+
+               return $result;
+       }
+
+       /**
+        * @since 1.27
+        * @param string[] $timeKeys List of prefixed time check keys
+        * @param mixed[] $wrappedValues
+        * @param float $now
+        * @return array[] List of purge value arrays
+        */
+       private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
+               $purgeValues = [];
+               foreach ( $timeKeys as $timeKey ) {
+                       $purge = isset( $wrappedValues[$timeKey] )
+                               ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
+                               : false;
+                       if ( $purge === false ) {
+                               // Key is not set or malformed; regenerate
+                               $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
+                               $this->cache->add( $timeKey, $newVal, self::$CHECK_KEY_TTL );
+                               $purge = $this->parsePurgeValue( $newVal );
+                       }
+                       $purgeValues[] = $purge;
+               }
+               return $purgeValues;
+       }
+
+       /**
+        * Set the value of a key in cache
+        *
+        * Simply calling this method when source data changes is not valid because
+        * the changes do not replicate to the other WAN sites. In that case, delete()
+        * should be used instead. This method is intended for use on cache misses.
+        *
+        * If the data was read from a snapshot-isolated transactions (e.g. the default
+        * REPEATABLE-READ in innoDB), use 'since' to avoid the following race condition:
+        *   - a) T1 starts
+        *   - b) T2 updates a row, calls delete(), and commits
+        *   - c) The HOLDOFF_TTL passes, expiring the delete() tombstone
+        *   - d) T1 reads the row and calls set() due to a cache miss
+        *   - e) Stale value is stuck in cache
+        *
+        * Setting 'lag' and 'since' help avoids keys getting stuck in stale states.
+        *
+        * Be aware that this does not update the process cache for getWithSetCallback()
+        * callers. Keys accessed via that method are not generally meant to also be set
+        * using this primitive method.
+        *
+        * Do not use this method on versioned keys accessed via getWithSetCallback().
+        *
+        * Example usage:
+        * @code
+        *     $dbr = wfGetDB( DB_REPLICA );
+        *     $setOpts = Database::getCacheSetOptions( $dbr );
+        *     // Fetch the row from the DB
+        *     $row = $dbr->selectRow( ... );
+        *     $key = $cache->makeKey( 'building', $buildingId );
+        *     $cache->set( $key, $row, $cache::TTL_DAY, $setOpts );
+        * @endcode
+        *
+        * @param string $key Cache key
+        * @param mixed $value
+        * @param int $ttl Seconds to live. Special values are:
+        *   - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
+        *   - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted)
+        * @param array $opts Options map:
+        *   - lag: Seconds of replica DB lag. Typically, this is either the replica DB lag
+        *      before the data was read or, if applicable, the replica DB lag before
+        *      the snapshot-isolated transaction the data was read from started.
+        *      Use false to indicate that replication is not running.
+        *      Default: 0 seconds
+        *   - since: UNIX timestamp of the data in $value. Typically, this is either
+        *      the current time the data was read or (if applicable) the time when
+        *      the snapshot-isolated transaction the data was read from started.
+        *      Default: 0 seconds
+        *   - pending: Whether this data is possibly from an uncommitted write transaction.
+        *      Generally, other threads should not see values from the future and
+        *      they certainly should not see ones that ended up getting rolled back.
+        *      Default: false
+        *   - lockTSE: If excessive replication/snapshot lag is detected, then store the value
+        *      with this TTL and flag it as stale. This is only useful if the reads for this key
+        *      use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
+        *      then it will still add on to this TTL in the excessive lag scenario.
+        *      Default: WANObjectCache::TSE_NONE
+        *   - staleTTL: Seconds to keep the key around if it is stale. The get()/getMulti()
+        *      methods return such stale values with a $curTTL of 0, and getWithSetCallback()
+        *      will call the regeneration callback in such cases, passing in the old value
+        *      and its as-of time to the callback. This is useful if adaptiveTTL() is used
+        *      on the old value's as-of time when it is verified as still being correct.
+        *      Default: WANObjectCache::STALE_TTL_NONE
+        *   - creating: Optimize for the case where the key does not already exist.
+        *      Default: false
+        *   - version: Integer version number signifiying the format of the value.
+        *      Default: null
+        *   - walltime: How long the value took to generate in seconds. Default: 0.0
+        * @note Options added in 1.28: staleTTL
+        * @note Options added in 1.33: creating
+        * @note Options added in 1.34: version, walltime
+        * @return bool Success
+        */
+       final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
+               $now = $this->getCurrentTime();
+               $lag = $opts['lag'] ?? 0;
+               $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
+               $pending = $opts['pending'] ?? false;
+               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
+               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
+               $creating = $opts['creating'] ?? false;
+               $version = $opts['version'] ?? null;
+               $walltime = $opts['walltime'] ?? 0.0;
+
+               if ( $ttl < 0 ) {
+                       return true;
+               }
+
+               // Do not cache potentially uncommitted data as it might get rolled back
+               if ( $pending ) {
+                       $this->logger->info(
+                               'Rejected set() for {cachekey} due to pending writes.',
+                               [ 'cachekey' => $key ]
+                       );
+
+                       return true; // no-op the write for being unsafe
+               }
+
+               $logicalTTL = null; // logical TTL override
+               // Check if there's a risk of writing stale data after the purge tombstone expired
+               if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
+                       // Case A: any long-running transaction
+                       if ( $age > self::MAX_READ_LAG ) {
+                               if ( $lockTSE >= 0 ) {
+                                       // Store value as *almost* stale to avoid cache and mutex stampedes
+                                       $logicalTTL = self::TTL_SECOND;
+                                       $this->logger->info(
+                                               'Lowered set() TTL for {cachekey} due to snapshot lag.',
+                                               [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+                                       );
+                               } else {
+                                       $this->logger->info(
+                                               'Rejected set() for {cachekey} due to snapshot lag.',
+                                               [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+                                       );
+
+                                       return true; // no-op the write for being unsafe
+                               }
+                       // Case B: high replication lag; lower TTL instead of ignoring all set()s
+                       } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
+                               if ( $lockTSE >= 0 ) {
+                                       $logicalTTL = min( $ttl ?: INF, self::TTL_LAGGED );
+                               } else {
+                                       $ttl = min( $ttl ?: INF, self::TTL_LAGGED );
+                               }
+                               $this->logger->warning(
+                                       'Lowered set() TTL for {cachekey} due to replication lag.',
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+                               );
+                       // Case C: medium length request with medium replication lag
+                       } elseif ( $lockTSE >= 0 ) {
+                               // Store value as *almost* stale to avoid cache and mutex stampedes
+                               $logicalTTL = self::TTL_SECOND;
+                               $this->logger->info(
+                                       'Lowered set() TTL for {cachekey} due to high read lag.',
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+                               );
+                       } else {
+                               $this->logger->info(
+                                       'Rejected set() for {cachekey} due to high read lag.',
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+                               );
+
+                               return true; // no-op the write for being unsafe
+                       }
+               }
+
+               // Wrap that value with time/TTL/version metadata
+               $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now, $walltime );
+               $storeTTL = $ttl + $staleTTL;
+
+               if ( $creating ) {
+                       $ok = $this->cache->add( self::$VALUE_KEY_PREFIX . $key, $wrapped, $storeTTL );
+               } else {
+                       $ok = $this->cache->merge(
+                               self::$VALUE_KEY_PREFIX . $key,
+                               function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
+                                       // A string value means that it is a tombstone; do nothing in that case
+                                       return ( is_string( $cWrapped ) ) ? false : $wrapped;
+                               },
+                               $storeTTL,
+                               1 // 1 attempt
+                       );
+               }
+
+               return $ok;
+       }
+
+       /**
+        * Purge a key from all datacenters
+        *
+        * This should only be called when the underlying data (being cached)
+        * changes in a significant way. This deletes the key and starts a hold-off
+        * period where the key cannot be written to for a few seconds (HOLDOFF_TTL).
+        * This is done to avoid the following race condition:
+        *   - a) Some DB data changes and delete() is called on a corresponding key
+        *   - b) A request refills the key with a stale value from a lagged DB
+        *   - c) The stale value is stuck there until the key is expired/evicted
+        *
+        * This is implemented by storing a special "tombstone" value at the cache
+        * key that this class recognizes; get() calls will return false for the key
+        * and any set() calls will refuse to replace tombstone values at the key.
+        * For this to always avoid stale value writes, the following must hold:
+        *   - a) Replication lag is bounded to being less than HOLDOFF_TTL; or
+        *   - b) If lag is higher, the DB will have gone into read-only mode already
+        *
+        * Note that set() can also be lag-aware and lower the TTL if it's high.
+        *
+        * Be aware that this does not clear the process cache. Even if it did, callbacks
+        * used by getWithSetCallback() might still return stale data in the case of either
+        * uncommitted or not-yet-replicated changes (callback generally use replica DBs).
+        *
+        * When using potentially long-running ACID transactions, a good pattern is
+        * to use a pre-commit hook to issue the delete. This means that immediately
+        * after commit, callers will see the tombstone in cache upon purge relay.
+        * It also avoids the following race condition:
+        *   - a) T1 begins, changes a row, and calls delete()
+        *   - b) The HOLDOFF_TTL passes, expiring the delete() tombstone
+        *   - c) T2 starts, reads the row and calls set() due to a cache miss
+        *   - d) T1 finally commits
+        *   - e) Stale value is stuck in cache
+        *
+        * Example usage:
+        * @code
+        *     $dbw->startAtomic( __METHOD__ ); // start of request
+        *     ... <execute some stuff> ...
+        *     // Update the row in the DB
+        *     $dbw->update( ... );
+        *     $key = $cache->makeKey( 'homes', $homeId );
+        *     // Purge the corresponding cache entry just before committing
+        *     $dbw->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) {
+        *         $cache->delete( $key );
+        *     } );
+        *     ... <execute some stuff> ...
+        *     $dbw->endAtomic( __METHOD__ ); // end of request
+        * @endcode
+        *
+        * The $ttl parameter can be used when purging values that have not actually changed
+        * recently. For example, a cleanup script to purge cache entries does not really need
+        * a hold-off period, so it can use HOLDOFF_TTL_NONE. Likewise for user-requested purge.
+        * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback().
+        *
+        * If called twice on the same key, then the last hold-off TTL takes precedence. For
+        * idempotence, the $ttl should not vary for different delete() calls on the same key.
+        *
+        * @param string $key Cache key
+        * @param int $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL
+        * @return bool True if the item was purged or not found, false on failure
+        */
+       final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
+               if ( $ttl <= 0 ) {
+                       // Publish the purge to all datacenters
+                       $ok = $this->relayDelete( self::$VALUE_KEY_PREFIX . $key );
+               } else {
+                       // Publish the purge to all datacenters
+                       $ok = $this->relayPurge( self::$VALUE_KEY_PREFIX . $key, $ttl, self::HOLDOFF_TTL_NONE );
+               }
+
+               $kClass = $this->determineKeyClassForStats( $key );
+               $this->stats->increment( "wanobjectcache.$kClass.delete." . ( $ok ? 'ok' : 'error' ) );
+
+               return $ok;
+       }
+
+       /**
+        * Fetch the value of a timestamp "check" key
+        *
+        * The key will be *initialized* to the current time if not set,
+        * so only call this method if this behavior is actually desired
+        *
+        * The timestamp can be used to check whether a cached value is valid.
+        * Callers should not assume that this returns the same timestamp in
+        * all datacenters due to relay delays.
+        *
+        * The level of staleness can roughly be estimated from this key, but
+        * if the key was evicted from cache, such calculations may show the
+        * time since expiry as ~0 seconds.
+        *
+        * Note that "check" keys won't collide with other regular keys.
+        *
+        * @param string $key
+        * @return float UNIX timestamp
+        */
+       final public function getCheckKeyTime( $key ) {
+               return $this->getMultiCheckKeyTime( [ $key ] )[$key];
+       }
+
+       /**
+        * Fetch the values of each timestamp "check" key
+        *
+        * This works like getCheckKeyTime() except it takes a list of keys
+        * and returns a map of timestamps instead of just that of one key
+        *
+        * This might be useful if both:
+        *   - a) a class of entities each depend on hundreds of other entities
+        *   - b) these other entities are depended upon by millions of entities
+        *
+        * The later entities can each use a "check" key to invalidate their dependee entities.
+        * However, it is expensive for the former entities to verify against all of the relevant
+        * "check" keys during each getWithSetCallback() call. A less expensive approach is to do
+        * these verifications only after a "time-till-verify" (TTV) has passed. This is a middle
+        * ground between using blind TTLs and using constant verification. The adaptiveTTL() method
+        * can be used to dynamically adjust the TTV. Also, the initial TTV can make use of the
+        * last-modified times of the dependant entities (either from the DB or the "check" keys).
+        *
+        * Example usage:
+        * @code
+        *     $value = $cache->getWithSetCallback(
+        *         $cache->makeGlobalKey( 'wikibase-item', $id ),
+        *         self::INITIAL_TTV, // initial time-till-verify
+        *         function ( $oldValue, &$ttv, &$setOpts, $oldAsOf ) use ( $checkKeys, $cache ) {
+        *             $now = microtime( true );
+        *             // Use $oldValue if it passes max ultimate age and "check" key comparisons
+        *             if ( $oldValue &&
+        *                 $oldAsOf > max( $cache->getMultiCheckKeyTime( $checkKeys ) ) &&
+        *                 ( $now - $oldValue['ctime'] ) <= self::MAX_CACHE_AGE
+        *             ) {
+        *                 // Increase time-till-verify by 50% of last time to reduce overhead
+        *                 $ttv = $cache->adaptiveTTL( $oldAsOf, self::MAX_TTV, self::MIN_TTV, 1.5 );
+        *                 // Unlike $oldAsOf, "ctime" is the ultimate age of the cached data
+        *                 return $oldValue;
+        *             }
+        *
+        *             $mtimes = []; // dependency last-modified times; passed by reference
+        *             $value = [ 'data' => $this->fetchEntityData( $mtimes ), 'ctime' => $now ];
+        *             // Guess time-till-change among the dependencies, e.g. 1/(total change rate)
+        *             $ttc = 1 / array_sum( array_map(
+        *                 function ( $mtime ) use ( $now ) {
+        *                     return 1 / ( $mtime ? ( $now - $mtime ) : 900 );
+        *                 },
+        *                 $mtimes
+        *             ) );
+        *             // The time-to-verify should not be overly pessimistic nor optimistic
+        *             $ttv = min( max( $ttc, self::MIN_TTV ), self::MAX_TTV );
+        *
+        *             return $value;
+        *         },
+        *         [ 'staleTTL' => $cache::TTL_DAY ] // keep around to verify and re-save
+        *     );
+        * @endcode
+        *
+        * @see WANObjectCache::getCheckKeyTime()
+        * @see WANObjectCache::getWithSetCallback()
+        *
+        * @param string[] $keys
+        * @return float[] Map of (key => UNIX timestamp)
+        * @since 1.31
+        */
+       final public function getMultiCheckKeyTime( array $keys ) {
+               $rawKeys = [];
+               foreach ( $keys as $key ) {
+                       $rawKeys[$key] = self::$TIME_KEY_PREFIX . $key;
+               }
+
+               $rawValues = $this->cache->getMulti( $rawKeys );
+               $rawValues += array_fill_keys( $rawKeys, false );
+
+               $times = [];
+               foreach ( $rawKeys as $key => $rawKey ) {
+                       $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
+                       if ( $purge !== false ) {
+                               $time = $purge[self::$PURGE_TIME];
+                       } else {
+                               // Casting assures identical floats for the next getCheckKeyTime() calls
+                               $now = (string)$this->getCurrentTime();
+                               $this->cache->add(
+                                       $rawKey,
+                                       $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
+                                       self::$CHECK_KEY_TTL
+                               );
+                               $time = (float)$now;
+                       }
+
+                       $times[$key] = $time;
+               }
+
+               return $times;
+       }
+
+       /**
+        * Purge a "check" key from all datacenters, invalidating keys that use it
+        *
+        * This should only be called when the underlying data (being cached)
+        * changes in a significant way, and it is impractical to call delete()
+        * on all keys that should be changed. When get() is called on those
+        * keys, the relevant "check" keys must be supplied for this to work.
+        *
+        * The "check" key essentially represents a last-modified time of an entity.
+        * When the key is touched, the timestamp will be updated to the current time.
+        * Keys using the "check" key via get(), getMulti(), or getWithSetCallback() will
+        * be invalidated. This approach is useful if many keys depend on a single entity.
+        *
+        * The timestamp of the "check" key is treated as being HOLDOFF_TTL seconds in the
+        * future by get*() methods in order to avoid race conditions where keys are updated
+        * with stale values (e.g. from a lagged replica DB). A high TTL is set on the "check"
+        * key, making it possible to know the timestamp of the last change to the corresponding
+        * entities in most cases. This might use more cache space than resetCheckKey().
+        *
+        * When a few important keys get a large number of hits, a high cache time is usually
+        * desired as well as "lockTSE" logic. The resetCheckKey() method is less appropriate
+        * in such cases since the "time since expiry" cannot be inferred, causing any get()
+        * after the reset to treat the key as being "hot", resulting in more stale value usage.
+        *
+        * Note that "check" keys won't collide with other regular keys.
+        *
+        * @see WANObjectCache::get()
+        * @see WANObjectCache::getWithSetCallback()
+        * @see WANObjectCache::resetCheckKey()
+        *
+        * @param string $key Cache key
+        * @param int $holdoff HOLDOFF_TTL or HOLDOFF_TTL_NONE constant
+        * @return bool True if the item was purged or not found, false on failure
+        */
+       final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
+               // Publish the purge to all datacenters
+               $ok = $this->relayPurge( self::$TIME_KEY_PREFIX . $key, self::$CHECK_KEY_TTL, $holdoff );
+
+               $kClass = $this->determineKeyClassForStats( $key );
+               $this->stats->increment( "wanobjectcache.$kClass.ck_touch." . ( $ok ? 'ok' : 'error' ) );
+
+               return $ok;
+       }
+
+       /**
+        * Delete a "check" key from all datacenters, invalidating keys that use it
+        *
+        * This is similar to touchCheckKey() in that keys using it via get(), getMulti(),
+        * or getWithSetCallback() will be invalidated. The differences are:
+        *   - a) The "check" key will be deleted from all caches and lazily
+        *        re-initialized when accessed (rather than set everywhere)
+        *   - b) Thus, dependent keys will be known to be stale, but not
+        *        for how long (they are treated as "just" purged), which
+        *        effects any lockTSE logic in getWithSetCallback()
+        *   - c) Since "check" keys are initialized only on the server the key hashes
+        *        to, any temporary ejection of that server will cause the value to be
+        *        seen as purged as a new server will initialize the "check" key.
+        *
+        * The advantage here is that the "check" keys, which have high TTLs, will only
+        * be created when a get*() method actually uses that key. This is better when
+        * a large number of "check" keys are invalided in a short period of time.
+        *
+        * Note that "check" keys won't collide with other regular keys.
+        *
+        * @see WANObjectCache::get()
+        * @see WANObjectCache::getWithSetCallback()
+        * @see WANObjectCache::touchCheckKey()
+        *
+        * @param string $key Cache key
+        * @return bool True if the item was purged or not found, false on failure
+        */
+       final public function resetCheckKey( $key ) {
+               // Publish the purge to all datacenters
+               $ok = $this->relayDelete( self::$TIME_KEY_PREFIX . $key );
+
+               $kClass = $this->determineKeyClassForStats( $key );
+               $this->stats->increment( "wanobjectcache.$kClass.ck_reset." . ( $ok ? 'ok' : 'error' ) );
+
+               return $ok;
+       }
+
+       /**
+        * Method to fetch/regenerate cache keys
+        *
+        * On cache miss, the key will be set to the callback result via set()
+        * (unless the callback returns false) and that result will be returned.
+        * The arguments supplied to the callback are:
+        *   - $oldValue : current cache value or false if not present
+        *   - &$ttl : a reference to the TTL which can be altered
+        *   - &$setOpts : a reference to options for set() which can be altered
+        *   - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present (since 1.28)
+        *
+        * It is strongly recommended to set the 'lag' and 'since' fields to avoid race conditions
+        * that can cause stale values to get stuck at keys. Usually, callbacks ignore the current
+        * value, but it can be used to maintain "most recent X" values that come from time or
+        * sequence based source data, provided that the "as of" id/time is tracked. Note that
+        * preemptive regeneration and $checkKeys can result in a non-false current value.
+        *
+        * Usage of $checkKeys is similar to get() and getMulti(). However, rather than the caller
+        * having to inspect a "current time left" variable (e.g. $curTTL, $curTTLs), a cache
+        * regeneration will automatically be triggered using the callback.
+        *
+        * The $ttl argument and "hotTTR" option (in $opts) use time-dependant randomization
+        * to avoid stampedes. Keys that are slow to regenerate and either heavily used
+        * or subject to explicit (unpredictable) purges, may need additional mechanisms.
+        * The simplest way to avoid stampedes for such keys is to use 'lockTSE' (in $opts).
+        * If explicit purges are needed, also:
+        *   - a) Pass $key into $checkKeys
+        *   - b) Use touchCheckKey( $key ) instead of delete( $key )
+        *
+        * Example usage (typical key):
+        * @code
+        *     $catInfo = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-attributes', $catId ),
+        *         // Time-to-live (in seconds)
+        *         $cache::TTL_MINUTE,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return $dbr->selectRow( ... );
+        *        }
+        *     );
+        * @endcode
+        *
+        * Example usage (key that is expensive and hot):
+        * @code
+        *     $catConfig = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'site-cat-config' ),
+        *         // Time-to-live (in seconds)
+        *         $cache::TTL_DAY,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return CatConfig::newFromRow( $dbr->selectRow( ... ) );
+        *         },
+        *         [
+        *             // Calling touchCheckKey() on this key invalidates the cache
+        *             'checkKeys' => [ $cache->makeKey( 'site-cat-config' ) ],
+        *             // Try to only let one datacenter thread manage cache updates at a time
+        *             'lockTSE' => 30,
+        *             // Avoid querying cache servers multiple times in a web request
+        *             'pcTTL' => $cache::TTL_PROC_LONG
+        *         ]
+        *     );
+        * @endcode
+        *
+        * Example usage (key with dynamic dependencies):
+        * @code
+        *     $catState = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-state', $cat->getId() ),
+        *         // Time-to-live (seconds)
+        *         $cache::TTL_HOUR,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             // Determine new value from the DB
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return CatState::newFromResults( $dbr->select( ... ) );
+        *         },
+        *         [
+        *              // The "check" keys that represent things the value depends on;
+        *              // Calling touchCheckKey() on any of them invalidates the cache
+        *             'checkKeys' => [
+        *                 $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ),
+        *                 $cache->makeKey( 'people-present', $cat->getHouseId() ),
+        *                 $cache->makeKey( 'cat-laws', $cat->getCityId() ),
+        *             ]
+        *         ]
+        *     );
+        * @endcode
+        *
+        * Example usage (key that is expensive with too many DB dependencies for "check keys"):
+        * @code
+        *     $catToys = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-toys', $catId ),
+        *         // Time-to-live (seconds)
+        *         $cache::TTL_HOUR,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             // Determine new value from the DB
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return CatToys::newFromResults( $dbr->select( ... ) );
+        *         },
+        *         [
+        *              // Get the highest timestamp of any of the cat's toys
+        *             'touchedCallback' => function ( $value ) use ( $catId ) {
+        *                 $dbr = wfGetDB( DB_REPLICA );
+        *                 $ts = $dbr->selectField( 'cat_toys', 'MAX(ct_touched)', ... );
+        *
+        *                 return wfTimestampOrNull( TS_UNIX, $ts );
+        *             },
+        *             // Avoid DB queries for repeated access
+        *             'pcTTL' => $cache::TTL_PROC_SHORT
+        *         ]
+        *     );
+        * @endcode
+        *
+        * Example usage (hot key holding most recent 100 events):
+        * @code
+        *     $lastCatActions = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-last-actions', 100 ),
+        *         // Time-to-live (in seconds)
+        *         10,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             // Start off with the last cached list
+        *             $list = $oldValue ?: [];
+        *             // Fetch the last 100 relevant rows in descending order;
+        *             // only fetch rows newer than $list[0] to reduce scanning
+        *             $rows = iterator_to_array( $dbr->select( ... ) );
+        *             // Merge them and get the new "last 100" rows
+        *             return array_slice( array_merge( $new, $list ), 0, 100 );
+        *        },
+        *        [
+        *             // Try to only let one datacenter thread manage cache updates at a time
+        *             'lockTSE' => 30,
+        *             // Use a magic value when no cache value is ready rather than stampeding
+        *             'busyValue' => 'computing'
+        *        ]
+        *     );
+        * @endcode
+        *
+        * Example usage (key holding an LRU subkey:value map; this can avoid flooding cache with
+        * keys for an unlimited set of (constraint,situation) pairs, thereby avoiding elevated
+        * cache evictions and wasted memory):
+        * @code
+        *     $catSituationTolerabilityCache = $this->cache->getWithSetCallback(
+        *         // Group by constraint ID/hash, cat family ID/hash, or something else useful
+        *         $this->cache->makeKey( 'cat-situation-tolerability-checks', $groupKey ),
+        *         WANObjectCache::TTL_DAY, // rarely used groups should fade away
+        *         // The $scenarioKey format is $constraintId:<ID/hash of $situation>
+        *         function ( $cacheMap ) use ( $scenarioKey, $constraintId, $situation ) {
+        *             $lruCache = MapCacheLRU::newFromArray( $cacheMap ?: [], self::CACHE_SIZE );
+        *             $result = $lruCache->get( $scenarioKey ); // triggers LRU bump if present
+        *             if ( $result === null || $this->isScenarioResultExpired( $result ) ) {
+        *                 $result = $this->checkScenarioTolerability( $constraintId, $situation );
+        *                 $lruCache->set( $scenarioKey, $result, 3 / 8 );
+        *             }
+        *             // Save the new LRU cache map and reset the map's TTL
+        *             return $lruCache->toArray();
+        *         },
+        *         [
+        *             // Once map is > 1 sec old, consider refreshing
+        *             'ageNew' => 1,
+        *             // Update within 5 seconds after "ageNew" given a 1hz cache check rate
+        *             'hotTTR' => 5,
+        *             // Avoid querying cache servers multiple times in a request; this also means
+        *             // that a request can only alter the value of any given constraint key once
+        *             'pcTTL' => WANObjectCache::TTL_PROC_LONG
+        *         ]
+        *     );
+        *     $tolerability = isset( $catSituationTolerabilityCache[$scenarioKey] )
+        *         ? $catSituationTolerabilityCache[$scenarioKey]
+        *         : $this->checkScenarioTolerability( $constraintId, $situation );
+        * @endcode
+        *
+        * @see WANObjectCache::get()
+        * @see WANObjectCache::set()
+        *
+        * @param string $key Cache key made from makeKey() or makeGlobalKey()
+        * @param int $ttl Seconds to live for key updates. Special values are:
+        *   - WANObjectCache::TTL_INDEFINITE: Cache forever (subject to LRU-style evictions)
+        *   - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted)
+        * @param callable $callback Value generation function
+        * @param array $opts Options map:
+        *   - checkKeys: List of "check" keys. The key at $key will be seen as stale when either
+        *      touchCheckKey() or resetCheckKey() is called on any of the keys in this list. This
+        *      is useful if thousands or millions of keys depend on the same entity. The entity can
+        *      simply have its "check" key updated whenever the entity is modified.
+        *      Default: [].
+        *   - graceTTL: If the key is invalidated (by "checkKeys"/"touchedCallback") less than this
+        *      many seconds ago, consider reusing the stale value. The odds of a refresh becomes
+        *      more likely over time, becoming certain once the grace period is reached. This can
+        *      reduce traffic spikes when millions of keys are compared to the same "check" key and
+        *      touchCheckKey() or resetCheckKey() is called on that "check" key. This option is not
+        *      useful for avoiding traffic spikes in the case of the key simply expiring on account
+        *      of its TTL (use "lowTTL" instead).
+        *      Default: WANObjectCache::GRACE_TTL_NONE.
+        *   - lockTSE: If the key is tombstoned or invalidated (by "checkKeys"/"touchedCallback")
+        *      less than this many seconds ago, try to have a single thread handle cache regeneration
+        *      at any given time. Other threads will use stale values if possible. If, on miss,
+        *      the time since expiration is low, the assumption is that the key is hot and that a
+        *      stampede is worth avoiding. Note that if the key falls out of cache then concurrent
+        *      threads will all run the callback on cache miss until the value is saved in cache.
+        *      The only stampede protection in that case is from duplicate cache sets when the
+        *      callback takes longer than WANObjectCache::SET_DELAY_HIGH_MS milliseconds; consider
+        *      using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
+        *      set, the higher the worst-case staleness of returned values can be. Also note that
+        *      this option does not by itself handle the case of the key simply expiring on account
+        *      of its TTL, so make sure that "lowTTL" is not disabled when using this option. Avoid
+        *      combining this option with delete() as it can always cause a stampede due to their
+        *      being no stale value available until after a thread completes the callback.
+        *      Use WANObjectCache::TSE_NONE to disable this logic.
+        *      Default: WANObjectCache::TSE_NONE.
+        *   - busyValue: Specify a placeholder value to use when no value exists and another thread
+        *      is currently regenerating it. This assures that cache stampedes cannot happen if the
+        *      value falls out of cache. This also mitigates stampedes when value regeneration
+        *      becomes very slow (greater than $ttl/"lowTTL"). If this is a closure, then it will
+        *      be invoked to get the placeholder when needed.
+        *      Default: null.
+        *   - pcTTL: Process cache the value in this PHP instance for this many seconds. This avoids
+        *      network I/O when a key is read several times. This will not cache when the callback
+        *      returns false, however. Note that any purges will not be seen while process cached;
+        *      since the callback should use replica DBs and they may be lagged or have snapshot
+        *      isolation anyway, this should not typically matter.
+        *      Default: WANObjectCache::TTL_UNCACHEABLE.
+        *   - pcGroup: Process cache group to use instead of the primary one. If set, this must be
+        *      of the format ALPHANUMERIC_NAME:MAX_KEY_SIZE, e.g. "mydata:10". Use this for storing
+        *      large values, small yet numerous values, or some values with a high cost of eviction.
+        *      It is generally preferable to use a class constant when setting this value.
+        *      This has no effect unless pcTTL is used.
+        *      Default: WANObjectCache::PC_PRIMARY.
+        *   - version: Integer version number. This lets callers make breaking changes to the format
+        *      of cached values without causing problems for sites that use non-instantaneous code
+        *      deployments. Old and new code will recognize incompatible versions and purges from
+        *      both old and new code will been seen by each other. When this method encounters an
+        *      incompatibly versioned value at the provided key, a "variant key" will be used for
+        *      reading from and saving to cache. The variant key is specific to the key and version
+        *      number provided to this method. If the variant key value is older than that of the
+        *      provided key, or the provided key is non-existant, then the variant key will be seen
+        *      as non-existant. Therefore, delete() calls invalidate the provided key's variant keys.
+        *      The "checkKeys" and "touchedCallback" options still apply to variant keys as usual.
+        *      Avoid storing class objects, as this reduces compatibility (due to serialization).
+        *      Default: null.
+        *   - minAsOf: Reject values if they were generated before this UNIX timestamp.
+        *      This is useful if the source of a key is suspected of having possibly changed
+        *      recently, and the caller wants any such changes to be reflected.
+        *      Default: WANObjectCache::MIN_TIMESTAMP_NONE.
+        *   - hotTTR: Expected time-till-refresh (TTR) in seconds for keys that average ~1 hit per
+        *      second (e.g. 1Hz). Keys with a hit rate higher than 1Hz will refresh sooner than this
+        *      TTR and vise versa. Such refreshes won't happen until keys are "ageNew" seconds old.
+        *      This uses randomization to avoid triggering cache stampedes. The TTR is useful at
+        *      reducing the impact of missed cache purges, since the effect of a heavily referenced
+        *      key being stale is worse than that of a rarely referenced key. Unlike simply lowering
+        *      $ttl, seldomly used keys are largely unaffected by this option, which makes it
+        *      possible to have a high hit rate for the "long-tail" of less-used keys.
+        *      Default: WANObjectCache::HOT_TTR.
+        *   - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less
+        *      than this. It becomes more likely over time, becoming certain once the key is expired.
+        *      This helps avoid cache stampedes that might be triggered due to the key expiring.
+        *      Default: WANObjectCache::LOW_TTL.
+        *   - ageNew: Consider popularity refreshes only once a key reaches this age in seconds.
+        *      Default: WANObjectCache::AGE_NEW.
+        *   - staleTTL: Seconds to keep the key around if it is stale. This means that on cache
+        *      miss the callback may get $oldValue/$oldAsOf values for keys that have already been
+        *      expired for this specified time. This is useful if adaptiveTTL() is used on the old
+        *      value's as-of time when it is verified as still being correct.
+        *      Default: WANObjectCache::STALE_TTL_NONE
+        *   - touchedCallback: A callback that takes the current value and returns a UNIX timestamp
+        *      indicating the last time a dynamic dependency changed. Null can be returned if there
+        *      are no relevant dependency changes to check. This can be used to check against things
+        *      like last-modified times of files or DB timestamp fields. This should generally not be
+        *      used for small and easily queried values in a DB if the callback itself ends up doing
+        *      a similarly expensive DB query to check a timestamp. Usages of this option makes the
+        *      most sense for values that are moderately to highly expensive to regenerate and easy
+        *      to query for dependency timestamps. The use of "pcTTL" reduces timestamp queries.
+        *      Default: null.
+        * @return mixed Value found or written to the key
+        * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf
+        * @note Options added in 1.31: staleTTL, graceTTL
+        * @note Options added in 1.33: touchedCallback
+        * @note Callable type hints are not used to avoid class-autoloading
+        */
+       final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
+               $version = $opts['version'] ?? null;
+               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
+               $pCache = ( $pcTTL >= 0 )
+                       ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
+                       : null;
+
+               // Use the process cache if requested as long as no outer cache callback is running.
+               // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
+               // process cached values are more lagged than persistent ones as they are not purged.
+               if ( $pCache && $this->callbackDepth == 0 ) {
+                       $cached = $pCache->get( $this->getProcessCacheKey( $key, $version ), INF, false );
+                       if ( $cached !== false ) {
+                               return $cached;
+                       }
+               }
+
+               $res = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
+               list( $value, $valueVersion, $curAsOf ) = $res;
+               if ( $valueVersion !== $version ) {
+                       // Current value has a different version; use the variant key for this version.
+                       // Regenerate the variant value if it is not newer than the main value at $key
+                       // so that purges to the main key propagate to the variant value.
+                       list( $value ) = $this->fetchOrRegenerate(
+                               $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
+                               $ttl,
+                               $callback,
+                               [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts
+                       );
+               }
+
+               // Update the process cache if enabled
+               if ( $pCache && $value !== false ) {
+                       $pCache->set( $this->getProcessCacheKey( $key, $version ), $value );
+               }
+
+               return $value;
+       }
+
+       /**
+        * Do the actual I/O for getWithSetCallback() when needed
+        *
+        * @see WANObjectCache::getWithSetCallback()
+        *
+        * @param string $key
+        * @param int $ttl
+        * @param callable $callback
+        * @param array $opts Options map for getWithSetCallback()
+        * @return array Ordered list of the following:
+        *   - Cached or regenerated value
+        *   - Cached or regenerated value version number or null if not versioned
+        *   - Timestamp of the current cached value at the key or null if there is no value
+        * @note Callable type hints are not used to avoid class-autoloading
+        */
+       private function fetchOrRegenerate( $key, $ttl, $callback, array $opts ) {
+               $checkKeys = $opts['checkKeys'] ?? [];
+               $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
+               $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
+               $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
+               $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
+               $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
+               $touchedCb = $opts['touchedCallback'] ?? null;
+               $initialTime = $this->getCurrentTime();
+
+               $kClass = $this->determineKeyClassForStats( $key );
+
+               // Get the current key value and its metadata
+               $curTTL = self::PASS_BY_REF;
+               $curInfo = self::PASS_BY_REF; /** @var array $curInfo */
+               $curValue = $this->get( $key, $curTTL, $checkKeys, $curInfo );
+               // Apply any $touchedCb invalidation timestamp to get the "last purge timestamp"
+               list( $curTTL, $LPT ) = $this->resolveCTL( $curValue, $curTTL, $curInfo, $touchedCb );
+               // Use the cached value if it exists and is not due for synchronous regeneration
+               if (
+                       $this->isValid( $curValue, $curInfo['asOf'], $minAsOf ) &&
+                       $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
+               ) {
+                       $preemptiveRefresh = (
+                               $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
+                               $this->worthRefreshPopular( $curInfo['asOf'], $ageNew, $hotTTR, $initialTime )
+                       );
+                       if ( !$preemptiveRefresh ) {
+                               $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
+
+                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
+                       } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) ) {
+                               $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
+
+                               return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
+                       }
+               }
+
+               // Determine if there is stale or volatile cached value that is still usable
+               $isKeyTombstoned = ( $curInfo['tombAsOf'] !== null );
+               if ( $isKeyTombstoned ) {
+                       // Key is write-holed; use the (volatile) interim key as an alternative
+                       list( $possValue, $possInfo ) = $this->getInterimValue( $key, $minAsOf );
+                       // Update the "last purge time" since the $touchedCb timestamp depends on $value
+                       $LPT = $this->resolveTouched( $possValue, $LPT, $touchedCb );
+               } else {
+                       $possValue = $curValue;
+                       $possInfo = $curInfo;
+               }
+
+               // Avoid overhead from callback runs, regeneration locks, and cache sets during
+               // hold-off periods for the key by reusing very recently generated cached values
+               if (
+                       $this->isValid( $possValue, $possInfo['asOf'], $minAsOf, $LPT ) &&
+                       $this->isVolatileValueAgeNegligible( $initialTime - $possInfo['asOf'] )
+               ) {
+                       $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
+
+                       return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
+               }
+
+               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
+               $busyValue = $opts['busyValue'] ?? null;
+               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
+               $version = $opts['version'] ?? null;
+
+               // Determine whether one thread per datacenter should handle regeneration at a time
+               $useRegenerationLock =
+                       // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
+                       // deduce the key hotness because |$curTTL| will always keep increasing until the
+                       // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
+                       // is not set, constant regeneration of a key for the tombstone lifetime might be
+                       // very expensive. Assume tombstoned keys are possibly hot in order to reduce
+                       // the risk of high regeneration load after the delete() method is called.
+                       $isKeyTombstoned ||
+                       // Assume a key is hot if requested soon ($lockTSE seconds) after invalidation.
+                       // This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
+                       ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ) ||
+                       // Assume a key is hot if there is no value and a busy fallback is given.
+                       // This avoids stampedes on eviction or preemptive regeneration taking too long.
+                       ( $busyValue !== null && $possValue === false );
+
+               // If a regeneration lock is required, threads that do not get the lock will try to use
+               // the stale value, the interim value, or the $busyValue placeholder, in that order. If
+               // none of those are set then all threads will bypass the lock and regenerate the value.
+               $hasLock = $useRegenerationLock && $this->claimStampedeLock( $key );
+               if ( $useRegenerationLock && !$hasLock ) {
+                       if ( $this->isValid( $possValue, $possInfo['asOf'], $minAsOf ) ) {
+                               $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
+
+                               return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
+                       } elseif ( $busyValue !== null ) {
+                               $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
+                               $this->stats->increment( "wanobjectcache.$kClass.$miss.busy" );
+
+                               return [ $this->resolveBusyValue( $busyValue ), $version, $curInfo['asOf'] ];
+                       }
+               }
+
+               // Generate the new value given any prior value with a matching version
+               $setOpts = [];
+               $preCallbackTime = $this->getCurrentTime();
+               ++$this->callbackDepth;
+               try {
+                       $value = $callback(
+                               ( $curInfo['version'] === $version ) ? $curValue : false,
+                               $ttl,
+                               $setOpts,
+                               ( $curInfo['version'] === $version ) ? $curInfo['asOf'] : null
+                       );
+               } finally {
+                       --$this->callbackDepth;
+               }
+               $postCallbackTime = $this->getCurrentTime();
+
+               // How long it took to fetch, validate, and generate the value
+               $elapsed = max( $postCallbackTime - $initialTime, 0.0 );
+
+               // Attempt to save the newly generated value if applicable
+               if (
+                       // Callback yielded a cacheable value
+                       ( $value !== false && $ttl >= 0 ) &&
+                       // Current thread was not raced out of a regeneration lock or key is tombstoned
+                       ( !$useRegenerationLock || $hasLock || $isKeyTombstoned ) &&
+                       // Key does not appear to be undergoing a set() stampede
+                       $this->checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock )
+               ) {
+                       // How long it took to generate the value
+                       $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
+                       $this->stats->timing( "wanobjectcache.$kClass.regen_walltime", 1e3 * $walltime );
+                       // If the key is write-holed then use the (volatile) interim key as an alternative
+                       if ( $isKeyTombstoned ) {
+                               $this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
+                       } else {
+                               $finalSetOpts = [
+                                       'since' => $setOpts['since'] ?? $preCallbackTime,
+                                       'version' => $version,
+                                       'staleTTL' => $staleTTL,
+                                       'lockTSE' => $lockTSE, // informs lag vs performance trade-offs
+                                       'creating' => ( $curValue === false ), // optimization
+                                       'walltime' => $walltime
+                               ] + $setOpts;
+                               $this->set( $key, $value, $ttl, $finalSetOpts );
+                       }
+               }
+
+               $this->yieldStampedeLock( $key, $hasLock );
+
+               $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
+               $this->stats->increment( "wanobjectcache.$kClass.$miss.compute" );
+
+               return [ $value, $version, $curInfo['asOf'] ];
+       }
+
+       /**
+        * @param string $key
+        * @return bool Success
+        */
+       private function claimStampedeLock( $key ) {
+               // Note that locking is not bypassed due to I/O errors; this avoids stampedes
+               return $this->cache->add( self::$MUTEX_KEY_PREFIX . $key, 1, self::$LOCK_TTL );
+       }
+
+       /**
+        * @param string $key
+        * @param bool $hasLock
+        */
+       private function yieldStampedeLock( $key, $hasLock ) {
+               if ( $hasLock ) {
+                       // The backend might be a mcrouter proxy set to broadcast DELETE to *all* the local
+                       // datacenter cache servers via OperationSelectorRoute (for increased consistency).
+                       // Since that would be excessive for these locks, use TOUCH to expire the key.
+                       $this->cache->changeTTL( self::$MUTEX_KEY_PREFIX . $key, $this->getCurrentTime() - 60 );
+               }
+       }
+
+       /**
+        * @param float $age Age of volatile/interim key in seconds
+        * @return bool Whether the age of a volatile value is negligible
+        */
+       private function isVolatileValueAgeNegligible( $age ) {
+               return ( $age < mt_rand( self::$RECENT_SET_LOW_MS, self::$RECENT_SET_HIGH_MS ) / 1e3 );
+       }
+
+       /**
+        * @param string $key
+        * @param string $kClass
+        * @param float $elapsed Seconds spent regenerating the value
+        * @param float $lockTSE
+        * @param bool $hasLock
+        * @return bool Whether it is OK to proceed with a key set operation
+        */
+       private function checkAndSetCooloff( $key, $kClass, $elapsed, $lockTSE, $hasLock ) {
+               $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $elapsed );
+
+               // If $lockTSE is set, the lock was bypassed because there was no stale/interim value,
+               // and $elapsed indicates that regeration is slow, then there is a risk of set()
+               // stampedes with large blobs. With a typical scale-out infrastructure, CPU and query
+               // load from $callback invocations is distributed among appservers and replica DBs,
+               // but cache operations for a given key route to a single cache server (e.g. striped
+               // consistent hashing).
+               if ( $lockTSE < 0 || $hasLock ) {
+                       return true; // either not a priori hot or thread has the lock
+               } elseif ( $elapsed <= self::$SET_DELAY_HIGH_MS * 1e3 ) {
+                       return true; // not enough time for threads to pile up
+               }
+
+               $this->cache->clearLastError();
+               if (
+                       !$this->cache->add( self::$COOLOFF_KEY_PREFIX . $key, 1, self::$COOLOFF_TTL ) &&
+                       // Don't treat failures due to I/O errors as the key being in cooloff
+                       $this->cache->getLastError() === BagOStuff::ERR_NONE
+               ) {
+                       $this->stats->increment( "wanobjectcache.$kClass.cooloff_bounce" );
+
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * @param mixed $value
+        * @param float|null $curTTL
+        * @param array $curInfo
+        * @param callable|null $touchedCallback
+        * @return array (current time left or null, UNIX timestamp of last purge or null)
+        * @note Callable type hints are not used to avoid class-autoloading
+        */
+       private function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
+               if ( $touchedCallback === null || $value === false ) {
+                       return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'] ) ];
+               }
+
+               $touched = $touchedCallback( $value );
+               if ( $touched !== null && $touched >= $curInfo['asOf'] ) {
+                       $curTTL = min( $curTTL, self::$TINY_NEGATIVE, $curInfo['asOf'] - $touched );
+               }
+
+               return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'], $touched ) ];
+       }
+
+       /**
+        * @param mixed $value
+        * @param float|null $lastPurge
+        * @param callable|null $touchedCallback
+        * @return float|null UNIX timestamp of last purge or null
+        * @note Callable type hints are not used to avoid class-autoloading
+        */
+       private function resolveTouched( $value, $lastPurge, $touchedCallback ) {
+               return ( $touchedCallback === null || $value === false )
+                       ? $lastPurge // nothing to derive the "touched timestamp" from
+                       : max( $touchedCallback( $value ), $lastPurge );
+       }
+
+       /**
+        * @param string $key
+        * @param float $minAsOf Minimum acceptable "as of" timestamp
+        * @return array (cached value or false, cache key metadata map)
+        */
+       private function getInterimValue( $key, $minAsOf ) {
+               $now = $this->getCurrentTime();
+
+               if ( $this->useInterimHoldOffCaching ) {
+                       $wrapped = $this->cache->get( self::$INTERIM_KEY_PREFIX . $key );
+
+                       list( $value, $keyInfo ) = $this->unwrap( $wrapped, $now );
+                       if ( $this->isValid( $value, $keyInfo['asOf'], $minAsOf ) ) {
+                               return [ $value, $keyInfo ];
+                       }
+               }
+
+               return $this->unwrap( false, $now );
+       }
+
+       /**
+        * @param string $key
+        * @param mixed $value
+        * @param int $ttl
+        * @param int|null $version Value version number
+        * @param float $walltime How long it took to generate the value in seconds
+        */
+       private function setInterimValue( $key, $value, $ttl, $version, $walltime ) {
+               $ttl = max( self::$INTERIM_KEY_TTL, (int)$ttl );
+
+               $wrapped = $this->wrap( $value, $ttl, $version, $this->getCurrentTime(), $walltime );
+               $this->cache->merge(
+                       self::$INTERIM_KEY_PREFIX . $key,
+                       function () use ( $wrapped ) {
+                               return $wrapped;
+                       },
+                       $ttl,
+                       1
+               );
+       }
+
+       /**
+        * @param mixed $busyValue
+        * @return mixed
+        */
+       private function resolveBusyValue( $busyValue ) {
+               return ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
+       }
+
+       /**
+        * Method to fetch multiple cache keys at once with regeneration
+        *
+        * This works the same as getWithSetCallback() except:
+        *   - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys()
+        *   - b) The $callback argument expects a callback taking the following arguments:
+        *         - $id: ID of an entity to query
+        *         - $oldValue : the prior cache value or false if none was present
+        *         - &$ttl : a reference to the new value TTL in seconds
+        *         - &$setOpts : a reference to options for set() which can be altered
+        *         - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present
+        *        Aside from the additional $id argument, the other arguments function the same
+        *        way they do in getWithSetCallback().
+        *   - c) The return value is a map of (cache key => value) in the order of $keyedIds
+        *
+        * @see WANObjectCache::getWithSetCallback()
+        * @see WANObjectCache::getMultiWithUnionSetCallback()
+        *
+        * Example usage:
+        * @code
+        *     $rows = $cache->getMultiWithSetCallback(
+        *         // Map of cache keys to entity IDs
+        *         $cache->makeMultiKeys(
+        *             $this->fileVersionIds(),
+        *             function ( $id ) use ( $cache ) {
+        *                 return $cache->makeKey( 'file-version', $id );
+        *             }
+        *         ),
+        *         // Time-to-live (in seconds)
+        *         $cache::TTL_DAY,
+        *         // Function that derives the new key value
+        *         function ( $id, $oldValue, &$ttl, array &$setOpts ) {
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             // Load the row for this file
+        *             $queryInfo = File::getQueryInfo();
+        *             $row = $dbr->selectRow(
+        *                 $queryInfo['tables'],
+        *                 $queryInfo['fields'],
+        *                 [ 'id' => $id ],
+        *                 __METHOD__,
+        *                 [],
+        *                 $queryInfo['joins']
+        *             );
+        *
+        *             return $row ? (array)$row : false;
+        *         },
+        *         [
+        *             // Process cache for 30 seconds
+        *             'pcTTL' => 30,
+        *             // Use a dedicated 500 item cache (initialized on-the-fly)
+        *             'pcGroup' => 'file-versions:500'
+        *         ]
+        *     );
+        *     $files = array_map( [ __CLASS__, 'newFromRow' ], $rows );
+        * @endcode
+        *
+        * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys()
+        * @param int $ttl Seconds to live for key updates
+        * @param callable $callback Callback the yields entity regeneration callbacks
+        * @param array $opts Options map
+        * @return mixed[] Map of (cache key => value) in the same order as $keyedIds
+        * @since 1.28
+        */
+       final public function getMultiWithSetCallback(
+               ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
+       ) {
+               // Load required keys into process cache in one go
+               $this->warmupCache = $this->getRawKeysForWarmup(
+                       $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
+                       $opts['checkKeys'] ?? []
+               );
+               $this->warmupKeyMisses = 0;
+
+               // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
+               $id = null; // current entity ID
+               $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) {
+                       return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf );
+               };
+
+               $values = [];
+               foreach ( $keyedIds as $key => $id ) { // preserve order
+                       $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
+               }
+
+               $this->warmupCache = [];
+
+               return $values;
+       }
+
+       /**
+        * Method to fetch/regenerate multiple cache keys at once
+        *
+        * This works the same as getWithSetCallback() except:
+        *   - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys()
+        *   - b) The $callback argument expects a callback returning a map of (ID => new value)
+        *        for all entity IDs in $ids and it takes the following arguments:
+        *          - $ids: a list of entity IDs that require cache regeneration
+        *          - &$ttls: a reference to the (entity ID => new TTL) map
+        *          - &$setOpts: a reference to options for set() which can be altered
+        *   - c) The return value is a map of (cache key => value) in the order of $keyedIds
+        *   - d) The "lockTSE" and "busyValue" options are ignored
+        *
+        * @see WANObjectCache::getWithSetCallback()
+        * @see WANObjectCache::getMultiWithSetCallback()
+        *
+        * Example usage:
+        * @code
+        *     $rows = $cache->getMultiWithUnionSetCallback(
+        *         // Map of cache keys to entity IDs
+        *         $cache->makeMultiKeys(
+        *             $this->fileVersionIds(),
+        *             function ( $id ) use ( $cache ) {
+        *                 return $cache->makeKey( 'file-version', $id );
+        *             }
+        *         ),
+        *         // Time-to-live (in seconds)
+        *         $cache::TTL_DAY,
+        *         // Function that derives the new key value
+        *         function ( array $ids, array &$ttls, array &$setOpts ) {
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             // Load the rows for these files
+        *             $rows = [];
+        *             $queryInfo = File::getQueryInfo();
+        *             $res = $dbr->select(
+        *                 $queryInfo['tables'],
+        *                 $queryInfo['fields'],
+        *                 [ 'id' => $ids ],
+        *                 __METHOD__,
+        *                 [],
+        *                 $queryInfo['joins']
+        *             );
+        *             foreach ( $res as $row ) {
+        *                 $rows[$row->id] = $row;
+        *                 $mtime = wfTimestamp( TS_UNIX, $row->timestamp );
+        *                 $ttls[$row->id] = $this->adaptiveTTL( $mtime, $ttls[$row->id] );
+        *             }
+        *
+        *             return $rows;
+        *         },
+        *         ]
+        *     );
+        *     $files = array_map( [ __CLASS__, 'newFromRow' ], $rows );
+        * @endcode
+        *
+        * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys()
+        * @param int $ttl Seconds to live for key updates
+        * @param callable $callback Callback the yields entity regeneration callbacks
+        * @param array $opts Options map
+        * @return mixed[] Map of (cache key => value) in the same order as $keyedIds
+        * @since 1.30
+        */
+       final public function getMultiWithUnionSetCallback(
+               ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
+       ) {
+               $checkKeys = $opts['checkKeys'] ?? [];
+               unset( $opts['lockTSE'] ); // incompatible
+               unset( $opts['busyValue'] ); // incompatible
+
+               // Load required keys into process cache in one go
+               $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
+               $this->warmupCache = $this->getRawKeysForWarmup( $keysByIdGet, $checkKeys );
+               $this->warmupKeyMisses = 0;
+
+               // IDs of entities known to be in need of regeneration
+               $idsRegen = [];
+
+               // Find out which keys are missing/deleted/stale
+               $curTTLs = [];
+               $asOfs = [];
+               $curByKey = $this->getMulti( $keysByIdGet, $curTTLs, $checkKeys, $asOfs );
+               foreach ( $keysByIdGet as $id => $key ) {
+                       if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
+                               $idsRegen[] = $id;
+                       }
+               }
+
+               // Run the callback to populate the regeneration value map for all required IDs
+               $newSetOpts = [];
+               $newTTLsById = array_fill_keys( $idsRegen, $ttl );
+               $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
+
+               // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
+               $id = null; // current entity ID
+               $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
+                       use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts )
+               {
+                       if ( array_key_exists( $id, $newValsById ) ) {
+                               // Value was already regerated as expected, so use the value in $newValsById
+                               $newValue = $newValsById[$id];
+                               $ttl = $newTTLsById[$id];
+                               $setOpts = $newSetOpts;
+                       } else {
+                               // Pre-emptive/popularity refresh and version mismatch cases are not detected
+                               // above and thus $newValsById has no entry. Run $callback on this single entity.
+                               $ttls = [ $id => $ttl ];
+                               $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
+                               $ttl = $ttls[$id];
+                       }
+
+                       return $newValue;
+               };
+
+               // Run the cache-aside logic using warmupCache instead of persistent cache queries
+               $values = [];
+               foreach ( $keyedIds as $key => $id ) { // preserve order
+                       $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
+               }
+
+               $this->warmupCache = [];
+
+               return $values;
+       }
+
+       /**
+        * Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp
+        *
+        * This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
+        * broadcasting in mcrouter setups and also avoids races with new tombstones.
+        *
+        * @param string $key Cache key
+        * @param int $purgeTimestamp UNIX timestamp of purge
+        * @param bool &$isStale Whether the key is stale
+        * @return bool Success
+        * @since 1.28
+        */
+       final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
+               $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
+               $wrapped = $this->cache->get( self::$VALUE_KEY_PREFIX . $key );
+               if ( is_array( $wrapped ) && $wrapped[self::$FLD_TIME] < $minAsOf ) {
+                       $isStale = true;
+                       $this->logger->warning( "Reaping stale value key '$key'." );
+                       $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
+                       $ok = $this->cache->changeTTL( self::$VALUE_KEY_PREFIX . $key, $ttlReap );
+                       if ( !$ok ) {
+                               $this->logger->error( "Could not complete reap of key '$key'." );
+                       }
+
+                       return $ok;
+               }
+
+               $isStale = false;
+
+               return true;
+       }
+
+       /**
+        * Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp
+        *
+        * @param string $key Cache key
+        * @param int $purgeTimestamp UNIX timestamp of purge
+        * @param bool &$isStale Whether the key is stale
+        * @return bool Success
+        * @since 1.28
+        */
+       final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
+               $purge = $this->parsePurgeValue( $this->cache->get( self::$TIME_KEY_PREFIX . $key ) );
+               if ( $purge && $purge[self::$PURGE_TIME] < $purgeTimestamp ) {
+                       $isStale = true;
+                       $this->logger->warning( "Reaping stale check key '$key'." );
+                       $ok = $this->cache->changeTTL( self::$TIME_KEY_PREFIX . $key, self::TTL_SECOND );
+                       if ( !$ok ) {
+                               $this->logger->error( "Could not complete reap of check key '$key'." );
+                       }
+
+                       return $ok;
+               }
+
+               $isStale = false;
+
+               return false;
+       }
+
+       /**
+        * @see BagOStuff::makeKey()
+        * @param string $class Key class
+        * @param string ...$components Key components (starting with a key collection name)
+        * @return string Colon-delimited list of $keyspace followed by escaped components
+        * @since 1.27
+        */
+       public function makeKey( $class, ...$components ) {
+               return $this->cache->makeKey( ...func_get_args() );
+       }
+
+       /**
+        * @see BagOStuff::makeGlobalKey()
+        * @param string $class Key class
+        * @param string ...$components Key components (starting with a key collection name)
+        * @return string Colon-delimited list of $keyspace followed by escaped components
+        * @since 1.27
+        */
+       public function makeGlobalKey( $class, ...$components ) {
+               return $this->cache->makeGlobalKey( ...func_get_args() );
+       }
+
+       /**
+        * Hash a possibly long string into a suitable component for makeKey()/makeGlobalKey()
+        *
+        * @param string $component A raw component used in building a cache key
+        * @return string 64 character HMAC using a stable secret for public collision resistance
+        * @since 1.34
+        */
+       public function hash256( $component ) {
+               return hash_hmac( 'sha256', $component, $this->secret );
+       }
+
+       /**
+        * Get an iterator of (cache key => entity ID) for a list of entity IDs
+        *
+        * The callback takes an ID string and returns a key via makeKey()/makeGlobalKey().
+        * There should be no network nor filesystem I/O used in the callback. The entity
+        * ID/key mapping must be 1:1 or an exception will be thrown. If hashing is needed,
+        * then use the hash256() method.
+        *
+        * Example usage for the default keyspace:
+        * @code
+        *     $keyedIds = $cache->makeMultiKeys(
+        *         $modules,
+        *         function ( $module ) use ( $cache ) {
+        *             return $cache->makeKey( 'module-info', $module );
+        *         }
+        *     );
+        * @endcode
+        *
+        * Example usage for mixed default and global keyspace:
+        * @code
+        *     $keyedIds = $cache->makeMultiKeys(
+        *         $filters,
+        *         function ( $filter ) use ( $cache ) {
+        *             return ( strpos( $filter, 'central:' ) === 0 )
+        *                 ? $cache->makeGlobalKey( 'regex-filter', $filter )
+        *                 : $cache->makeKey( 'regex-filter', $filter )
+        *         }
+        *     );
+        * @endcode
+        *
+        * Example usage with hashing:
+        * @code
+        *     $keyedIds = $cache->makeMultiKeys(
+        *         $urls,
+        *         function ( $url ) use ( $cache ) {
+        *             return $cache->makeKey( 'url-info', $cache->hash256( $url ) );
+        *         }
+        *     );
+        * @endcode
+        *
+        * @see WANObjectCache::makeKey()
+        * @see WANObjectCache::makeGlobalKey()
+        * @see WANObjectCache::hash256()
+        *
+        * @param string[]|int[] $ids List of entity IDs
+        * @param callable $keyCallback Function returning makeKey()/makeGlobalKey() on the input ID
+        * @return ArrayIterator Iterator of (cache key => ID); order of $ids is preserved
+        * @throws UnexpectedValueException
+        * @since 1.28
+        */
+       final public function makeMultiKeys( array $ids, $keyCallback ) {
+               $idByKey = [];
+               foreach ( $ids as $id ) {
+                       // Discourage triggering of automatic makeKey() hashing in some backends
+                       if ( strlen( $id ) > 64 ) {
+                               $this->logger->warning( __METHOD__ . ": long ID '$id'; use hash256()" );
+                       }
+                       $key = $keyCallback( $id, $this );
+                       // Edge case: ignore key collisions due to duplicate $ids like "42" and 42
+                       if ( !isset( $idByKey[$key] ) ) {
+                               $idByKey[$key] = $id;
+                       } elseif ( (string)$id !== (string)$idByKey[$key] ) {
+                               throw new UnexpectedValueException(
+                                       "Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
+                               );
+                       }
+               }
+
+               return new ArrayIterator( $idByKey );
+       }
+
+       /**
+        * Get an (ID => value) map from (i) a non-unique list of entity IDs, and (ii) the list
+        * of corresponding entity values by first appearance of each ID in the entity ID list
+        *
+        * For use with getMultiWithSetCallback() and getMultiWithUnionSetCallback().
+        *
+        * *Only* use this method if the entity ID/key mapping is trivially 1:1 without exception.
+        * Key generation method must utitilize the *full* entity ID in the key (not a hash of it).
+        *
+        * Example usage:
+        * @code
+        *     $poems = $cache->getMultiWithSetCallback(
+        *         $cache->makeMultiKeys(
+        *             $uuids,
+        *             function ( $uuid ) use ( $cache ) {
+        *                 return $cache->makeKey( 'poem', $uuid );
+        *             }
+        *         ),
+        *         $cache::TTL_DAY,
+        *         function ( $uuid ) use ( $url ) {
+        *             return $this->http->run( [ 'method' => 'GET', 'url' => "$url/$uuid" ] );
+        *         }
+        *     );
+        *     $poemsByUUID = $cache->multiRemap( $uuids, $poems );
+        * @endcode
+        *
+        * @see WANObjectCache::makeMultiKeys()
+        * @see WANObjectCache::getMultiWithSetCallback()
+        * @see WANObjectCache::getMultiWithUnionSetCallback()
+        *
+        * @param string[]|int[] $ids Entity ID list makeMultiKeys()
+        * @param mixed[] $res Result of getMultiWithSetCallback()/getMultiWithUnionSetCallback()
+        * @return mixed[] Map of (ID => value); order of $ids is preserved
+        * @since 1.34
+        */
+       final public function multiRemap( array $ids, array $res ) {
+               if ( count( $ids ) !== count( $res ) ) {
+                       // If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
+                       // ArrayIterator will have less entries due to "first appearance" de-duplication
+                       $ids = array_keys( array_flip( $ids ) );
+                       if ( count( $ids ) !== count( $res ) ) {
+                               throw new UnexpectedValueException( "Multi-key result does not match ID list" );
+                       }
+               }
+
+               return array_combine( $ids, $res );
+       }
+
+       /**
+        * Get the "last error" registered; clearLastError() should be called manually
+        * @return int ERR_* class constant for the "last error" registry
+        */
+       final public function getLastError() {
+               $code = $this->cache->getLastError();
+               switch ( $code ) {
+                       case BagOStuff::ERR_NONE:
+                               return self::ERR_NONE;
+                       case BagOStuff::ERR_NO_RESPONSE:
+                               return self::ERR_NO_RESPONSE;
+                       case BagOStuff::ERR_UNREACHABLE:
+                               return self::ERR_UNREACHABLE;
+                       default:
+                               return self::ERR_UNEXPECTED;
+               }
+       }
+
+       /**
+        * Clear the "last error" registry
+        */
+       final public function clearLastError() {
+               $this->cache->clearLastError();
+       }
+
+       /**
+        * Clear the in-process caches; useful for testing
+        *
+        * @since 1.27
+        */
+       public function clearProcessCache() {
+               $this->processCaches = [];
+       }
+
+       /**
+        * Enable or disable the use of brief caching for tombstoned keys
+        *
+        * When a key is purged via delete(), there normally is a period where caching
+        * is hold-off limited to an extremely short time. This method will disable that
+        * caching, forcing the callback to run for any of:
+        *   - WANObjectCache::getWithSetCallback()
+        *   - WANObjectCache::getMultiWithSetCallback()
+        *   - WANObjectCache::getMultiWithUnionSetCallback()
+        *
+        * This is useful when both:
+        *   - a) the database used by the callback is known to be up-to-date enough
+        *        for some particular purpose (e.g. replica DB has applied transaction X)
+        *   - b) the caller needs to exploit that fact, and therefore needs to avoid the
+        *        use of inherently volatile and possibly stale interim keys
+        *
+        * @see WANObjectCache::delete()
+        * @param bool $enabled Whether to enable interim caching
+        * @since 1.31
+        */
+       final public function useInterimHoldOffCaching( $enabled ) {
+               $this->useInterimHoldOffCaching = $enabled;
+       }
+
+       /**
+        * @param int $flag ATTR_* class constant
+        * @return int QOS_* class constant
+        * @since 1.28
+        */
+       public function getQoS( $flag ) {
+               return $this->cache->getQoS( $flag );
+       }
+
+       /**
+        * Get a TTL that is higher for objects that have not changed recently
+        *
+        * This is useful for keys that get explicit purges and DB or purge relay
+        * lag is a potential concern (especially how it interacts with CDN cache)
+        *
+        * Example usage:
+        * @code
+        *     // Last-modified time of page
+        *     $mtime = wfTimestamp( TS_UNIX, $page->getTimestamp() );
+        *     // Get adjusted TTL. If $mtime is 3600 seconds ago and $minTTL/$factor left at
+        *     // defaults, then $ttl is 3600 * .2 = 720. If $minTTL was greater than 720, then
+        *     // $ttl would be $minTTL. If $maxTTL was smaller than 720, $ttl would be $maxTTL.
+        *     $ttl = $cache->adaptiveTTL( $mtime, $cache::TTL_DAY );
+        * @endcode
+        *
+        * Another use case is when there are no applicable "last modified" fields in the DB,
+        * and there are too many dependencies for explicit purges to be viable, and the rate of
+        * change to relevant content is unstable, and it is highly valued to have the cached value
+        * be as up-to-date as possible.
+        *
+        * Example usage:
+        * @code
+        *     $query = "<some complex query>";
+        *     $idListFromComplexQuery = $cache->getWithSetCallback(
+        *         $cache->makeKey( 'complex-graph-query', $hashOfQuery ),
+        *         GraphQueryClass::STARTING_TTL,
+        *         function ( $oldValue, &$ttl, array &$setOpts, $oldAsOf ) use ( $query, $cache ) {
+        *             $gdb = $this->getReplicaGraphDbConnection();
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += GraphDatabase::getCacheSetOptions( $gdb );
+        *
+        *             $newList = iterator_to_array( $gdb->query( $query ) );
+        *             sort( $newList, SORT_NUMERIC ); // normalize
+        *
+        *             $minTTL = GraphQueryClass::MIN_TTL;
+        *             $maxTTL = GraphQueryClass::MAX_TTL;
+        *             if ( $oldValue !== false ) {
+        *                 // Note that $oldAsOf is the last time this callback ran
+        *                 $ttl = ( $newList === $oldValue )
+        *                     // No change: cache for 150% of the age of $oldValue
+        *                     ? $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, 1.5 )
+        *                     // Changed: cache for 50% of the age of $oldValue
+        *                     : $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, .5 );
+        *             }
+        *
+        *             return $newList;
+        *        },
+        *        [
+        *             // Keep stale values around for doing comparisons for TTL calculations.
+        *             // High values improve long-tail keys hit-rates, though might waste space.
+        *             'staleTTL' => GraphQueryClass::GRACE_TTL
+        *        ]
+        *     );
+        * @endcode
+        *
+        * @param int|float $mtime UNIX timestamp
+        * @param int $maxTTL Maximum TTL (seconds)
+        * @param int $minTTL Minimum TTL (seconds); Default: 30
+        * @param float $factor Value in the range (0,1); Default: .2
+        * @return int Adaptive TTL
+        * @since 1.28
+        */
+       public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
+               if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
+                       $mtime = (int)$mtime; // handle fractional seconds and string integers
+               }
+
+               if ( !is_int( $mtime ) || $mtime <= 0 ) {
+                       return $minTTL; // no last-modified time provided
+               }
+
+               $age = $this->getCurrentTime() - $mtime;
+
+               return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
+       }
+
+       /**
+        * @return int Number of warmup key cache misses last round
+        * @since 1.30
+        */
+       final public function getWarmupKeyMisses() {
+               return $this->warmupKeyMisses;
+       }
+
+       /**
+        * Do the actual async bus purge of a key
+        *
+        * This must set the key to "PURGED:<UNIX timestamp>:<holdoff>"
+        *
+        * @param string $key Cache key
+        * @param int $ttl Seconds to keep the tombstone around
+        * @param int $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key
+        * @return bool Success
+        */
+       protected function relayPurge( $key, $ttl, $holdoff ) {
+               if ( $this->mcrouterAware ) {
+                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
+                       $ok = $this->cache->set(
+                               "/*/{$this->cluster}/{$key}",
+                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
+                               $ttl
+                       );
+               } else {
+                       // This handles the mcrouter and the single-DC case
+                       $ok = $this->cache->set(
+                               $key,
+                               $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_TTL_NONE ),
+                               $ttl
+                       );
+               }
+
+               return $ok;
+       }
+
+       /**
+        * Do the actual async bus delete of a key
+        *
+        * @param string $key Cache key
+        * @return bool Success
+        */
+       protected function relayDelete( $key ) {
+               if ( $this->mcrouterAware ) {
+                       // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+                       // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
+                       $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
+               } else {
+                       // Some other proxy handles broadcasting or there is only one datacenter
+                       $ok = $this->cache->delete( $key );
+               }
+
+               return $ok;
+       }
+
+       /**
+        * @param string $key
+        * @param int $ttl Seconds to live
+        * @param callable $callback
+        * @param array $opts
+        * @return bool Success
+        * @note Callable type hints are not used to avoid class-autoloading
+        */
+       private function scheduleAsyncRefresh( $key, $ttl, $callback, $opts ) {
+               if ( !$this->asyncHandler ) {
+                       return false;
+               }
+               // Update the cache value later, such during post-send of an HTTP request
+               $func = $this->asyncHandler;
+               $func( function () use ( $key, $ttl, $callback, $opts ) {
+                       $opts['minAsOf'] = INF; // force a refresh
+                       $this->fetchOrRegenerate( $key, $ttl, $callback, $opts );
+               } );
+
+               return true;
+       }
+
+       /**
+        * Check if a key is fresh or in the grace window and thus due for randomized reuse
+        *
+        * If $curTTL > 0 (e.g. not expired) this returns true. Otherwise, the chance of returning
+        * true decrease steadily from 100% to 0% as the |$curTTL| moves from 0 to $graceTTL seconds.
+        * This handles widely varying levels of cache access traffic.
+        *
+        * If $curTTL <= -$graceTTL (e.g. already expired), then this returns false.
+        *
+        * @param float $curTTL Approximate TTL left on the key if present
+        * @param int $graceTTL Consider using stale values if $curTTL is greater than this
+        * @return bool
+        */
+       private function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
+               if ( $curTTL > 0 ) {
+                       return true;
+               } elseif ( $graceTTL <= 0 ) {
+                       return false;
+               }
+
+               $ageStale = abs( $curTTL ); // seconds of staleness
+               $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
+               if ( $curGTTL <= 0 ) {
+                       return false; //  already out of grace period
+               }
+
+               // Chance of using a stale value is the complement of the chance of refreshing it
+               return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
+       }
+
+       /**
+        * Check if a key is nearing expiration and thus due for randomized regeneration
+        *
+        * This returns false if $curTTL >= $lowTTL. Otherwise, the chance of returning true
+        * increases steadily from 0% to 100% as the $curTTL moves from $lowTTL to 0 seconds.
+        * This handles widely varying levels of cache access traffic.
+        *
+        * If $curTTL <= 0 (e.g. already expired), then this returns false.
+        *
+        * @param float $curTTL Approximate TTL left on the key if present
+        * @param float $lowTTL Consider a refresh when $curTTL is less than this
+        * @return bool
+        */
+       protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
+               if ( $lowTTL <= 0 ) {
+                       return false;
+               } elseif ( $curTTL >= $lowTTL ) {
+                       return false;
+               } elseif ( $curTTL <= 0 ) {
+                       return false;
+               }
+
+               $chance = ( 1 - $curTTL / $lowTTL );
+
+               return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
+       }
+
+       /**
+        * Check if a key is due for randomized regeneration due to its popularity
+        *
+        * This is used so that popular keys can preemptively refresh themselves for higher
+        * consistency (especially in the case of purge loss/delay). Unpopular keys can remain
+        * in cache with their high nominal TTL. This means popular keys keep good consistency,
+        * whether the data changes frequently or not, and long-tail keys get to stay in cache
+        * and get hits too. Similar to worthRefreshExpiring(), randomization is used.
+        *
+        * @param float $asOf UNIX timestamp of the value
+        * @param int $ageNew Age of key when this might recommend refreshing (seconds)
+        * @param int $timeTillRefresh Age of key when it should be refreshed if popular (seconds)
+        * @param float $now The current UNIX timestamp
+        * @return bool
+        */
+       protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
+               if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
+                       return false;
+               }
+
+               $age = $now - $asOf;
+               $timeOld = $age - $ageNew;
+               if ( $timeOld <= 0 ) {
+                       return false;
+               }
+
+               $popularHitsPerSec = 1;
+               // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
+               // Note that the "expected # of refreshes" for the ramp-up time range is half
+               // of what it would be if P(refresh) was at its full value during that time range.
+               $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::$RAMPUP_TTL / 2, 1 );
+               // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
+               // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
+               // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
+               $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
+
+               // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
+               $chance *= ( $timeOld <= self::$RAMPUP_TTL ) ? $timeOld / self::$RAMPUP_TTL : 1;
+
+               return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
+       }
+
+       /**
+        * Check if $value is not false, versioned (if needed), and not older than $minTime (if set)
+        *
+        * @param array|bool $value
+        * @param float $asOf The time $value was generated
+        * @param float $minAsOf Minimum acceptable "as of" timestamp
+        * @param float|null $purgeTime The last time the value was invalidated
+        * @return bool
+        */
+       protected function isValid( $value, $asOf, $minAsOf, $purgeTime = null ) {
+               // Avoid reading any key not generated after the latest delete() or touch
+               $safeMinAsOf = max( $minAsOf, $purgeTime + self::$TINY_POSTIVE );
+
+               if ( $value === false ) {
+                       return false;
+               } elseif ( $safeMinAsOf > 0 && $asOf < $minAsOf ) {
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * @param mixed $value
+        * @param int $ttl Seconds to live or zero for "indefinite"
+        * @param int|null $version Value version number or null if not versioned
+        * @param float $now Unix Current timestamp just before calling set()
+        * @param float $walltime How long it took to generate the value in seconds
+        * @return array
+        */
+       private function wrap( $value, $ttl, $version, $now, $walltime ) {
+               // Returns keys in ascending integer order for PHP7 array packing:
+               // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
+               $wrapped = [
+                       self::$FLD_FORMAT_VERSION => self::$VERSION,
+                       self::$FLD_VALUE => $value,
+                       self::$FLD_TTL => $ttl,
+                       self::$FLD_TIME => $now
+               ];
+               if ( $version !== null ) {
+                       $wrapped[self::$FLD_VALUE_VERSION] = $version;
+               }
+               if ( $walltime >= self::$GENERATION_SLOW_SEC ) {
+                       $wrapped[self::$FLD_GENERATION_TIME] = $walltime;
+               }
+
+               return $wrapped;
+       }
+
+       /**
+        * @param array|string|bool $wrapped The entry at a cache key
+        * @param float $now Unix Current timestamp (preferrably pre-query)
+        * @return array (value or false if absent/tombstoned/malformed, value metadata map).
+        * The cache key metadata includes the following metadata:
+        *   - asOf: UNIX timestamp of the value or null if there is no value
+        *   - curTTL: remaining time-to-live (negative if tombstoned) or null if there is no value
+        *   - version: value version number or null if the if there is no value
+        *   - tombAsOf: UNIX timestamp of the tombstone or null if there is no tombstone
+        */
+       private function unwrap( $wrapped, $now ) {
+               $value = false;
+               $info = [ 'asOf' => null, 'curTTL' => null, 'version' => null, 'tombAsOf' => null ];
+
+               if ( is_array( $wrapped ) ) {
+                       // Entry expected to be a cached value; validate it
+                       if (
+                               ( $wrapped[self::$FLD_FORMAT_VERSION] ?? null ) === self::$VERSION &&
+                               $wrapped[self::$FLD_TIME] >= $this->epoch
+                       ) {
+                               if ( $wrapped[self::$FLD_TTL] > 0 ) {
+                                       // Get the approximate time left on the key
+                                       $age = $now - $wrapped[self::$FLD_TIME];
+                                       $curTTL = max( $wrapped[self::$FLD_TTL] - $age, 0.0 );
+                               } else {
+                                       // Key had no TTL, so the time left is unbounded
+                                       $curTTL = INF;
+                               }
+                               $value = $wrapped[self::$FLD_VALUE];
+                               $info['version'] = $wrapped[self::$FLD_VALUE_VERSION] ?? null;
+                               $info['asOf'] = $wrapped[self::$FLD_TIME];
+                               $info['curTTL'] = $curTTL;
+                       }
+               } else {
+                       // Entry expected to be a tombstone; parse it
+                       $purge = $this->parsePurgeValue( $wrapped );
+                       if ( $purge !== false ) {
+                               // Tombstoned keys should always have a negative current $ttl
+                               $info['curTTL'] = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
+                               $info['tombAsOf'] = $purge[self::$PURGE_TIME];
+                       }
+               }
+
+               return [ $value, $info ];
+       }
+
+       /**
+        * @param string[] $keys
+        * @param string $prefix
+        * @return string[] Prefix keys; the order of $keys is preserved
+        */
+       protected static function prefixCacheKeys( array $keys, $prefix ) {
+               $res = [];
+               foreach ( $keys as $key ) {
+                       $res[] = $prefix . $key;
+               }
+
+               return $res;
+       }
+
+       /**
+        * @param string $key String of the format <scope>:<class>[:<class or variable>]...
+        * @return string A collection name to describe this class of key
+        */
+       private function determineKeyClassForStats( $key ) {
+               $parts = explode( ':', $key, 3 );
+
+               return $parts[1] ?? $parts[0]; // sanity
+       }
+
+       /**
+        * @param string|array|bool $value Possible string of the form "PURGED:<timestamp>:<holdoff>"
+        * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer),
+        *  or false if value isn't a valid purge value
+        */
+       private function parsePurgeValue( $value ) {
+               if ( !is_string( $value ) ) {
+                       return false;
+               }
+
+               $segments = explode( ':', $value, 3 );
+               if (
+                       !isset( $segments[0] ) ||
+                       !isset( $segments[1] ) ||
+                       "{$segments[0]}:" !== self::$PURGE_VAL_PREFIX
+               ) {
+                       return false;
+               }
+
+               if ( !isset( $segments[2] ) ) {
+                       // Back-compat with old purge values without holdoff
+                       $segments[2] = self::HOLDOFF_TTL;
+               }
+
+               if ( $segments[1] < $this->epoch ) {
+                       // Values this old are ignored
+                       return false;
+               }
+
+               return [
+                       self::$PURGE_TIME => (float)$segments[1],
+                       self::$PURGE_HOLDOFF => (int)$segments[2],
+               ];
+       }
+
+       /**
+        * @param float $timestamp
+        * @param int $holdoff In seconds
+        * @return string Wrapped purge value
+        */
+       private function makePurgeValue( $timestamp, $holdoff ) {
+               return self::$PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
+       }
+
+       /**
+        * @param string $group
+        * @return MapCacheLRU
+        */
+       private function getProcessCache( $group ) {
+               if ( !isset( $this->processCaches[$group] ) ) {
+                       list( , $size ) = explode( ':', $group );
+                       $this->processCaches[$group] = new MapCacheLRU( (int)$size );
+               }
+
+               return $this->processCaches[$group];
+       }
+
+       /**
+        * @param string $key
+        * @param int $version
+        * @return string
+        */
+       private function getProcessCacheKey( $key, $version ) {
+               return $key . ' ' . (int)$version;
+       }
+
+       /**
+        * @param ArrayIterator $keys
+        * @param array $opts
+        * @return string[] Map of (ID => cache key)
+        */
+       private function getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
+               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
+
+               $keysMissing = [];
+               if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
+                       $version = $opts['version'] ?? null;
+                       $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
+                       foreach ( $keys as $key => $id ) {
+                               if ( !$pCache->has( $this->getProcessCacheKey( $key, $version ), $pcTTL ) ) {
+                                       $keysMissing[$id] = $key;
+                               }
+                       }
+               }
+
+               return $keysMissing;
+       }
+
+       /**
+        * @param string[] $keys
+        * @param string[]|string[][] $checkKeys
+        * @return string[] List of cache keys
+        */
+       private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
+               if ( !$keys ) {
+                       return [];
+               }
+
+               $keysWarmUp = [];
+               // Get all the value keys to fetch...
+               foreach ( $keys as $key ) {
+                       $keysWarmUp[] = self::$VALUE_KEY_PREFIX . $key;
+               }
+               // Get all the check keys to fetch...
+               foreach ( $checkKeys as $i => $checkKeyOrKeys ) {
+                       if ( is_int( $i ) ) {
+                               // Single check key that applies to all value keys
+                               $keysWarmUp[] = self::$TIME_KEY_PREFIX . $checkKeyOrKeys;
+                       } else {
+                               // List of check keys that apply to value key $i
+                               $keysWarmUp = array_merge(
+                                       $keysWarmUp,
+                                       self::prefixCacheKeys( $checkKeyOrKeys, self::$TIME_KEY_PREFIX )
+                               );
+                       }
+               }
+
+               $warmupCache = $this->cache->getMulti( $keysWarmUp );
+               $warmupCache += array_fill_keys( $keysWarmUp, false );
+
+               return $warmupCache;
+       }
+
+       /**
+        * @return float UNIX timestamp
+        * @codeCoverageIgnore
+        */
+       protected function getCurrentTime() {
+               if ( $this->wallClockOverride ) {
+                       return $this->wallClockOverride;
+               }
+
+               $clockTime = (float)time(); // call this first
+               // microtime() uses an initial gettimeofday() call added to usage clocks.
+               // This can severely drift from time() and the microtime() value of other threads
+               // due to undercounting of the amount of time elapsed. Instead of seeing the current
+               // time as being in the past, use the value of time(). This avoids setting cache values
+               // that will immediately be seen as expired and possibly cause stampedes.
+               return max( microtime( true ), $clockTime );
+       }
+
+       /**
+        * @param float|null &$time Mock UNIX timestamp for testing
+        * @codeCoverageIgnore
+        */
+       public function setMockTime( &$time ) {
+               $this->wallClockOverride =& $time;
+               $this->cache->setMockTime( $time );
+       }
+}
diff --git a/includes/libs/objectcache/wancache/WANObjectCacheReaper.php b/includes/libs/objectcache/wancache/WANObjectCacheReaper.php
new file mode 100644 (file)
index 0000000..fb8a754
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\ScopedCallback;
+
+/**
+ * Class for scanning through chronological, log-structured data or change logs
+ * and locally purging cache keys related to entities that appear in this data.
+ *
+ * This is useful for repairing cache when purges are missed by using a reliable
+ * stream, such as Kafka or a replicated MySQL table. Purge loss between datacenters
+ * is expected to be more common than within them.
+ *
+ * @since 1.28
+ */
+class WANObjectCacheReaper implements LoggerAwareInterface {
+       /** @var WANObjectCache */
+       protected $cache;
+       /** @var BagOStuff */
+       protected $store;
+       /** @var callable */
+       protected $logChunkCallback;
+       /** @var callable */
+       protected $keyListCallback;
+       /** @var LoggerInterface */
+       protected $logger;
+
+       /** @var string */
+       protected $channel;
+       /** @var int */
+       protected $initialStartWindow;
+
+       /**
+        * @param WANObjectCache $cache Cache to reap bad keys from
+        * @param BagOStuff $store Cache to store positions use for locking
+        * @param callable $logCallback Callback taking arguments:
+        *          - The starting position as a UNIX timestamp
+        *          - The starting unique ID used for breaking timestamp collisions or null
+        *          - The ending position as a UNIX timestamp
+        *          - The maximum number of results to return
+        *        It returns a list of maps of (key: cache key, pos: UNIX timestamp, id: unique ID)
+        *        for each key affected, with the corrosponding event timestamp/ID information.
+        *        The events should be in ascending order, by (timestamp,id).
+        * @param callable $keyCallback Callback taking arguments:
+        *          - The WANObjectCache instance
+        *          - An object from the event log
+        *        It should return a list of WAN cache keys.
+        *        The callback must fully duck-type test the object, since can be any model class.
+        * @param array $params Additional options:
+        *          - channel: the name of the update event stream.
+        *          - initialStartWindow: seconds back in time to start if the position is lost.
+        *            Default: 1 hour.
+        *          - logger: an SPL monolog instance [optional]
+        */
+       public function __construct(
+               WANObjectCache $cache,
+               BagOStuff $store,
+               callable $logCallback,
+               callable $keyCallback,
+               array $params
+       ) {
+               $this->cache = $cache;
+               $this->store = $store;
+
+               $this->logChunkCallback = $logCallback;
+               $this->keyListCallback = $keyCallback;
+               if ( isset( $params['channel'] ) ) {
+                       $this->channel = $params['channel'];
+               } else {
+                       throw new UnexpectedValueException( "No channel specified." );
+               }
+
+               $this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
+               $this->logger = $params['logger'] ?? new NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * Check and reap stale keys based on a chunk of events
+        *
+        * @param int $n Number of events
+        * @return int Number of keys checked
+        */
+       final public function invoke( $n = 100 ) {
+               $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
+               $scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
+               if ( !$scopeLock ) {
+                       return 0;
+               }
+
+               $now = time();
+               $status = $this->store->get( $posKey );
+               if ( !$status ) {
+                       $status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
+               }
+
+               // Get events for entities who's keys tombstones/hold-off should have expired by now
+               $events = call_user_func_array(
+                       $this->logChunkCallback,
+                       [ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
+               );
+
+               $event = null;
+               $keyEvents = [];
+               foreach ( $events as $event ) {
+                       $keys = call_user_func_array(
+                               $this->keyListCallback,
+                               [ $this->cache, $event['item'] ]
+                       );
+                       foreach ( $keys as $key ) {
+                               unset( $keyEvents[$key] ); // use only the latest per key
+                               $keyEvents[$key] = [
+                                       'pos' => $event['pos'],
+                                       'id' => $event['id']
+                               ];
+                       }
+               }
+
+               $purgeCount = 0;
+               $lastOkEvent = null;
+               foreach ( $keyEvents as $key => $keyEvent ) {
+                       if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
+                               break;
+                       }
+                       ++$purgeCount;
+                       $lastOkEvent = $event;
+               }
+
+               if ( $lastOkEvent ) {
+                       $ok = $this->store->merge(
+                               $posKey,
+                               function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
+                                       if ( !$curValue ) {
+                                               // Use new position
+                                       } else {
+                                               $curCoord = [ $curValue['pos'], $curValue['id'] ];
+                                               $newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
+                                               if ( $newCoord < $curCoord ) {
+                                                       // Keep prior position instead of rolling it back
+                                                       return $curValue;
+                                               }
+                                       }
+
+                                       return [
+                                               'pos' => $lastOkEvent['pos'],
+                                               'id' => $lastOkEvent['id'],
+                                               'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
+                                       ];
+                               },
+                               IExpiringStore::TTL_INDEFINITE
+                       );
+
+                       $pos = $lastOkEvent['pos'];
+                       $id = $lastOkEvent['id'];
+                       if ( $ok ) {
+                               $this->logger->info( "Updated cache reap position ($pos, $id)." );
+                       } else {
+                               $this->logger->error( "Could not update cache reap position ($pos, $id)." );
+                       }
+               }
+
+               ScopedCallback::consume( $scopeLock );
+
+               return $purgeCount;
+       }
+
+       /**
+        * @return array|bool Returns (pos, id) map or false if not set
+        */
+       public function getState() {
+               $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
+
+               return $this->store->get( $posKey );
+       }
+}
index 8b65397..e82c735 100644 (file)
@@ -1337,7 +1337,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                // Avoid the overhead of logging calls unless debug mode is enabled
                if ( $this->getFlag( self::DBO_DEBUG ) ) {
                        $this->queryLogger->debug(
-                               "{method} [{runtime}s]: $sql",
+                               "{method} [{runtime}s] {db_host}: $sql",
                                [
                                        'method' => $fname,
                                        'db_host' => $this->getServer(),
index 812064a..4b6afe7 100644 (file)
@@ -31,9 +31,12 @@ use InvalidArgumentException;
  * @since 1.28
  */
 interface ILBFactory {
+       /** @var int Don't save DB positions at all */
        const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
-       const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
-       const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
+       /** @var int Save DB positions, but don't wait on remote DCs */
+       const SHUTDOWN_CHRONPROT_ASYNC = 1;
+       /** @var int Save DB positions, waiting on all DCs */
+       const SHUTDOWN_CHRONPROT_SYNC = 2;
 
        /**
         * Construct a manager of ILoadBalancer objects
@@ -140,6 +143,8 @@ interface ILBFactory {
        /**
         * Get cached (tracked) load balancers for all main database clusters
         *
+        * The default cluster name is ILoadBalancer::CLUSTER_MAIN_DEFAULT
+        *
         * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
         * @since 1.29
         */
@@ -154,7 +159,8 @@ interface ILBFactory {
        public function getAllExternalLBs();
 
        /**
-        * Execute a function for each tracked load balancer
+        * Execute a function for each currently tracked (instantiated) load balancer
+        *
         * The callback is called with the load balancer as the first parameter,
         * and $params passed as the subsequent parameters.
         *
@@ -164,7 +170,8 @@ interface ILBFactory {
        public function forEachLB( $callback, array $params = [] );
 
        /**
-        * Prepare all tracked load balancers for shutdown
+        * Prepare all currently tracked (instantiated) load balancers for shutdown
+        *
         * @param int $mode One of the class SHUTDOWN_* constants
         * @param callable|null $workCallback Work to mask ChronologyProtector writes
         * @param int|null &$cpIndex Position key write counter for ChronologyProtector
index 46d8c06..98607ce 100644 (file)
@@ -177,11 +177,10 @@ class LoadBalancer implements ILoadBalancer {
                                $server['replica'] = true;
                        }
                        $this->servers[$i] = $server;
-                       $serverGroupLoads = [ self::GROUP_GENERIC => $server['load'] ];
-                       $serverGroupLoads += ( $server['groupLoads'] ?? [] );
-                       foreach ( $serverGroupLoads as $group => $ratio ) {
+                       foreach ( ( $server['groupLoads'] ?? [] ) as $group => $ratio ) {
                                $this->groupLoads[$group][$i] = $ratio;
                        }
+                       $this->groupLoads[self::GROUP_GENERIC][$i] = $server['load'];
                }
 
                $localDomain = isset( $params['localDomain'] )
index cbba80a..1922f7d 100644 (file)
@@ -1326,9 +1326,18 @@ class ParserOutput extends CacheTime {
        }
 
        public function __sleep() {
-               return array_diff(
-                       array_keys( get_object_vars( $this ) ),
-                       [ 'mParseStartTime' ]
+               return array_filter( array_keys( get_object_vars( $this ) ),
+                       function ( $field ) {
+                               if ( $field === 'mParseStartTime' ) {
+                                       return false;
+                               } elseif ( strpos( $field, "\0" ) !== false ) {
+                                       // Unserializing unknown private fields in HHVM causes
+                                       // member variables with nulls in their names (T229366)
+                                       return false;
+                               } else {
+                                       return true;
+                               }
+                       }
                );
        }
 
index fca06c9..d0f6729 100644 (file)
@@ -224,7 +224,7 @@ class MessageBlobStore implements LoggerAwareInterface {
                        }
                }
 
-               $json = FormatJson::encode( (object)$messages );
+               $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
                // @codeCoverageIgnoreStart
                if ( $json === false ) {
                        $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
index 2336a37..9892b15 100644 (file)
@@ -36,65 +36,41 @@ use Wikimedia\WrappedString;
  *    https://www.mediawiki.org/wiki/ResourceLoader
  */
 class ResourceLoader implements LoggerAwareInterface {
-       /** @var int */
-       const CACHE_VERSION = 8;
+       /** @var Config $config */
+       protected $config;
+       /** @var MessageBlobStore */
+       protected $blobStore;
 
-       /** @var bool */
-       protected static $debugMode = null;
+       /** @var LoggerInterface */
+       private $logger;
 
-       /**
-        * Module name/ResourceLoaderModule object pairs
-        * @var array
-        */
+       /** @var ResourceLoaderModule[] Map of (module name => ResourceLoaderModule) */
        protected $modules = [];
-
-       /**
-        * Associative array mapping module name to info associative array
-        * @var array
-        */
+       /** @var array[] Map of (module name => associative info array) */
        protected $moduleInfos = [];
-
-       /** @var Config $config */
-       protected $config;
-
        /**
-        * List of module names that contain QUnit test suites
-        * @var string[]
+        * Associative array mapping framework ids to a list of names of test suite modules
+        * like [ 'qunit' => [ 'mediawiki.tests.qunit.suites', 'ext.foo.tests', ... ], ... ]
+        * @var array
         */
+       protected $testModuleNames = [];
+       /** @var string[] List of module names that contain QUnit test suites */
        protected $testSuiteModuleNames = [];
 
-       /**
-        * E.g. [ 'source-id' => 'http://.../load.php' ]
-        * @var array
-        */
+       /** @var array Map of (source => path); E.g. [ 'source-id' => 'http://.../load.php' ] */
        protected $sources = [];
-
-       /**
-        * Errors accumulated during current respond() call.
-        * @var array
-        */
+       /** @var array Errors accumulated during current respond() call */
        protected $errors = [];
-
-       /**
-        * List of extra HTTP response headers provided by loaded modules.
-        *
-        * Populated by makeModuleResponse().
-        *
-        * @var array
-        */
+       /** @var string[] Extra HTTP response headers from modules loaded in makeModuleResponse() */
        protected $extraHeaders = [];
 
-       /**
-        * @var MessageBlobStore
-        */
-       protected $blobStore;
+       /** @var bool */
+       protected static $debugMode = null;
 
-       /**
-        * @var LoggerInterface
-        */
-       private $logger;
+       /** @var int */
+       const CACHE_VERSION = 8;
 
-       /** @var string JavaScript / CSS pragma to disable minification. **/
+       /** @var string JavaScript / CSS pragma to disable minification. * */
        const FILTER_NOMIN = '/*@nomin*/';
 
        /**
@@ -725,6 +701,8 @@ class ResourceLoader implements LoggerAwareInterface {
                        if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
                                return; // output handled
                        }
+               } else {
+                       $fileCache = null;
                }
 
                // Generate a response
@@ -739,15 +717,17 @@ class ResourceLoader implements LoggerAwareInterface {
                        }
                }
 
-               // Save response to file cache unless there are errors
-               if ( isset( $fileCache ) && !$this->errors && $missing === [] ) {
-                       // Cache single modules and images...and other requests if there are enough hits
-                       if ( ResourceFileCache::useFileCache( $context ) ) {
-                               if ( $fileCache->isCacheWorthy() ) {
-                                       $fileCache->saveText( $response );
-                               } else {
-                                       $fileCache->incrMissesRecent( $context->getRequest() );
-                               }
+               // Consider saving the response to file cache (unless there are errors).
+               if ( $fileCache &&
+                       !$this->errors &&
+                       $missing === [] &&
+                       ResourceFileCache::useFileCache( $context )
+               ) {
+                       if ( $fileCache->isCacheWorthy() ) {
+                               // There were enough hits, save the response to the cache
+                               $fileCache->saveText( $response );
+                       } else {
+                               $fileCache->incrMissesRecent( $context->getRequest() );
                        }
                }
 
index 206f47b..94e8a3e 100644 (file)
@@ -66,8 +66,8 @@ class ResourceLoaderContext implements MessageLocalizer {
                $this->request = $request;
                $this->logger = $resourceLoader->getLogger();
 
-               // Future developers: Use WebRequest::getRawVal() instead of getVal().
-               // The getVal() method performs slow Language+UTF logic. (f303bb9360)
+               // Optimisation: Use WebRequest::getRawVal() instead of getVal(). We don't
+               // need the slow Language+UTF logic meant for user input here. (f303bb9360)
 
                // List of modules
                $modules = $request->getRawVal( 'modules' );
index c376fa7..ed2d09c 100644 (file)
@@ -26,6 +26,7 @@ use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Psr\Log\NullLogger;
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\RelPath;
 use Wikimedia\ScopedCallback;
 
@@ -33,65 +34,63 @@ use Wikimedia\ScopedCallback;
  * Abstraction for ResourceLoader modules, with name registration and maxage functionality.
  */
 abstract class ResourceLoaderModule implements LoggerAwareInterface {
-       # Type of resource
-       const TYPE_SCRIPTS = 'scripts';
-       const TYPE_STYLES = 'styles';
-       const TYPE_COMBINED = 'combined';
-
-       # Desired load type
-       // Module only has styles (loaded via <style> or <link rel=stylesheet>)
-       const LOAD_STYLES = 'styles';
-       // Module may have other resources (loaded via mw.loader from a script)
-       const LOAD_GENERAL = 'general';
-
-       # sitewide core module like a skin file or jQuery component
-       const ORIGIN_CORE_SITEWIDE = 1;
-
-       # per-user module generated by the software
-       const ORIGIN_CORE_INDIVIDUAL = 2;
-
-       # sitewide module generated from user-editable files, like MediaWiki:Common.js, or
-       # modules accessible to multiple users, such as those generated by the Gadgets extension.
-       const ORIGIN_USER_SITEWIDE = 3;
-
-       # per-user module generated from user-editable files, like User:Me/vector.js
-       const ORIGIN_USER_INDIVIDUAL = 4;
-
-       # an access constant; make sure this is kept as the largest number in this group
-       const ORIGIN_ALL = 10;
+       /** @var Config */
+       protected $config;
+       /** @var LoggerInterface */
+       protected $logger;
 
-       # script and style modules form a hierarchy of trustworthiness, with core modules like
-       # skins and jQuery as most trustworthy, and user scripts as least trustworthy.  We can
-       # limit the types of scripts and styles we allow to load on, say, sensitive special
-       # pages like Special:UserLogin and Special:Preferences
+       /**
+        * Script and style modules form a hierarchy of trustworthiness, with core modules
+        * like skins and jQuery as most trustworthy, and user scripts as least trustworthy. We can
+        * limit the types of scripts and styles we allow to load on, say, sensitive special
+        * pages like Special:UserLogin and Special:Preferences
+        * @var int
+        */
        protected $origin = self::ORIGIN_CORE_SITEWIDE;
 
+       /** @var string|null Module name */
        protected $name = null;
+       /** @var string[] What client platforms the module targets (e.g. desktop, mobile) */
        protected $targets = [ 'desktop' ];
 
-       // In-object cache for file dependencies
+       /** @var array Map of (variant => indirect file dependencies) */
        protected $fileDeps = [];
-       // In-object cache for message blob (keyed by language)
+       /** @var array Map of (language => in-object cache for message blob) */
        protected $msgBlobs = [];
-       // In-object cache for version hash
+       /** @var array Map of (context hash => cached module version hash) */
        protected $versionHash = [];
-       // In-object cache for module content
+       /** @var array Map of (context hash => cached module content) */
        protected $contents = [];
 
-       /**
-        * @var Config
-        */
-       protected $config;
-
-       /**
-        * @var array|bool
-        */
+       /** @var string|bool Deprecation string or true if deprecated; false otherwise */
        protected $deprecated = false;
 
+       /** @var string Scripts only */
+       const TYPE_SCRIPTS = 'scripts';
+       /** @var string Styles only */
+       const TYPE_STYLES = 'styles';
+       /** @var string Scripts and styles */
+       const TYPE_COMBINED = 'combined';
+
+       /** @var string Module only has styles (loaded via <style> or <link rel=stylesheet>) */
+       const LOAD_STYLES = 'styles';
+       /** @var string Module may have other resources (loaded via mw.loader from a script) */
+       const LOAD_GENERAL = 'general';
+
+       /** @var int Sitewide core module like a skin file or jQuery component */
+       const ORIGIN_CORE_SITEWIDE = 1;
+       /** @var int Per-user module generated by the software */
+       const ORIGIN_CORE_INDIVIDUAL = 2;
        /**
-        * @var LoggerInterface
+        * Sitewide module generated from user-editable files, like MediaWiki:Common.js,
+        * or modules accessible to multiple users, such as those generated by the Gadgets extension.
+        * @var int
         */
-       protected $logger;
+       const ORIGIN_USER_SITEWIDE = 3;
+       /** @var int Per-user module generated from user-editable files, like User:Me/vector.js */
+       const ORIGIN_USER_INDIVIDUAL = 4;
+       /** @var int An access constant; make sure this is kept as the largest number in this group */
+       const ORIGIN_ALL = 10;
 
        /**
         * Get this module's name. This is set when the module is registered
@@ -210,7 +209,6 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
        /**
         * @since 1.27
         * @param LoggerInterface $logger
-        * @return null
         */
        public function setLogger( LoggerInterface $logger ) {
                $this->logger = $logger;
@@ -950,12 +948,12 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                                $parser = self::javaScriptParser();
                                $err = null;
                                try {
-                                       Wikimedia\suppressWarnings();
+                                       AtEase::suppressWarnings();
                                        $parser->parse( $contents, $fileName, 1 );
                                } catch ( Exception $e ) {
                                        $err = $e;
                                } finally {
-                                       Wikimedia\restoreWarnings();
+                                       AtEase::restoreWarnings();
                                }
                                if ( $err ) {
                                        // Send the error to the browser console client-side.
@@ -989,9 +987,9 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
         * @return int UNIX timestamp
         */
        protected static function safeFilemtime( $filePath ) {
-               Wikimedia\suppressWarnings();
+               AtEase::suppressWarnings();
                $mtime = filemtime( $filePath ) ?: 1;
-               Wikimedia\restoreWarnings();
+               AtEase::restoreWarnings();
                return $mtime;
        }
 
index 6b38ee4..a982de2 100644 (file)
@@ -283,8 +283,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        }
 
                        if ( $versionHash !== '' && strlen( $versionHash ) !== 7 ) {
-                               $this->getLogger()->warning(
-                                       "Module '{module}' produced an invalid version hash: '{version}'.",
+                               $e = new RuntimeException( "Badly formatted module version hash" );
+                               $resourceLoader->outputErrorAndLog( $e,
+                                               "Module '{module}' produced an invalid version hash: '{version}'.",
                                        [
                                                'module' => $name,
                                                'version' => $versionHash,
@@ -339,16 +340,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                return $out;
        }
 
-       /**
-        * @private For internal use by SpecialJavaScriptTest
-        * @since 1.32
-        * @return array
-        * @codeCoverageIgnore
-        */
-       public function getBaseModulesInternal() {
-               return $this->getBaseModules();
-       }
-
        /**
         * Base modules implicitly available to all modules.
         *
index 0c5d4da..a057aa8 100644 (file)
@@ -61,7 +61,7 @@ abstract class SearchDatabase extends SearchEngine {
         * Perform a full text search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        abstract protected function doSearchTextInDB( $term );
 
@@ -77,7 +77,7 @@ abstract class SearchDatabase extends SearchEngine {
         * Perform a title-only search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        abstract protected function doSearchTitleInDB( $term );
 
index 31af13d..87a7861 100644 (file)
@@ -433,10 +433,13 @@ abstract class SearchEngine {
        /**
         * Find snippet highlight settings for all users
         * @return array Contextlines, contextchars
+        * @deprecated in 1.34 use the SearchHighlighter constants directly
+        * @see SearchHighlighter::DEFAULT_CONTEXT_CHARS
+        * @see SearchHighlighter::DEFAULT_CONTEXT_LINES
         */
        public static function userHighlightPrefs() {
-               $contextlines = 2; // Hardcode this. Old defaults sucked. :)
-               $contextchars = 75; // same as above.... :P
+               $contextlines = SearchHighlighter::DEFAULT_CONTEXT_LINES;
+               $contextchars = SearchHighlighter::DEFAULT_CONTEXT_CHARS;
                return [ $contextlines, $contextchars ];
        }
 
@@ -486,6 +489,7 @@ abstract class SearchEngine {
         * @param Title $t Title we're indexing
         * @param Content|null $c Content of the page to index
         * @return string
+        * @deprecated since 1.34 use Content::getTextForSearchIndex directly
         */
        public function getTextFromContent( Title $t, Content $c = null ) {
                return $c ? $c->getTextForSearchIndex() : '';
@@ -497,6 +501,7 @@ abstract class SearchEngine {
         * rather silly handling, it should return true here instead.
         *
         * @return bool
+        * @deprecated since 1.34 no longer needed since getTextFromContent is being deprecated
         */
        public function textAlreadyUpdatedForIndex() {
                return false;
index 6c01f79..2579942 100644 (file)
@@ -29,6 +29,9 @@ use MediaWiki\MediaWikiServices;
  * @ingroup Search
  */
 class SearchHighlighter {
+       const DEFAULT_CONTEXT_LINES = 2;
+       const DEFAULT_CONTEXT_CHARS = 75;
+
        protected $mCleanWikitext = true;
 
        /**
@@ -50,7 +53,12 @@ class SearchHighlighter {
         * @param int $contextchars
         * @return string
         */
-       public function highlightText( $text, $terms, $contextlines, $contextchars ) {
+       public function highlightText(
+               $text,
+               $terms,
+               $contextlines = self::DEFAULT_CONTEXT_LINES,
+               $contextchars = self::DEFAULT_CONTEXT_CHARS
+       ) {
                global $wgSearchHighlightBoundaries;
 
                if ( $text == '' ) {
@@ -507,7 +515,12 @@ class SearchHighlighter {
         * @param int $contextchars
         * @return string
         */
-       public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
+       public function highlightSimple(
+               $text,
+               $terms,
+               $contextlines = self::DEFAULT_CONTEXT_LINES,
+               $contextchars = self::DEFAULT_CONTEXT_CHARS
+       ) {
                $lines = explode( "\n", $text );
 
                $terms = implode( '|', $terms );
@@ -557,7 +570,11 @@ class SearchHighlighter {
         * @param int $contextchars Average number of characters per line
         * @return string
         */
-       public function highlightNone( $text, $contextlines, $contextchars ) {
+       public function highlightNone(
+               $text,
+               $contextlines = self::DEFAULT_CONTEXT_LINES,
+               $contextchars = self::DEFAULT_CONTEXT_CHARS
+       ) {
                $match = [];
                $text = ltrim( $text ) . "\n"; // make sure the preg_match may find the last line
                $text = str_replace( "\n\n", "\n", $text ); // remove empty lines
index 6a23bb3..3c0675f 100644 (file)
@@ -33,7 +33,7 @@ class SearchMssql extends SearchDatabase {
         * Perform a full text search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTextInDB( $term ) {
                $dbr = $this->lb->getConnectionRef( DB_REPLICA );
@@ -46,7 +46,7 @@ class SearchMssql extends SearchDatabase {
         * Perform a title-only search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTitleInDB( $term ) {
                $dbr = $this->lb->getConnectionRef( DB_REPLICA );
index 4a6b93b..ff21367 100644 (file)
@@ -163,7 +163,7 @@ class SearchMySQL extends SearchDatabase {
         * Perform a full text search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTextInDB( $term ) {
                return $this->searchInternal( $term, true );
@@ -173,7 +173,7 @@ class SearchMySQL extends SearchDatabase {
         * Perform a title-only search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTitleInDB( $term ) {
                return $this->searchInternal( $term, false );
index 7240e81..d0869bc 100644 (file)
@@ -64,11 +64,11 @@ class SearchOracle extends SearchDatabase {
         * Perform a full text search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTextInDB( $term ) {
                if ( $term == '' ) {
-                       return new SqlSearchResultSet( false, '' );
+                       return null;
                }
 
                $dbr = $this->lb->getConnectionRef( DB_REPLICA );
@@ -80,11 +80,11 @@ class SearchOracle extends SearchDatabase {
         * Perform a title-only search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTitleInDB( $term ) {
                if ( $term == '' ) {
-                       return new SqlSearchResultSet( false, '' );
+                       return null;
                }
 
                $dbr = $this->lb->getConnectionRef( DB_REPLICA );
index 1d71c87..b924b29 100644 (file)
@@ -138,8 +138,8 @@ class SearchResult {
        protected function initText() {
                if ( !isset( $this->mText ) ) {
                        if ( $this->mRevision != null ) {
-                               $this->mText = $this->searchEngine->getTextFromContent(
-                                               $this->mTitle, $this->mRevision->getContent() );
+                               $content = $this->mRevision->getContent();
+                               $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
                        } else { // TODO: can we fetch raw wikitext for commons images?
                                $this->mText = '';
                        }
index dedcdff..9375ef2 100644 (file)
@@ -165,7 +165,7 @@ class SearchSqlite extends SearchDatabase {
         * Perform a full text search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTextInDB( $term ) {
                return $this->searchInternal( $term, true );
@@ -175,7 +175,7 @@ class SearchSqlite extends SearchDatabase {
         * Perform a title-only search query and return a result set.
         *
         * @param string $term Raw search term
-        * @return SqlSearchResultSet
+        * @return SqlSearchResultSet|null
         */
        protected function doSearchTitleInDB( $term ) {
                return $this->searchInternal( $term, false );
index 25e87e7..9804e44 100644 (file)
@@ -51,18 +51,15 @@ class SqlSearchResult extends SearchResult {
                global $wgAdvancedSearchHighlighting;
                $this->initText();
 
-               // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
-               list( $contextlines, $contextchars ) = $this->searchEngine->userHighlightPrefs();
-
                $h = new SearchHighlighter();
                if ( count( $this->terms ) > 0 ) {
                        if ( $wgAdvancedSearchHighlighting ) {
-                               return $h->highlightText( $this->mText, $this->terms, $contextlines, $contextchars );
+                               return $h->highlightText( $this->mText, $this->terms );
                        } else {
-                               return $h->highlightSimple( $this->mText, $this->terms, $contextlines, $contextchars );
+                               return $h->highlightSimple( $this->mText, $this->terms );
                        }
                } else {
-                       return $h->highlightNone( $this->mText, $contextlines, $contextchars );
+                       return $h->highlightNone( $this->mText );
                }
        }
 
index 87c899f..184cdbb 100644 (file)
@@ -106,6 +106,13 @@ class SpecialChangeContentModel extends FormSpecialPage {
                        }
                        $fields['pagetitle']['readonly'] = true;
                        $fields += [
+                               'currentmodel' => [
+                                       'type' => 'text',
+                                       'name' => 'currentcontentmodel',
+                                       'default' => $this->title->getContentModel(),
+                                       'label-message' => 'changecontentmodel-current-label',
+                                       'readonly' => true
+                               ],
                                'model' => [
                                        'type' => 'select',
                                        'name' => 'model',
index ecbbc25..161b41a 100644 (file)
@@ -150,10 +150,16 @@ class MovePageForm extends UnlistedSpecialPage {
                $out->addModules( 'mediawiki.misc-authed-ooui' );
                $this->addHelpLink( 'Help:Moving a page' );
 
-               $out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
-                       'movepagetext' :
-                       'movepagetext-noredirectfixer'
-               );
+               $handlerSupportsRedirects = ContentHandler::getForTitle( $this->oldTitle )
+                       ->supportsRedirects();
+
+               if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
+                       $out->addWikiMsg( 'movepagetext' );
+               } else {
+                       $out->addWikiMsg( $handlerSupportsRedirects ?
+                               'movepagetext-noredirectfixer' :
+                               'movepagetext-noredirectsupport' );
+               }
 
                if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
                        $out->wrapWikiMsg(
@@ -306,8 +312,6 @@ class MovePageForm extends UnlistedSpecialPage {
                        }
                }
 
-               $handler = ContentHandler::getForTitle( $this->oldTitle );
-
                $out->enableOOUI();
                $fields = [];
 
@@ -371,7 +375,7 @@ class MovePageForm extends UnlistedSpecialPage {
                }
 
                if ( $user->isAllowed( 'suppressredirect' ) ) {
-                       if ( $handler->supportsRedirects() ) {
+                       if ( $handlerSupportsRedirects ) {
                                $isChecked = $this->leaveRedirect;
                                $isDisabled = false;
                        } else {
index fdac4a2..2261fcb 100644 (file)
@@ -148,72 +148,66 @@ class UserGroupMembership {
         * the function fails if there is a conflicting membership entry (same user and
         * group) already in the table.
         *
-        * @throws MWException
+        * @throws UnexpectedValueException
         * @param bool $allowUpdate Whether to perform "upsert" instead of INSERT
         * @param IDatabase|null $dbw If you have one available
         * @return bool Whether or not anything was inserted
         */
        public function insert( $allowUpdate = false, IDatabase $dbw = null ) {
-               if ( $dbw === null ) {
-                       $dbw = wfGetDB( DB_MASTER );
-               }
-
-               // Purge old, expired memberships from the DB
-               $hasExpiredRow = $dbw->selectField(
-                       'user_groups',
-                       '1',
-                       [ 'ug_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
-                       __METHOD__
-               );
-               if ( $hasExpiredRow ) {
-                       JobQueueGroup::singleton()->lazyPush( new UserGroupExpiryJob() );
-               }
-
-               // Check that the values make sense
                if ( $this->group === null ) {
                        throw new UnexpectedValueException(
-                               'Don\'t try inserting an uninitialized UserGroupMembership object' );
+                               'Cannot insert an uninitialized UserGroupMembership instance'
+                       );
                } elseif ( $this->userId <= 0 ) {
                        throw new UnexpectedValueException(
                                'UserGroupMembership::insert() needs a positive user ID. ' .
-                               'Did you forget to add your User object to the database before calling addGroup()?' );
+                               'Perhaps addGroup() was called before the user was added to the database.'
+                       );
                }
 
+               $dbw = $dbw ?: wfGetDB( DB_MASTER );
                $row = $this->getDatabaseArray( $dbw );
+
+               $dbw->startAtomic( __METHOD__ );
                $dbw->insert( 'user_groups', $row, __METHOD__, [ 'IGNORE' ] );
                $affected = $dbw->affectedRows();
-
-               // Don't collide with expired user group memberships
-               // Do this after trying to insert, in order to avoid locking
                if ( !$affected ) {
-                       $conds = [
-                               'ug_user' => $row['ug_user'],
-                               'ug_group' => $row['ug_group'],
-                       ];
-                       // if we're unconditionally updating, check that the expiry is not already the
-                       // same as what we are trying to update it to; otherwise, only update if
-                       // the expiry date is in the past
+                       // Conflicting row already exists; it should be overriden if it is either expired
+                       // or if $allowUpdate is true and the current row is different than the loaded row.
+                       $conds = [ 'ug_user' => $row['ug_user'], 'ug_group' => $row['ug_group'] ];
                        if ( $allowUpdate ) {
-                               if ( $this->expiry ) {
-                                       $conds[] = 'ug_expiry IS NULL OR ug_expiry != ' .
-                                               $dbw->addQuotes( $dbw->timestamp( $this->expiry ) );
-                               } else {
-                                       $conds[] = 'ug_expiry IS NOT NULL';
-                               }
+                               // Update the current row if its expiry does not match that of the loaded row
+                               $conds[] = $this->expiry
+                                       ? 'ug_expiry IS NULL OR ug_expiry != ' .
+                                               $dbw->addQuotes( $dbw->timestamp( $this->expiry ) )
+                                       : 'ug_expiry IS NOT NULL';
                        } else {
+                               // Update the current row if it is expired
                                $conds[] = 'ug_expiry < ' . $dbw->addQuotes( $dbw->timestamp() );
                        }
+                       $dbw->update(
+                               'user_groups',
+                               [ 'ug_expiry' => $this->expiry ? $dbw->timestamp( $this->expiry ) : null ],
+                               $conds,
+                               __METHOD__
+                       );
+                       $affected = $dbw->affectedRows();
+               }
+               $dbw->endAtomic( __METHOD__ );
 
-                       $row = $dbw->selectRow( 'user_groups', $this::selectFields(), $conds, __METHOD__ );
-                       if ( $row ) {
-                               $dbw->update(
-                                       'user_groups',
-                                       [ 'ug_expiry' => $this->expiry ? $dbw->timestamp( $this->expiry ) : null ],
-                                       [ 'ug_user' => $row->ug_user, 'ug_group' => $row->ug_group ],
-                                       __METHOD__ );
-                               $affected = $dbw->affectedRows();
+               // Purge old, expired memberships from the DB
+               $fname = __METHOD__;
+               DeferredUpdates::addCallableUpdate( function () use ( $dbw, $fname ) {
+                       $hasExpiredRow = $dbw->selectField(
+                               'user_groups',
+                               '1',
+                               [ 'ug_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
+                               $fname
+                       );
+                       if ( $hasExpiredRow ) {
+                               JobQueueGroup::singleton()->push( new UserGroupExpiryJob() );
                        }
-               }
+               } );
 
                return $affected > 0;
        }
index f490dfe..3089341 100644 (file)
@@ -78,7 +78,8 @@
                        "Omar Ghrida",
                        "AHmed Khaled",
                        "البراء صالح",
-                       "Dyolf77 (WMF)"
+                       "Dyolf77 (WMF)",
+                       "HitomiAkane"
                ]
        },
        "tog-underline": "سطر تحت الوصلات:",
        "zip-bad": "ملف ZIP هذا معطوب أو لا يمكن قراءته لسبب آخر.\nلا يمكن التحقق من سلامته.",
        "zip-unsupported": "هذا ملف ZIP يستخدم بعض مزايا ZIP التي لا يدعمها ميدياويكي.\nلا يمكن التحقق من سلامته.",
        "uploadstash": "تحميل مخبأ",
-       "uploadstash-summary": " توفر هذه الصفحة الوصول إلى الملفات التي يتم تحميلها (أو في أثناء عملية التحميل) ولكنها لم تنشر بعد. هذه الملفات هي غير مرئية لأحد إلا للمستخدم الذين تم الرفع لهم.",
+       "uploadstash-summary": "توفر هذه الصفحة الوصول إلى الملفات التي يتم رفعها ولكنها لم تُنشَر بعد في الويكي، هذه الملفات غير مرئية لأحد إلا للمستخدم الذي رفعها.",
        "uploadstash-clear": "مسح الملفات المخبأة",
        "uploadstash-nofiles": "ليس لديك أي ملفات مخبأة.",
        "uploadstash-badtoken": "فشل أداء ذلك العمل، ربما لأن وثائق تفويض التحرير الخاصة بك منتهية الصلاحية. من فضلك حاول مرة أخرى.",
        "changecontentmodel": "تغيير نموذج المحتوى لصفحة",
        "changecontentmodel-legend": "غير نموذج المحتوى",
        "changecontentmodel-title-label": "عنوان الصفحة",
+       "changecontentmodel-current-label": "نموذج المحتوى الحالي",
        "changecontentmodel-model-label": "نموذج محتوى جديد",
        "changecontentmodel-reason-label": "السبب:",
        "changecontentmodel-submit": "تغيير",
        "block-log-flags-angry-autoblock": "المنع التلقائي المتقدم مفعل",
        "block-log-flags-hiddenname": "اسم المستخدم مخفي",
        "range_block_disabled": "إمكانية مدير النظام لمنع نطاق معطلة.",
+       "ipb-prevent-user-talk-edit": "يجب السماح بتحرير صفحة نقاشه لمنع جزئي، ما لم يتضمن تقييدا على نطاق نقاش المستخدم.",
        "ipb_expiry_invalid": "تاريخ الانتهاء غير صحيح.",
        "ipb_expiry_old": "توقيت انتهاء المنع واقع في الماضي.",
        "ipb_expiry_temp": "عمليات منع أسماء المستخدمين المخفية يجب أن تكون دائمة.",
        "move-page-legend": "نقل الصفحة",
        "movepagetext": "باستخدام  الاستمارة بالأسفل بإمكانك أن تغير اسم الصفحة، وأن تنقل تاريخها إلى الاسم الجديد.\nالعنوان القديم سيصبح تحويلة للعنوان الجديد.\nيمكنك أن تترك التحويلات التي تشير إلى العنوان الأصلي كما هي لتقوم البوتات بتحديثها تلقائياً.\nإذا اخترت أن تقوم بالتحديث يدوياً، فتأكد من عدم وجود تحويلات [[Special:DoubleRedirects|مزدوجة]] أو [[Special:BrokenRedirects|مكسورة]] وقم بتصحيحها.\nأنت المسؤول عن التأكد من أن الوصلات تصل إلى الصفحات التي يفترض أن تصل إليها.\n\nلاحظ أنه <strong>لن يتم</strong> نقل الصفحة إذا وجدت صفحة في العنوان الجديد، إلا إذا كانت صفحة تحويل، ولا تاريخ لها.\nهذا يعني أنك تستطيع استرجاع الصفحة إلى مكانها لو قمت بخطأ، ولا يمكنك نسخ هذه الصفحة فوق صفحة موجودة.\n\n<strong>ملاحظة:</strong>\n\nهذا قد يكون تغييراً كارثياً وغير متوقع لصفحة مشهورة؛\nمن فضلك تأكد أنك تفهم عواقب هذا الفعل قبل أن تستمر.",
        "movepagetext-noredirectfixer": "باستخدام  الاستمارة بالأسفل بإمكانك أن تغير اسم الصفحة، وأن تنقل تاريخها إلى الاسم الجديد.\nالعنوان القديم سيصبح تحويلة للعنوان الجديد.\nيمكنك تحديث التحويلات التي تشير إلى العنوان الأصلي تلقائياً.\nلو اخترت ألا تفعل، تأكد من عدم وجود تحويلات [[Special:DoubleRedirects|مزدوجة]] أو [[Special:BrokenRedirects|مكسورة]].\nأنت المسؤول عن التأكد من أن الوصلات تصل إلى الصفحات التي يفترض أن تصل إليها.\n\nلاحظ أنه <strong>لن يتم</strong>  نقل الصفحة إذا كان هناك صفحة بنفس العنوان الجديد، إلا إذا كانت فارغة، أو تحويلة لا تاريخ لها.\nهذا يعني أنك تستطيع استرجاع الصفحة إلى مكانها لو قمت بخطأ، وأنك لا يمكنك الكتابة على صفحة موجودة.\n\n<strong>ملاحظة</strong> \n\nهذا قد يكون تغييراً كارثياً وغير متوقع لصفحة مشهورة؛\nمن فضلك تأكد أنك تفهم عواقب هذا الفعل قبل أن تستمر.",
+       "movepagetext-noredirectsupport": "سيؤدي استخدام النموذج أدناه إلى إعادة تسمية صفحة، مع نقل كل تاريخها إلى الاسم الجديد،\nأنت مسئول عن التأكد من أن الروابط لا تزال تشير إلى المكان الذي من المفترض أن تذهب إليه.\n\nلاحظ أنه <strong>لن</strong> يتم نقل الصفحة إذا كانت هناك بالفعل صفحة في العنوان الجديد:\nهذا يعني أنه يمكنك إعادة تسمية صفحة إلى المكان الذي تمت إعادة تسميتها منه إذا ارتكبت خطأً، ولا يمكنك استبدال صفحة موجودة.\n\n<strong>ملاحظة:</strong>\nيمكن أن يكون هذا تغييرا جذريا وغير متوقع لصفحة شائعة؛\nيُرجَى التأكد من أنك تفهم عواقب هذا قبل المتابعة.",
        "movepagetalktext": "لو علمت على هذا الصندوق، فصفحة النقاش المرفقة يتم نقلها أوتوماتيكيا للعنوان الجديد، إلا لو كانت صفحة نقاش غير فارغة هناك بالفعل.\n\nفي هذه الحالة، فسيتعين عليك نقل أو دمج الصفحة يدويا لو رغبت في ذلك.",
        "moveuserpage-warning": "<strong>تحذير:</strong> أنت على وشك نقل صفحة مستخدم. من فضلك لاحظ أن الصفحة وحدها سوف تنقل وأن المستخدم <em>لن</em> تعاد تسميته.",
        "movecategorypage-warning": "<strong>تحذير:</strong> أنت على وشك نقل صفحة التصنيف إلى عنوان جديد؛ <em>لن</em> تنقل الصفحات المندرجة تحت التصنيف إلى العنوان الجديد.",
index 0fd9873..f863d06 100644 (file)
@@ -25,7 +25,8 @@
                        "Sagsag",
                        "Bodhisattwa",
                        "Vlad5250",
-                       "ৰাজীৱ গোস্বামী"
+                       "ৰাজীৱ গোস্বামী",
+                       "আফতাবুজ্জামান"
                ]
        },
        "tog-underline": "সংযোগসমূহ অধোৰেখিত কৰক:",
        "enotif_body_intro_changed": "{{SITENAME}}ৰ পৃষ্ঠা $1, $PAGEEDITDATE তাৰিখে {{gender:$2|$2}}ৰ দ্বাৰা সলনি কৰা হৈছিল, বৰ্তমানৰ সংস্কৰণৰ বাবে $3 চাওক।",
        "enotif_lastvisited": "আপোনাৰ শেষ পৰিদৰ্শনৰ পিছত হোৱা সকলো সালসলনিৰ বাবে $1 চাওক ।",
        "enotif_lastdiff": "এই পৰিৱৰ্তনটো চাবৰ বাবে $1 চাওক ।",
-       "enotif_anon_editor": "বà§\87নামà§\80 à¦¸à¦¦à¦¸à§\8dয $1",
+       "enotif_anon_editor": "নামহà§\80ন à¦¬à§\8dযবহারà¦\95ারà§\80 $1",
        "enotif_body": "প্ৰিয় $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nসম্পাদকৰ সাৰাংশ: $PAGESUMMARY $PAGEMINOREDIT\n\nসম্পাদকজনক যোগাযোগ কৰক:\nমেইল: $PAGEEDITOR_EMAIL\nৱিকি: $PAGEEDITOR_WIKI\n\nআপুনি এই পৃষ্ঠাটো প্ৰৱেশ কৰি নোচোৱালৈকে আন সালসলনিৰ কোনো জাননী দিয়া নহ’ব। আপুনি আপোনাৰ লক্ষ্য-তালিকাৰ পৃষ্ঠাবোৰৰ জাননী ফ্লেগ পূৰ্বৰ অৱস্থালৈও ঘূৰাই নিব পাৰে ।\n\nআপোনাৰ {{SITENAME}} জাননী ব্যৱস্থা\n\n--\nআপোনাৰ ই-মেইল জাননী ছেটিং সলনি কৰিবলৈ চাওক\n{{canonicalurl:{{#special:Preferences}}}}\n\nআপোনাৰ লক্ষ্য-তালিকাৰ ছেটিং সলনি কৰিবলৈ চাওক\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nআপোনাৰ লক্ষ্য-তালিকাৰ পৰা পৃষ্ঠা বিলোপ কৰিবলৈ চাওক\n$UNWATCHURL\n\nপ্ৰতিক্ৰিয়া আৰু অধিক সহযোগিতাৰ বাবে:\n$HELPPAGE",
        "created": "সৃষ্টি কৰা হ’ল",
        "changed": "সলোৱা হৈছে",
index ea05ebc..189dddb 100644 (file)
        "search-interwiki-more": "(más)",
        "search-interwiki-more-results": "más resultaos",
        "search-relatedarticle": "Rellacionáu",
+       "search-invalid-sort-order": "Nun se reconoció la ordenación por $1, aplicaráse la ordenación predeterminada. Los tipos d'ordenación válidos son: $2",
        "searchrelated": "rellacionáu",
        "searchall": "toos",
        "showingresults": "Abaxo s'{{PLURAL:$1|amuesa hasta <strong>un</strong> resultáu|amuesen <strong>$1</strong> resultaos}}, principando por #<strong>$2</strong>.",
        "rcfilters-clear-all-filters": "Borrar tolos filtros",
        "rcfilters-show-new-changes": "Ver los cambeos nuevos dende $1",
        "rcfilters-search-placeholder": "Filtriar cambeos (usa'l menú o busca'l nome del filtru)",
+       "rcfilters-search-placeholder-mobile": "Filtros",
        "rcfilters-invalid-filter": "Filtru inválidu",
        "rcfilters-empty-filter": "Nun hai filtros activos. Amuésense toles contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
        "newtitle": "Títulu nuevu:",
        "move-watch": "Vixilar les páxines d'orixe y destín",
        "movepagebtn": "Treslladar la páxina",
-       "pagemovedsub": "Treslláu correctu",
+       "pagemovedsub": "Treslláu correutu",
        "cannotmove": "La páxina nun pudo treslladase {{PLURAL:$1|pol siguiente motivu|polos siguientes motivos}}:",
        "movepage-moved": "<strong>«$1» treslladóse a «$2»</strong>",
        "movepage-moved-redirect": "Creóse una redireición.",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "La contraseña nun pué tar na llista de les 100.000 contraseñes más usaes.",
        "passwordpolicies-policyflag-forcechange": "tien de camudase al aniciar sesión",
        "passwordpolicies-policyflag-suggestchangeonlogin": "suxerir cambiu al aniciar sesión",
+       "mycustomjsredirectprotected": "Nun tienes permisu pa editar esta páxina en JavaScript porque ye una redireición que nun apunta dientro del to espaciu d'usuariu.",
        "easydeflate-invaliddeflate": "El conteníu dau nun ta comprimíu correutamente",
        "unprotected-js": "Por razones de seguridá, JavaScript nun puede cargase dende páxines ensin protexer. Crea javascript sólo nel espaciu de nomes MediaWiki: o como subpáxina d'usuariu",
        "userlogout-continue": "¿Desees zarrar la sesión?"
index f29ed77..4c81857 100644 (file)
                        "Macofe",
                        "Matma Rex",
                        "ShimunUfesoj",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Brazal.dang"
                ]
        },
        "tog-underline": "Linyahan an kilyawan:",
        "tog-hideminor": "Tagoon an saradít na mga pagliwat sa dae pa sana nahaloy na mga pagbabàgo",
        "tog-hidepatrolled": "Tagóa an patrolyadong mga paghirá sa nakakaági pa sanáng pagbabàgo",
        "tog-newpageshidepatrolled": "Tagoon an patrolyadong mga pahina gikan sa baguhong listahan nin pahina",
+       "tog-hidecategorization": "Itago an pagkagrupo kan mga pahina",
        "tog-extendwatchlist": "Palakbanga an bantay-listahan (watchlist) na maipahiling an gabos na pinagbago, bako sana an pinakahurihang binago",
        "tog-usenewrc": "Pangrupong mga kaliwatan sa kada pahina kan mga dae pa sana nahaloy na mga kaliwatan asin bantay-listahan",
        "tog-numberheadings": "Tolos-bilang na mga pamayohán",
        "tog-watchlisthideliu": "Tagoon an mga pagbabagong nahimo kan mga nakalaog na paragamit gikan sa bantayang listahan",
        "tog-watchlisthideanons": "Tagoon an mga pagbabagong nahimo kan mga bakong bistadong paragamit gikan sa bantayang listahan",
        "tog-watchlisthidepatrolled": "Tagoon an mga patrolyadong pagbabago gikan sa bantayang listahan",
+       "tog-watchlisthidecategorization": "Itago an pagkagrupo kan mga pahina",
        "tog-ccmeonemails": "Ipadara sako an mga kopya kan e-koreo na pinadara ko sa ibang mga paragamit",
        "tog-diffonly": "Dai tabi ihayag an laog kan pahina sa ibaba nin mga diffs",
        "tog-showhiddencats": "Ihayag an nakatagong mga kategorya",
        "tog-norollbackdiff": "Omidohon an diff matapos himoon an pagbalikot",
        "tog-useeditwarning": "Patanidan ako kunsoarin na ako nagbaya sa pahinang pigliliwat na dae naitatagama an mga kaliwatan",
        "tog-prefershttps": "Pirmeng gumamit nin sarong seguradong koneksyon kunsoarin na ika nakalaog na",
-       "underline-always": "Parati",
+       "underline-always": "Pirmi",
        "underline-never": "Dae pa lamang",
        "underline-default": "Kublit o kilyaw na panugmad",
        "editfont-style": "Baguhon an estilo nin kalwig sa sinasakupan",
        "october-date": "Oktobre $1",
        "november-date": "Nobyembre $1",
        "december-date": "Disyembre $1",
+       "period-am": "Aga",
+       "period-pm": "Hapon",
        "pagecategories": "{{PLURAL:$1|Kategorya|Mga kategorya}}",
        "category_header": "Mga pahina sa kategoryang \"$1\"",
        "subcategories": "Mga sub-kategorya",
        "morenotlisted": "Ining listahan bakong kumpleto.",
        "mypage": "Pahina",
        "mytalk": "Mag-ulay",
-       "anontalk": "Urulay para kaining IP estada",
+       "anontalk": "Mag-ulay",
        "navigation": "Paglibotlibot",
        "and": "&#32;asin",
        "faq": "PHK (Pirmehang Hinahapot na mga Kahaputan)",
        "returnto": "Magbalik sa $1.",
        "tagline": "Gikan sa {{SITENAME}}",
        "help": "Katabangan",
+       "help-mediawiki": "Tabang tungkol sa MediaWiki",
        "search": "Maghanap",
        "search-ignored-headings": " #<!-- walaton ining linya eksaktong siring sana kaini --> <pre> \n# Mga Kapamayuhanan na pinagpapabayaan sa paghahanap. \n# Mga Kaliwatan kaini magkaka-epekto matapos na an pahina na igwang kapamayuhanan maipaghukdo. \n# Ika makakapagpuwersa sa pahina na maihuhukdo otro sa paagi nin paghimo nin sarong blangko na pagliwat. # An Sintaks iyo ining minasunod: \n# * An gabos magpoon sa sarong karakter na \"#\" sagkod sa tapos kan linya iyo an sarong komento \n# * An lambang linya na bakong blangko iyo an eksaktong titulo na pababayaan, kaso asin gabos na bagay \nMga Panultulan\nPanluwas na mga sugpon\nHilingon man \n#</pre> <!-- walaton ining linya eksaktong siring sana kaini -->",
        "searchbutton": "Maghanap",
-       "go": "Dumani",
-       "searcharticle": "Lakaw",
+       "go": "Dumanán",
+       "searcharticle": "Dumanán",
        "history": "Historiya nin pahina",
        "history_short": "Historiya",
+       "history_small": "historiya",
        "updatedmarker": "dinagdagan poon kan sakong huring pagbisita",
        "printableversion": "Nalilimbag na bersyon",
-       "permalink": "Permanenteng kilyawan",
+       "permalink": "Permanenteng sugpon",
        "print": "Ilimbag",
        "view": "Tànawon",
        "view-foreign": "Hilingon sa $1",
        "delete": "Puraon",
        "undelete_short": "Dae puraon an {{PLURAL:$1|sarong pagliwat|$1 mga pagliwat}}",
        "viewdeleted_short": "Hilingon {{PLURAL:$1|sarong pinura na pagliwat|$1 mga pinura na pagliwat}}",
-       "protect": "Protektari",
+       "protect": "Protektaran",
        "protect_change": "Ribayan",
        "unprotect": "Ribayan an proteksyon",
        "newpage": "Bàguhong pahina",
        "talk": "Urulayan",
        "views": "Mga Tanawon",
        "toolbox": "Mga gamiton:",
+       "tool-link-userrights": "Ribayan {{GENDER:$1|paragamit}} an grupo",
+       "tool-link-userrights-readonly": "Hilingon {{GENDER:$1|paragamit}} an grupo",
+       "tool-link-emailuser": "E-surat kaining {{GENDER:$1|paragamit}}",
        "imagepage": "Tànawon an pahina nin sagunson (file)",
        "mediawikipage": "Tànawon an pahina kan mensahe",
        "templatepage": "Tànawon an pahina kan panguyog",
        "viewhelppage": "Tànawon an pahina nin pagtabang",
-       "categorypage": "Tànawon an pahina nin kategorya",
+       "categorypage": "Tànawon an pahina nin kategoriya",
        "viewtalkpage": "Tànawon an urulay",
        "otherlanguages": "Sa ibang mga lengguwahe",
        "redirectedfrom": "(Pinagbalikwat gikan sa $1)",
        "pool-timeout": "Timeout naghahalat para makapanugpon",
        "pool-queuefull": "An grupong panproseso panoon",
        "pool-errorunknown": "Bakong bistadong sala",
+       "poolcounter-usage-error": "Sala sa paggamit: $1",
        "aboutsite": "Dapít sa {{SITENAME}}",
        "aboutpage": "Project:Mapanonongod",
        "copyright": "An kalamnan manunumpungan sa laog kan $1 o baya notado na ining laen.",
        "disclaimers": "Mga Pangindahan",
        "disclaimerpage": "Project:Pangkagabsán na pangindahan",
        "edithelp": "Pantabang sa pagliliwat",
+       "helppage-top-gethelp": "Katabangan",
        "mainpage": "Panginot na Pahina",
        "mainpage-description": "Panginot na Pahina",
        "policy-url": "Project:Kalakawan",
        "hidetoc": "tagoon",
        "collapsible-collapse": "Pahalipoton",
        "collapsible-expand": "Pahiwason",
+       "confirmable-confirm": "Sigurado {{GENDER:$1|ka}} na?",
        "confirmable-yes": "Iyo",
        "confirmable-no": "Dae",
        "thisisdeleted": "Hilingon o isulit an $1?",
        "filerenameerror": "Dai natàwan nin bàgong ngaran an file na \"$1\" sa \"$2\".",
        "filedeleteerror": "Dai naparà an file na \"$1\".",
        "directorycreateerror": "Dai nagibo an direktorya na \"$1\".",
+       "directoryreadonlyerror": "Ang directory \"$1\" pambasa lang.",
+       "directorynotreadableerror": "Ang directory \"$1\" dae nababasa.",
        "filenotfound": "Dai nahanap an file na \"$1\".",
        "unexpected": "Dai pighuhunà na balór: \"$1\"=\"$2\".",
        "formerror": "Salâ: Dae maisusumiter an porma.",
        "badarticleerror": "Ining aksyon dae magigibo sa pahinang ini.",
        "cannotdelete": "An pahina o an sagunson (file) na \"$1\" dae tabi napupura.\nIni puwede nang napura kan iba.",
        "cannotdelete-title": "Dae mapura an pahina na \"$1\"",
+       "delete-scheduled": "Ang pahina \"$1\" nakatakda nang puraon.\nMagin mapagpasensiya.",
        "delete-hook-aborted": "An pagpura pinundo kan pangawit.\nIni dae nagtao nin kapaliwanagan.",
        "no-null-revision": "Dae makakamukna nin baguhong bunyaw na rebisyon para sa pahina \"$1\"",
        "badtitle": "Raot na titulo",
        "cascadeprotected": "Ining pahina pinagprotehiran gikan sa pagliliwat nin huli ta kabaling pinagbalyo sa minasunod na {{PLURAL:$1|pahina, na iyo ngani an|mga pahina, na iyo ngani an mga}} protektado na igwa nin \"pasurunod\" na opsyong pinagbuksan:\n$2",
        "namespaceprotected": "Ika mayong permiso sa pagliwat nin mga pahina sa <strong>$1</strong> na ngarang-espasyo.",
        "customcssprotected": "Ika mayong permiso sa pagliwat kaining pahinang CSS, nin huli ta ini naglalaman kan personal na panuytoy (settings) kan ibang paragamit.",
+       "customjsonprotected": "Ika mayong permiso sa pagliwat kaining pahinang JSON, nin huli ta ini naglalaman kan personal na panuytoy (settings) kan ibang paragamit.",
        "customjsprotected": "Ika mayong permiso sa pagliwat kaining pahinang JavaScript, nin huli ta ini naglalaman kan personal na panuytoy (settings) kan ibang paragamit.",
+       "sitecssprotected": "Ika mayong permiso sa pagliwat kaining pahinang CSS, nin huli ta ini pwedeng makaapekto sa ibang bisita.",
+       "sitejsonprotected": "Ika mayong permiso sa pagliwat kaining pahinang JSON, nin huli ta ini pwedeng makaapekto sa ibang bisita.",
+       "sitejsprotected": "Ika mayong permiso sa pagliwat kaining pahinang JavaScript, nin huli ta ini pwedeng makaapekto sa ibang bisita.",
        "mycustomcssprotected": "Ika mayo nin permiso sa pagliwat kaining CSS na pahina.",
+       "mycustomjsonprotected": "Ika mayo nin permiso sa pagliwat kaining JSON na pahina.",
        "mycustomjsprotected": "Ika mayo nin permiso sa pagliwat kaining JavaScript na pahina.",
        "myprivateinfoprotected": "Ika daeng permiso na magliliwat kan pribado mong impormasyon.",
        "mypreferencesprotected": "Ika daeng permiso na magliliwat kan saimong mga kamuyahan.",
        "ns-specialprotected": "Mga espesyal na pahina dae makakapagliwat.",
        "titleprotected": "Ining titulo pinagprotektaran poon pagkamukna ni [[User:$1|$1]].\nAn rason na pinagtao iyo na <em>$2</em>.",
        "filereadonlyerror": "Dae kinayang baguhon an sagunson (file) \"$1$ nin huli ta an repositoryo kan sagunson \"$2\" yaon sa kamugtakan na basahon sana.\n\nAn administrador na iyo an nagkandado kaini nagpahayag kaining kapaliwanagan: \"$3\".",
+       "invalidtitle": "Salang titulo.",
        "invalidtitle-knownnamespace": "Imbalidong titulo na igwang espasyadong ngaran na \"$2\" asin teksto na \"$3\"",
        "invalidtitle-unknownnamespace": "Imbalidong titulo na igwang nin bakong bistado na bilang kan espasyadong ngaran na $1 asin teksto na \"$2\"",
        "exception-nologin": "Dai ka nakapaglaog",
        "virus-scanfailed": "An paghingipid (scan) nagpalya (Koda $1)",
        "virus-unknownscanner": "bakong bistadong antivirus:",
        "logouttext": "'''Ika ngunyan nakaluwas na.'''\n\nTandai tabi na an nagkapirang mga pahina puwedeng maipagpapadagos na ipagpapahiling siring sa ika baga yaon sa laog pa, sagkod na maiklarado an saimong pankilyaw na kaaganan.",
+       "logout-failed": "Dae pa makaluwas ngunyan: $1",
+       "cannotlogoutnow-title": "Dae pa makaluwas ngunyan",
        "welcomeuser": "Marhayong pag-abot, $1!",
        "welcomecreation-msg": "An saimong panindog pinagmukna na.\nDae malingaw na liwaton an saimong [[Special:Preferences|{{SITENAME}} mga kamuyahan]].",
        "yourname": "Pangaran kan paragamit:",
        "createacct-yourpasswordagain-ph": "Pakikaag otro an sekretong panlaog",
        "userlogin-remembermypassword": "Dagos mo akong giromdomon na nakalaog",
        "userlogin-signwithsecure": "Gamiton an seguradong koneksyon",
+       "cannotlogin-title": "Dai makalaog",
+       "cannotlogin-text": "Dai posible an paglaog.",
+       "cannotloginnow-title": "Dae pa makalaog ngunyan",
+       "cannotloginnow-text": "Dai posible an paglaog kun magamit nin $1.",
+       "cannotcreateaccount-title": "Dai makagibo nin account",
        "yourdomainname": "An saimong kasakupan:",
        "password-change-forbidden": "Ika dae makapagliwat nin sekretong panlaog sa wiking ini.",
        "externaldberror": "Igwa gayod sala sa arinman kan patunay sa datos-sarayan o ika dae pinagtugutan na bâgohon an saimong panluwas na panindog.",
        "login": "Maglaog",
+       "login-security": "Patunayan an saimong pagkakabisto",
        "nav-login-createaccount": "Maglaog / magmukna nin panindog",
        "logout": "Magluwas",
        "userlogout": "Magluwas",
        "userlogin-resetpassword-link": "Nalingawan mo an saimong pasa-taramon?",
        "userlogin-helplink2": "Katabangan sa paglalaog",
        "userlogin-loggedin": "Ika nakalaog na tabi bilang si {{GENDER:$1|$1}}.\nGamita an porma sa ibaba sa paglaog bilang ibang paragamit.",
+       "userlogin-reauth": "Kaipuhan maglaog ulit para mapatunayan na ika {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Magmukna nin ibang panindog",
        "createacct-emailrequired": "Estada kan e-surat",
        "createacct-emailoptional": "E-surat na estada (opsyonal)",
        "createacct-reason": "Rason",
        "createacct-reason-ph": "Tadaw ta ika magmumukna nin ibang panindog",
        "createacct-submit": "Muknaon an saimong panindog",
-       "createacct-another-submit": "Magmukna nin ibang panindog",
+       "createacct-another-submit": "Magmukna nin panindog",
+       "createacct-continue-submit": "Magpadagos sa paggibo nin panlaog",
+       "createacct-another-continue-submit": "Magpadagos sa paggibo nin panlaog",
        "createacct-benefit-heading": "{{SITENAME}} pinaghimo kan mga tawong siring mo.",
        "createacct-benefit-body1": "{{PLURAL:$1|niliwat|mga niliwat}}",
        "createacct-benefit-body2": "{{PLURAL:$1|pahina|mga pahina}}",
        "createacct-benefit-body3": "pinakahuring {{PLURAL:$1|paraambag|mga paraambag}}",
        "badretype": "An mga sekretong panlaog mong pinagtatak bakong pareho.",
+       "usernameinprogress": "An pagmukna kaning palaog kan paragamit nagpuon na. Maghalat tabi.",
        "userexists": "Paragamit na ngarang piglaog may naggagamit na.\nPakipili nin ibang ngaran tabi.",
        "loginerror": "An paglaog napasalâ",
        "createacct-error": "Kasalaan sa pagmumukna nin panindog",
        "nocookieslogin": "{{SITENAME}} naggagamit nin mga cookies para sa maglaog na mga paragamit.\nIka igwang mga cookies na dae pinagana.\nTabi paganaha sinda asin otroha giraray.",
        "nocookiesfornew": "An panindog kan paragamit dae pinagmukna, nin huli ta dae nyamo kumpirmado an pinaggikanan kaini.\nPakipaseguro na saimong pinagana an cookies, ikarga giraray ining pahina asin probaran mo otro.",
        "noname": "Ika dae tabi nakapagkaag nin sarong balidong pangaran nin paragamit.",
-       "loginsuccesstitle": "Matrayumpo an saimong paglaog",
+       "loginsuccesstitle": "Nakapaglaog na",
        "loginsuccess": "'''Ika ngunyan nakalaog na sa {{SITENAME}} bilang si \"$1\".'''",
        "nosuchuser": "Dae pang paragamit na ginagamit an pangaran na \"$1\".\nAn mga ngaran nin paragamit sensitibo gayo sa tipahan.\nPakireparo kan saimong espeling, o [[Special:CreateAccount|Magmukna nin bagong panindog]].",
        "nosuchusershort": "Mayo po tabing paragamit na an pangaran \"$1\".\nPaki-tsek an saimong espeling.",
        "eauthentsent": "Sarong pankumpirmasyon na e-surat an ipinadara sa isinambit na estada nin e-surat.\nBago an ibang e-surat ipinapadara sa panindog, ika igwang susunudon na mga instruksyon na yaon sa e-surat, tanganing kumpirmaron na an panindog tunay talagang saimo.",
        "throttled-mailpassword": "Sarong e-surat sa pagliliwat kan sekretong panlaog an ipinadara na, sa laog nin {{PLURAL:$1|hour|$1 hours}}.\nTangarig malikayan an abuso, saro sanang e-surat sa pagliliwat kan sekretong panlaog an ipinapadara sa lambang {{PLURAL:$1|hour|$1 hours}}.",
        "mailerror": "Salâ an pagpadará kan koreo: $1",
-       "acct_creation_throttle_hit": "Mga bisita kaining wiki na ginagamit an saimong IP address nagmukna nin {{PLURAL:$1|1 panindog|$1 mga panindog}} sa nakaaging aldaw, na iyo ngani an maximum na pinagtutugot sa laog kan peryodong panahon.\nBilang resulta, an mga bisita na naggagamit kaining IP address dae nguna makakamukna nin mga panindog.",
+       "acct_creation_throttle_hit": "Mga bisita kaining wiki na ginagamit an saimong IP address nagmukna nin {{PLURAL:$1|1 panindog|$1 mga panindog}} sa nakaaging aldaw $2, na iyo ngani an maximum na pinagtutugot sa laog kan peryodong panahon.\nBilang resulta, an mga bisita na naggagamit kaining IP address dae nguna makakamukna nin mga panindog.",
        "emailauthenticated": "An saimong e-surat na estada pinagkumpirma kan $2 mga alas $3.",
        "emailnotauthenticated": "An saimong e-surat na estada dae pa tabi pinagkumpirma.\nMayo tabing e-surat na ipagpapadara para sa arinman kan mga minasunod na mga estima.",
        "noemailprefs": "Magkaag nin sarong e-koreong address sa saimong mga kabotan para gumana ining mga estima.",
        "createacct-another-realname-tip": "An totoong pangaran opsyonal.\nKun gustuhon mong itao ini, ini paggagamiton sa pagtatao nin pagkakabistohan kan paragamit para sa saindang mga kaggibohan.",
        "pt-login": "Maglaog",
        "pt-login-button": "Maglaog",
+       "pt-login-continue-button": "Magpadagos sa paglaog",
        "pt-createaccount": "Magmukna nin panindog",
        "pt-userlogout": "Magluwas",
        "php-mail-error-unknown": "Bakong bantog na kasalaan sa PHP mail() function.",
        "retypenew": "Itaták giraray an bàgong panlaog:",
        "resetpass_submit": "Ipwesto an sekretong panlaog dangan maglaog",
        "changepassword-success": "An saimong pasa-taramon matrayumpong pinagliwat na!",
+       "changepassword-throttled": "Nakapaghimo ka na nin grabe kadakol na pagprubar na maglaog sa dae pa sana nahahaloy. Tabi man pakihalat nin $1 bago ka magprubar giraray.",
+       "botpasswords": "Mga sekretong panlaog kan bot",
+       "botpasswords-disabled": "An mga Bot paswords pinugulan.",
+       "botpasswords-existing": "Mga sekretong panlaog kan bot",
+       "botpasswords-createnew": "Magibo nin bagong sekretong panlaog kan bot",
+       "botpasswords-editexisting": "Baguhon an dati nang sekretong panlaog kan bot",
+       "botpasswords-label-needsreset": "(an sekretong panlaog kaipuhan baguhon)",
+       "botpasswords-label-appid": "Ngaran kan bot:",
+       "botpasswords-label-create": "Muknaon",
+       "botpasswords-label-update": "Panumpay",
+       "botpasswords-label-cancel": "Kanselaron",
+       "botpasswords-label-delete": "Puraon",
+       "botpasswords-label-resetpassword": "Pakibago kan sekretong panlaog",
+       "botpasswords-label-grants-column": "Tinugutan",
+       "botpasswords-bad-appid": "An ngaran kan bot \"$1\" dae tugma.",
+       "botpasswords-created-title": "Gibo na an sekretong panlaog kan bot",
+       "botpasswords-deleted-title": "An sekretong panlaog kan bot pinura na",
        "resetpass_forbidden": "An mga sekretong panlaog dae puwedeng maribayan",
+       "resetpass_forbidden-reason": "An mga sekretong panlaog dae puwedeng maribayan: $1",
        "resetpass-no-info": "Ika dapat nakalaog na tanganing direktang makagamit kaining pahina.",
        "resetpass-submit-loggedin": "Ribayan an sekretong panlaog",
        "resetpass-submit-cancel": "I-kansela",
        "resetpass-abort-generic": "Pagliwat kan sikretong panlaog ipinagpauntok kan sarong ekstensyon.",
        "resetpass-expired": "An saimong pasa-taramon nagpalso na. Tabi man pakikaag nin sarong baguhong pasa-taramon tanganing makalaog ka.",
        "resetpass-expired-soft": "An saimong pasa-taramon nagpalso na, asin kinakaipuhan na baguhon. Tabi man pakipili nin sarong baguhong pasa-taramon ngunyan, o i-klik an \"{{int:authprovider-resetpass-skip-label}}\" kun baguhon sa aro-atyan.",
+       "resetpass-validity": "An saimong pasa-taramon nagpalso na. $1\n\nTabi man pakikaag nin sarong baguhong pasa-taramon tanganing makalaog ka.",
+       "resetpass-validity-soft": "An saimong pasa-taramon nagpalso na, asin kinakaipuhan na baguhon. $1\nTabi man pakipili nin sarong baguhong pasa-taramon ngunyan, o i-klik an \"{{int:authprovider-resetpass-skip-label}}\" kun baguhon sa aro-atyan.",
        "passwordreset": "Pakibago kan sekretong panlaog",
        "passwordreset-text-one": "Kumpletuhon ining porma sa pagliwat otro kan saimong pasa-taramon.",
        "passwordreset-text-many": "{{PLURAL:$1|Kaagi an saro sa mga kaaganan tanganing makaresibe nin sarong temporaryong pasa-taramon sa paagi kan e-surat.}}",
        "passwordreset-emailtext-user": "Paragamit $1 sa {{SITENAME}} naghahagad nin sarong pagiromdom kan detalye nin saimong panindog para sa {{SITENAME}}\n($4). An minasunod na paragamit {{PLURAL:$3|panindog iyo an|mga panindog iyo an}} na asosyado kaining e-koreong address:\n\n$2\n\n\n{{PLURAL:$3|Ining temporaryong sekretong panlaog|Ining mga temporaryong panlaog}} mapapaso sa {{PLURAL:$5|sarong aldaw|$5 mga aldaw}}.\nIka dapat na maglaog asin magpili nin sarong bagong sekretong panlaog ngunyan. Kun ibang tawo an naghimo kaining kahagadan, o kun saimo nang nagiromdoman an saimong orihinal na sekretong panlaog, asin habo mo nang ribayan ini, ipasapara mo na sana an mensaheng ini asin ipadagos mo nang gamiton an saimong lumang sekretong panlaog.",
        "passwordreset-emailelement": "Paragamit-ngaran: \n$1\n\nTemporaryong sekretong panlaog: \n$2",
        "passwordreset-emailsentemail": "Sarong e-surat sa pagliliwat kan sekretong panlaog an ipinadara na.",
+       "passwordreset-emailsentusername": "Sarong e-surat sa pagliliwat kan sekretong panlaog an ipinadara na.",
+       "passwordreset-invalidemail": "Dae pwede an e-surat",
        "changeemail": "Ribayan an e-koreong address",
        "changeemail-header": "Ribayan an panindog na e-koreong address",
        "changeemail-no-info": "Ika dapat nakalaog na tanganing direktang makagamit kaining pahina.",
        "sig_tip": "An saimong pirma na igwang tatak-oras",
        "hr_tip": "Pabalagbag na linya (gamiton paminsan-minsan)",
        "summary": "Sumaryo:",
-       "subject": "Subheto/kapamayuhan:",
+       "subject": "Tema",
        "minoredit": "Ini sarong dikiton na pagliwat",
        "watchthis": "Bantayan ining pahina",
        "savearticle": "Itagáma an pahina",
+       "savechanges": "Itagama an mga kaliwatan",
+       "savearticle-start": "Itagama an pahina",
+       "savechanges-start": "Itagama an mga kaliwatan",
        "preview": "Tànawón",
        "showpreview": "Ipahiling an patanaw",
        "showdiff": "Ipahiling an mga kaliwatan",
        "anoneditwarning": "<strong>Patanid:</strong> Ika dae nakalaog. An saimong estada kan IP mahihiling kan publiko kun ika makahimo nin arinman na mga pagliliwat. Kun ika <strong>[$1 naglaog]</strong> o <strong>[$2 magmukna nin panindog]</strong>, an saimong mga pagliliwat ipagpapanungod sa saimong ngaran-paragamit, kaiba an iba pang mga benepisyo.",
        "anonpreviewwarning": "Dae ka tabi nakalaog. An pagtatagama matala kan saimong IP address sa historya nin pagliwat sa pahinang ini.",
        "missingsummary": "<strong>Pagiromdom:</strong>Ika dae pa nakapagtao nin sumaryo sa pagliwat. Kun i-klik mo an \"$1\" giraray, an saimong pagliwat ipagtatagama na mayo kaiyan.",
-       "missingcommenttext": "Pakikaag nin sarong komento sa ibaba.",
+       "missingcommenttext": "Magkaag nin komento sa ibaba.",
        "missingcommentheader": "'''Pagiromdom:''' Ika dae tabi nagtao nin sarong panultol (subject)/Pamayong linya (headline) para kaining sinambit mo.\nKun saimong pinduton an \"$1\" giraray, an saimong pigliwat matatagama na mayo kaiyan.",
-       "summary-preview": "Paenot na patanaw nin sumaryo:",
-       "subject-preview": "Paenot na patanaw sa Subheto/kapamayuhan:",
+       "summary-preview": "Paenot na patanawkang sumaryo kan pagliwat:",
+       "subject-preview": "Paenot na patanaw sa tema:",
        "blockedtitle": "An paragamit pinagbagat",
        "blockedtext": "'''An saimong paragamit na ngaran o IP address pinagkubkob.'''\n\nAn pagkubkob hinimo ni $1.\nAn rason na ipinagtao iyo an  ''$2''.\n\n* Pagpoon kan pagkubkob: $8\n* Pagpasó kan pagkubkob: $6\n* Katuyuhan kan parakubkob: $7\n\nIka puwedeng magkontak sa $1 or ibang [[{{MediaWiki:Grouppage-sysop}}|administrador]] tanganing pag-orolayan an pagkubkob.\nIka dae makakagamit kan 'e-koreo kaining paragamit' na panuytuyan laen lang na may sarong balidong e-koreo address na ipinahayag sa saimong [[Special:Preferences|panindog na mga kabotan]] asin ika dae pinagkubkob para sa paggamit kaini.\nAn saimong presenteng IP address iyo $3, asin an kubkob ID iyo #$5.\nPakibale na lang tabi an gabos na mga detalye sa itaas sa anuman na mga kahaputan na saimong himoon.",
        "autoblockedtext": "An saimong IP address awtomatikong pinagkubkob nin huli ta ini pinaggamit kan ibang paragamit, na pinagkubkob ni $1.\nAn rason na ipinagtao iyo na:\n\n:''$2''\n\n* Pagpoon kan pagkubkob: $8\n* Pagpasó kan pagkubkob: $6\n* Katuyuhan kan parakubkob: $7\n\nPuwede mong kontakon si $1 o saro sa [[{{MediaWiki:Grouppage-sysop}}|mga administrador]] tanganing pag-orolayan an kubkob.\n\nPatanid tabi dae mo puwedeng gamiton an \"e-koreo kaining paragamit\" estima laen lang kun ika igwa nin sarong balidong e-koreo address na rehistrado sa saimong [[Special:Preferences|paragamit na mga kabotan]] asin ika dae pinagkubkob para sa paggamit kaini.\n\nAn saimong presenteng IP address iyo an $3, asin and Kubkob ID iyo an #$5.\nPakibale tabi an gabos na mga detalye sa itaas sa arinman na mga kahaputan na saimong himoon.",
        "userjspreview": "'''Giromdomon tabi na pigtetest/pighihiling mo sana an patanaw kan saimong JavaScript nin paragamit, dai pa ini naitagama!'''",
        "sitecsspreview": "'''Giromdoma baya na ika nagtatanaw pa sana kaining CSS.'''\n'''Ini dae pa tabi naitatagama!'''",
        "sitejspreview": "'''Giromdoma baya na ika nagtatatanaw pa sana kaining koda sa JavaScript.'''\n'''Ini dae pa tabi naitatagama!'''",
-       "userinvalidconfigtitle": "'''Patanid:''' Mayong ''skin'' na \"$1\". Giromdomon tabî na an .css asin .js na mga páhina naggagamit nin titulong nakasurat sa sadit na letras, halimbawa {{ns:user}}:Foo/vector.css bakong {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Patanid:''' Mayong ''skin'' na \"$1\". Giromdomon tabî na an .css, .json, asin .js na mga páhina naggagamit nin titulong nakasurat sa sadit na letras, halimbawa {{ns:user}}:Foo/vector.css bakong {{ns:user}}:Foo/Vector.css.",
        "updated": "(Pinagsugpunan na)",
        "note": "'''Paisi:'''",
        "previewnote": "'''Giromdoma na ini sarong patanaw pa sana.'''\nAn saimong mga pinagriliwat dae pa tabi naitatagama!",
        "continue-editing": "Magduman sa lugar nin pagliliwat",
        "previewconflict": "Mahihilíng sa patànaw na ini an tekstong nasa itaas na lugar nin paghirá arog sa maipapahiling kun ini an itatagama mo.",
-       "session_fail_preview": "'''Despensa! Dai mi naipadagos an paghirá mo huli sa pagkawara nin datos kan sesyon.\nProbaran tabì giraray. Kun dai man giraray magibo, probaran na magluwas dangan maglaog giraray.'''",
-       "session_fail_preview_html": "'''Sori po! Dae tabi nyamo maiproseso an saimong pagliwat nin huli sa kawaraan kan datos sa sesyon.'''\n\n''Nin huli ta {{SITENAME}} igwa nin bakong pang naprosesong HTML pinagpagana, an patanaw ipinagtago bilang pag-ingat kontra sa atake kan JavaScript.''\n\n'''Kun ini sarong lehitimong pagprubar nin pagliwat, paki-otro tabi giraray.'''\nKun ini dae man giraray guminana, magprubar na [[Special:UserLogout|magluwas]] asin maglaog giraray.",
+       "session_fail_preview": "'''Despensa! Dai mi naipadagos an paghirá mo huli sa pagkawara nin datos kan sesyon.\nProbaran tabì giraray. Kun dai man giraray magibo, probaran [[Special:UserLogout|na magluwas]] dangan maglaog giraray.'''",
+       "session_fail_preview_html": "'''Sori po! Dae tabi nyamo maiproseso an saimong pagliwat nin huli sa kawaraan kan datos sa sesyon.'''\n\n<em>Nin huli ta {{SITENAME}} igwa nin bakong pang naprosesong HTML pinagpagana, an patanaw ipinagtago bilang pag-ingat kontra sa atake kan JavaScript.<em>\n\n'''Kun ini sarong lehitimong pagprubar nin pagliwat, paki-otro tabi giraray.'''\nKun ini dae man giraray guminana, magprubar na [[Special:UserLogout|magluwas]] asin maglaog giraray, asin siguraduhon na ang browser nag-aako nin cookies sa site na ini.",
        "token_suffix_mismatch": "'''Dai pigtogotan an paghirá mo ta sinabrit kan client mo an punctuation characters.\nDai pigtogotan ining paghirá tangarig maibitaran na maraot an teksto kan pahina.\nNanyayari nanggad ini kun naggagamit ka nin bakong maraháy asin dai bistong web-based proxy service.'''",
        "edit_form_incomplete": "'''An ibang mga parte kan porma nin pagliwat dae nakaabot sa serbidor; paki-dobleng mansay na an saimong mga pinagliwat bilog na yaon pa asin paki-otro giraray.'''",
        "editing": "Pigliliwat an $1",
        "readonlywarning": "'''Patanid tabi: An datos-sarayan nakakandado para sa maintenance, kaya ika dae makakapagtagama kan saimong mga pinagriliwat sa ngunyan.'''\nIka mapuwedeng makakopya asin idukot an saimong teksto pasiring sa sarong sagunson kan teksto asin itagama ini sa bandang huri.\n\nAn administrador na iyo an nagkandado kaini naghayag kaining kapaliwanagan: $1",
        "protectedpagewarning": "'''Patanid tabi: Ining pahina pinagprotektaran tanganing an mga paragamit sana na igwang pribilihiyo bilang administrador an makakapagliwat kaini.'''\nAn pinakahuring entrada sa talaan pinaghaya sa ibaba bilang reperensiya:",
        "semiprotectedpagewarning": "'''Note:''' Ining pahina pinagprotektaran na tanganing an mga rehistradong mga paragamit sana an mapuwedeng makapagliwat kaini.\nAn pinakahuring entrada sa talaan pinaghaya sa ibaba bilang reperensiya:",
-       "cascadeprotectedwarning": "<strong>Patanid:</strong> Ining pahina pinagprotehiran na tanganing an mga paragamit na igwa nin pan-administrador na mga pribilihiyo an makakaliwat kaini nin huli ta ini kabaling pinagbalyo sa minasunod na protektadong pasurunod na {{PLURAL:$1|pahina|mga pahina}}:",
+       "cascadeprotectedwarning": "<strong>Patanid:</strong> Ining pahina pinagprotehiran na tanganing an mga paragamit na igwa nin pan-administrador na mga [[Special:ListGroupRights|pribilihiyo]] an makakaliwat kaini nin huli ta ini kabaling pinagbalyo sa minasunod na protektadong pasurunod na {{PLURAL:$1|pahina|mga pahina}}:",
        "titleprotectedwarning": "'''Patanid tabi: Ining pahina pinagprotektaran na tanganing [[Special:ListGroupRights|espesipikong karapatan]] minakaipo tanganing magmukna kaini.'''\nAn pinakahuring entrada sa talaan pinaghaya sa ibaba bilang reperensiya:",
        "templatesused": "{{PLURAL:$1|Template|Mga Panguyog}} na pinaggamit kaining pahina:",
        "templatesusedpreview": "{{PLURAL:$1|Template|Mga Panguyog}} na pinaggamit kaining patanaw:",
        "defaultmessagetext": "Tugmadong mensahe sa teksto",
        "content-failed-to-parse": "Nagpalya sa paglunhay an $2 na laman para sa $1 na modelo: $3",
        "invalid-content-data": "Imbalidong datos nin laman",
-       "content-not-allowed-here": "\"$1\" na laman dae pinagtutugutan sa pahina [[:$2]]",
+       "content-not-allowed-here": "\"$1\" na laman dae pinagtutugutan sa pahina [[:$2]] sa \"$3\"",
        "editwarning-warning": "Sa pagbaya kaining pahina magkakausa saimo na mawara an anuman na mga kaliwatan na saimong pinaghimo. Kun ika nakapaglaog na, ika puwedeng makapagpauntok kaining patanid sa \"{{int:prefs-editing}}\" na seksyon kan saimong mga kamuyahan.",
+       "editpage-invalidcontentmodel-title": "Kalamnan nin pormat bakong suportado",
        "editpage-notsupportedcontentformat-title": "Kalamnan nin pormat bakong suportado",
        "editpage-notsupportedcontentformat-text": "An pormat nin kalamnan na $1 bakong suportado kan modelong kalamnan na $2.",
+       "slot-name-main": "Kapamayuhanan",
        "content-model-wikitext": "wiki-teksto",
        "content-model-text": "yanong-teksto",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
+       "content-json-empty-object": "Halion an bagay",
+       "content-json-empty-array": "Halion an array",
        "duplicate-args-warning": "<strong>Patanid:</strong> [[:$1]] nag-aapod [[:$2]] na igwa nin sobra sa sarong halaga para sa \"$3\" na parametro. An pinakahuring halaga sanang ipinagtao an magagamit.",
        "expensive-parserfunction-warning": "'''Patanid tabi:''' Ining pahina naglalaman nin grabe kadakulon na ekspensibong programang pambaranga sa punksyon nin mga pag-aapod.\n\nIni dapat magkaigwa nin menos sanang $2 {{PLURAL:$2|apod|mga apod}}, igwa na {{PLURAL:$1|ngunyan nin $1 apod|ngunyan nin $1 mga apod}}.",
        "expensive-parserfunction-category": "Mga pahina na igwa nin grabe kadakulon na mga ekspensibong programang pambaranga sa punksyon nin mga pag-aapod",
        "revdelete-no-file": "An sagunson na pinaghayag dae tabi eksistido.",
        "revdelete-show-file-confirm": "Segurado ka tabi na gusto mo matanaw sarong pinagpurang pagbabago kan sagunson \"<nowiki>$1</nowiki>\" poon $2 sa $3?",
        "revdelete-show-file-submit": "Iyo tabi",
+       "revdelete-selected-text": "{{PLURAL:$1|Selected revision|Mga napiling rebisyon}} kan [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Selected log event|Mga piniling talaan kan mga pangyayari}}:",
        "revdelete-confirm": "Pakikumpirma tabi na ika tuyong gumibo kaini, na saimong naintindihan an mga konsekuwensiya, asin ta ika pinaghihimo ini na uyon sa [[{{MediaWiki:Policy-url}}|an palisiya]].",
        "revdelete-suppress-text": "An paglulubog dapat '''sana''' magagamit para sa minasunod na mga kaso:\n*Potensiyal na libeloso an impormasyon\n*Bakong angay an personal na impormasyon\n*:''mga estada nin ini-erokan asin mga numero kan telepono, nasyunal na numero nin kabistohan, asin iba pa.''",
        "mergehistory-empty": "Mayong mga pagbabago na puwedeng mapagtiripon.",
        "mergehistory-done": "$3 {{PLURAL:$3|pagbabago|mga pagbabago}} sa $1 matrayumpong napagtiripon na magin [[:$2]].",
        "mergehistory-fail": "Dae tabi makayanan na makapaghimo nin historiyang pagtiripon, tabi pakihiling giraray an pahina asin parametro kan oras.",
+       "mergehistory-fail-bad-timestamp": "Imbalido an timestamp.",
+       "mergehistory-fail-invalid-source": "Imbalido an ginikanang pahina.",
+       "mergehistory-fail-invalid-dest": "Imbalido an papadumanan na pahina.",
        "mergehistory-no-source": "Gikanang pahina $1 bakong eksistido.",
        "mergehistory-no-destination": "Destinasyong pahina $1 bakong eksistido.",
        "mergehistory-invalid-source": "Gikanang pahina kaipuhan magin saro na balidong titulo.",
        "diff-multi-manyusers": "({{PLURAL:$1|Sarong intermediate na pagbabago|$1 mga intermediate na mga pagbabago}} na sobra sa $2 {{PLURAL:$2|paragamit|mga paragamit}} dae pinaghahayag)",
        "difference-missing-revision": "{{PLURAL:$2|sarong rebisyon|$2 mga rebisyon}} kaining diperensiya ($1) {{PLURAL:$2|na iyo an|kaidto na iyo an}} dae nanagboan.\n\nIni pirmihan na pinagkakausa sa paagi nin pagsusunod nin luwas sa petsang diff na kasugponan pasiring sa sarong pahina na pinagpura na.\nAn mga detalye mapuwedeng matatagboan sa [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} talaan kan pinagpuraan].",
        "searchresults": "Resulta kan paghahánap",
+       "search-filter-title-prefix-reset": "Maghanap sa gabos na pahina",
        "searchresults-title": "Resulta kan paghahanap para sa \"$1\"",
        "titlematches": "Angay an título kan artíkulo",
        "textmatches": "Angay an teksto nin páhina",
        "notextmatches": "Mayong ángay na teksto nin páhina",
        "prevn": "an nakaagi{{PLURAL:$1|$1}}",
        "nextn": "an masunód{{PLURAL:$1|$1}}",
+       "prev-page": "← nakaaging pahina",
+       "next-page": "sunod na pahina →",
        "prevn-title": "Dati $1 {{PLURAL:$1|resulta|mga resulta}}",
        "nextn-title": "Sunod $1  {{PLURAL:$1|resulta|mga resulta}}",
        "shown-title": "Ipahiling $1  {{PLURAL:$1|resulta|mga resulta}} sa kada pahina",
        "search-result-category-size": "{{PLURAL:$1|1 miyembro|$1 mga miyembro}} ({{PLURAL:$2|1 subkategorya|$2 mga subkategorya}}, {{PLURAL:$3|1 sagunson|$3 mga sagunson}})",
        "search-redirect": "(panukdong hali sa $1)",
        "search-section": "(Seksyon $1)",
+       "search-category": "(kategorya $1)",
        "search-file-match": "(minatugma sa nilalaog kan saguson)",
        "search-suggest": "Boot mong ipakahulugan: $1",
+       "search-rewritten": "Nagpahiling nin resulta para sa $1. Naghanap nin bako sa $2.",
        "search-interwiki-caption": "Tugang na mga proyekto",
        "search-interwiki-default": "$1 na mga resulta:",
        "search-interwiki-more": "(dakol pa)",
+       "search-interwiki-more-results": "Dakul pang resulta",
        "search-relatedarticle": "Kauyon",
        "searchrelated": "kauyon",
        "searchall": "gabós",
        "powersearch-togglelabel": "Pamili:",
        "powersearch-toggleall": "Gabos",
        "powersearch-togglenone": "Wara",
+       "powersearch-remember": "Girumdumon an pinili para sa mga susunod na paghanap",
        "search-external": "Panluwas na paghahanap",
        "searchdisabled": "Pigpopogolan mûna an paghanap sa {{SITENAME}}. Mientras tanto, pwede ka man maghanap sa Google. Giromdomon tabî na an mga indise kan laog ninda sa {{SITENAME}} pwede ser na lumâ na.",
        "search-error": "May salang nangyari habang naghahanap:$1",
+       "search-warning": "May salang nangyari habang naghahanap: $1",
        "preferences": "Mga kabòtan",
        "mypreferences": "Mga Kamuyahan ko",
        "prefs-edits": "Bilang kan mga hirá:",
-       "prefsnologintext2": "Tabi man $1 tanganing maikaag an mga kamuyahan nin paragamit.",
+       "prefsnologintext2": "Tabi man maglaog tanganing maikaag an mga kamuyahan nin paragamit.",
        "prefs-skin": "''Skin''",
        "skin-preview": "Tânawon",
        "datedefault": "Mayong kabôtan",
        "prefs-personal": "Pambisto nin parágamit",
        "prefs-rc": "Mga kaaagi pa sanang pagribay",
        "prefs-watchlist": "Pigbabantayan",
+       "prefs-editwatchlist": "Hirahón an pigbabantayan",
+       "prefs-editwatchlist-label": "Baguhon an mga entry sa saimong bantay-listahan:",
+       "prefs-editwatchlist-edit": "Hilingon asin magtanggal nin mga titulo sa saimong bantay-listahan",
+       "prefs-editwatchlist-raw": "Liwaton an hilaw na bantay-listahan",
+       "prefs-editwatchlist-clear": "Linigon an bantay-listahan",
        "prefs-watchlist-days": "Mga aldaw na ipahiling sa batay-listahan:",
        "prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|aldaw|mga aldaw}}",
        "prefs-watchlist-edits": "Máximong número nin pagbabâgo na ipapahiling sa pinadakulang lista nin pigbabantayan:",
        "prefs-watchlist-token": "Token sa Bantay-listahan:",
        "prefs-misc": "Lain",
        "prefs-resetpass": "Liwaton an sekretong panlaog",
-       "prefs-changeemail": "Liwaton an e-surat na adres",
+       "prefs-changeemail": "Ribayan an e-koreong address",
        "prefs-setemail": "Tuytuyon an e-surat na adres",
        "prefs-email": "E-surat na mga pagpipilian",
        "prefs-rendering": "Hitsurahon",
        "restoreprefs": "Balikon an gabos na panugmad na mga panuytoy (sa gabos na mga seksyon)",
        "prefs-editing": "Pighihira",
        "searchresultshead": "Hanápon",
-       "stub-threshold": "Kasagkoran kan <a href=\"#\" class=\"stub\">takod kan tambô</a> pigpopormato:",
+       "stub-threshold": "Kasagkoran kan <a href=\"#\" class=\"stub\">takod kan tambô</a> pigpopormato ($1):",
+       "stub-threshold-sample-link": "ehemplo",
        "stub-threshold-disabled": "Pinagpundo",
        "recentchangesdays": "Mga aldáw na ipapahilíng sa mga nakakaági pa sanáng pagbabàgó:",
        "recentchangesdays-max": "Maksimum $1 {{PLURAL:$1|aldaw|mga aldaw}}",
-       "recentchangescount": "Numero kan mga pagliliwat na ipapahiling na pirmihan:",
-       "prefs-help-recentchangescount": "Kabali kaini an dae pa nahaloy na mga kaliwatan, mga historiyang pahina, asin mga talaan.",
+       "recentchangescount": "Numero kan mga pagliliwat na ipapahiling sa pinakabago, historia kan pahina, paglaog:",
+       "prefs-help-recentchangescount": "Maksimum na numero: 1000",
        "prefs-help-watchlist-token2": "Ini an sikretong susi sa bahugan kan web sa saimong bantay-listahan.\nAn siisay man na makaaram kaini makakapagbasa kan saimong bantay-listahan, kaya dae mo ipagheras ini.\n[[Special:ResetTokens|I-klik digde kun kaipo mong baguhon it]].",
        "savedprefs": "Itinagama na an mga kabôtan mo.",
        "timezonelegend": "Pan-oras na sona:",
        "default": "pwestong normal",
        "prefs-files": "Mga dokumento",
        "prefs-custom-css": "Kustombreng CSS",
+       "prefs-custom-json": "Custom JSON",
        "prefs-custom-js": "Kustombreng JavaScript",
-       "prefs-common-config": "Pinagheras na CSS/JavaScript para sa gabos na mga kalapatan:",
+       "prefs-common-config": "Pinagheras na CSS/JSON/JavaScript para sa gabos na mga kalapatan:",
        "prefs-reset-intro": "Ika makakagamit kaining pahina tanganing ilapat giraray an saimong mga kabotan sa panugmad kan sayt.\nIni dae tabi matitingkog.",
        "prefs-emailconfirm-label": "Kumpirmasyon sa E-koreo",
        "youremail": "E-surat:",
        "username": "{{GENDER:$1|Pangaran nin paragamit}}:",
        "prefs-memberingroups": "{{GENDER:$2|Miyembro}} kan {{PLURAL:$1|grupo|mga grupo}}:",
+       "group-membership-link-with-expiry": "$1 (hanggan $2)",
        "prefs-registration": "Rehistrasyong oras:",
        "yourrealname": "Totoong pangaran:",
        "yourlanguage": "Tataramon:",
        "gender-female": "Siya nagliliwat nin mga pahina sa wiki",
        "prefs-help-gender": "An panuytoy kaining kamuyahan opsyonal.\nAn panuklob minagamit kan saiyang kahalagahan sa pagpanungod saimo asin sa pagsambit saimo sa iba pa na naggagamit nin maninigong gramatikal na kabolosan.\nIning impormasyon isasapubliko.",
        "email": "E-koreo",
-       "prefs-help-realname": "Opsyonal an totoong pangaran asin kun itatao mo ini, gagamiton ini yangarig an mga sinurat mo maatribuir saimo.",
+       "prefs-help-realname": "Opsyonal an totoong pangaran asin kun itatao mo ini, gagamiton ini tangarig an mga sinurat mo maatribuir saimo.",
        "prefs-help-email": "An e-surat na adres sarong opsyonal, alagad ini kinakaipohan para sa pagtuytoy otro kan sekretong panlaog, kun ika malingaw kan saimong sekretong panlaog.",
        "prefs-help-email-others": "Ika kan man pumili na magtugot sa iba na makontak ka sa e-surat sa paagi nin sarong kasugponan na yaon sa saimong pahina nin paragamit o olay.\nAn saimong e-surat na adres dae ipagbuyagyag kunsoarin na an ibang paragamit makontak saimo.",
        "prefs-help-email-required": "Kaipuhan an e-koreo.",
        "prefs-dateformat": "Pampetsang pormat",
        "prefs-timeoffset": "Pan-oras na tapal",
        "prefs-advancedediting": "Pankagabsan na mga Pagpipilian",
+       "prefs-developertools": "Mga kagamitan nin Paragibo",
        "prefs-editor": "Paraliwat",
        "prefs-preview": "Patânaw",
        "prefs-advancedrc": "Pangenot na mga pagpipilian",
        "prefs-advancedwatchlist": "Abantidong mga pagpipilian",
        "prefs-displayrc": "Ihayag an mga pagpipilian",
        "prefs-displaywatchlist": "Ipahiling ang mga pagpipilian",
+       "prefs-changesrc": "Ipinahiling an mga pagbabago",
+       "prefs-changeswatchlist": "Ipinahiling an mga pagbabago",
+       "prefs-pageswatchlist": "Mga binabantayan na mga pahina",
        "prefs-tokenwatchlist": "Paduos",
        "prefs-diffs": "Diffs",
        "prefs-help-prefershttps": "Ining kamuyahan magkaka-epekto sa masunod mong paglaog.",
        "prefs-tabs-navigation-hint": "Pantama: Ika makakagamit nin wala asin too na pansusing pana tanganing magnabigar sa tahaw kan mga tanda na yaon sa listahan nin mga panandaan.",
-       "userrights": "Pagmaneho kan mga derecho nin paragamit",
-       "userrights-lookup-user": "Magmaného kan mga grupo nin parágamit",
+       "userrights": "Karapatan kan paragamit",
+       "userrights-lookup-user": "Magpili nin parágamit",
        "userrights-user-editname": "Ilaog an pangaran kan parágamit:",
-       "editusergroup": "Hirahón an mga Grupo kan Parágamit",
+       "editusergroup": "Ipahiling an mga Grupo kan Parágamit",
        "editinguser": "Sinasanglian an mga karapatan kan paragamit na si {{GENDER:$1|paragamit}} <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Hirahón an mga grupo kan parágamit",
-       "saveusergroups": "Itagama an mga Grupo nin Páragamit",
+       "userrights-editusergroup": "Hirahón an mga {{GENDER:$1|grupo kan parágamit}}",
+       "userrights-viewusergroup": "Hilingon {{GENDER:$1|paragamit}} an grupo",
+       "saveusergroups": "Itagama an {{GENDER:$1|mga Grupo nin Páragamit}}",
        "userrights-groupsmember": "Myembro kan:",
        "userrights-groupsmember-auto": "Implisitong miyembro kan:",
        "userrights-groups-help": "Ika puwedeng magbago kan mga grupo na kinabalihan kaining paragamit:\n*An natsekan na kahon minapasabot na an paragamit kabali sa grupong yan.\n*An mayong tsek na kahon minapasabot na an paragamit bakong kabali sa grupong yan.\n* A * minapahiwatig na ika dae puwedeng makapaghale kan grupo kun naidagdag mo na ini, or vice versa.",
        "userrights-nodatabase": "An datos-sarayan $1 bakong eksistido o bakong lokal.",
        "userrights-changeable-col": "Mga grupo na mapuwede mong baguhon",
        "userrights-unchangeable-col": "Mga grupo na dae mo mapuwedeng baguhon",
+       "userrights-expiry-current": "Mapalso sa $1",
+       "userrights-expiry-none": "Dai napapalso",
+       "userrights-expiry": "Mápasó:",
+       "userrights-expiry-existing": "Eksistidong oras nin pagpalso: $3, $2",
+       "userrights-expiry-othertime": "Ibang oras:",
+       "userrights-expiry-options": "1 aldaw:1 aldaw, 1 semana:1 semana, 1 bulan:1 bulan, 3 bulan:3 bulan, 6 bulan:6 bulan, 1 taon:1 taon",
        "userrights-conflict": "Kumplikto sa mga kaliwatan nin mga katanosan kan paragamit! Tabi man pakirikisa asin kumpirmaron an saimong mga kaliwatan.",
        "group": "Grupo:",
        "group-user": "Mga Paragamit",
        "group-autoconfirmed-member": "{{GENDER:$1|auto-kumpirmadong paragamit}}",
        "group-bot-member": "{{GENDER:$1|bot}}",
        "group-sysop-member": "{{GENDER:$1|administrador}}",
+       "group-interface-admin-member": "{{GENDER:$1|administrador kan interface}}",
        "group-bureaucrat-member": "{{GENDER:$1|burokrata}}",
        "group-suppress-member": "{{GENDER:$1|tagapagmato}}",
        "grouppage-user": "{{ns:project}}:Mga Paragamit",
        "grouppage-autoconfirmed": "{{ns:project}}:Mga enseguidang nakonpirmar na parágamit",
        "grouppage-bot": "{{ns:project}}:Mga bot",
        "grouppage-sysop": "{{ns:project}}:Mga tagamató",
+       "grouppage-interface-admin": "{{ns:project}}:Mga administrador kan interface",
        "grouppage-bureaucrat": "{{ns:project}}:Mga bureaucrat",
        "grouppage-suppress": "{{ns:project}}:Tagapagmato",
        "right-read": "Magbasa kan mga pahina",
        "right-move": "Ibalyo an mga pahina",
        "right-move-subpages": "Ibalyo an mga pahina kaiba an saindang mga sub-pahina",
        "right-move-rootuserpages": "Ibalyo an ugat nin mga pahina kan paragamit",
+       "right-move-categorypages": "Ilipat an mga pahina kan kategorya",
        "right-movefile": "Ibalyo an mga sagunson",
        "right-suppressredirect": "Dae tabi magmukna nin paotrong direksyon gikan sa ginikanang mga pahina kunsoarin magbabalyo nin mga pahina",
        "right-upload": "Ipagkarga an mga sagunson (file)",
        "right-browsearchive": "Hanapon an pinagpurang mga pahina",
        "right-undelete": "Dae puraon an pahina",
        "right-suppressrevision": "Hilngon otro asin balikon an mga pagbabagong itinago gikan sa mga administrador",
+       "right-viewsuppressed": "Hilingon an mga rebisyon na nakatago sa iba pang mga paragamit",
        "right-suppressionlog": "Tanawon an pribadong mga talaan",
        "right-block": "Kubkubon an ibang mga paragamit sa pagliliwat",
        "right-blockemail": "Kubkubon an paragamit na makapagpadara nin e-koreo",
        "right-editusercss": "Liwaton an CSS na mga sagunson kan ibang mga paragamit",
        "right-edituserjson": "Liwaton an JSON na mga sagunson kan ibang mga paragamit",
        "right-edituserjs": "Liwaton an JavaScript na mga sagunson kan ibang mga paragamit",
+       "right-editsitecss": "Liwaton an buong CSS",
+       "right-editsitejson": "Liwaton an buong JSON",
+       "right-editsitejs": "Liwaton an buong JavaScript",
        "right-editmyusercss": "Liwaton an saimong sadireng paragamit na sagunson sa CSS",
        "right-editmyuserjson": "Liwaton an saimong sadireng paragamit na sagunson sa JSON",
        "right-editmyuserjs": "Liwaton an saimong sadireng paragamit na sagunson sa JavaScript",
        "right-siteadmin": "Kandaduhan asin dae pagkandaduhan an datos-sarayan",
        "right-override-export-depth": "Eksportaron an mga pahina kabali na an pinagkilyawan na mga pahina sagkod sa rarom na 5",
        "right-sendemail": "Magpadara nin e-koreo sa ibang mga paragamit",
-       "grant-editmycssjs": "Liwaton an saimong paragamit CSS/JavaScript",
-       "grant-editmyoptions": "Liwaton an saimong paragamit na mga kamuyahan",
+       "right-managechangetags": "Maggibo asin maghali [[Special:Tags|tags]]",
+       "grant-group-page-interaction": "Interaksyon sa mga pahina",
+       "grant-group-file-interaction": "Interaksyon sa mga medya",
+       "grant-group-watchlist-interaction": "Interaksyon sa saimong bantay-listahan",
+       "grant-group-email": "Ipadara an e-surat",
+       "grant-blockusers": "Bagaton asin maghali nin pagkabagat kan mga paragamit",
+       "grant-createaccount": "Magmukna nin panindog",
+       "grant-createeditmovepage": "Maggibo, magliwat asin maglipat nin pahina",
+       "grant-editmycssjs": "Liwaton an saimong paragamit CSS/JSON/JavaScript",
+       "grant-editmyoptions": "Liwaton an saimong paragamit na mga kamuyahan asin configurayson kan JSON",
+       "grant-editmywatchlist": "Liwaton an saimong bantay-listahan",
+       "grant-editpage": "Liwaton an mga yaun nang pahina",
+       "grant-editprotected": "Liwaton an mga napoprotektaran na pahina",
+       "grant-protect": "Magprotekta asin magtanggal nin proteksyon sa mga pahina",
+       "grant-sendemail": "Magpadara nin e-koreo sa ibang mga paragamit",
+       "grant-uploadeditmovefile": "Magkarga, magribay asin maglipat nin mga sagunson",
+       "grant-uploadfile": "Magkarga nin bagong mga sagunson",
        "grant-viewdeleted": "Tanawon an pinagpurang mga sagunson asin pahina",
+       "grant-viewmywatchlist": "Tanawon an saimong bantay-listahan",
        "newuserlogpage": "Paragamit na talaan nin pagmukna",
        "newuserlogpagetext": "Ini an talaan kan mga pagmukna nin paragamit.",
        "rightslog": "Usip nin derechos nin paragamit",
        "rightslogtext": "Ini an historial kan mga pagbabâgo sa mga derecho nin parágamit.",
        "action-read": "basaha ining pahina",
        "action-edit": "liwatón ining pahina",
-       "action-createpage": "magmukna nin mga pahina",
+       "action-createpage": "Muknaon ining pahina",
        "action-createtalk": "Magmukna nin mga pahina sa orolayan",
        "action-createaccount": "Muknaon ining panindog kan paragamit",
+       "action-history": "Hilingon an historiya kaining pahina",
        "action-minoredit": "marakahan ining pagliwat bilang menor",
        "action-move": "ibalyo ining pahina",
        "action-move-subpages": "ibalyo ining pahina, asin kaiba an mga sub-pahina",
        "action-move-rootuserpages": "ibalyo an ugat kan mga pahina nin mga paragamit",
+       "action-move-categorypages": "Ilipat an mga pahina kan kategorya",
        "action-movefile": "ibalyo ining sagunson",
        "action-upload": "ikarga ining mga sagunson",
        "action-reupload": "sampawan ining eksistidong sagunson",
        "action-delete": "puraon ining pahina",
        "action-deleterevision": "puraon ining pagbabago",
        "action-deletedhistory": "tanawon an pinagpurang historiya kaining pahina",
+       "action-deletedtext": "hilingon an pinura na mga rebisyon",
        "action-browsearchive": "hanapon an pinagpurang mga pahina",
        "action-undelete": "dae pagpuraon ining pahina",
        "action-suppressrevision": "hilngon otro asin ibalik ining pinagtagong pagbabago",
        "action-userrights-interwiki": "liwaton an paragamit na mga karapatan kan mga paragamit nin ibang wikis",
        "action-siteadmin": "ikandado o dae ikandado an datos-sarayan",
        "action-sendemail": "magpadara nin mga e-koreo",
+       "action-editmyoptions": "liwaton an sadiri mong mga kamuyahan",
        "action-editmywatchlist": "liwaton an saimong bantay-listahan",
        "action-viewmywatchlist": "tanawon an saimong bantay-listahan",
        "action-viewmyprivateinfo": "tanawon an saimong pribadong impormasyon",
        "action-editmyprivateinfo": "liwaton an saimong pribadong impormasyon",
+       "action-purge": "tanggalon an pahinang ini",
+       "action-apihighlimits": "maggamit nin harahalangkaw na sagkodan sa mga kahaputan kan API",
+       "action-autoconfirmed": "Dai magin apektado sa paagi kan rata na nakabase sa IP na mga sagkodan",
+       "action-bigdelete": "Puraon an mga pahina na igwang darakulang mga historiya",
+       "action-blockemail": "bagaton an paragamit na makapagpadara nin e-koreo",
+       "action-bot": "Pagtratuhon bilang awtomatikong proseso",
+       "action-editprotected": "Liwaton an mga pahina na protektado bilang \"{{int:protect-level-sysop}}\"",
+       "action-editsemiprotected": "Liwaton an mga pahina na protektado bilang \"{{int:protect-level-autoconfirmed}}\"",
+       "action-editinterface": "Liwaton an interface kan paragamit",
+       "action-editusercss": "Liwaton an CSS na mga sagunson kan ibang mga paragamit",
+       "action-edituserjson": "Liwaton an JSON na mga sagunson kan ibang mga paragamit",
+       "action-edituserjs": "Liwaton an JavaScript na mga sagunson kan ibang mga paragamit",
+       "action-editsitecss": "Liwaton an buong CSS",
+       "action-editsitejson": "Liwaton an buong JSON",
+       "action-editsitejs": "Liwaton an buong JavaScript",
+       "action-editmyusercss": "Liwaton an saimong sadireng paragamit na sagunson sa CSS",
+       "action-editmyuserjson": "Liwaton an saimong sadiring paragamit na sagunson sa JSON",
+       "action-editmyuserjs": "Liwaton an saimong sadiring paragamit na sagunson sa JavaScript",
+       "action-viewsuppressed": "Hilingon an mga rebisyon na nakatago dawa na isay na paragamit",
+       "action-hideuser": "Kubkubon an pangaran nin paragamit, itago ini sa publiko",
+       "action-ipblock-exempt": "Sampawan an pangubkob kan IP, awtomatikong-kubkob asin panhalawig na kubkob",
+       "action-unblockself": "Halion an bagat sa sadiri",
+       "action-noratelimit": "Dae magin apektado sa paagi kan rata nin mga sagkodan",
+       "action-reupload-own": "Patungan an eksistido nang mga pahina na ipinagkarga sa paagi mo",
+       "action-nominornewtalk": "Dae gayod nagkaigwa nin menor na pagliwat sa mga pahina nin orolayan minasulpang nin bunyaw kan bagong mga mensahe",
+       "action-markbotedits": "Markahan an pinagbalik na mga niliwat bilang bot na panliwat",
+       "action-patrolmarks": "Tanawon an pinakahuring mga pagbabago na markadong patrol",
+       "action-override-export-depth": "Eksportaron an mga pahina kabali na an pinagkilyawan na mga pahina sagkod sa rarom na 5",
+       "action-suppressredirect": "Dae tabi magmukna nin paotrong direksyon gikan sa ginikanang mga pahina kunsoarin magbabalyo nin mga pahina",
        "nchanges": "$1 {{PLURAL:$1|kaliwatan|mga kaliwatan}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|poon kaidtong huring bisita}}",
        "enhancedrc-history": "historiya",
        "recentchanges-legend-heading": "<strong>Kabalaynan:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (hilngon man [[Special:NewPages|listahan kan mga baguhong pahina]])",
        "recentchanges-legend-plusminus": "(''±saro-duwa-tolo'')",
+       "recentchanges-submit": "Ipahiling",
+       "rcfilters-tag-remove": "Halion '$1'",
+       "rcfilters-group-results-by-page": "Grupong resulta ayon sa pahina",
+       "rcfilters-activefilters": "Mga aktibong pangsara",
+       "rcfilters-activefilters-hide": "Itago",
+       "rcfilters-activefilters-show": "Ipahiling",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|aldaw|mga aldaw}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|oras|mga oras}}",
+       "rcfilters-quickfilters": "Itagama an saraan",
+       "rcfilters-quickfilters-placeholder-title": "Mayo pang pangsara na nakatagama",
+       "rcfilters-savedqueries-defaultlabel": "Itagama an saraan",
+       "rcfilters-savedqueries-rename": "Pangaranan liwat",
+       "rcfilters-savedqueries-remove": "Puraon",
+       "rcfilters-savedqueries-new-name-label": "Pangaran",
+       "rcfilters-savedqueries-apply-label": "Magmukna nin pangsara",
+       "rcfilters-savedqueries-cancel-label": "Kanselaron",
+       "rcfilters-clear-all-filters": "Klaradohon an mga saraan",
+       "rcfilters-search-placeholder-mobile": "Mga saraan",
+       "rcfilters-filterlist-title": "Mga pangsara",
+       "rcfilters-highlightmenu-title": "Magpili nin kulay",
+       "rcfilters-filterlist-noresults": "Mayong pangsara na nahiling",
+       "rcfilters-filter-editsbyself-label": "Mga kaliwatan mo",
+       "rcfilters-filter-editsbyself-description": "An saimong mga kaarambagan",
+       "rcfilters-filter-editsbyother-label": "Mga kaliwatan nin iba",
+       "rcfilters-filter-editsbyother-description": "Gabos na kaliwatan pwera kan saimo.",
+       "rcfilters-filter-user-experience-level-registered-label": "Rehistrado",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Dai rehistrado",
+       "rcfilters-filter-user-experience-level-experienced-label": "Mga eksperyensyadong paragamit",
+       "rcfilters-filter-bots-label": "Bot",
+       "rcfilters-filter-reviewstatus-unpatrolled-label": "Dai patrolado",
+       "rcfilters-filter-minor-label": "Sadit na kaliwatan",
+       "rcfilters-filtergroup-watchlist": "Mga binabantayan na mga pahina",
+       "rcfilters-filter-watchlist-watched-label": "Nasa bantay-listahan",
+       "rcfilters-filter-watchlist-notwatched-label": "Mayo sa bantay-listahan",
+       "rcfilters-filter-watchlistactivity-unseen-label": "Dai nahiling na pagbabago",
+       "rcfilters-filtergroup-changetype": "Klase kan pagbago",
+       "rcfilters-filter-pageedits-label": "Pagliwat sa pahina",
+       "rcfilters-filter-newpages-label": "Pagmukna kan pahina",
+       "rcfilters-filter-newpages-description": "Mga kaliwatan na nagibo nin bagong pahina",
+       "rcfilters-filter-categorization-label": "Pagbago sa kategorya",
+       "rcfilters-filtergroup-lastrevision": "Sa ngunyan na rebisyon",
+       "rcfilters-filter-lastrevision-label": "Sa ngunyan na rebisyon",
+       "rcfilters-filter-previousrevision-label": "Bako an pinakabagong rebisyon",
        "rcnotefrom": "Sa ibaba {{PLURAL:$5|iyo an kaliwatan|an mga kaliwatan}} poon kan <strong>$3, $4</strong> (sagkod <strong>$1</strong> an pinapahiling).",
+       "rclistfromreset": "Liwaton an pagpili kan petsa",
        "rclistfrom": "Ipahiling an baguhon na mga kaliwatan magpoon kan $3 $2",
        "rcshowhideminor": "$1 saradit na mga pagliwat",
        "rcshowhideminor-show": "Ipatanaw",
        "uploaderror": "Salâ an pagkarga",
        "upload-recreate-warning": "'''Patanid tabi: An sagunson sa pangaran kaini pinagpura o pinagbalyo na tabi.'''\n\nAn talaan kan pagkapura asin pagkabalyo para sa pahinang ini yaon digde para sa saimong konbenyensiya:",
        "uploadtext": "Gamita an porma sa ibaba tanganing makapagkarga nin mga sagunson.\nPara hilngon o hanapon an dati nang pinagkargang mga sagunson, magduman tabi sa [[Special:FileList|listahan kan pinagkargang mga sagunson]], mga pagkarga asin pagkarga otro pinagtala man sa [[Special:Log/upload|talaan nin pagkakarga]], mga pinagpura na yaon sa [[Special:Log/delete|talaan nin pagkapura]].\n\nSa pagbali nin sarong sagunson sa sarong pahina, gamita tabi an takod kan saro sa mga minasunod na mga porma:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' sa paggamit kan bilog na bersyon kan sagunson\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|alt text]]</nowiki></code>''' sa paggamit kan 200 pixel na lawig kan pagkakua sa sarong kahon na yaon sa parteng wala nin gaygayan na yaon an 'alt text' bilang deskripsyon\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' para sa direktang nakakatakod sa sagunson na dae pinagpapahiling na sarong sagunson",
-       "upload-permitted": "Pinagtutugutang mga tipo nin sagunson: $1",
-       "upload-preferred": "Pinagpapaurog na mga tipo nin sagunson: $1",
-       "upload-prohibited": "Pinagbabawal na mga tipo nin sagunson: $1.",
+       "upload-permitted": "Pinagtutugutang mga tipo nin sagunson {{PLURAL:$2|type|types}}: $1",
+       "upload-preferred": "Pinagpapaurog na mga tipo nin sagunson {{PLURAL:$2|type|types}}: $1",
+       "upload-prohibited": "Pinagbabawal na mga tipo nin sagunson {{PLURAL:$2|type|types}}: $1.",
        "uploadlogpage": "Ikarga an katalaanan",
        "uploadlogpagetext": "Yaon sa ibaba an sarong listahan kan dae pa sanang nahahaloy na pinagkargang mga sagunson.\nHilngon tabi an [[Special:NewFiles|galleriya kan mga bagong sagunson]] para sa mas biswal na lantawon.",
        "filename": "Pangaran kan dokumento",
        "largefileserver": "Mas dakula an ''file'' sa pigtotogotan na sokol kan ''server''.",
        "emptyfile": "Garo mayong laog an ''file'' na kinarga mo. Pwede ser na salâ ining tipo nin ''filename''. Isegurado tabî kun talagang boot mong ikarga ining ''file''.",
        "windows-nonascii-filename": "Ining wiki dae tabi nagsusuporta kan mga pangaran kan sagunson na igwang espesyal na mga karakter.",
-       "fileexists": "Igwa nang ''file'' na may parehong pangaran sa ini, sosogon tabî an <strong>[[:$1]]</strong> kun dai ka seguradong ribayan ini.\n[[$1|thumb]]",
+       "fileexists": "Igwa nang ''file'' na may parehong pangaran sa ini, sosogon tabî an <strong>[[:$1]]</strong> kun {{GENDER:|ika}} dai ka seguradong ribayan ini.\n[[$1|thumb]]",
        "filepageexists": "An pahinang pandeskripsyon kaining sagunson pinagmukna na tabi sa <strong>[[:$1]]</strong>, alagad mayong sagunson na igwa kaining pangaran sa ngunyan nag-eeksister.\nAn sumaryong na saimong ipinaglaog dae minaluwas sa pahina kan deskription.\nTanganing gibohon na an saimong sumaryo magluwas duman, kaipohan mong manwal na pagliliwat kaini.\n[[$1|thumb]]",
        "fileexists-extension": "May ''file'' na may parehong pangaran: [[$2|thumb]]\n* Pangaran kan pigkakargang ''file'': <strong>[[:$1]]</strong>\n* Pangaran kan yaon nang ''file'': <strong>[[:$2]]</strong>\nMagpili tabî nin ibang pangaran.",
        "fileexists-thumbnail-yes": "An ''file'' garo ladawan kan pinasadit ''(thumbnail)''. [[$1|thumb]]\nSosogon tabî an ''file'' <strong>[[:$1]]</strong>.\nKun an sinosog na ''file'' iyo an parehong ladawan na nasa dating sokol, dai na kaipuhan magkarga nin iba pang retratito.",
        "upload-too-many-redirects": "An kilyawan nagkaigwa nin kadakol na mga kaliwatan",
        "upload-http-error": "Sarong HTTP na kasalaan an nangyari: $1",
        "upload-copy-upload-invalid-domain": "Pangungupkop nin kopya bakong puwede gikan sa kinasakupan kaini.",
+       "upload-form-label-infoform-date": "Petsa",
        "backend-fail-stream": "Dae maipakupsit an sagunson $1.",
        "backend-fail-backup": "Dae makapagtago nin saro pang kopya an sagunson $1.",
        "backend-fail-notexists": "An sagunson na $1 bakong eksistido.",
        "backend-fail-read": "Dae makakabasa nin sagunson $1.",
        "backend-fail-create": "Dae makakapagsurat nin sagunson $1.",
        "backend-fail-maxsize": "Dae makakapagsuat nin sagunson $1 nin huli ta ini grabe kadakula nin {{PLURAL:$2|sarong byte|$2 bytes}}.",
-       "backend-fail-readonly": "An sarayan na panampad \"$1\" yaon sa estado na basahon-sana. An rason na pinagtao iyo na: \"''$2''\"",
+       "backend-fail-readonly": "An sarayan na panampad \"$1\" yaon sa estado na basahon-sana. An rason na pinagtao iyo na: <em>$2</em>",
        "backend-fail-synced": "An sagunson \"$1\" yaon sa estado na bakong konsistido sa laog kan mga panampad na sarayan",
        "backend-fail-connect": "Dae nakakapagsugpon sa panampad na sarayan \"$1\".",
        "backend-fail-internal": "Sarong bakong bistadong kasalaan an nangyari sa panampad na sarayan \"$1\".",
        "uploadstash-summary": "An pahinang ini minatao nin agihan pasiring sa mga sagunson na ikinarga na (o baya yaon pa sa proseso nin pagkakarga) alagad dae pa naipublisa sa wiki. An mga sagunson na ini bakong hiling sa kiisay man kundi sa paragamit na nagkarga kan mga ini.",
        "uploadstash-clear": "Pinaglinigan na makantidad na mga sagunson",
        "uploadstash-nofiles": "Ika mayo nin mahalagang mga sagunson.",
-       "uploadstash-badtoken": "An paggibo kan aksyon na yan bakong matrayumpo, baka nin huli ta an saimong kredensiyal sa pagliliwat nagpaso na.",
+       "uploadstash-badtoken": "An paggibo kan aksyon na yan bakong matrayumpo, baka nin huli ta an saimong kredensiyal sa pagliliwat nagpaso na. Uliton giraray.",
        "uploadstash-errclear": "An paglilinig kan mga sagunson bakong matrayumpo.",
        "uploadstash-refresh": "Papreskoha otro an listahan kan mga sagunson",
        "invalid-chunk-offset": "Imbalidong tagpas na pampahale",
        "license-header": "Paglisensiya",
        "nolicense": "Mayong pigpilî",
        "license-nopreview": "(Mayong patânaw)",
-       "upload_source_url": " (sarong tama, na bukas sa publikong URL)",
-       "upload_source_file": " (sarong ''file'' sa kompyuter mo)",
+       "upload_source_url": "(an sagunson napili tama, asin bukas sa publikong URL)",
+       "upload_source_file": "(sarong ''file'' sa kompyuter mo)",
        "listfiles-summary": "Ining espesyal na pahina minapahiling kan gabos na ipinagkargang mga sagunson.",
        "listfiles_search_for": "Hanápon an pangaran kan retrato:",
        "imgfile": "dokumento",
        "statistics-files": "Pinagkargang mga sagunson",
        "statistics-edits": "Mga pagliwat sa pahina magpoon pa na an {{SITENAME}} pinagmukna.",
        "statistics-edits-average": "Katahaw kan mga pagliliwat sa kada pahina",
-       "statistics-users": "Rehistrado [[Special:ListUsers|users]]",
+       "statistics-users": "Rehistradong mga paragamit",
        "statistics-users-active": "Mga Aktibong Paragamit",
        "statistics-users-active-desc": "Mga paragamit na may ginibong aksyon sa nakaaging {{PLURAL:$1|aldaw|$1 mga aldaw}}",
        "pageswithprop": "Mga pahina na igwang pahina nin kagrugaring",
        "mostrevisions": "Mga artikulong may pinakadakol na pagpakarháy",
        "prefixindex": "Gabos na mga pahina na igwa nin enotang panigmit",
        "prefixindex-namespace": "Gabos na mga pahina na igwa nin enotang panigmit ($1 espasyong ngaran)",
-       "prefixindex-strip": "Waknison an pangenot na panigmit na yaon sa listahan",
+       "prefixindex-strip": "Itago an panginot na panigmit na yaon sa listahan",
        "shortpages": "Haralìpot na pahina",
        "longpages": "Mga halabang pahina",
        "deadendpages": "Mga pahinang mayong luwasan",
        "protectedpages-indef": "Daeng sagkod na proteksyon sana",
        "protectedpages-cascade": "Mga pasurunod na proteksyon sana",
        "protectedpagesempty": "Mayong pang páhina an napoprotehiran kaining mga parametros.",
+       "protectedpages-performer": "Nagpoprotektang paragamit",
+       "protectedpages-unknown-performer": "Dai bistong paragamit",
        "protectedtitles": "Protektadong mga titulo",
        "protectedtitlesempty": "Mayong mga titulo sa presente an protektado kaining mga parametro.",
        "listusers": "Lista nin paragamit",
        "listusers-editsonly": "Ipahiling sana an mga paragamit na igwang mga pinagliwat",
+       "listusers-temporarygroupsonly": "Ipahiling sana an paragamit na nasa temporaryong grupo kan mga paragamit",
        "listusers-creationsort": "Salansanon sa paagi kan petsa nin pagmukna",
        "listusers-desc": "Salansanon sa paibabang pasurunod",
        "usereditcount": "$1 {{PLURAL:$1|pigliwat|mga pigliwat}}",
        "querypage-disabled": "Ining espesyal na pahina pinagpundo nin huli sa kaggibohang mga rason.",
        "apisandbox": "Kahong-buhangin kan API",
        "apisandbox-api-disabled": "An API dae pinagpagana sa sityong ini.",
-       "apisandbox-intro": "Gamitong ining pahina sa pag-eksperimento kan '''MediaWiki web service API'''.\nKonsultaron an  [https://www.mediawiki.org/wiki/API:Main_page the API documentation] para sa iba pang mga detalye sa paggamit kan API. Ehemplo: [https://www.mediawiki.org/wiki/API#A_simple_example kuahon an laman kan Pangenot na Pahina]. Magpili nin aksyon tanganing hilngon an mga kadagdagan na mga ehemplo.",
+       "apisandbox-intro": "Gamitong ining pahina sa pag-eksperimento kan '''MediaWiki web service API'''.\nKonsultaron an [[mw:API:Main page|the API documentation]] para sa iba pang mga detalye sa paggamit kan API. Ehemplo: [https://www.mediawiki.org/wiki/API#A_simple_example kuahon an laman kan Pangenot na Pahina]. Magpili nin aksyon tanganing hilngon an mga kadagdagan na mga ehemplo.",
        "apisandbox-submit": "Maghimo nin kahagadan",
        "apisandbox-reset": "Klaro",
        "apisandbox-examples": "Ehemplo",
        "apisandbox-results": "Resulta",
        "apisandbox-request-url-label": "Hagad URL:",
-       "apisandbox-request-time": "Hagad oras:$1",
+       "apisandbox-request-time": "Hagad oras: {{PLURAL:$1|$1 ms}}",
        "booksources": "Mga Ginikanan kan libro",
        "booksources-search-legend": "Maghanap para sa mga ginikanang libro",
        "booksources-search": "Hanápon",
        "cachedspecial-viewing-cached-ts": "Ika nakahiling sa sarong pinagsaray na bersyon kaining pahina, na mapuwedeng bakong aktuwal na kumpleto talaga.",
        "cachedspecial-refresh-now": "Hilngon an pinakahuri.",
        "categories": "Mga Kategoriya",
-       "categoriespagetext": "An minasunod {{PLURAL:$1|kategorya na may laog na|mga kategorya na may laog na}} mga pahina o midya.\n[[Special:UnusedCategories|Dae ginamit na mga kategorya]] dae ipinapahiling digde.\nAsin man hilnga an [[Special:WantedCategories|kinakaipong mga kategorya]].",
+       "categoriespagetext": "An minasunod {{PLURAL:$1|kategorya na may laog na|mga kategorya na may laog na}} mga pahina o midya.\nAsin hilnga an [[Special:WantedCategories|kinakaipong mga kategorya]].",
        "categoriesfrom": "Pahilnga an mga kategorya magpoon sa:",
        "deletedcontributions": "Parâon an mga kontribusyon kan parágamit",
        "deletedcontributions-title": "Parâon an mga kontribusyon kan parágamit",
        "emailccsubject": "Kopya kan saimong mensahe sa $1: $2",
        "emailsent": "Naipadará na an e-surat",
        "emailsenttext": "Naipadará na su e-surat mo.",
-       "emailuserfooter": "Ining e-surat ipinadara sa paagi nin $1 pasiring ki $2 kan \"E-surat na paragamit\" na punksyon kan {{SITENAME}}.",
+       "emailuserfooter": "Ining e-surat {{GENDER:$1|ipinadara}} sa paagi nin $1 pasiring ki {{GENDER:$2|$2}} kan \"{{int:emailuser}}\"  na punksyon kan {{SITENAME}}. Kun {{GENDER:$2|ika}} magsimbag sa e-surat, {{GENDER:$2|ang saimong}} e-surat ipapadara sa {{GENDER:$1|orihinal na tagapagpadara}}, ipapahiling an {{GENDER:$2|saimong}} e-surat sa {{GENDER:$1|pinadarahan}}.",
        "usermessage-summary": "Magwawalat nin pansistemang mensahe.",
        "usermessage-editor": "Pansistemang mensahero",
        "watchlist": "Bantay-listahan",
        "watchlistanontext": "Pakipalaog tabi tanganing makapaghiling o makapagliwat sa mga aytem na yaon sa saimong bantay-listahan.",
        "watchnologin": "Mayô sa laog",
        "addwatch": "Idagdag sa bantay-listahan",
-       "addedwatchtext": "Ining pahina \"[[:$1]]\" dinadagdag sa saimong mga [[Special:Watchlist|Bantay-listahan]].\nAn maabot na mga pagbabâgo sa páhinang ini asin sa asosyadong páhina nin olay paglilistahon duman.",
+       "addedwatchtext": "Ining pahina \"[[:$1]]\" dinadagdag sa saimong mga [[Special:Watchlist|Bantay-listahan]].",
        "removewatch": "Halion gikan sa bantay-listahan",
        "removedwatchtext": "An pahina \"[[:$1]]\" pinaghale gikan sa [[Special:Watchlist|saimong bantay-listahan]].",
        "watch": "Bantayán",
        "wlheader-showupdated": "Mga pahina na pinagriliwat poon kaidtong huri kang nagbisita sainda ipinapatanaw na '''mahîbog'''",
        "wlnote": "Sa ibaba kan {{PLURAL:$1|huring pagbabago|mga huring <strong>$1</strong> pagbabago}} sa nakalihis na {{PLURAL:$2|oras|'''$2''' mga oras}}, magpoon pa kan $3, $4.",
        "wlshowlast": "Ipahilíng an nakalihis na $1 na mga oras mga $2 na mga aldaw",
+       "wlshowhideliu": "rehistradong mga paragamit",
        "watchlist-options": "Bantay-listahan na mga pagpipilian",
        "watching": "Pigbabantayan...",
        "unwatching": "Dai pigbabantayan...",
        "deletepage": "Paraon an pahina",
        "confirm": "Kompermaron",
        "excontent": "Ini an dating laog: '$1'",
-       "excontentauthor": "ini an dating laog: '$1' (asin an unikong kontribuidor si '[[Special:Contributions/$2|$2]]')",
+       "excontentauthor": "ini an dating laog: '$1' (asin an unikong kontribuidor si '[[Special:Contributions/$2|$2]]')([[User talk:$2|talk]])",
        "exbeforeblank": "Ini an dating laog bagô blinankohán: '$1'",
        "delete-confirm": "Puraon \"$1\"",
        "delete-legend": "Paraon",
        "delete-edit-reasonlist": "Pagliwat kan mga rason nin pagpupura",
        "delete-toobig": "Ining pahina igwa nin dakulaong historiya sa pagliwat, minasobrang $1 {{PLURAL:$1|rebisyon|mga rebisyon}}.\nAn pagpupura kan nasambit na mga pahina dae pinagtutugot tanganing maiwasan an aksidenteng pagka-antala kan {{SITENAME}}.",
        "delete-warning-toobig": "Ining pahina igwa nin dakulaong historiya sa pagliwat, minasobrang $1 {{PLURAL:$1|rebisyon|mga rebisyon}}.\nAn pagpupura kaini mapuwedeng makapag-antala sa mga operasyon kan datos-sarayan kan {{SITENAME}}; magpadagos tabi na igwang pag-iingat.",
-       "deleting-backlinks-warning": "'''Patanid:''' An ibang mga pahina nakatakod sa pahina na muya mong pagpupuraon.",
+       "deleting-backlinks-warning": "'''Patanid:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|An ibang mga pahina]] nakatakod sa pahina na muya mong pagpupuraon.",
        "rollback": "Mga paghihira na pabalík",
        "rollbacklink": "pabalikwaton",
        "rollbacklinkcount": "ibalik $1 {{PLURAL:$1|pagliwat|mga pagliwat}}",
        "editcomment": "An sumaryo kan pagliwat: <em>$1</em>.",
        "revertpage": "Ibinalik na mga pagliwat ni [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) sagkod sa huring rebisyon ni [[User:$1|$1]]",
        "revertpage-nouser": "Binalikwat na mga pagliliwat kan sarong nakatagong paragamit sa huring rebisyon ni {{GENDER:$1|[[User:$1|$1]]}}",
-       "rollback-success": "Binawî na mga paghirá ni $1; pigbalik sa dating bersyón ni $2.",
+       "rollback-success": "Binawî na mga paghirá ni {{GENDER:$3|$1}};; pigbalik sa dating bersyón ni {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Nagpalyang sesyon",
        "sessionfailure": "Garo may problema sa paglaog mo;\nkinanselár ining aksyón bilang sarong paglikay kontra sa ''session hijacking''.\nPindotón tabî an \"back\" asin ikarga giraray an páhinang ginikanan mo, dangan probarán giraray.",
        "protectlogpage": "Katalaanan nin proteksyon",
        "undeletepagetext": "An minasunod na {{PLURAL:$1|pahina pinagpura na alagad yaon|$1 mga pahina pinagpura na alagad yaraon }} pa man sa arkibo asin puwedeng maipagbalik.\nAn arkibo mapupuwedeng peryodikal na paglilinigan.",
        "undelete-fieldset-title": "Ibalik an mga rebisyon",
        "undeleteextrahelp": "Tanganing maibalik an enterong historiya kan pahina, pabayae na an gabos na mga kahon nin tsek dae pagkaagan asin i-klik mo an '''''{{int:undeletebtn}}'''''.\nTanganing gibohon an piniling restorasyon, i-tsek mo an mga kahon na kinatangudan kan mga rebisyon na ipagbabalik, asin i-klik an '''''{{int:undeletebtn}}'''''.",
-       "undeleterevisions": "$1 {{PLURAL:$1|na pagriribay|na mga pagriribay}} na nakaarchibo",
+       "undeleterevisions": "$1 {{PLURAL:$1|na pagriribay|na mga pagriribay}} na napura",
        "undeletehistory": "Kun saimong ipagbalik an pahina, an gabos nga mga rebisyon ipagbabalik sa historiya.\nKun an baguhon na pahina na igwang kaparehas na ngaran naimukna na poon kan puraon, an ipinagbalik na mga rebisyon minaluwas sa nakaagi nang historiya.",
        "undeleterevdel": "An dae pagpupura dae paggigibohon kun ini magreresulta sa kaibabawan kan pahina o rebisyon kan sagunson bilang parsiyal na pinagpura.\nSa arog na mga kaso, kaipuhan mong haleon an tsek o tagoon an pinakabaguhon na pinagpurang rebisyon.",
        "undeletehistorynoadmin": "Pigparâ na ining péhina. Mahihiling an rason sa epitome sa babâ, kasabay sa mga detalye kan mga parágamit na naghira kaining páhina bago pigparâ. Sa mga administrador sana maipapahiling an mga pagribay sa mismong tekstong ini.",
        "tooltip-invert": "I-tsek ining kahon tanganing tagoon an mga pagbabago sa mga pahina na yaon sa laog kan pinagpiling espasyong-ngaran (asin an asosyado na espasyong-ngaran kun may tsek)",
        "namespace_association": "Asosyado na espasyong-ngaran",
        "tooltip-namespace_association": "I-tsek ining kahon tangani man ibali an olay o subheto na espasyong-ngaran na asosyado sa pinagpili na espasyong-ngaran",
-       "blanknamespace": "(Pangenot)",
+       "blanknamespace": "(Panginot)",
        "contributions": "{{GENDER:$1|Paragamit}} na mga kaambagan",
        "contributions-title": "Mga kontribusyon kan paragamit para sa $1",
        "mycontris": "Mga Kaarambagan",
        "anoncontribs": "Mga Kaarambagan",
        "contribsub2": "Para ki {{GENDER:$3|$1}} ($2)",
+       "contributions-userdoesnotexist": "Paragamit na panindog \"$1\" bako tabing rehistrado.",
        "nocontribs": "Mayong mga pagbabago na nahanap na kapadis sa ining mga criteria.",
        "uctop": "sa ngunyan",
        "month": "Poon bulan (asin mas amay):",
        "year": "Poon taon (asin mas amay):",
+       "date": "Poon bulan (asin mas amay):",
        "sp-contributions-newbies": "Ipahiling an mga kaarambagan kan mga baguhong panindog sana",
        "sp-contributions-newbies-sub": "Para sa mga bàgong account",
        "sp-contributions-newbies-title": "Mga kontribusyon kan paragamit para sa baguhon an mga panindog",
        "sp-contributions-blocklog": "Bagáton an katalaanan",
-       "sp-contributions-deleted": "pinagpurang mga kontribusyon kan paragamit",
+       "sp-contributions-suppresslog": "pinagpurang mga kontribusyon kan {{GENDER:$1|paragamit}}",
+       "sp-contributions-deleted": "pinagpurang mga kontribusyon kan {{GENDER:$1|paragamit}}",
        "sp-contributions-uploads": "mga ikinarga",
        "sp-contributions-logs": "mga tinalaan",
        "sp-contributions-talk": "olayan",
-       "sp-contributions-userrights": "manihamento sa mga karapatan kan paragamit",
+       "sp-contributions-userrights": "manihamento sa mga karapatan kan {{GENDER:$1|paragamit}}",
        "sp-contributions-blocked-notice": "Ining paragamit sa presente pinagbarahan.\nAn pinakahuring entrada sa talaan nin pagbara nakahaya sa ibaba bilang reperensiya:",
        "sp-contributions-blocked-notice-anon": "Ining IP adres sa presente pinagbarahan.\nAn pinakahuring entrada sa talaan nin pagbara nakahaya sa ibaba bilang reperensiya:",
        "sp-contributions-search": "Maghanap nin mga kaarambagan",
        "block": "Barahon an paragamit",
        "unblock": "Haleon an bara kan paragamit",
        "blockip": "Bagáton {{KASARIAN:$1|paragamit}}",
-       "blockiptext": "Gamiton an pormularyo sa babâ para bagaton an pagsurat kan sarong espesipikong IP o ngaran nin parágamit.\nDapat gibohon sana ini para maibitaran vandalismo, asin kompirmi sa [[{{MediaWiki:Policy-url}}|palakaw]].\nMagkaag nin espisipikong rason (halimbawa, magtao nin ehemplo kan mga páhinang rinaot).",
+       "blockiptext": "Gamiton an pormularyo sa babâ para bagaton an pagsurat kan sarong espesipikong IP o ngaran nin parágamit.\nDapat gibohon sana ini para maibitaran vandalismo, asin kompirmi sa [[{{MediaWiki:Policy-url}}|palakaw]].\nMagkaag nin espisipikong rason (halimbawa, magtao nin ehemplo kan mga páhinang rinaot).\nPwede mong bagaton an IP gamit an [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax;an pinakadakulang sakop na tinutugutan iyo an /$1 para sa IPv4 asin /$2 para sa IPv6.",
        "ipaddressorusername": "direksyon nin IP o gahâ:",
        "ipbreason": "Rason:",
        "ipbreason-dropdown": "*Mga komon na rason sa pagbagat\n** Nagkakaag nin salang impormasyon\n** Naghahalî nin mga laog kan páhina\n** Nagkakaag nin mga takod na ''spam'' kan mga panluwas na ''site''\n** Nagkakaag nin kalokohan/ringaw sa mga pahina\n** Gawî-gawing makatakót/makauyám\n** Nag-aabuso nin mga lain-lain na ''account''\n** Dai akong ngaran nin parágamit",
        "ipb-hardblock": "Pugulan an yaon sa laog na mga paragamit na magliliwat gikan kaining IP adres",
-       "ipbcreateaccount": "Pugulon an pagibo nin kuenta.",
-       "ipbemailban": "Pugolan ining paragamit na magpadara nin e-surat",
+       "ipbcreateaccount": "Magmukna nin panindog",
+       "ipbemailban": "Nagpapadara nin e-surat",
        "ipbenableautoblock": "Enseguidang bagaton an huring direccion nin  IP na ginamit kaining paragamit, asin kon ano pang ibang IP na proprobaran nindang gamiton",
        "ipbsubmit": "Bagáton ining parágamit",
        "ipbother": "Ibang oras:",
        "ipboptions": "2ng oras:2 hours,1ng aldaw:1 day,3ng aldaw:3 days,1ng semana:1 week,2ng semana:2 weeks,1ng bulan:1 month,3ng bulan:3 months,6 na bulan:6 months,1ng taon:1 year,daeng kasagkoran:infinite",
        "ipbhidename": "Tagoon an ngaran nin paragamit gikan sa mga pagliliwat asin mga listahan",
        "ipbwatchuser": "Bantayi ining gamit kan paragamit asin mga pahina nin olayan",
-       "ipb-disableusertalk": "Pugulan ining paragamit na magliliwat kan saiyang sadireng pahina nin olayan habang ini barado",
+       "ipb-disableusertalk": "Magliwat kan saiyang sadiring pahina nin olayan",
        "ipb-change-block": "Barahan-otro an paragamit na igwa kaining mga panuytoy",
        "ipb-confirm": "Kumpirmaron an pagbara",
        "badipaddress": "Dai pwede ining IP",
        "emailblock": "binagát an e-surat",
        "blocklist-nousertalk": "dae makakaliwat kan sadireng pahina nin olayan",
        "ipblocklist-empty": "Mayong laog an lista nin mga binagat.",
-       "ipblocklist-no-results": "Dai nabagat an hinagad na direccion nin IP o ngaran nin paragamit.",
+       "ipblocklist-no-results": "Mayong bagat na nahiling sa IP o ngaran nin paragamit.",
        "blocklink": "bagáton",
        "unblocklink": "haleon an bagat",
        "change-blocklink": "ribayan an bagat",
        "contribslink": "mga ambág",
        "emaillink": "ipadara an e-surat",
-       "autoblocker": "Enseguidang binagat an saimong direccion nin IP ta kaaaging ginamit ini ni \"[[User:$1|$1]]\". An rason nin pagbagat ni $1: \"$2\"",
+       "autoblocker": "Enseguidang binagat an saimong  IP ta kaaaging ginamit ini ni \"[[User:$1|$1]]\". An rason nin pagbagat ni $1: \"$2\"",
        "blocklogpage": "Katalaanan nin bagat",
        "blocklog-showlog": "Ining paragamit dati nang pinagbarahan.\nAn talaan nin pagbara nakahaya sa ibaba bilang reperensiya:",
        "blocklog-showsuppresslog": "Ining paragamit pinagkubkob asin dati nang ipinagtago.\nAn talaan nin pagpaunlok ipinagtao sa ibaba para hilingan.",
        "range_block_disabled": "Pigpopondo an abilidad kan sysop na maggibo nin bagat na hilera.",
        "ipb_expiry_invalid": "Dai pwede ini bilang oras kan pagpasó.",
        "ipb_expiry_temp": "Itinagong pangaran nin paragamit na nagkukubkob dapat na magin permanente.",
-       "ipb_hide_invalid": "Dae nakayanan na untukon ining panindog; ini gayod nagkaigwa nin kadakulon na mga pagliliwat.",
+       "ipb_hide_invalid": "Dae nakayanan na untukon ining panindog; ini gayod nagkaigwa nin kadakulon na mga {{PLURAL:$1|one edit|$1 pagliliwat}}.",
        "ipb_already_blocked": "An \"$1\" pinagkubkob na",
        "ipb-needreblock": "An $1 pinagkubkob na. Gusto mong liwaton an mga panuytoy?",
        "ipb-otherblocks-header": "An ibang {{PLURAL:$1|kubkob|mga kubkob}}",
        "movenotallowedfile": "Ika mayo nin permiso na magbabalyo nin mga sagunson.",
        "cant-move-user-page": "Ika mayo nin permiso na magbabalyo nin mga pahina nin paragamit (laen pa sa mga sub-pahina).",
        "cant-move-to-user-page": "Ika mayo nin permiso na magbabalyo nin pahina paduman sa sa sarong pahina nin paragamit (laen pa sa sub-pahina nin paragamit).",
-       "newtitle": "Sa bàgong titulong:",
+       "newtitle": "Sa bàgong titulo:",
        "move-watch": "Bantayán ining pahina",
        "movepagebtn": "Ibalyó an pahina",
        "pagemovedsub": "Naibalyó na",
        "movenosubpage": "Ining pahina mayo nin mga sub-pahina.",
        "movereason": "Rason:",
        "revertmove": "balikon",
-       "delete_and_move_text": "==Kaipuhan na parâon==\n\nIgwa nang páhina na \"[[:$1]]\". Gusto mong parâon ini tangarig maibalyó?",
+       "delete_and_move_text": "Igwa nang páhina na \"[[:$1]]\". Gusto mong parâon ini tangarig maibalyó?",
        "delete_and_move_confirm": "Iyo, parâon an pahina",
        "delete_and_move_reason": "Pinagpura sa paghimo nin dalan para maibalyo gikan sa \"[[$1]]\"",
        "selfmove": "Pareho an páhinang ginikanan asin destinasyon; dai pwedeng ibalyó an sarong páhina sa sadiri.",
        "move-leave-redirect": "Walaton an sarong panlikwat sa likod",
        "protectedpagemovewarning": "'''Patanid:''' Ining pahina protektado tangani na an mga paragamit sana na igwang administrador na mga pribilihiyo an makakapagbalyo kaini.\nAn pinakahuring entrada sa talaan pinagtao sa ibaba para sa reperensiya:",
        "semiprotectedpagemovewarning": "'''Giromdomon:''' Ining pahina protektado tanganing an mga rehistradong paragamit sana an makakabalyo kaini. \nAn pinakahuring entrada sa talaan pinagtao sa ibaba para sa reperensiya:",
-       "move-over-sharedrepo": "== Yaon nang Sagunson ==\n[[:$1]] yaon na sa pinagheras na repositoryo. An pagbabalyo nin sagunson paduman kaining titulo masalambaw sa pinagheras na sagunson.",
+       "move-over-sharedrepo": "[[:$1]] yaon na sa pinagheras na repositoryo. An pagbabalyo nin sagunson paduman kaining titulo masalambaw sa pinagheras na sagunson.",
        "file-exists-sharedrepo": "An pangaran nin saguson na pinili ginagamit na sa pinagheras na repositoryo.\nPakipili kan ibang pangaran.",
        "export": "Paluwason an mga pahina",
        "exporttext": "Pwede mong ipadara an teksto asin historya nin paghirá kan sarong partikular na páhina o grupo nin mga páhina na nakapatos sa ibang XML. Pwede ining ipadara sa ibang wiki gamit an MediaWiki sa paagi kan [[Special:Import|pagpadara nin páhina]].\n\nPara makapadara nin mga páhina, ilaag an mga titulo sa kahon para sa teksto sa babâ, sarong titulo kada linya, dangan pilîon kun boot mo presenteng bersyón asin dating bersyón, na may mga linya kan historya, o an presenteng bersyón sana na may impormasyon manonongod sa huring hirá.\n\nSa kaso kan huri, pwede ka man na maggamit nin takod, arog kan [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] para sa páhinang \"[[{{MediaWiki:Mainpage}}]]\".",
        "thumbnail_gd-library": "Bakong kumpleto an kasalansanan kan kalibrohang GD: Nawawara an trabaho kan $1",
        "thumbnail_image-missing": "An sagunson garo baga nawawara: $1",
        "import": "Ilaog an mga páhina",
-       "importinterwiki": "Ipadara an Transwiki",
+       "importinterwiki": "importaron an mga pahina gikan sa ibang wiki",
        "import-interwiki-text": "Pumili nin sarong wiki asin titulo kan pahina na importaron.\nMga petsa nin kaliwatan asin pangaran kan mga paraliwat pagpepreserbaron.\nGabos na aksyon nin importa sa transwiki nakatala sa [[Special:Log/import|talaan nin importa]].",
        "import-interwiki-sourcepage": "Gikanang pahina:",
        "import-interwiki-history": "Kopyahon an gabos na mga bersyón para sa páhinang ini",
        "importuploaderrortemp": "Pagkarga kan ini-importang sagunson nagpalya.\nAn temporaryong polder nawawara.",
        "import-parse-failure": "XML importang panabot puminalya",
        "import-noarticle": "Mayong pahina na maiimporta!",
-       "import-nonewrevisions": "An gabos na mga rebisyon dati nang importado.",
+       "import-nonewrevisions": "Mayong rebisyon na pig-import (dati nang yaun o dai binali huli sa mga sala)",
        "xml-error-string": "$1 sa linya $2, kol $3 (bayta $4): $5",
        "import-upload": "Ikarga an XML na datos",
-       "import-token-mismatch": "Nawara an datos kan sesyon.\nPaki-otro giraray.",
+       "import-token-mismatch": "Pagkawara nin datos kan sesyon.\n\nProbaran tabì giraray. Kun dai man giraray magibo, probaran [[Special:UserLogout|na magluwas]] dangan maglaog giraray asin siguraduhon na nagtutugot an browser sa mga cookies hali sa site na ini.",
        "import-invalid-interwiki": "Dae makakapag-importa gikan sa pinagsambit na wiki.",
        "import-error-edit": "An pahina \"$1\" bakong importado nin huli ta ika dae tinutugutan na magliliwat kaini.",
        "import-error-create": "An pahina \"$1\" bakong importado nin huli ta ika dae tinutugutan na magmumukna kaini.",
        "import-rootpage-nosubpage": "Espasyong-ngaran \"$1\" kan ugat na pahina dae minatugot nin pan-irarom na mga pahina.",
        "importlogpage": "Usip nin pagpalaog",
        "importlogpagetext": "Administratibong mga importadong pahina na igwang historiya nin pagliliwat gikan sa ibang wikis.",
-       "import-logentry-upload-detail": "$1 {{PLURAL:$1|rebisyon|mga rebisyon}}",
+       "import-logentry-upload-detail": "$1 {{PLURAL:$1|rebisyon|mga rebisyon}} na importado",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|rebisyon|mga rebisyon}} gikan sa $2",
        "javascripttest": "Testing sa JavaScript",
        "javascripttest-qunit-intro": "Hilngon [$1 dokumentasyon sa pagtesting] sa mediawiki.org.",
        "tooltip-search": "Hanápon an {{SITENAME}}",
        "tooltip-search-go": "Magduman sa pahina na igwa kaining eksaktong pangaran kun eksistido",
        "tooltip-search-fulltext": "Hanápon an mga pahina para kaining teksto",
-       "tooltip-p-logo": "Bisitahon an Pangenot na Pahina",
+       "tooltip-p-logo": "Bisitahon an Panginot na Pahina",
        "tooltip-n-mainpage": "Bisitahon an Pangenot na Pahina",
        "tooltip-n-mainpage-description": "Bisitahon an Pangenot na Pahina",
        "tooltip-n-portal": "Manunungod sa proyekto, ano an saimong maginibo, saen makanumpong nin mga bagay",
        "anonymous": "Bako-bistadong {{PLURAL:$1|paragamit|mga paragamit}} kan {{SITENAME}}",
        "siteuser": "Paragamit kan {{SITENAME}} na si $1",
        "anonuser": "{{SITENAME}} bako-bistadong paragamit $1",
-       "lastmodifiedatby": "Ining páhina huring binago sa $2, $1 ni $3.",
+       "lastmodifiedatby": "Ining pahina huring binago sa $2, $1 ni $3.",
        "othercontribs": "Binase ini sa trabaho ni $1.",
        "others": "iba pa",
-       "siteusers": "{{SITENAME}} {{PLURAL:$2|paragamit|mga paragamit}} $1",
+       "siteusers": "{{SITENAME}}{{PLURAL:$2|paragamit|mga paragamit}}$1",
        "anonusers": "{{SITENAME}} bako-bistadong {{PLURAL:$2|paragamit|mga paragamit}} $1",
        "creditspage": "Mga krédito nin páhina",
        "nocredits": "Mayong talastas kan kredito para sa ining pahina.",
        "scarytranscludefailed-httpstatus": "[An paghigkos kan panguyog nagpalya para sa $1: HTTP $2]",
        "scarytranscludetoolong": "[An kilyawan grabe kahalaba]",
        "deletedwhileediting": "'''Patanid tabi''': Ining pahina pinagpura matapos na ika nagpoon na magliliwat!",
-       "confirmrecreate": "Si [[User:$1|$1]] ([[User talk:$1|olay]]) pigparâ ining páhina pagkatapos mong magpoon kan paghira ta:\n: ''$2''\nIkonpirmar tabi na talagang gusto mong gibohon giraray ining pahina.",
-       "confirmrecreate-noreason": "Paragamit [[User:$1|$1]] ([[User talk:$1|Olay]]) an nagpura kaining pahina matapos na ika nagpoon na magliliwat. Pakikumpirma tabi na ika boot na muknaon otro ining pahina.",
+       "confirmrecreate": "Si [[User:$1|$1]] ([[User talk:$1|olay]]){{GENDER:$1|pigpara}} ining páhina pagkatapos mong magpoon kan paghira ta:\n: <em>$2</em>\nIkonpirmar tabi na talagang gusto mong gibohon giraray ining pahina.",
+       "confirmrecreate-noreason": "Paragamit [[User:$1|$1]] ([[User talk:$1|Olay]]){{GENDER:$1|an nagpura}} kaining pahina matapos na ika nagpoon na magliliwat. Pakikumpirma tabi na ika boot na muknaon otro ining pahina.",
        "recreate": "Gibohón giraray",
        "confirm_purge_button": "Sige",
        "confirm-purge-top": "Halîon an an aliho kaining páhina?",
        "confirm-watch-top": "Idadagdag ining pahina sa saimong bantay-listahan?",
        "confirm-unwatch-button": "OK tabi",
        "confirm-unwatch-top": "Haleon ining pahina gikan sa saimong bantay-listahan?",
+       "confirm-mcrrestore-title": "Ibalik an mga rebisyon",
        "imgmultipageprev": "← nakaaging pahina",
        "imgmultipagenext": "sunod na pahina →",
        "imgmultigo": "Dumanán!",
        "tags-active-no": "Dae",
        "tags-edit": "liwatón",
        "tags-hitcount": "$1 {{PLURAL:$1|kaliwatan|mga kaliwatan}}",
+       "tags-edit-nooldid-title": "Imbalidong target nin pagbabago",
        "comparepages": "Ikumpara an mga pahina",
        "compare-page1": "Pahina 1",
        "compare-page2": "Pahina 2",
        "compare-revision-not-exists": "An rebisyon na saimong pinagsambit bakong eksistido.",
        "dberr-problems": "Sori! Ining sityo igwang naeksperiyensiyahan na mga kakundian sa teknikal.",
        "dberr-again": "Prubaring maghalat tabi nin nagkapirang minutos asin otrohon ikarga.",
-       "dberr-info": "(Dae makakontak sa serbidor kan datos-sarayan: $1)",
+       "dberr-info": "(Dae makakontak sa serbidor kan datos-sarayan:$1)",
        "dberr-info-hidden": "(Dae makakontak sa serbidor kan datos-sarayan)",
        "htmlform-invalid-input": "Igwa nin mga problema an iba sa saimong pinaglaog",
        "htmlform-select-badoption": "An halaga na saimong pinagsambit bakong saro sa balidong pagpipilian.",
        "logentry-newusers-create2": "An panindog kan paragamit $3 {{GENDER:$2|pinagmukna}} na ni $1",
        "logentry-newusers-byemail": "An panindog kan paragamit $3 {{GENDER:$2|pinagmukna}} ni $1 asin an sekretong panlaog ipinadara na sa paagi nin e-surat",
        "logentry-newusers-autocreate": "An panindong kan paragamit $1 awtomatikong {{GENDER:$2|pinagmukna}} na",
-       "logentry-rights-rights": "$1 {{GENDER:$2|pinagliwat}} kan pangrupong pagkamiyembro para sa $3 gikan sa $4 pasiring sa $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|pinagliwat}} kan pangrupong pagkamiyembro para sa {{GENDER:$6|$3}} gikan sa $4 pasiring sa $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|nagliwat}} kan pangrupong pagkamiyembro para sa $3",
        "logentry-rights-autopromote": "$1 awtomatikong {{GENDER:$2|pinagpalangkaw}} gikan sa $4 pasiring sa $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|pig-upload}} $3",
        "feedback-subject": "Subheto",
        "feedback-submit": "Isumite",
        "feedback-thanks": "Salamat! An saimong balik-simbag pinagposte sa pahina \"[$2 $1]\".",
+       "feedback-useragent": "Ahente nin paragamit:",
        "searchsuggest-search": "{{SITENAME}}",
        "searchsuggest-containing": "may laog na...",
        "api-error-badtoken": "Panlaog na kasalaan: Raot na pangilip",
        "limitreport-expansiondepth": "Kinatugmadan kan pinakahalangkaw na kahiwasan",
        "limitreport-expensivefunctioncount": "Bilang kan hiro nin mamahalon na parabangay",
        "expandtemplates": "Bigwakon an mga panguyog",
-       "expand_templates_intro": "Ining espesyal na pahina minakua nin teksto asin minabigwak kan gabos na mga panguyog na yaon kaini na paoro-otro.\nIni man minabigwak sa punksyon kan suportadong parabangay na arog kan <code><nowiki>{{</nowiki>#language:…}}</code> asin mga kapilyangan arog kan <code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nSa katunayan, ini minapabigwak kan gabos na bagay na yaon sa mga dobleng panandayan.",
+       "expand_templates_intro": "Ining espesyal na pahina minakua nin teksto asin minabigwak kan gabos na mga panguyog na yaon kaini na paoro-otro.\nIni man minabigwak sa punksyon kan suportadong parabangay na arog kan \n<code><nowiki>{{</nowiki>#language:…}}</code> asin mga kapilyangan arog kan \n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nSa katunayan, ini minapabigwak kan gabos na bagay na yaon sa mga dobleng panandayan.",
        "expand_templates_title": "Kontekstong titulo, para sa {{FULLPAGENAME}}, ibpa.:",
        "expand_templates_input": "Ikaag an teksto.",
        "expand_templates_output": "Resulta",
        "expand_templates_generate_xml": "Ipahiling an panlunhay na kahoy nin XML",
        "expand_templates_preview": "Patânaw",
        "mw-widgets-abandonedit": "Nakakaseguro ka na gusto mong bumalik sa kamugtakan nin pagtanaw na dae nagtatagamang enot?",
+       "mw-widgets-dateinput-no-date": "Mayong pigpilî",
        "mw-widgets-dateinput-placeholder-day": "TTTT-BB-AA",
        "mw-widgets-dateinput-placeholder-month": "TTTT-BB",
-       "randomrootpage": "Purakan na ugat nin pahina"
+       "date-range-from": "Poon na petsa:",
+       "randomrootpage": "Purakan na ugat nin pahina",
+       "log-action-filter-delete-revision": "Pagpura kan mga pagbabago",
+       "revid": "rebisyon $1"
 }
index dbc65ab..cd0cacf 100644 (file)
@@ -77,7 +77,7 @@
        "tog-watchlisthideliu": "অ্যাকাউন্টে প্রবেশকৃত ব্যবহারকারীদের সম্পাদনাগুলি নজরতালিকায় লুকিয়ে রাখা হোক",
        "tog-watchlistreloadautomatically": "প্রতিবার একটি ছাঁকনি পরিবর্তন হওয়া মাত্রই স্বয়ংক্রিয়ভাবে নজরতালিকাটি পুনঃলোড করা হোক (জাভাস্ক্রিপ্ট প্রয়োজন)",
        "tog-watchlistunwatchlinks": "পরিবর্তনসহ দেখা পাতাগুলিতে সরাসরি দেখা/না দেখার ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) চিহ্ন যুক্ত করুন (এই কার্যকারিতা জন্য জাভাস্ক্রিপ্ট প্রয়োজন)",
-       "tog-watchlisthideanons": "বà§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80দà§\87র à¦¸à¦®à§\8dপাদনাà¦\97à§\81লি à¦¨à¦\9cরতালিà¦\95ায় আড়ালে রাখা হোক",
+       "tog-watchlisthideanons": "নà¦\9cরতালিà¦\95ায় à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80দà§\87র à¦¸à¦®à§\8dপাদনাà¦\97à§\81লি আড়ালে রাখা হোক",
        "tog-watchlisthidepatrolled": "পরীক্ষিত সম্পাদনাগুলি নজরতালিকায় লুকিয়ে রাখা হোক",
        "tog-watchlisthidecategorization": "পাতার শ্রেণীবদ্ধকরণ লুকিয়ে রাখা হোক",
        "tog-ccmeonemails": "অন্য ব্যবহারকারীর কাছে আমার পাঠানো ইমেইলের একটি প্রতিলিপি আমাকে পাঠানো হোক",
        "history_short": "ইতিহাস",
        "history_small": "ইতিহাস",
        "updatedmarker": "আপনার শেষ পরিদর্শনের পর থেকে হালনাগাদকৃত",
-       "printableversion": "à¦\9bাপার যোগ্য সংস্করণ",
+       "printableversion": "মà§\81দà§\8dরণযোগ্য সংস্করণ",
        "permalink": "স্থায়ী সংযোগ",
        "print": "মুদ্রণ",
        "view": "দেখুন",
        "otherlanguages": "অন্যান্য ভাষাসমূহ",
        "redirectedfrom": "($1 থেকে পুনর্নির্দেশিত)",
        "redirectpagesub": "পুনর্নির্দেশ পাতা",
-       "redirectto": "পুননির্দেশিত হয়েছে:",
+       "redirectto": "পà§\81নরà§\8dনিরà§\8dদà§\87শিত à¦¹à¦¯à¦¼à§\87à¦\9bà§\87:",
        "lastmodifiedat": "এই পাতা শেষ সম্পাদিত হয়েছে $2টার সময়, $1 তারিখে।",
        "viewcount": "এই পাতাটি {{PLURAL:$1|একবার|$1 বার}} দেখা হয়েছে।",
        "protectedpage": "সুরক্ষিত পাতা",
        "virus-scanfailed": "স্ক্যান করা যাচ্ছে না (কোড $1)",
        "virus-unknownscanner": "অজানা এন্টিভাইরাস:",
        "logouttext": "'''আপনি এখন আপনার অ্যাকাউন্ট থেকে প্রস্থান করেছেন।'''\n\nনোট করুন যে কিছু পাতায় আপনাকে এখনও প্রবেশ অবস্থায় দেখাবে, যতক্ষণ না আপনি ব্রাউজার ক্যাশ পরিষ্কার করছেন।",
+       "logging-out-notify": "আপনাকে প্রস্থান করানো হচ্ছে, দয়া করে অপেক্ষা করুন।",
+       "logout-failed": "এখন প্রস্থান করা যাবে না: $1",
        "cannotlogoutnow-title": "এখন প্রস্থান করা যাবে না",
        "cannotlogoutnow-text": "$1 ব্যবহার করার সময় প্রস্থান করা সম্ভব নয়।",
        "welcomeuser": "স্বাগতম, $1!",
        "autoblockedtext": "আপনার আইপি ঠিকানাটিকে স্বয়ংক্রিয়ভাবে সম্পাদনায় বাধাদান করা হয়েছে কারণ এমন আরেকজন ব্যবহারকারী এটি ব্যবহার করেছেন, যাকে $1 বাধা দিয়েছেন।\nযে কারণে বাধা দেওয়া হয়েছে সেটি হল:\n\n:<em>$2</em>\n\n* বাধা শুরুর সময়: $8\n* বাধা শেষের সময়: $6\n* যাকে বাধাদান করা হয়েছে: $7\n\nআপনি $1-এর সাথে কিংবা অন্য যেকোন [[{{MediaWiki:Grouppage-sysop}}|প্রশাসকের]] সাথে যোগাযোগ করে এই বাধা সংক্রান্ত বিষয়ে আলোচনা করতে পারেন।\n\nলক্ষ্য করুন, আপনি \"{{int:emailuser}}\" বৈশিষ্ট্যটি ব্যবহার করতে পারবেন না যদি না আপনার [[Special:Preferences|অ্যাকাউন্টের পছন্দসমূহে]] একটি বৈধ ইমেইল ঠিকানা নিবন্ধিত না থাকে এবং আপনাকে এটি ব্যবহার করা থেকে অবরুদ্ধ না করা হয়ে থাকে।\n\nআপনার বর্তমান আইপি ঠিকানা হচ্ছে $3, এবং বাধা নং হল #$5।\nদয়া করে আপনার যেকোন জিজ্ঞাসাতে উপরের সমস্ত বিবরণ অন্তর্ভুক্ত করুন।",
        "systemblockedtext": "আপনার ব্যবহারকারী নাম অথবা আইপি ঠিকানাটিকে স্বয়ংক্রিয়ভাবে মিডিয়াউইকি দ্বারা বাধাদান করা হয়েছে। যে কারণটি দেওয়া হয়েছে, সেটি হল:\n\n:<em>$2</em>\n\n* বাধা শুরুর সময়: $8\n* বাধা উঠিয়ে নেয়ার সময়: $6\n* যাকে বাধাদান করা হয়েছে: $7\n\nআপনার বর্তমান আইপি ঠিকানাটি হল $3।\nদয়া করে আপনার যেকোন জিজ্ঞাসাতে উপরের সমস্ত বিবরণ অন্তর্ভুক্ত করুন।",
        "blockednoreason": "কোন কারণ দেওয়া হয়নি",
+       "blockedtext-composite": "<strong>আপনার ব্যবহারকারী নাম অথবা আইপি ঠিকানাটিকে বাধা দেয়া হয়েছে।</strong>\n\nযে কারণটি দেওয়া হয়েছে, সেটি হল:\n\n:<em>$2</em>\n\n* বাধা শুরুর সময়: $8\n* বাধা উঠিয়ে নেয়ার সময়: $6\n\n* $5\n\nআপনার বর্তমান আইপি ঠিকানাটি হল $3।\nদয়া করে আপনার যেকোন জিজ্ঞাসাতে উপরের সমস্ত বিবরণ অন্তর্ভুক্ত করুন।",
        "whitelistedittext": "পাতায় সম্পাদনা করতে অনুগ্রহ করে $1 করুন।",
        "confirmedittext": "কোন সম্পাদনা করার আগে আপনার ই-মেইল ঠিকানাটি অবশ্যই নিশ্চিত করতে হবে। দয়া করে আপনার ই-মেইল ঠিকানাটি [[Special:Preferences|ব্যবহারকারীর পছন্দতালিকায়]] ঠিকমত দিন।",
        "nosuchsectiontitle": "অনুচ্ছেদ পাওয়া যায়নি",
        "accmailtext": "[[User talk:$1|$1]] এর জন্য দৈব ভাবে উৎপন্ন শব্দ চাবি $2 এ পাঠানো হয়েছে।\nলগ-ইন করার পর ''[[Special:ChangePassword|পাসওয়ার্ড পরিবর্তন]]'' পাতা থেকে এটি পরিবর্তন করা যাব।",
        "newarticle": "(নতুন)",
        "newarticletext": "আপনি এমন একটি পাতার সংযোগ অনুসরণ করছেন, যার অস্তিত্ব নেই।\nপাতাটি তৈরি করতে, নিচের বাক্সে তা টাইপ করা শুরু করুন (আরও তথ্য জানতে [$1 সহায়িকা পাতা] দেখুন)।\nআপনি যদি ভুল করে এখানে এসে থাকেন, তাহলে আপনার ব্রাউজারের <strong>পিছন</strong> বোতামে ক্লিক করুন।",
-       "anontalkpagetext": "----\n<em>à¦\8fà¦\9fি à¦\8fà¦\95à¦\9fি à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80র à¦\86লাপà§\87র à¦ªà¦¾à¦¤à¦¾, à¦¯à¦¿à¦¨à¦¿ à¦\8fà¦\96নà¦\93 à¦\95à§\8bন à¦\85à§\8dযাà¦\95াà¦\89নà§\8dà¦\9f à¦¤à§\88রি à¦\95রà§\87ননি, à¦\95িà¦\82বা à¦¤à¦¿à¦¨à¦¿ à¦\85à§\8dযাà¦\95াà¦\89নà§\8dà¦\9fà¦\9fি à¦¬à§\8dযবহার à¦\95রà¦\9bà§\87ন à¦¨à¦¾à¥¤</em>\nà¦\86মরা à¦¤à¦¾à¦\87 à¦¸à¦¾à¦\82à¦\96à§\8dযিà¦\95 à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¬à§\8dযবহার à¦\95রà§\87 à¦¤à¦¾à¦\81দà§\87র à¦¶à¦¨à¦¾à¦\95à§\8dত à¦\95রà¦\9bি।\nà¦\8fà¦\95াধিà¦\95 à¦¬à§\8dযবহারà¦\95ারà§\80 à¦\8fরà¦\95ম à¦\8fà¦\95à¦\9fি à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¬à§\8dযবহার à¦\95রতà§\87 à¦ªà¦¾à¦°à§\87ন।\nà¦\86পনি à¦¯à¦¦à¦¿ à¦\8fà¦\95à¦\9cন à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80 à¦¹à¦¯à¦¼à§\87 à¦¥à¦¾à¦\95à§\87ন à¦\8fবà¦\82 à¦¯à¦¦à¦¿ à¦\85নà§\81ভব à¦\95রà§\87ন à¦¯à§\87 à¦\86পনার à¦ªà§\8dরতি à¦\85পà§\8dরাসà¦\99à§\8dà¦\97িà¦\95 à¦®à¦¨à§\8dতবà§\8dয à¦\95রা à¦¹à¦¯à¦¼à§\87à¦\9bà§\87, à¦¤à¦¾à¦¹à¦²à§\87 à¦\85নà§\8dযানà§\8dয à¦¬à§\87নামà§\80 ব্যবহারকারীর সাথে ভবিষ্যতে বিভ্রান্তি এড়াতে অনুগ্রহ করে [[Special:CreateAccount|একটি অ্যাকাউন্ট তৈরি করুন]] অথবা  [[Special:UserLogin|অ্যাকাউন্টে প্রবেশ করুন]]।",
+       "anontalkpagetext": "----\n<em>à¦\8fà¦\9fি à¦\8fà¦\95à¦\9cন à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80র à¦\86লাপà§\87র à¦ªà¦¾à¦¤à¦¾, à¦¯à¦¿à¦¨à¦¿ à¦\8fà¦\96নà¦\93 à¦\95à§\8bন à¦\85à§\8dযাà¦\95াà¦\89নà§\8dà¦\9f à¦¤à§\88রি à¦\95রà§\87ননি, à¦\95িà¦\82বা à¦¤à¦¿à¦¨à¦¿ à¦\85à§\8dযাà¦\95াà¦\89নà§\8dà¦\9fà¦\9fি à¦¬à§\8dযবহার à¦\95রà¦\9bà§\87ন à¦¨à¦¾à¥¤</em>\nà¦\86মরা à¦¤à¦¾à¦\87 à¦¸à¦¾à¦\82à¦\96à§\8dযিà¦\95 à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¬à§\8dযবহার à¦\95রà§\87 à¦¤à¦¾à¦\81দà§\87র à¦¶à¦¨à¦¾à¦\95à§\8dত à¦\95রà¦\9bি।\nà¦\8fà¦\95াধিà¦\95 à¦¬à§\8dযবহারà¦\95ারà§\80 à¦\8fরà¦\95ম à¦\8fà¦\95à¦\9fি à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¬à§\8dযবহার à¦\95রতà§\87 à¦ªà¦¾à¦°à§\87ন।\nà¦\86পনি à¦¯à¦¦à¦¿ à¦\8fà¦\95à¦\9cন à¦¨à¦¾à¦®à¦¹à§\80ন à¦¬à§\8dযবহারà¦\95ারà§\80 à¦¹à¦¯à¦¼à§\87 à¦¥à¦¾à¦\95à§\87ন à¦\8fবà¦\82 à¦¯à¦¦à¦¿ à¦\85নà§\81ভব à¦\95রà§\87ন à¦¯à§\87 à¦\86পনার à¦ªà§\8dরতি à¦\85পà§\8dরাসà¦\99à§\8dà¦\97িà¦\95 à¦®à¦¨à§\8dতবà§\8dয à¦\95রা à¦¹à¦¯à¦¼à§\87à¦\9bà§\87, à¦¤à¦¾à¦¹à¦²à§\87 à¦\85নà§\8dযানà§\8dয à¦¨à¦¾à¦®à¦¹à§\80ন ব্যবহারকারীর সাথে ভবিষ্যতে বিভ্রান্তি এড়াতে অনুগ্রহ করে [[Special:CreateAccount|একটি অ্যাকাউন্ট তৈরি করুন]] অথবা  [[Special:UserLogin|অ্যাকাউন্টে প্রবেশ করুন]]।",
        "noarticletext": "বর্তমানে এই পাতায় কোন লেখা নেই।\nআপনি চাইলে অন্যান্য পাতায় [[Special:Search/{{PAGENAME}}| এই শিরোনামটি অনুসন্ধান করতে পারেন]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} এ সম্পর্কিত লগ অনুসন্ধান করতে পারেন], \nকিংবা [{{fullurl:{{FULLPAGENAME}}|action=edit}} এই পাতাটি তৈরি করতে পারেন]</span>।",
        "noarticletext-nopermission": "বর্তমানে এই পাতায় কোন লেখা নেই।\nআপনি চাইলে অন্য পাতায় [[Special:Search/{{PAGENAME}}| শিরোনামটি অনুসন্ধান করতে পারেন]], অথবা <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} সম্পর্কিত লগ অনুসন্ধান করতে পারেন]</span>, কিন্তু আপনার এই পাতাটি তৈরী করার অনুমতি নেই।",
        "missing-revision": "\"{{FULLPAGENAME}}\" এর #$1তম সংস্করণটি প্রদর্শন সম্ভব নয়।\n\nসাধারণত মুছে ফেলা হয়েছে এমন পাতার মেয়াদ উত্তীর্ণ ইতিহাসের সংযোগ অনুসরণ করার কারণে এটি হতে পারে। \n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} অপসারণ লগে] বিস্তারিত তথ্য জানা যাবে।",
        "previewconflict": "এই প্রাকদর্শনটি সম্পাদনা ক্ষেত্রের উপরের অংশটির টেক্সট সংরক্ষণ করলে যেরকম দেখাবে, তা দেখাচ্ছে।",
        "session_fail_preview": "দুঃখিত! সেশন ডাটা হারিয়ে যাওয়ার কারণে আপনার সম্পাদনাটি সংরক্ষণ করা সম্ভব হয়নি।\n\nআপনি সম্ভবত সংযোগ হারিয়েছন। <strong>দয়া করে যাচাই করুন যে আপনি এখনও প্রবেশরত রয়েছেন এবং আবার চেষ্টা করুন</strong>। যদি এটি এখনও কাজ না করে, তাহলে দয়া করে [[Special:UserLogout|অ্যাকাউন্ট থেকে প্রস্থান করুন]] এবং আবার অ্যাকাউন্টে প্রবেশ করে চেষ্টা করুন এবং এবং পরীক্ষা করুন যে আপনার ব্রাউজার এই সাইটে কুকি ব্যবহারের অনুমতি দেয়।",
        "session_fail_preview_html": "দুঃখিত! সেশনের উপাত্ত হারিয়ে যাওয়ার কারণে আমরা আপনার সম্পাদনাটি প্রক্রিয়াভুক্ত করতে পারিনি।\n\n<em>{{SITENAME}}-এ raw HTML সক্রিয় আছে বলে জাভাস্ক্রিপ্ট ভিত্তিক আক্রমণ থেকে প্রতিরক্ষার জন্য প্রাকদর্শনটি দেখানো হচ্ছে না।</em>\n\n<strong>যদি এটি সম্পাদনার একটি বৈধ প্রচেষ্টা হয়, তবে অনুগ্রহ করে আবার চেষ্টা করুন।</strong>\nযদি তারপরেও কাজ না হয়, তবে অ্যাকাউন্ট থেকে [[Special:UserLogout|বেরিয়ে গিয়ে]] আবার প্রবেশ করুন, এবং পরীক্ষা করে দেখুন যে আপনার ব্রাউজারে এই সাইট থেকে কুকি রাখার অনুমতি আছে কি না।",
-       "token_suffix_mismatch": "'''আপনার সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে, কারণ আপনার ক্লায়েন্ট প্রোগ্রামটি সম্পাদনা টেক্সটের বিরামচিহ্নগুলি গুলিয়ে ফেলেছে। পাতাটির টেক্সটে যাতে ক্ষতি না হয় সেজন্য সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে। আপনি কোন ত্রুটিপূর্ণ ওয়েব-ভিত্তিক বেনামী প্রক্সি সেবা ব্যবহার করলে এরকম হতে পারে।'''",
+       "token_suffix_mismatch": "<strong>আপনার সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে, কারণ আপনার ক্লায়েন্ট প্রোগ্রামটি সম্পাদনা পাঠ্যের বিরামচিহ্নগুলি গুলিয়ে ফেলেছে।</strong>\nপাতাটির পাঠ্যে যাতে ক্ষতি না হয় সেজন্য সম্পাদনাটি প্রত্যাখ্যান করা হয়েছে।\nআপনি কোন ত্রুটিপূর্ণ ওয়েব-ভিত্তিক বেনামী প্রক্সি সেবা ব্যবহার করলে এরকম হতে পারে।",
        "edit_form_incomplete": "'''আপনার সম্পাদনার কিছু অংশ সার্ভারে পৌছায় নি; আপনার সম্পাদনা সম্পূর্ণরুপে আছে কিনা নিশ্চিত হয়ে আবার চেষ্টা করুন'''",
        "editing": "সম্পাদনা করছেন: $1",
        "creating": "$1 পাতাটি তৈরি করছেন",
        "rcfilters-clear-all-filters": "সব ছাঁকনি পরিষ্কার করুন",
        "rcfilters-show-new-changes": "$1 থেকে নতুনতর পরিবর্তনসমূহ দেখুন",
        "rcfilters-search-placeholder": "সাম্প্রতিক পরিবর্তনসমূহ ছাঁকুন (ব্রাউজ বা টাইপ করা শুরু করুন)",
+       "rcfilters-search-placeholder-mobile": "ছাঁকনি",
        "rcfilters-invalid-filter": "অকার্যকর ছাঁকনি",
        "rcfilters-empty-filter": "কোনো সক্রিয় ফিল্টার নেই। সমস্ত অবদান দেখানো হয়েছে।",
        "rcfilters-filterlist-title": "ছাঁকনি",
        "ipblocklist-otherblocks": "অন্যান্য {{PLURAL:$1|বাধাঁ|বাধাঁসমূহ}}",
        "infiniteblock": "অসীম",
        "expiringblock": "$1 তারিখের $2 এ মেয়াদোত্তীর্ণ হবে",
-       "anononlyblock": "শুধু বেনামীদের",
+       "anononlyblock": "শুধুমাত্র বেনামীগণ",
        "noautoblockblock": "স্বয়ংক্রিয় বাধাদান নিষ্ক্রিয় করা হয়েছে",
        "createaccountblock": "অ্যাকাউন্ট সৃষ্টিতে বাধা দেওয়া হয়েছে",
        "emailblock": "ই-মেইল বাধা দেয়া হয়েছে",
        "reblock-logentry": "[[$1]]-এর বাধাদান সেটিং পরিবর্তন করেছেন যেটি শেষ হবার মেয়াদ $2 $3",
        "blocklogtext": "এটি ব্যবহারকারীদেরকে বাধা দানের বা বাধা তুলে নেওয়ার লগ।\nস্বয়ংক্রিয়ভাবে বাধাদানকৃত আইপি ঠিকানাগুলি এখানে তালিকাবদ্ধ করা হয়নি।\nবর্তমানে সক্রিয় নিষিদ্ধকরণ ও বাধাদানের তালিকার জন্য [[Special:BlockList| বাধাদান তালিকা]] দেখুন।",
        "unblocklogentry": "$1-এর উপর বাধা তুলে নেয়া হয়েছে",
-       "block-log-flags-anononly": "à¦\95à§\87বল à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80রা",
+       "block-log-flags-anononly": "শà§\81ধà§\81মাতà§\8dর à¦¬à§\87নামà§\80 à¦¬à§\8dযবহারà¦\95ারà§\80à¦\97ণ",
        "block-log-flags-nocreate": "অ্যাকাউন্ট সৃষ্টি নিষ্ক্রিয় করা হয়েছে",
        "block-log-flags-noautoblock": "স্বয়ংক্রিয় বাধাদান নিষ্ক্রিয়",
        "block-log-flags-noemail": "ই-মেইলে বাধা আছে",
        "block-log-flags-nousertalk": "নিজের আলাপের পাতা সম্পাদনা করতে পারবে না",
-       "block-log-flags-angry-autoblock": "à¦\89নà§\8dনত à¦\85à¦\9fà§\8bবà§\8dলà¦\95 সক্রিয়",
+       "block-log-flags-angry-autoblock": "à¦\89নà§\8dনত à¦¸à§\8dবয়à¦\82à¦\95à§\8dরিয় à¦¬à¦¾à¦§à¦¾à¦¦à¦¾à¦¨ সক্রিয়",
        "block-log-flags-hiddenname": "ব্যবহারকারী নাম লুক্কায়িত",
        "range_block_disabled": "প্রশাসকের পক্ষে আইপি ঠিকানার শ্রেণী বাধাদানের ক্ষমতা নিষ্ক্রিয় আছে।",
        "ipb_expiry_invalid": "মেয়াদোত্তীর্ণকাল অবৈধ।",
        "proxyblockreason": "আপনার আইপি ঠিকানাকে বাধা দেয়া হয়েছে কারণ এটি একটি উন্মুক্ত প্রক্সি। অনুগ্রহ করে আপনার ইন্টারনেট সেবা প্রদানকারী কোম্পানির সাথে কারিগরি সহায়তার ব্যাপারে যোগাযোগ করুন এবং এই গুরুত্বপূর্ণ নিরাপত্তা সমস্যার ব্যাপারে তাদেরকে অবহিত করুন।",
        "sorbsreason": "আপনার আইপি ঠিকানাটি {{SITENAME}}-এর ব্যবহার করা DNSBL-এ উন্মুক্ত প্রক্সি হিসেবে তালিকাভুক্ত আছে।",
        "sorbs_create_account_reason": "আপনার আইপি ঠিকানাটি {{SITENAME}}-এর ব্যবহার করা DNSBL-এ উন্মুক্ত প্রক্সি হিসেবে তালিকাভুক্ত আছে। আপনি কোন অ্যাকাউন্ট সৃষ্টি করতে পারবেন না।",
-       "softblockrangesreason": "বà§\87নামà§\80 à¦\85বদান à¦\86পনার à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¥à§\87à¦\95à§\87 অনুমোদিত নয় ($1)। দয়া করে প্রবেশ করুন।",
+       "softblockrangesreason": "à¦\86পনার à¦\86à¦\87পি à¦ à¦¿à¦\95ানা à¦¥à§\87à¦\95à§\87 à¦¬à§\87নামà§\87 à¦\85বদান à¦°à¦¾à¦\96া অনুমোদিত নয় ($1)। দয়া করে প্রবেশ করুন।",
        "xffblockreason": "X-Forwarded-For হেডারে থাকা আইপি ঠিকানাটি ব্লক করা হয়েছে, হয় এটি আপনার নিজের অথবা আপনার ব্যবহৃত প্রক্সি সার্ভারের আইপি ঠিকানা। ব্লক করার কারণ হল: $1",
        "cant-see-hidden-user": "আপনি যে ব্যবহারকারীকে ব্লক বা লুকিয়ে রাখতে চাচ্ছেন তাকে আগে থেকেই ব্লক বা লুকিয়ে রাখা হয়েছে। এছাড়া আপনার Hideuser অধিকার নেই, তাই আপনি ব্যবহারকারীর অবস্থা পরিবর্তন করতে পারবেন না।",
        "ipbblocked": "আপনি অন্য কোন ব্যবহারকরীকে বাধাদান বা বাধা বাতিল করতে পারবেন না, কারণ আপনি নিজেই অবরুদ্ধ রয়েছেন।",
        "group-bot.js": "/* এখানে সন্নিবেশিত জাভাস্ক্রিপ্ট শুধু বটের জন্য লোড হবে */",
        "group-sysop.js": "/* এখানে সন্নিবেশিত জাভাস্ক্রিপ্ট শুধু প্রশাসকদের জন্য লোড হবে */",
        "group-bureaucrat.js": "/* এখানে সন্নিবেশিত জাভাস্ক্রিপ্ট শুধু ব্যুরোক্র্যাটদের জন্য লোড হবে */",
-       "anonymous": "{{SITENAME}} এর বেনামী {{PLURAL:$1|ব্যবহারকারী|ব্যবহারকারীবৃন্দ}}",
+       "anonymous": "{{SITENAME}}-এর বেনামী {{PLURAL:$1|ব্যবহারকারী|ব্যবহারকারীগণ}}",
        "siteuser": "{{SITENAME}} ব্যবহারকারী $1",
        "anonuser": "{{SITENAME}} বেনামী ব্যবহারকারী $1",
        "lastmodifiedatby": "$3 কর্তৃক $2, $1 তারিখে এই পাতাটি শেষ সম্পাদিত হয়েছিল।",
        "mw-widgets-abandonedit-discard": "সম্পাদনা বাতিল করুন",
        "mw-widgets-abandonedit-keep": "সম্পাদনা অব্যাহত রাখুন",
        "mw-widgets-abandonedit-title": "আপনি কি নিশ্চিত?",
+       "mw-widgets-copytextlayout-copy": "অনুলিপি",
+       "mw-widgets-copytextlayout-copy-fail": "ক্লিপবোর্ডে অনুলিপি করা ব্যর্থ হয়েছে।",
+       "mw-widgets-copytextlayout-copy-success": "ক্লিপবোর্ডে অনুলিপি করা হয়েছে।",
        "mw-widgets-dateinput-no-date": "কোন তারিখ নির্বাচন করা হয়নি",
        "mw-widgets-dateinput-placeholder-day": "বববব-মম-দদ",
        "mw-widgets-dateinput-placeholder-month": "বববব-মম",
        "restrictionsfield-help": "লাইন প্রতি একটি আইপি ঠিকানা বা CIDR পরিসীমা। সবকিছু সক্রিয় করতে ব্যবহার করুন: :<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "ত্রুটি: $1",
        "edit-error-long": "ত্রুটিসমূহ:\n\n$1",
+       "specialmute": "নিঃশব্দ",
        "specialmute-submit": "নিশ্চিত করুন",
        "revid": "সংশোধন $1",
        "pageid": "পাতার আইডি $1",
index 5243ff8..42b937e 100644 (file)
        "rcfilters-activefilters-show": "Diskouez",
        "rcfilters-advancedfilters": "Siloù araokaet",
        "rcfilters-limit-title": "Kemmoù da vezañ diskouezet",
-       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|kemm|kemmoù}}, $2",
+       "rcfilters-limit-and-date-label": "$1 kemm, $2",
+       "rcfilters-date-popup-title": "Prantad amzer da glask",
        "rcfilters-days-title": "Deizioù paseet",
        "rcfilters-hours-title": "Eurioù paseet",
        "rcfilters-days-show-days": "($1 {{PLURAL:$1|deiz}})",
        "rcfilters-clear-all-filters": "Riñsañ an holl siloù",
        "rcfilters-show-new-changes": "Gwelet ar c'hemmoù diwezhañ",
        "rcfilters-search-placeholder": "Silañ ar c'hemmoù diwezhañ (merdeiñ pe kregiñ da skrivañ)",
+       "rcfilters-search-placeholder-mobile": "Siloù",
        "rcfilters-invalid-filter": "Sil direizh",
        "rcfilters-empty-filter": "Sil oberiant ebet. War wel emañ an holl gemmoù.",
        "rcfilters-filterlist-title": "Siloù",
        "logentry-import-interwiki": "{{GENDER:$2|Enporzhiet}} eo bet $3 gant $1 adalek ur wiki all",
        "logentry-import-interwiki-details": "{{GENDER:$2|Enporzhiet}} eo bet $3 adalek $5 gant $1 ($4 {{PLURAL:$4|adweladenn}})",
        "logentry-merge-merge": "$1 {{GENDER:$2|en deus lakaet|he deus lakaet}} $3 da gendeuziñ e $4 (adweladennoù betek $5)",
-       "logentry-move-move": "$1 en deus kaset ar bajenn $3 da $4",
-       "logentry-move-move-noredirect": "kaset ar bajenn $3 da $4 gant $1 hep adkas",
-       "logentry-move-move_redir": "kaset ar bajenn $3 da $4 gant $1 dreist un adkas",
-       "logentry-move-move_redir-noredirect": "kaset ar bajenn $3 da $4 gant $1 dreist un adkas hep lezel un adkas",
+       "logentry-move-move": "$1 {{GENDER:$2|en|he|en}} deus kaset ar bajenn $3 da $4",
+       "logentry-move-move-noredirect": "Kaset eo bet ar bajenn $3 da $4 gant $1 hep lezel un adkas",
+       "logentry-move-move_redir": "Kaset eo bet ar bajenn $3 da $4 gant $1 dreist un adkas",
+       "logentry-move-move_redir-noredirect": "Kaset eo bet ar bajenn $3 da $4 gant $1 dreist un adkas hep lezel un adkas",
        "logentry-patrol-patrol": "{{GENDER:$2|Merket}} eo bet an adweladenn $4 eus ar bajenn $3 evel gwiriet gant $1",
        "logentry-patrol-patrol-auto": "{{GENDER:$2|Merket}} eo bet ent emgefre an adweladenn $4 eus ar bajenn $3 evel gwiriet gant $1",
        "logentry-newusers-newusers": "{{GENDER:$2|Krouet}} eo bet ar gont implijer $1",
        "undelete-cantcreate": "N'hallit ket diziverkañ ar bajenn-mañ rak n'eus pajenn ebet gant an anv-mañ ha n'oc'h ket aotreet da grouiñ ar bajenn-mañ.",
        "pagedata-title": "Roadennoù ar bajenn",
        "pagedata-bad-title": "Titl direizh : $1.",
+       "passwordpolicies": "Reolennoù e keñver ar ger-tremen",
        "passwordpolicies-group": "Strollad",
        "passwordpolicies-policies": "Politikerezh",
        "passwordpolicies-policy-passwordcannotmatchusername": "Ar ger-kuzh ne c'hell ket bezañ an anv implijer"
index f6f7023..839a5dc 100644 (file)
        "edit-no-change": "S'ha ignorat la vostra modificació perquè no feia cap canvi al text.",
        "edit-slots-cannot-add": "{{PLURAL:$1|L'espai no està|Els espais no estan}} implementats aquí: $2.",
        "edit-slots-cannot-remove": "{{PLURAL:$1|L'espai següent és necessari i no es pot suprimir|Els espais següents són necessaris i no es poden suprimir}}: $2.",
+       "edit-slots-missing": "Hi {{PLURAL:$1|manca l'espai següent|manquen els espais següents}}: $2.",
        "postedit-confirmation-created": "S'ha creat la pàgina.",
        "postedit-confirmation-restored": "S'ha restaurat la pàgina.",
        "postedit-confirmation-saved": "S'ha desat la modificació.",
        "grant-editmycssjs": "Modifiqueu el vostre CSS/JSON/JavaScript d'usuari",
        "grant-editmyoptions": "Editeu les vostres preferències d'usuari i la configuració JSON",
        "grant-editmywatchlist": "Modifica la llista de seguiment",
+       "grant-editsiteconfig": "Modificar els CSS/JS del lloc web i d'usuari",
        "grant-editpage": "Modifica les pàgines existents",
        "grant-editprotected": "Modifica pàgines protegides",
        "grant-highvolume": "Edició d'alt volum",
        "action-changetags": "afegeix i elimina etiquetes a les revisions i les entrades de registre individuals",
        "action-deletechangetags": "eliminar etiquetes des de la base de dades",
        "action-purge": "purga la pàgina",
+       "action-apihighlimits": "utilitza límits majors en les consultes API",
+       "action-bigdelete": "eliminar les pàgines amb grans historials",
        "action-blockemail": "blocar un usuari per tal que no enviï correu",
        "action-bot": "ser tractat com un procés automatitzat",
        "action-editprotected": "modificar pàgines protegides com «{{int:protect-level-sysop}}»",
        "action-viewsuppressed": "mostrar revisions amagades de qualsevol usuari",
        "action-hideuser": "blocar un nom d'usuari, amagant-lo del públic",
        "action-unblockself": "desblocar-se un mateix",
+       "action-reupload-own": "sobreescriure fitxers existents que hàgiu carregat",
+       "action-suppressredirect": "no crear redireccions de les pàgines d'origen en reanomenar-les",
        "nchanges": "$1 {{PLURAL:$1|canvi|canvis}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|des de la darrera visita}}",
        "enhancedrc-history": "historial",
        "rcfilters-clear-all-filters": "Esborra tots els filtres",
        "rcfilters-show-new-changes": "Mostra els canvis nous des del $1",
        "rcfilters-search-placeholder": "Filtra els canvis recents (utilitzeu el menú o cerqueu el nom del filtre)",
+       "rcfilters-search-placeholder-mobile": "Filtres",
        "rcfilters-invalid-filter": "Filtre no vàlid",
        "rcfilters-empty-filter": "No hi ha cap filtre actiu. Es mostren totes les contribucions.",
        "rcfilters-filterlist-title": "Filtres",
        "ipb-confirm": "Confirma el blocatge",
        "ipb-sitewide": "A tot el lloc web",
        "ipb-partial": "Parcial",
+       "ipb-sitewide-help": "Cada pàgina en el wiki i totes les altres accions de contribució.",
        "ipb-partial-help": "Pàgines específiques o espais de noms.",
        "ipb-pages-label": "Pàgines",
        "ipb-namespaces-label": "Espais de noms",
        "move-watch": "Vigila aquesta pàgina",
        "movepagebtn": "Reanomena la pàgina",
        "pagemovedsub": "Reanomenament amb èxit",
+       "cannotmove": "No s'ha pogut reanomenar la pàgina {{PLURAL:$1|pel motiu següent|pels motius següents}}:",
        "movepage-moved": "'''«$1» s'ha mogut a «$2»'''",
        "movepage-moved-redirect": "S'ha creat una redirecció.",
        "movepage-moved-noredirect": "La creació d'una redirecció s'ha suprimit.",
        "confirm-mcrrestore-title": "Restaura una revisió",
        "confirm-mcrundo-title": "Desfés un canvi",
        "mcrundofailed": "Ha fallat el desfer",
+       "mcrundo-missingparam": "Manquen paràmetres obligatoris en la sol·licitud.",
        "mcrundo-changed": "La pàgina ha canviat d'ençà que heu vist la diferència. Reviseu el nou canvi.",
        "mcrundo-parse-failed": "No s'ha pogut analitzar la revisió nova: $1",
        "colon-separator": ":&#32;",
        "htmlform-date-invalid": "El valor que heu especificat no és una data reconeguda. Proveu d'utilitzar el format AAAA-MM-DD.",
        "htmlform-time-invalid": "El valor que heu especificat no és una hora reconeguda. Proveu d'utilitzar el format HH:MM:SS.",
        "htmlform-datetime-invalid": "El valor que heu especificat no és una data i hora reconeguda. Proveu d'utilitzar el format AAAA-MM-DD HH:MM:SS.",
+       "htmlform-time-toohigh": "El valor que heu especificat és posterior a la darrera hora permesa de $1.",
        "htmlform-datetime-toolow": "El valor que heu especificat és anterior a la data i hora més antiga permeses, $1.",
        "htmlform-datetime-toohigh": "El valor que heu especificat és posterior a la data i hora permeses, $1.",
        "htmlform-title-badnamespace": "[[:$1]] no es troba en l'espai de noms \"{{ns:$2}}\".",
        "logentry-suppress-block": "$1 {{GENDER:$2|ha blocat}} {{GENDER:$4|$3}} per un temps de $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|ha canviat}} la configuració de blocatge de {{GENDER:$4|$3}} per un temps de $5 $6",
        "logentry-import-upload": "$1 {{GENDER:$2|va importar}} $3 a través de càrrega de fitxer",
+       "logentry-import-upload-details": "$1 {{GENDER:$2|ha importat}} $3 per càrrega de fitxer ($4 {{PLURAL:$4|revisió|revisions}})",
        "logentry-import-interwiki": "$1 {{GENDER:$2|va importar}} $3 d'un altre wiki",
+       "logentry-import-interwiki-details": "$1 {{GENDER:$2|ha importat}} $3 de $5 ($4 {{PLURAL:$4|revisió|revisions}})",
        "logentry-merge-merge": "$1 {{GENDER:$2|ha fusionat}} $3 en $4 (revisions fins a $5)",
        "logentry-move-move": "$1 ha mogut $3 a $4",
        "logentry-move-move-noredirect": "$1 ha mogut $3 a $4 sense deixar una redirecció",
        "logentry-managetags-activate": "$1 {{GENDER:$2|ha activat}} l'etiqueta \"$4\" per a ser utilitzada en usuaris i bots",
        "logentry-managetags-deactivate": "$1 {{GENDER:$2|ha desactivat}} l'etiqueta \"$4\" per a ser utilitzada en usuaris i bots",
        "log-name-tag": "Registre d'etiquetes",
+       "logentry-tag-update-add-revision": "$1 {{GENDER:$2|ha afegit}} {{PLURAL:$7|l'etiqueta|les etiquetes}} $6 a la revisió $4 de la pàgina $3",
        "rightsnone": "(cap)",
        "rightslogentry-temporary-group": "$1 (temporal, fins a $2)",
        "feedback-adding": "S'està afegint el comentari a la pàgina...",
index 3bb077b..4da930e 100644 (file)
        "rcfilters-clear-all-filters": "Zrušit všechny filtry",
        "rcfilters-show-new-changes": "Zobrazit změny od $1",
        "rcfilters-search-placeholder": "Filtrovat poslední změny (použijte menu nebo vyhledejte název filtru)",
+       "rcfilters-search-placeholder-mobile": "Filtry",
        "rcfilters-invalid-filter": "Neplatný filtr",
        "rcfilters-empty-filter": "Žádné aktivní filtry. Zobrazeny jsou všechny příspěvky.",
        "rcfilters-filterlist-title": "Filtry",
        "move-page-legend": "Přesunout stránku",
        "movepagetext": "Použitím tohoto formuláře změníte název stránky a přesunete i celou její historii na nový název.\nPůvodní název se stane přesměrováním na nový název.\nPřesměrování na původní název můžete nechat aktualizovat automaticky.\nPokud nenecháte, nezapomeňte poté zkontrolovat [[Special:DoubleRedirects|dvojitá]] nebo [[Special:BrokenRedirects|přerušená]] přesměrování.\nJe vaší zodpovědností zajistit, aby odkazy stále vedly tam, kam mají.\n\nStránku <strong>není možné</strong> přejmenovat, pokud pod cílovým názvem již nějaká stránka existuje, s výjimkou situace, kdy je cílová stránka přesměrováním na tuto stránku a nemá žádnou historii editací.\nTo znamená, že stránku můžete přesunout zpět na původní název, pokud uděláte chybu, a že nemůžete přepsat existující stránku.\n\n<strong>Poznámka:</strong>\nPřejmenování oblíbené stránky může být drastická a nečekaná změna;\npředtím, než změnu provedete, se ujistěte, že chápete důsledky svého kroku.",
        "movepagetext-noredirectfixer": "Použitím tohoto formuláře změníte název stránky a přesunete i celou její historii na nový název.\nPůvodní název se stane přesměrováním na nový název.\nNezapomeňte poté zkontrolovat [[Special:DoubleRedirects|dvojitá]] nebo [[Special:BrokenRedirects|přerušená]] přesměrování.\nJe vaší zodpovědností zajistit, aby odkazy stále vedly tam, kam mají.\n\nStránku <strong>není možno</strong> přejmenovat, pokud pod cílovým názvem již nějaká stránka existuje, s výjimkou situace, kdy je cílová stránka prázdná nebo je přesměrováním na tuto stránku a nemá žádnou historii editací.\nTo znamená, že stránku můžete přesunout zpět na původní název, pokud uděláte chybu, a že nemůžete přepsat existující stránku.\n\n<strong>Poznámka:</strong>\nPřejmenování oblíbené stránky může být drastická a nečekaná změna; předtím, než změnu provedete, se prosím ujistěte, že chápete důsledky svého kroku.",
+       "movepagetext-noredirectsupport": "Použitím tohoto formuláře změníte název stránky a přesunete i celou její historii na nový název.\nJe vaší zodpovědností zajistit, aby odkazy stále vedly tam, kam mají.\n\nStránku <strong>není možné</strong> přejmenovat, pokud pod cílovým názvem již nějaká stránka existuje.\nTo znamená, že stránku můžete přesunout zpět na původní název, pokud uděláte chybu, a že nemůžete přepsat existující stránku.\n\n<strong>Poznámka:</strong>\nPřejmenování oblíbené stránky může být drastická a nečekaná změna;\npředtím, než změnu provedete, se ujistěte, že chápete důsledky svého kroku.",
        "movepagetalktext": "Pokud zaškrtnete toto pole, přidružená diskusní stránka bude automaticky přesunuta na nový název, leda by tam již neprázdná diskusní stránka existovala.\n\nV takovém případě musíte stránky přesunout nebo sloučit ručně, přejete-li si to.",
        "moveuserpage-warning": "'''Upozornění:''' Chystáte se přesunout uživatelskou stránku. Uvědomte si prosím, že bude přesunuta pouze tato stránka, ale uživatel ''nebude'' přejmenován.",
        "movecategorypage-warning": "<strong>Upozornění:</strong> Chystáte se přesunout stránku kategorie. Uvědomte si, že bude přesunuta pouze tato stránka a že žádné stránky v původní kategorii <em>nebudou</em> do nové překategorizovány.",
index 9988341..a509502 100644 (file)
        "changecontentmodel" : "Change content model of a page",
        "changecontentmodel-legend": "Change content model",
        "changecontentmodel-title-label": "Page title",
+       "changecontentmodel-current-label": "Current content model:",
        "changecontentmodel-model-label": "New content model",
        "changecontentmodel-reason-label": "Reason:",
        "changecontentmodel-submit": "Change",
        "move-page-legend": "Move page",
        "movepagetext": "Using the form below will rename a page, moving all of its history to the new name.\nThe old title will become a redirect page to the new title.\nYou can update redirects that point to the original title automatically.\nIf you choose not to, be sure to check for [[Special:DoubleRedirects|double]] or [[Special:BrokenRedirects|broken redirects]].\nYou are responsible for making sure that links continue to point where they are supposed to go.\n\nNote that the page will <strong>not</strong> be moved if there is already a page at the new title, unless the latter is a redirect and has no past edit history.\nThis means that you can rename a page back to where it was renamed from if you make a mistake, and you cannot overwrite an existing page.\n\n<strong>Note:</strong>\nThis can be a drastic and unexpected change for a popular page;\nplease be sure you understand the consequences of this before proceeding.",
        "movepagetext-noredirectfixer": "Using the form below will rename a page, moving all of its history to the new name.\nThe old title will become a redirect page to the new title.\nBe sure to check for [[Special:DoubleRedirects|double]] or [[Special:BrokenRedirects|broken redirects]].\nYou are responsible for making sure that links continue to point where they are supposed to go.\n\nNote that the page will <strong>not</strong> be moved if there is already a page at the new title, unless it is a redirect and has no past edit history.\nThis means that you can rename a page back to where it was renamed from if you make a mistake, and you cannot overwrite an existing page.\n\n<strong>Note:</strong>\nThis can be a drastic and unexpected change for a popular page;\nplease be sure you understand the consequences of this before proceeding.",
+       "movepagetext-noredirectsupport": "Using the form below will rename a page, moving all of its history to the new name.\nYou are responsible for making sure that links continue to point where they are supposed to go.\n\nNote that the page will <strong>not</strong> be moved if there is already a page at the new title.\nThis means that you can rename a page back to where it was renamed from if you make a mistake, and you cannot overwrite an existing page.\n\n<strong>Note:</strong>\nThis can be a drastic and unexpected change for a popular page;\nplease be sure you understand the consequences of this before proceeding.",
        "movepagetalktext": "If you check this box, the associated talk page will be automatically moved to new title, unless a non-empty talk page already exists there.\n\nIn this case, you will have to move or merge the page manually if desired.",
        "moveuserpage-warning": "<strong>Warning:</strong> You are about to move a user page. Please note that only the page will be moved and the user will <em>not</em> be renamed.",
        "movecategorypage-warning": "<strong>Warning:</strong> You are about to move a category page. Please note that only the page will be moved and any pages in the old category will <em>not</em> be recategorized into the new one.",
index baf32fd..93c5f93 100644 (file)
        "block-log-flags-angry-autoblock": "progresa aŭtoforbaro ebliĝis",
        "block-log-flags-hiddenname": "salutnomo kaŝita",
        "range_block_disabled": "La ebleco de administranto krei forbaritajn intervalojn da IP-adresoj estas malebligita.",
+       "ipb-prevent-user-talk-edit": "Redaktado de onia propra diskutpaĝo devas esti permesata pri parta forbaro, krom se ĝi inkluzivas malpermeson pri la uzanto-diskutpaĝa nomspaco.",
        "ipb_expiry_invalid": "Nevalida blokdaŭro.",
        "ipb_expiry_old": "Limdato antaŭas la nuntempon.",
        "ipb_expiry_temp": "Kaŝitaj salutnomaj blokoj estu daŭraj.",
        "move-page-legend": "Alinomi paĝon",
        "movepagetext": "Per la jena formulo vi povas ŝanĝi la nomon de iu paĝo, kunportante ĝian historion de redaktoj al la nova nomo.\nLa antaŭa titolo fariĝos alidirektilo al la nova titolo.\nVi povas ĝisdatigi alidirektilojn kiu indikas la originalan titolon aŭtomate.\nSe vi elektas ĝisdatigi permane, bonvolu kontroli [[Special:DoubleRedirects|duoblajn]] aŭ [[Special:BrokenRedirects|rompitajn alidirektilojn]].\nVi estas responsa por certigi ke ligilojn direktas fidinde.\n\nNotu, ke la paĝo '''ne''' estos movita se jam ekzistas paĝo ĉe la nova titolo, krom se tiu loko estas malplena aŭ alidirektilo al ĉi tiu paĝo, kaj sen antaŭa redaktohistorio.\nPro tio, vi ja povos removi la paĝon je la antaŭa titolo se vi mistajpus, kaj ne povas forviŝi ekzistantan paĝon per movo.\n\n'''Note:'''\nTio povas esti drasta kaj neatendita ŝanĝo por populara paĝo;\nbonvolu certigi vin, ke vi komprenas ties konsekvencojn antaŭ ol vi antaŭeniru.",
        "movepagetext-noredirectfixer": "Per jena formularo vi povas alinomigi paĝon, kaj movi tutan ĝian redaktohistorion al la nova nomo. \nLa antaŭa titolo alidirektigos onin al la nova titolo.\nKontrolu pri [[Special:DoubleRedirects|duoblajn]] aŭ [[Special:BrokenRedirects|nefunkciantajn alidirektilojn]].\nVi respondecas pri tio ke ligoj restas montrantaj ĝustadirekten.\n\nKonsciu ke la paĝo <strong>ne</strong> estas movota se jam ekzistas paĝo havanta la novan titolon, krom se ĝi estas alidirektilo sen antaŭa redaktohistorio.\nTio ĉi signifas ke vi povas alinomigi paĝon reen al antaŭa nomo se vi eraras, kaj vi ke vi ne povas anstataŭigi ekzistantan paĝon.\n\n<strong>Rimarko:</strong>\nEblas ke tio ĉi estas drasta kaj neatendita ŝanĝo de populara paĝo;\nAntaŭ daŭrigi, bonvolu certiĝi, ke vi komprenas la konsekvencojn de tiuj ĉi ŝanĝo.",
+       "movepagetext-noredirectsupport": "La jena formularo renomas paĝon, movante ĉiom da ĝia historio al la nova nomo.\nVi respondecas pri certigo ke ligiloj daŭre indikas la ĝustajn celojn.\n\nNotu ke la paĝo <strong>ne</strong> estos movita se jam ekzistas paĝo ĉe la nova titolo.\nTial, vi povas renomi paĝon reen al la originala titolo se vi misrenomis, kaj vi ne povas anstataŭigi ekzistantan paĝon.\n\n<strong>Noto:</strong>\nĈi tio estas eble drasta kaj neatendata ŝanĝo pri populara paĝo;\nbonvolu certigu ke vi komprenas la konsekvencon de tio, antaŭ ol fari tion.",
        "movepagetalktext": "Se vi validas tiun elektobutono, la asociata diskutpaĝo estos aŭtomate alinomita al nova titolo, krom se malplena diskutpaĝo jam ekzistas.\n\nTiujokaze, vi alinomigendos aŭ kunfandendos malaŭtomate la paĝon se vi tion deziras.",
        "moveuserpage-warning": "<strong>Averto:</strong> Vi preskaŭ alinomas paĝon de uzanto. Bonvolu noti ke nur la paĝo estos alinomita kaj la uzanto mem <em>ne</em> estos alinomita.",
        "movecategorypage-warning": "<strong>Averto:</strong> Vi baldaŭ movos kategorian paĝon. Bonvolu noti ke, nur la paĝo estos movita, kaj la paĝoj en la malnova kategorio <em>ne</em> transiros en la novan kategorion.",
index 6adbf38..ebc2405 100644 (file)
        "systemblockedtext": "Tu nombre de usuario o dirección IP ha sido bloqueado automáticamente por el software MediaWiki.\nLa razón dada es:\n\n:<em>$2</em>\n\n* Inicio del bloqueo: $8\n* Caducidad de bloqueo: $6\n* Destinatario del bloqueo: $7\n\nTu dirección IP actual es $3.\nPor favor, incluye todos los datos aquí mostrados en cualquier consulta que hagas.",
        "blockednoreason": "no se ha especificado el motivo",
        "blockedtext-composite": "<strong>Tu nombre de usuario o dirección IP han sido bloqueados.</strong>\n\nLa razón es:\n\n:<em>$2</em>.\n\n* Inicio del bloqueo: $8\n* Vencimiento del bloqueo más largo: $6\n\n* $5\n\nTu dirección IP actual es $3.\nPor favor, incluye todos los detalles anteriores en cualquier consulta que realices.",
+       "blockedtext-composite-ids": "Identificadores de bloqueo relevantes: $1 (tu dirección IP puede encontrarse también en alguna lista negra)",
        "blockedtext-composite-no-ids": "Tu dirección IP figura en varias listas negras",
-       "blockedtext-composite-reason": "Hay múltiples bloques contra tu cuenta y/o dirección IP.",
+       "blockedtext-composite-reason": "Existen múltiples bloqueos contra tu cuenta y/o dirección IP",
        "whitelistedittext": "Tienes que $1 para editar páginas.",
        "confirmedittext": "Debes confirmar tu dirección de correo electrónico antes de poder editar páginas. Por favor, configura y confirma tu dirección de correo a través de tus [[Special:Preferences|preferencias de usuario]].",
        "nosuchsectiontitle": "Sección no encontrada",
        "right-block": "Bloquear a otros usuarios para que no editen",
        "right-blockemail": "Bloquear a un usuario para que no pueda mandar correos electrónicos",
        "right-hideuser": "Bloquear un nombre de usuario, haciéndolo invisible",
-       "right-ipblock-exempt": "Evitar bloqueos a IP, automáticos y por intervalos",
+       "right-ipblock-exempt": "Evitar bloqueos de IP, bloqueos automáticos y bloqueos de rango",
        "right-unblockself": "Desbloquearse a sí mismo",
        "right-protect": "Cambiar niveles de protección y editar páginas protegidas en cascada",
        "right-editprotected": "Editar páginas protegidas como «{{int:protect-level-sysop}}»",
        "right-editmyusercss": "Editar tus archivos CSS",
        "right-editmyuserjson": "Editar tus propias páginas en formato JSON",
        "right-editmyuserjs": "Editar tus archivos JavaScript",
+       "right-editmyuserjsredirect": "Editar tus propias páginas de usuario en formato JavaScript cuando sean redirecciones",
        "right-viewmywatchlist": "Ver su propia lista de seguimiento",
        "right-editmywatchlist": "Editar su propia lista de seguimiento (algunas acciones seguirán añadiendo páginas aun sin este permiso).",
        "right-viewmyprivateinfo": "Ver su propia información privada (ej.: correo electrónico, nombre real)",
        "action-editmyusercss": "editar tus propios archivos CSS",
        "action-editmyuserjson": "editar tus propios archivos JSON",
        "action-editmyuserjs": "editar tus propios archivos JavaScript",
+       "action-editmyuserjsredirect": "editar tus propias páginas de usuario en formato JavaScript cuando sean redirecciones",
        "action-viewsuppressed": "ver revisiones ocultas de cualquier usuario",
        "action-hideuser": "bloquear un nombre de usuario, haciéndolo invisible",
-       "action-ipblock-exempt": "evitar bloques de IP, autobloqueos y bloqueos a distancia",
+       "action-ipblock-exempt": "evitar bloqueos de IP, bloqueos automáticos y bloqueos de rango",
        "action-unblockself": "desbloquearse a sí mismo",
        "action-noratelimit": "no resultar afectado por los límites de frecuencia de edición",
        "action-reupload-own": "sobrescribir archivos existentes subidos por uno mismo",
        "rcfilters-clear-all-filters": "Borrar todos los filtros",
        "rcfilters-show-new-changes": "Ver cambios nuevos desde $1",
        "rcfilters-search-placeholder": "Filtrar cambios (utiliza el menú o busca el nombre de un filtro)",
+       "rcfilters-search-placeholder-mobile": "Filtros",
        "rcfilters-invalid-filter": "Filtro no válido",
        "rcfilters-empty-filter": "No hay filtros activos. Se muestran todas las contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
index b7ad22b..715434e 100644 (file)
@@ -8,7 +8,8 @@
                        "SPQRobin",
                        "Siebrand",
                        "Sjoerddebruin",
-                       "Slomox"
+                       "Slomox",
+                       "Romaine"
                ]
        },
        "exif-imagewidth": "Breedte",
        "exif-photometricinterpretation-3": "Palet",
        "exif-photometricinterpretation-4": "Transparantiemasker",
        "exif-photometricinterpretation-5": "Gescheiden (waarschijnlijk CMYK)",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
        "exif-unknowndate": "Datum onbekend",
        "exif-orientation-1": "Normaal",
        "exif-orientation-2": "Horizontaal gespiegeld",
index 0eeec65..8d4fd7d 100644 (file)
        "revdelete-unsuppress": "حذف محدودیت‌ها در بازبینی‌های ترمیم‌شده",
        "revdelete-log": "دلیل:",
        "revdelete-submit": "اعمال بر {{PLURAL:$1|نسخهٔ|نسخه‌های}} انتخاب شده",
-       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø¨Ù\87â\80\8cرÙ\88ز شد.",
+       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø±Ù\88زآÙ\85د شد.",
        "revdelete-failure": "'''پیدایی نسخه‌ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
index 9b60e21..f1c9830 100644 (file)
        "rcfilters-clear-all-filters": "Effacer tous les filtres",
        "rcfilters-show-new-changes": "Afficher les nouvelles modifications depuis $1",
        "rcfilters-search-placeholder": "Filtrer les modifications (utiliser le menu ou rechercher le nom d'un filtre)",
+       "rcfilters-search-placeholder-mobile": "Filtres",
        "rcfilters-invalid-filter": "Filtre non valide",
        "rcfilters-empty-filter": "Aucun filtre actif. Toutes les contributions sont affichées.",
        "rcfilters-filterlist-title": "Filtres",
        "block-log-flags-angry-autoblock": "autoblocage amélioré activé",
        "block-log-flags-hiddenname": "nom d’utilisateur masqué",
        "range_block_disabled": "Le droit administrateur de créer des blocages de plages IP est désactivé.",
+       "ipb-prevent-user-talk-edit": "Modifier sa propre page de discussion peut être autorisé pour un blocage partiel, sauf s’il inclut une restriction sur l’espace de noms Discussion utilisateur.",
        "ipb_expiry_invalid": "Durée d'expiration incorrecte.",
        "ipb_expiry_old": "L’heure d’expiration est passée.",
        "ipb_expiry_temp": "Les blocages de noms d'utilisateurs cachés doivent être permanents.",
        "move-page-legend": "Renommer une page",
        "movepagetext": "Utilisez le formulaire ci-dessous pour renommer une page, en déplaçant tout son historique vers le nouveau nom. L’ancien titre deviendra une page de redirection vers le nouveau titre. \nVous pouvez mettre à jour automatiquement les redirections qui pointent vers le titre original. \nSi vous choisissez de ne pas le faire, assurez-vous de vérifier toute [[Special:DoubleRedirects|double redirection]] ou [[Special:BrokenRedirects|redirection cassée]]. Vous avez la responsabilité de vous assurer que les liens continuent de pointer vers leur destination supposée.\n\nNotez que la page ne sera <strong>pas</strong> renommée s’il existe déjà une page portant le nouveau titre, sauf si cette dernière est une simple redirection avec un historique de modifications vierge. \nCela signifie que vous pouvez de nouveau renommer une page vers sa position d’origine si vous avez fait une erreur et que vous ne pouvez pas écraser une page existante.\n\n<strong>Attention !</strong>\nCeci peut provoquer un changement radical et imprévu pour une page souvent consultée ; assurez-vous d’avoir compris les conséquences de votre démarche avant de continuer.",
        "movepagetext-noredirectfixer": "Utilisez le formulaire ci-dessous pour renommer une page, en déplaçant tout son historique vers le nouveau nom.\nL’ancien titre deviendra une page de redirection vers le nouveau titre.\nVérifiez bien les [[Special:DoubleRedirects|doubles redirections]] ou les [[Special:BrokenRedirects|redirections cassées]].\nVous avez la responsabilité de vous assurer que les liens continuent de pointer vers leur destination supposée.\n\nNotez que la page ne sera <strong>pas</strong> déplacée s’il existe déjà une page avec le nouveau titre, sauf si cette dernière a un historique de modifications vierge et est soit vide, soit une simple redirection. Ceci permet de renommer une page vers sa position d’origine si le déplacement s’avère erroné, et il est impossible d’écraser une page existante.\n\n<strong>Attention !</strong>\nCeci peut provoquer un changement radical et imprévu pour une page souvent consultée ; assurez-vous d’en avoir compris les conséquences avant de continuer.",
+       "movepagetext-noredirectsupport": "Utilisez le formulaire ci-desous pour renommer une page, et déplaçer toute son historique sous le nouveau nom.\nIl vous appartient de vérifier que tous les liens continuent à pointer vers leurs endroits respectifs.\n\nNotez bien que la page <strong>ne sera pas</strong> renommée s'il existe déjà une page portant le même titre que le nouveau tire choisi.\nCela signifie que vous pouvez revenir en arrière en renommant la page sous son ancien nom si vous avez faites une erreur, et que vous ne pouvez pas écraser une page existante.\n\n<strong>Note:</strong>\nCeci peut être une modification drastique et inattendue si la page est populaire;\nveuillez vous assurer de comprendre les conséquences de cela avant de continuer.",
        "movepagetalktext": "Si vous cochez cette case, la page de discussion associée sera automatiquement renommée, à moins qu’une page de discussion non vide existe déjà sous ce nouveau nom.\n\nDans ce cas, vous devrez renommer ou fusionner cette page de discussion manuellement si vous le désirez.",
        "moveuserpage-warning": "<strong>Attention :</strong> Vous êtes sur le point de renommer une page d’utilisateur. Veuillez noter que seule la page sera renommée et que l’utilisateur <em>ne</em> sera <em>pas</em> renommé.",
        "movecategorypage-warning": "<strong>Avertissement :</strong> Vous êtes sur le point de renommer une page de catégorie. Veuillez noter que seule la catégorie sera renommée et <em>qu’aucune</em> des pages de l’ancienne catégorie ne sera transférée dans la nouvelle.",
        "linkaccounts": "Lier les comptes",
        "linkaccounts-success-text": "Le compte a été lié.",
        "linkaccounts-submit": "Lier les comptes",
-       "cannotunlink-no-provider-title": "Il n’y a pas de compte lier à délier",
+       "cannotunlink-no-provider-title": "Il n’y a pas de compte lié à délier",
        "cannotunlink-no-provider": "Il n’y a pas de compte lié qui puisse être délié.",
        "unlinkaccounts": "Dissocier les comptes",
        "unlinkaccounts-success": "Le compte a été dissocié.",
index 9f477c3..6e8339a 100644 (file)
        "block-log-flags-angry-autoblock": "חסימה אוטומטית מתקדמת מופעלת",
        "block-log-flags-hiddenname": "שם המשתמש הוסתר",
        "range_block_disabled": "האפשרות לחסום טווח כתובות אינה פעילה.",
+       "ipb-prevent-user-talk-edit": "עריכת דף המשתמש צריכה להיות מותרת בחסימה חלקית, אלא אם כן היא כוללת הגבלה במרחב שיחת משתמש.",
        "ipb_expiry_invalid": "זמן פקיעת החסימה אינו תקין.",
        "ipb_expiry_old": "זמן הפקיעה כבר עבר.",
        "ipb_expiry_temp": "חסימות הכוללות הסתרת שם משתמש חייבות להיות לזמן בלתי מוגבל.",
        "move-page-legend": "העברת דף",
        "movepagetext": "ניתן להשתמש בטופס שלהלן כדי לשנות את השם של הדף הזה ולהעביר את כל היסטוריית העריכות שלו לשם החדש.\nהשם הישן יהפוך לדף הפניה אל השם החדש.\nבאפשרותך לעדכן באופן אוטומטי דפי הפניה שכרגע מפנים לשם הנוכחי של הדף.\nנא לוודא לאחר ההעברה שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|הפניות שבורות]] (אלא אם כן בחרת לבצע את העדכון האוטומטי הנ\"ל).\nכמו כן, באחריותך לוודא שכל הקישורים ימשיכו לקשר למקומות שאליהם הם אמורים לקשר.\n\nיש לשים לב לכך שהדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם כן הדף עם השם החדש הוא הפניה ואין לו עריכות קודמות.\nזה אומר שניתן יהיה להחזיר את הדף לשם המקורי במקרה שתיעשה טעות, אבל לא ניתן \"לדרוס\" דף קיים.\n\n<strong>לתשומת לבך:</strong>\nהעברה זו עלולה להיות שינוי דרסטי ומהותי לדף פופולרי;\nיש לקחת בחשבון את התוצאות של הפעולה הזאת לפני ביצוע ההעברה.",
        "movepagetext-noredirectfixer": "ניתן להשתמש בטופס שלהלן כדי לשנות את השם של הדף הזה ולהעביר את כל היסטוריית העריכות שלו לשם החדש.\nהשם הישן יהפוך לדף הפניה אל השם החדש.\nנא לוודא לאחר ההעברה שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|הפניות שבורות]].\nכמו כן, באחריותך לוודא שכל הקישורים ימשיכו לקשר למקומות שאליהם הם אמורים לקשר.\n\nיש לשים לב לכך שהדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם כן הדף עם השם החדש הוא הפניה ואין לו עריכות קודמות.\nזה אומר שניתן יהיה להחזיר את הדף לשם המקורי במקרה שתיעשה טעות, אבל לא ניתן \"לדרוס\" דף קיים.\n\n<strong>לתשומת לבך:</strong>\nהעברה זו עלולה להיות שינוי דרסטי ומהותי לדף פופולרי;\nיש לקחת בחשבון את התוצאות של הפעולה הזאת לפני ביצוע ההעברה.",
+       "movepagetext-noredirectsupport": "שימוש בטופס להלן ישנה את השם של הדף ויעביר את כל היסטוריית הגרסאות שלו לשם החדש.\nבאחריותך להבטיח שכל הקישורים אליו ימשיכו להצביע למקום שהם אמורים להגיע אליו.\n\nיש לשים לב לכך שהדף <strong>לא</strong> יועבר אם כבר יש דף בכותרת החדשה.\nזה אומר שבאפשרותך לשנות שם של דף חזרה לשם שממנו הוא הועבר במקרה של טעות, ושאי־אפשר לדרוס דף קיים.\n\n<strong>לתשומת לבך:</strong>\nזה יכול להיות שינוי קיצוני ובלתי־צפוי עבור דף פופולרי;\nנא לוודא שהבנת את ההשלכות של זה לפני המשך הפעולה.",
        "movepagetalktext": "אם האפשרות הזאת מסומנת, דף השיחה של הדף הזה יועבר אוטומטית לשם החדש, אלא אם קיים דף שיחה שאינו ריק תחת השם החדש. במקרה כזה, יש להעביר או למזג את הדפים באופן ידני, במידת הצורך.",
        "moveuserpage-warning": "<strong>אזהרה:</strong> הדף שיועבר הוא דף משתמש. חשוב לציין שרק הדף יועבר וששם המשתמש <em>לא</em> ישתנה.",
        "movecategorypage-warning": "<strong>אזהרה:</strong> הדף שיועבר הוא דף קטגוריה. חשוב לציין שרק הדף יועבר ושכל הדפים בקטגוריה הישנה <em>לא</em> יסווגו לקטגוריה החדשה.",
index eaf2f0c..fdbd336 100644 (file)
        "rcfilters-clear-all-filters": "Összes szűrő kikapcsolása",
        "rcfilters-show-new-changes": "$1 óta történt friss változtatások megtekintése",
        "rcfilters-search-placeholder": "Változtatások szűrése (használd a menüt vagy keress szűrőkre)",
+       "rcfilters-search-placeholder-mobile": "Szűrők",
        "rcfilters-invalid-filter": "Érvénytelen szűrő",
        "rcfilters-empty-filter": "Nincs aktív szűrő. Minden közreműködés látható.",
        "rcfilters-filterlist-title": "Szűrők",
index 95da3f6..3e98fb7 100644 (file)
        "retypenew": "Նորէն մուտքագրէք գաղտնաբառը",
        "resetpass_submit": "Հաստատեցէ՛ք անցաբառը եւ մուտք գործեցէ՛ք համակարգ",
        "changepassword-success": "Ձեր անցաբառը փոխուեցաւ։",
+       "botpasswords": "Մեքենայիկների անցաբառեր",
        "botpasswords-label-appid": "Մեքենայիկի անուն՝",
        "botpasswords-label-create": "Ստեղծել",
        "botpasswords-label-update": "Թարմացնել",
        "rev-showdeleted": "Ցուցադրել",
        "revdelete-show-file-submit": "Այո",
        "revdelete-hide-image": "Թաքցնել նիշքին բովանդակութիւնը",
+       "revdelete-radio-set": "Թաքուն",
        "revdelete-radio-unset": "Տեսանելի",
        "revdelete-log": "Պատճառ.",
        "pagehist": "Էջի պատմութիւն",
        "preferences": "Նախընտրութիւններ",
        "mypreferences": "Նախընտրութիւններ",
        "skin-preview": "Կանխաստուգել",
+       "prefs-rc": "Վերջին փոփոխութիւնները",
        "prefs-watchlist": "Հսկողութեան ցանկ",
        "prefs-editwatchlist-clear": "Մաքրել հսկողութեան ցանկը",
        "saveprefs": "Յիշել",
        "prefs-signature": "Ստորագրութիւն",
        "prefs-editor": "Խմբագրող",
        "prefs-preview": "Կանխաստուգել",
+       "userrights": "Մասնակիցների իրաւունքների կառավարում",
        "group": "Խումբ.",
        "group-user": "Մասնակիցներ",
        "group-bot": "Մեքենայիկներ",
        "rcfilters-activefilters-hide": "Թաքցնել",
        "rcfilters-activefilters-show": "Ցուցնել",
        "rcfilters-limit-title": "Ցուցադրուող արդիւնքներ",
+       "rcfilters-savedqueries-cancel-label": "Չեղարկել",
+       "rcfilters-search-placeholder-mobile": "Քամոց",
        "rcfilters-filtergroup-authorship": "Ներդրումներու հեղինակ",
        "rcfilters-filter-editsbyself-label": "Կողմէդ կատարուած փոփոխութիւնները",
        "rcfilters-filter-editsbyself-description": "Անձնական ներդրումներդ։",
        "upload-dialog-button-done": "Եղած է",
        "upload-dialog-button-save": "Յիշել",
        "upload-dialog-button-upload": "Վերբեռնել",
+       "upload-form-label-infoform-categories": "Ստորոգութիւններ",
        "license": "Արտօնագրութիւն՝",
        "license-header": "Արտօնագրում",
        "listfiles-delete": "ջնջել",
        "sharedupload-desc-here": "Այս նիշքը առնուած է $1-էն եւ կրնայ օգտագործուիլ այլ նախագիծերու մէջ։ $1-ի մէջ անոր [$2 նիշքը նկարագրող էջի]ի նկարագրութիւնը ներկայացուած է ստորեւ։",
        "filepage-nofile": "Այս անունով նիշք մը գոյութիւն չունի։",
        "upload-disallowed-here": "Այս նիշքը կարելի չէ ջնջել ու փոխարինել։",
+       "listredirects": "Վերայղումների ցանկ",
        "unusedtemplates": "Չօգտագործուող կաղապարներ",
+       "unusedtemplateswlh": "այլ յղումներ",
        "randompage": "Պատահական էջ",
        "randomincategory-category": "Ստորոգութիւն:",
+       "randomincategory-legend": "Պատահական էջ ստորոգութեան մէջ",
+       "randomredirect": "Պատահական վերայղում",
        "statistics": "Վիճակագրութիւն",
        "statistics-header-pages": "Էջերու վիճակագրութիւն",
        "statistics-header-edits": "Խմբագրումներու վիճակագրութիւն",
        "brokenredirects-edit": "խմբագրել",
        "brokenredirects-delete": "ջնջել",
        "withoutinterwiki": "Լեզուային յղումներ չպարունակող էջեր",
+       "withoutinterwiki-summary": "Հետեւեալ էջեր չունեն լեզւական յղումներ.",
        "withoutinterwiki-submit": "Ցուցնել",
        "fewestrevisions": "Նուազ վերաքաղուած էջեր",
        "nbytes": "$1 {{PLURAL:$1|պայթ}}",
        "ncategories": "$1 {{PLURAL:$1|ստորոգութիւն|ստորոգութիւններ}}",
+       "nlinks": "$1 {{PLURAL:$1|յղում|յղումներ}}",
        "nmembers": "$1 {{PLURAL:$1|անդամ|անդամներ}}",
        "uncategorizedpages": "Առանց ստորոգութիւններու էջեր",
        "uncategorizedcategories": "Ենթաստորոգութիւն չունեցող ստորոգութիւններ",
        "unusedcategories": "Չօգտագործուող ստորոգութիւններ",
        "unusedimages": "Չօգտագործուող նիշքեր",
        "wantedcategories": "Անհրաժեշտ ստորոգութիւններ",
+       "mostlinked": "Էջեր, որոնց շատ են յղվում",
+       "mostlinkedcategories": "Ստորոգութիւններ, որոնց շատ են յղվում",
+       "mostlinkedtemplates": "Կաղապարներ, որոնց շատ են յղվում",
        "prefixindex": "Բոլոր նախածանցներով էջերը",
        "prefixindex-submit": "Ցուցնել",
        "deadendpages": "Յղումներ չունեցող էջեր",
        "categories": "Ստորոգութիւններ",
        "categories-submit": "Ցուցնել",
        "deletedcontributions": "Մասնակիցի ջնջուած ներդրում",
+       "linksearch": "Արտաքին յղումներ",
        "linksearch-ok": "Որոնել",
        "listusers-submit": "Ցուցնել",
        "activeusers": "Աշխոյժ մասնակիցներու ցանկ",
        "namespace_association": "Առնչուած անուանատարածք",
        "tooltip-namespace_association": "Նշեցէ՛ք տուփիկը՝ ներառնելու համար տուեալ անուանատարածքին հետ կապուած քննարկումները կամ նիւթերու անուանատարածքը նոյնպէս։",
        "blanknamespace": "(Գլխաւոր)",
-       "contributions": "{{GENDER:$1|Մասնակիցի}} ներդրումները",
+       "contributions": "{{GENDER:$1|Մասնակիցի}} ներդրումներ",
        "contributions-title": "$1 մասնակիցի ներդրումը",
        "mycontris": "Ներդրումներ",
        "anoncontribs": "Ներդրումներ",
        "sp-contributions-uploads": "վերբեռնումներ",
        "sp-contributions-logs": "Տեղեկատետրեր",
        "sp-contributions-talk": "քննարկում",
+       "sp-contributions-userrights": "{{GENDER:$1|մասնակից}} իրաւունքների կառավարում",
        "sp-contributions-search": "Որոնել ներդրումները",
        "sp-contributions-username": "IP-հասցէ կամ մասնակիցի անուն.",
        "sp-contributions-toponly": "Ցոյց տալ միայն վերջին տարբերակի խմբագրումները",
        "whatlinkshere-hideredirs": "$1 վերայղումները",
        "whatlinkshere-hidetrans": "$1 ներառումները",
        "whatlinkshere-hidelinks": "$1 յղումները",
-       "whatlinkshere-hideimages": "$1 նիշքի յղումները",
+       "whatlinkshere-hideimages": "$1 նիշքի յղումներ",
        "whatlinkshere-filters": "Զտիչներ",
        "unblock": "Մասնակիցի արգելակումը վերցնել",
        "ipboptions": "2 ժամ:2 hours,1 օր:1 day,3 օր:3 days,1 շաբաթ:1 week,2 շաբաթ:2 weeks,1 ամիս:1 month,3 ամիս:3 months,6 ամիս:6 months,1 տարի:1 year,անժամկէտ:infinite",
        "ipb-pages-label": "Էջեր",
        "ipb-namespaces-label": "Անուանատարածքներ",
+       "blocklist": "Արգելափակուած մասնակիցներ",
        "infiniteblock": "Միշտ",
        "blocklist-editing-page": "էջեր",
        "blocklist-editing-ns": "անուանատարածքներ",
        "reblock-logentry": "փոխեց [[$1]] մասնակիցի արգելակումը՝ աւարտման $2 $3 ժամով",
        "block-log-flags-nocreate": "մասնակցային հաշիւի ստեղծումը արգիլուած է",
        "proxyblocker": "Փոխանորդի արգելակում",
+       "movepage-moved-redirect": "Ստեղծվել է վերայղում։",
        "movelogpage": "Տեղափոխութիւններու տեղեկատետր",
        "export": "Արտածել էջերը",
        "allmessages-language": "Լեզու.",
        "pageinfo-hidden-categories": "Թաքուն {{PLURAL:$1|խմբաւորում|խմբաւորումներ}} ($1)",
        "pageinfo-templates": "Օգտագործուած {{PLURAL:$1|կաղապար|կաղապարներ}} ($1)",
        "pageinfo-toolboxlink": "‎Էջի մասին տեղեկութիւն",
+       "pageinfo-redirectsto": "Վերայղում է դեպի",
        "pageinfo-contentpage": "Իբրեւ բովանդակութեան էջ հաշուըւած",
        "pageinfo-contentpage-yes": "Այո",
        "pageinfo-protect-cascading-yes": "Այո",
        "redirect-revision": "Էջի տարբերակներ",
        "redirect-file": "Նիշքի անունը",
        "specialpages": "Յատուկ էջեր",
+       "specialpages-note-restricted": "* Հասարակ հատուկ էջեր։\n* <span class=\"mw-specialpagerestricted\">Սահմանափակուած հատուկ էջեր։</span>",
        "specialpages-group-maintenance": "Շարունակական տեղեկագրեր",
        "tag-filter": "[[Special:Tags|Պիտակներու]] զտիչ՝",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Պիտակ}}]]: $2",
+       "tag-mw-new-redirect": "Նոր վերայղում",
        "tags-active-yes": "Այո",
        "tags-active-no": "Ոչ",
        "tags-hitcount": "{{PLURAL:$1|փոփոխութիւն}}",
+       "permanentlink": "Մշտական յղում",
        "htmlform-no": "Ոչ",
        "htmlform-yes": "Այո",
        "logentry-delete-delete": "$1 {{GENDER:$2|ջնջեց}} $3 էջը",
index 66930fd..52f01ef 100644 (file)
        "parser-unstrip-loop-warning": "Renkontresis nefinita procedo ('loop') en la funciono \"Unstrip\"",
        "undo-success": "La redakto ne povas desfacesar.\nVerifikez adinfre per komparo inter la du versioni se to esas fakte quon vu deziras facar; pose 'salvez' la modifiki por kompletigar la redakto.",
        "undo-failure": "Ne povis nuligar la redakto pro konflikti kun intermeza redakti.",
+       "undo-norev": "Ne povis desfacar la redakto, pro ol sive efacesis, sive ne existas.",
        "undo-summary-username-hidden": "Desfacar revizo $1 facita da celita uzero",
        "cantcreateaccount-text": "La kreo di konto de ica adreso IP (<strong>$1</strong>) blokusesis da [[User:$3|$3]].\n\nLa motivo, segun $3, esas <em>$2</em>",
        "cantcreateaccount-range-text": "La kreo di konti de IP-adresi de <strong>$1</strong>, qua inkluzas vua IP-adreso (<strong>$4</strong>), blokusesis dal uzero [[User:$3|$3]].\n\nLa motivo quon $3 informis por la blokuso esis <em>$2</em>",
        "pageswithprop": "Pagini kun atributo di pagino",
        "pageswithprop-legend": "Pagini kun atributo di pagino",
        "pageswithprop-text": "Ica pagino listas pagini qui havas partikulara propraji.",
+       "pageswithprop-reverse": "Ordenar inverse",
+       "pageswithprop-sortbyvalue": "Ordenar segun valoro di proprajo",
        "pageswithprop-submit": "Irez",
        "doubleredirects": "Duopla ridirektili",
        "doubleredirectstext": "Ca pagino montras pagini qui ridirektas ad altra ridirekto-pagini.\nSingla lineo kontenas ligili al unesma e a la duesma ridirekto, ed anke la emo di la duesma ridirekto, qua ordinare esas la \"vera\" emo-pagino a quo l'unesma ridirekto mustus apuntar.\n<del>Surstrekizata ligili</del> reparesis.",
        "ncategories": "$1 {{PLURAL:$1|kategorio|kategorii}}",
        "nlinks": "$1 {{PLURAL:$1|ligilo|ligili}}",
        "nmembers": "$1 {{PLURAL:$1|membro|membri}}",
+       "nmemberschanged": "$1 → $2 {{PLURAL:$2|membro|membri}}",
        "specialpage-empty": "Existas nula rezulti por ica informo.",
        "lonelypages": "Pagini sen ligili",
        "uncategorizedpages": "Nekategorizita pagini",
        "uctop": "aktuala",
        "month": "De monato (e plu frue):",
        "year": "De yaro (e plu frue):",
+       "date": "De (ed ante) la dato:",
        "sp-contributions-newbies": "Montrez nur kontributadi di la nova uzeri",
        "sp-contributions-newbies-sub": "Dil nova uzeri",
        "sp-contributions-newbies-title": "Kontributaji dil nova uzeri",
        "tag-mw-replace": "Remplasita",
        "tag-mw-replace-description": "Redakturi qui removas plua kam 90% de la kontenajo di ula pagino",
        "tag-mw-rollback": "Volvar addope",
+       "tag-mw-undo": "Desfacez",
        "tags-title": "Etiketi",
        "tags-intro": "Ica pagino montras l'etiketi qui povas uzesar dal informatik-programo por markizar ula redakturo, e lia signifiko.",
        "tags-tag": "Nomo dil etiketo",
index a8c88a5..5c29cf1 100644 (file)
@@ -74,6 +74,7 @@
        "tog-norollbackdiff": "Ekki sýna breytingu eftir að endurvakning síðu hefur verið gerð.",
        "tog-useeditwarning": "Vara mig við þegar ég fer frá breytingarsíðu með óvistaðar breytingar",
        "tog-prefershttps": "Alltaf nota örugga tengingu þegar þú skráir þig inn",
+       "tog-showrollbackconfirmation": "Sýna staðfestingarglugga þegar smellt er á taka aftur tengil",
        "underline-always": "Alltaf",
        "underline-never": "Aldrei",
        "underline-default": "Skinn eða sjálfgefið í vafra",
        "modifiedarticleprotection": "breytti verndunarstigi fyrir \"[[$1]]\"",
        "unprotectedarticle": "afverndaði „[[$1]]“",
        "movedarticleprotection": "verndunarstilling hefur verið færð frá „[[$2]]“ á „[[$1]]“",
+       "protectedarticle-comment": "{{GENDER:$2|Verndaði}} „[[$1]]“",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|Breytti verndunarstigi}} fyrir „[[$1]]“",
+       "unprotectedarticle-comment": "{{GENDER:$2|Fjarlægði verndun}} á „[[$1]]“",
        "protect-title": "Vernda „$1“",
        "protect-title-notallowed": "Skoða verndunarstig $1",
        "prot_1movedto2": "[[$1]] færð á [[$2]]",
        "autoblocklist": "Sjálfvirk bönn",
        "autoblocklist-submit": "Leita",
        "autoblocklist-legend": "Sýna sjálfvirk bönn",
+       "autoblocklist-localblocks": "{{PLURAL:$1|Staðbundið sjálfvirkt bann|Staðbundin sjálfvirk bönn}}",
        "autoblocklist-total-autoblocks": "Samanlagður fjöldi sjálfvirkra banna: $1",
        "autoblocklist-empty": "Listinn yfir sjálfvirk bönn er tómur.",
        "autoblocklist-otherblocks": "{{PLURAL:$1|Annað sjálfvirkt bann|Önnur sjálfvirk bönn}}",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Merki}}]]: $2",
        "tag-mw-new-redirect": "Ný endurbeining",
        "tag-mw-removed-redirect": "Fjarlægði endurbeiningu",
+       "tag-mw-blank": "Tæming",
+       "tag-mw-blank-description": "Breytingar sem tæma síðu.",
        "tag-mw-replace": "Skipt út",
+       "tag-mw-replace-description": "Breytingar sem fjarlægja meira en 90% af innihaldi síðna.",
        "tag-mw-rollback": "Afturköllun",
+       "tag-mw-rollback-description": "Breytingar sem taka til baka fyrri breytingar með taka til baka tenglinum.",
        "tag-mw-undo": "Afturkalla",
+       "tag-mw-undo-description": "Breytingar sem taka til baka fyrri breytingar með {{int:editundo}} tenglinum",
        "tags-title": "Merki",
        "tags-intro": "Þessi síða sýnir merkin sem hugbúnaðurinn gæti merkt breytingar með, og hvað þau þýða.",
        "tags-tag": "Heiti merkis",
        "htmlform-date-placeholder": "ÁÁÁÁ-MM-DD",
        "htmlform-time-placeholder": "KK:MM:SS",
        "htmlform-datetime-placeholder": "ÁÁÁÁ-MM-DD KK:MM:SS",
+       "htmlform-date-invalid": "Uppgefið gildi er ekki þekkt. Reyndu að nota ÁÁÁÁ-MM-DD formið.",
+       "htmlform-time-invalid": "Uppgefið gildi er ekki þekktur tími. Reyndu að nota KK:MM:SS formið.",
+       "htmlform-datetime-invalid": "Uppgefið gildi er ekki þekkt dagsetning og tími. Reyndu að nota ÁÁÁÁ-MM-DD KK:MM:SS formið.",
+       "htmlform-date-toolow": "Gildið sem þú gafst upp er fyrir elstu leyfðu dagsetninguna $1.",
+       "htmlform-date-toohigh": "Gildið sem þú gafst upp er eftir síðustu leyfðu dagsetninguna $1.",
+       "htmlform-time-toolow": "Gildið sem þú gafst upp er fyrir elstu leyfðu tímasetninguna $1.",
+       "htmlform-time-toohigh": "Gildið sem þú gafst upp er eftir síðustu leyfðu tímasetninguna $1.",
+       "htmlform-datetime-toolow": "Gildið sem þú gafst upp er fyrir elstu leyfðu dagsetninguna og tímasetninguna $1.",
+       "htmlform-datetime-toohigh": "Gildið sem þú gafst upp er eftir síðustu leyfðu dagsetninguna og tímasetninguna $1.",
        "htmlform-title-badnamespace": "[[:$1]] er ekki í \"{{ns:$2}}\" nafnrýminu.",
        "htmlform-title-not-creatable": "\"$1\" er ekki hægt að nota við að búa til titil á síðu",
        "htmlform-title-not-exists": "$1 er ekki til",
        "logentry-block-block": "$1 {{GENDER:$2|bannaði}} {{GENDER:$4|$3}}, rennur út eftir $5 $6",
        "logentry-block-unblock": "$1 {{GENDER:$2|afbannaði}} {{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1 {{GENDER:$2|breytti}} bann stillingum fyrir {{GENDER:$4|$3}}, rennur út $5 $6",
+       "logentry-partialblock-block": "$1 {{GENDER:$2|bannaði}} {{GENDER:$4|$3}} frá því að breyta $7, rennur út eftir $5 $6",
+       "logentry-partialblock-reblock": "$1 {{GENDER:$2|breytti}} bönnunar stillingum fyrir {{GENDER:$4|$3}} og hindraði breytingar á $7, rennur út $5 $6",
+       "logentry-non-editing-block-block": "$1 {{GENDER:$2|bannaði}} {{GENDER:$4|$3}} frá sérstökum aðgerðum öðrum en breytingum, rennur út $5 $6",
+       "logentry-non-editing-block-reblock": "$1 {{GENDER:$2|breytti}} bönnunarstillingum fyrir {{GENDER:$4|$3}} fyrir sérstakar aðgerðir aðrar en breytingar, rennur út eftir $5 $6",
        "logentry-suppress-block": "$1 {{GENDER:$2|bannaði}} {{GENDER:$4|$3}}, rennur út eftir $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|breytti}} bannstillingum fyrir {{GENDER:$4|$3}}, rennur út $5 $6",
        "logentry-import-upload": "$1 {{GENDER:$2|flutti inn}} $3 úr innsendri skrá",
index e912aef..672027c 100644 (file)
        "passwordpolicies-policy-passwordnotinlargeblacklist": "La password non può essere nell'elenco delle 100 000 password utilizzate più comunemente.",
        "easydeflate-invaliddeflate": "Il contenuto fornito non è compresso correttamente",
        "unprotected-js": "Per motivi di sicurezza, non è possibile caricare JavaScript da pagine non protette. Crea javascript solo nel namespace MediaWiki o come sottopagina Utente",
-       "userlogout-continue": "Vuoi uscire?"
+       "userlogout-continue": "Vuoi davvero uscire?"
 }
index 5fac6ac..71aebe9 100644 (file)
        "rcfilters-clear-all-filters": "すべてのフィルターをクリア",
        "rcfilters-show-new-changes": "$1 から最新の変更を表示",
        "rcfilters-search-placeholder": "絞り込みを行う(メニューから選択、またはフィルター名で検索)",
+       "rcfilters-search-placeholder-mobile": "フィルター",
        "rcfilters-invalid-filter": "無効なフィルター",
        "rcfilters-empty-filter": "絞り込みは行われていません。全ての項目が表示されます。",
        "rcfilters-filterlist-title": "フィルター",
index f85a0f5..b4c04cc 100644 (file)
        "rcfilters-clear-all-filters": "필터 모두 지우기",
        "rcfilters-show-new-changes": "$1 이후 새 변경사항 보기",
        "rcfilters-search-placeholder": "필터 변경(메뉴를 사용하거나 필터 이름을 검색하세요)",
+       "rcfilters-search-placeholder-mobile": "필터",
        "rcfilters-invalid-filter": "유효하지 않은 필터",
        "rcfilters-empty-filter": "활성화된 필터가 없습니다. 모든 기여가 표시됩니다.",
        "rcfilters-filterlist-title": "필터",
index af92424..094b875 100644 (file)
        "thu": "Pşm",
        "fri": "În",
        "sat": "Şem",
-       "january": "rêbendan",
-       "february": "reşemî",
+       "january": "kanûna paşîn",
+       "february": "sibat",
        "march": "adar",
-       "april": "avrêl",
+       "april": "nîsan",
        "may_long": "gulan",
-       "june": "pûşper",
+       "june": "hezîran",
        "july": "tîrmeh",
-       "august": "gelawêj",
-       "september": "rezber",
-       "october": "kewçêr",
-       "november": "sermawez",
-       "december": "berfanbar",
-       "january-gen": "Rêbendan",
-       "february-gen": "Reşemî",
-       "march-gen": "Adar",
-       "april-gen": "Avrêl",
+       "august": "tebax",
+       "september": "îlon",
+       "october": "çiriya pêşîn",
+       "november": "çiriya paşîn",
+       "december": "kanûna pêşîn",
+       "january-gen": "kanûna paşîn",
+       "february-gen": "sibat",
+       "march-gen": "adar",
+       "april-gen": "nîsan",
        "may-gen": "gulan",
-       "june-gen": "pûşper",
-       "july-gen": "Tîrmeh",
-       "august-gen": "Gelawêj",
-       "september-gen": "rezber",
-       "october-gen": "Kewçêr",
-       "november-gen": "Sermawez",
-       "december-gen": "berfanbar",
-       "jan": "rêb",
-       "feb": "reş",
+       "june-gen": "hezîran",
+       "july-gen": "tîrmeh",
+       "august-gen": "tebax",
+       "september-gen": "îlon",
+       "october-gen": "çiriya pêşîn",
+       "november-gen": "çiriya paşîn",
+       "december-gen": "kanûna pêşîn",
+       "jan": "kpa",
+       "feb": "sib",
        "mar": "adr",
-       "apr": "avr",
+       "apr": "nîs",
        "may": "gln",
-       "jun": "pûş",
+       "jun": "hez",
        "jul": "tîr",
-       "aug": "Glw",
-       "sep": "rez",
-       "oct": "kew",
-       "nov": "ser",
-       "dec": "ber",
-       "january-date": "Rêbendan $1",
-       "february-date": "Reşemî $1",
+       "aug": "teb",
+       "sep": "îln",
+       "oct": "çpê",
+       "nov": "çpa",
+       "dec": "kpê",
+       "january-date": "Kanûna paşîn $1",
+       "february-date": "Sibat $1",
        "march-date": "Adar $1",
-       "april-date": "Avrêl $1",
+       "april-date": "Nîsan $1",
        "may-date": "Gulan $1",
        "june-date": "Pûşper $1",
        "july-date": "Tîrmeh $1",
-       "august-date": "Gelawêj $1",
+       "august-date": "$1 tebax",
        "september-date": "Rezber $1",
        "october-date": "Kewçêr $1",
        "november-date": "Sermawez $1",
        "deletepage": "Rûpelê jê bibe",
        "confirm": "Pesend bike",
        "excontent": "Naveroka berê: \"$1\"",
-       "excontentauthor": "Naveroka vê rûpelê ev bû: '$1' (û tenya bikarhêner '$2' bû)",
+       "excontentauthor": "Naveroka rûpelê ev bû: \"$1\", û tenê ya bikarhêner \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|talk]] bû.)",
        "exbeforeblank": "Naverok berî betalkirinê ev bû: \"$1\"",
        "delete-confirm": "Jêbirina \"$1\"",
        "delete-legend": "Jê bibe",
index d61a2e6..85f9d18 100644 (file)
        "rcfilters-clear-all-filters": "All Filteren eidelmaachen",
        "rcfilters-show-new-changes": "Nei Ännerungen zanter $1 weisen",
        "rcfilters-search-placeholder": "Ännerunge filteren (benotzt de Menü oder sicht nom Numm vum Filter)",
+       "rcfilters-search-placeholder-mobile": "Filteren",
        "rcfilters-invalid-filter": "Net valabele Filter",
        "rcfilters-empty-filter": "Keen aktive Filter. All Kontributioune gi gewisen.",
        "rcfilters-filterlist-title": "Filteren",
index b0a9171..f24aa1d 100644 (file)
        "right-rollback": "چوئارشٱ کردن سریع ڤیرایشؽا آخری کاریارؽ کاْ یاٛ بٱلگٱ ڤیژٱ ناْ ڤیرایش کردٱ",
        "right-markbotedits": "نشوݩ دار کردن ڤیرایشؽا چوئاشٱ بیٱ چی ڤیرایشؽا یاٛ روبات",
        "right-noratelimit": "کارگرا ناٛیئن د مٱئدۊدیٱت سۏرعٱت",
-       "right-import": "بÙ\84Ú¯Ù\87 Û\8cا Ù\86Ù\87 Ø¯ Ù\88Û\8cÚ©Û\8c Ù\87Ù\86Û\8c Ù\88ارد Ø¨Ú©Û\8cد",
-       "right-importupload": "دئÙ\86 Ø¨Ù\84Ú¯Ù\87 Û\8cا Ø¯ Û\8cÙ\87 Ú¯Ù\84 Ø¬Ø§Ù\86Û\8cا Ø³Ù\88ار Ø¨Û\8cÙ\87",
-       "right-patrol": "سردیاری کردن د ویرایشتیا کسونا تر",
-       "right-autopatrol": "سردیاری کردن خودانجوم د ویرایشتیا خوش",
-       "right-patrolmarks": "دیئن سردیس سردیاری کردن د آلشتیا ایسنی",
-       "right-unwatchedpages": "دیئن نوم گه بلگه یا دیئه نبیه",
-       "right-mergehistory": "وه یک شیوسن ویرگار ای بلگه",
-       "right-userrights": "حقوق همه کاریاریانه ویرایشت بکید",
-       "right-userrights-interwiki": "حقوق همه کاریاریانه د ویکی یا هنی ویرایشت بکید",
-       "right-siteadmin": "پاگا Ø¯Ù\88Ù\86سÙ\85Ù\86Û\8c Ù\86Ù\87 Ù\82Ù\84Ù\81 Ø¨Ú©Û\8cد Û\8cا Ù\86Ú©Û\8cد",
-       "right-override-export-depth": "وه در دئن بلگه یایی که بلگه یا هوم پیوند بیه تا پی یا 5 ها دشو",
-       "right-sendemail": "سی کاریاریا هنی انجومانامه کل بکید",
+       "right-import": "بٱÙ\84Ú¯Ù±Û\8cا Ù\86اÙ\92 Ø¯ Ú¤Û\8cÚ©Û\8c Ù\87Ù\86Û\8c Ú¤Ø§Ø±Ø¯ Ø¨Ù±Ú©Ø½Øª",
+       "right-importupload": "داÙ\9bئÙ\86 Ø¨Ù±Ù\84Ú¯Ù±Û\8cا Ø¯ Û\8cاÙ\9b Ø¬Ø§Ù\86Û\8cا Ø³Ú¤Ø§Ø± Ø¨Û\8cÙ±",
+       "right-patrol": "سٱردؽاری کردن د ڤیرایشؽا کٱسونؽاْ تر",
+       "right-autopatrol": "سٱردؽاری کردن خودٱنجوم د ڤیرایشؽا خوش",
+       "right-patrolmarks": "دیئن سٱردیس سٱردؽاری کردن د آلشتؽا ایسنی",
+       "right-unwatchedpages": "دیئن نومگٱ بٱلگٱیا دییٱ ناٛیٱ",
+       "right-mergehistory": "ڤ یٱک شؽڤسن ڤیرگار اؽ بٱلگٱ",
+       "right-userrights": "هقوق هٱمٱ کاریارؽا ناْ ڤیرایش بٱکؽت",
+       "right-userrights-interwiki": "هقوق هٱمٱ کاریارؽا ناْ د ڤیکی یا هنی ڤیرایش بٱکؽت",
+       "right-siteadmin": "Ù\82Ù\84Ù\81 Ú©Ø±Ø¯Ù\86 Û\89 Ú¤Ø§Ø² Ú©Ø±Ø¯Ù\86 Ù¾Ø§Û\8cگا Ø¯Ù\88Ù\86سÙ\85Ù±Ù\86Û\8c",
+       "right-override-export-depth": "ڤ دٱر داٛئن بٱگٱیایؽ کاْ بٱلگٱیا هوم پاٛڤٱن بیٱ تا قیلٛی 5 ها دشو",
+       "right-sendemail": "سی کاریارؽا هنی ٱنجومانامٱ کلٛ بٱکؽت",
        "right-managechangetags": "راس کئردئن [[Special:سأردیسیا|سأردیسیا]] پاکسا کئردئن د رئسینە جا",
        "right-applychangetags": "ڤئ کار گئرئتئنئ [[Special:سأردیسیا|سأردیسیا]] ڤاگئرد آلئشتیا ھأرکومئشوٙ.",
        "right-changetags": "Add and remove arbitrary [[Special:Tags|tags]] on individual revisions and log entries",
        "newuserlogpage": "دۏرس بیٱ ڤا کاریار",
-       "newuserlogpagetext": "Û\8cÙ\87 Ù¾Ù\87رستÙ\86Ù\88Ù\85Ù\87 Ø±Ø§Ø³ Ø¨Û\8cئÙ\86 Ú©Ø§Ø±Û\8cارÙ\87",
-       "rightslog": "Ù¾Ù\87رستÙ\86Ù\88Ù\85Ù± Ù\87Ù\88Ù\82Û\8aÙ\82 Ú©Ø§Ø±Û\8cار",
-       "rightslogtext": "Û\8cÙ\87 Ù¾Ù\87رستÙ\86Ù\88Ù\85Ù\87 Ø¢Ù\84شتÛ\8cا Ø­Ù\82Ù\88Ù\82 Ú©Ø§Ø±Û\8cارÙ\87.",
-       "action-read": "ای بلگه نه بحو",
-       "action-edit": "اؽ بٱلگٱ ناْ ڤيرايش بٱكیت",
+       "newuserlogpagetext": "Û\8cÙ± Ù¾Ù\87رستÙ\86Ù\88Ù\85Ù± Ø¯Û\8fرس Ø¨Û\8cئÙ\86 Ú©Ø§Ø±Û\8cار Ø¦Ù±.",
+       "rightslog": "پهرستنومٱ هقۊق کاریار",
+       "rightslogtext": "Û\8cÙ± Ù¾Ù\87رستÙ\86Ù\88Ù\85Ù± Ø¢Ù\84شتؽا Ù\87Ù\82Ù\88Ù\82 Ú©Ø§Ø±Û\8cار Ø¦Ù±.",
+       "action-read": "ڤٱنن اؽ بٱلگٱ",
+       "action-edit": "اؽ بٱلگٱ ناْ ڤيرايش بٱكؽت",
        "action-createpage": "راس کردن بلگیا",
        "action-createtalk": "بلگه یا چک چنه نه راس بکید",
        "action-createaccount": "هساو اؽ کاریار ناْ دۏرس بٱکؽت",
-       "action-history": "ویرگار ای بلگه نه بوینیت",
-       "action-minoredit": "ای ویرایشت نه چی یه حیرده ویرایشت نشو بیئت",
+       "action-history": "ڤیرگار اؽ بٱلگٱ ناْ باٛینؽت",
+       "action-minoredit": "اؽ ڤیرایش ناْ چی یاٛ هیردٱ ڤیرایش نشوݩ باٛیؽت",
        "action-move": "اؽ بٱلگٱ ناْ جا ڤ جا کو",
-       "action-move-subpages": "اÛ\8c Ø¨Ù\84Ú¯Ù\87 Ù\88 Ø²Û\8cر Ø¨Ù\84Ú¯Ù\87 Û\8cاشÙ\87 Ø¬Ø§ Ù\88Ù\87 Ø¬Ø§ Ø¨Ú©Û\8cد",
-       "action-move-rootuserpages": "بÙ\84Ú¯Ù\87 Û\8cا Ø±Û\8cØ´Ù\87 Ø§Û\8c Ú©Ø§Ø±Û\8cار Ù\86Ù\87 Ø¬Ø§ Ù\88Ù\87 Ø¬Ø§ Ø¨Ú©Û\8cد",
-       "action-move-categorypages": "جا وه جا کردن دسه بلگه یا",
-       "action-movefile": "ای جانیا نه جا وه جا بکید",
-       "action-upload": "ای جانیا نه سوار بکید",
-       "action-reupload": "نیسئین ری جانیا ایسنی",
-       "action-reupload-shared": "باطÙ\84 Ú©Ø±Ø¯Ù\86 Ø§Û\8c Ø¬Ø§Ù\86Û\8cا Ø±Û\8c Û\8cÙ\87 Ú¯Ù\84 Ú¯Ù\86جÛ\8cÙ\86Ù\87 Ù\87Ù\88Ù\85بئر",
-       "action-upload_by_url": "ای جانیا نه د یو آر ال سوار بکید",
-       "action-writeapi": "د Ù\86Û\8cسÙ\86Ù\86 Ø§Û\8c Ù¾Û\8c Ø¢Û\8c Ø§Ø³ØªÙ\81ادÙ\87 Ø¨Ú©Û\8cد",
-       "action-delete": "ای بلگه نه پاکسا کو",
+       "action-move-subpages": "ؽ Ø¨Ù±Ù\84Ú¯Ù± Û\89 Ø²Ø½Ø± Ø¨Ù±Ù\84Ú¯Ù±Û\8cا Ø´Ø§Ù\92 Ø¬Ø§ Ú¤ Ø¬Ø§ Ø¨Ù±Ú©Ø½Øª",
+       "action-move-rootuserpages": "بٱÙ\84Ú¯Ù±Û\8cا Ø±Û\8cشاÙ\9bÛ\8c Ú©Ø§Ø±Û\8cار Ù\86اÙ\92 Ø¬Ø§ Ú¤ Ø¬Ø§ Ø¨Ù±Ú©Ø½Øª",
+       "action-move-categorypages": "جا ڤ جا کردن دٱسٱ بٱلگٱیا",
+       "action-movefile": "اؽ جانؽا ناْ جا ڤ جا بٱکؽت",
+       "action-upload": "اؽ جانؽا ناْ سڤار بٱکؽت",
+       "action-reupload": "نیسٱنن ری جانؽا ایسنی",
+       "action-reupload-shared": "باتÙ\84 Ú©Ø±Ø¯Ù\86 Ø§Ø½ Ø¬Ø§Ù\86ؽا Ø±Û\8c Û\8cاÙ\9b Ú¯Ù±Ù\86جÛ\8cÙ\86Ù± Ù\87Ù\88Ù\85بٱر",
+       "action-upload_by_url": "اؽ جانؽا ناْ د یۊ آر اْل سڤار بٱکؽت",
+       "action-writeapi": "د Ù\86Û\8cسٱÙ\86Ù\86 Ø§Û\8c Ù¾Û\8c Ø¢Û\8c Ø§Ù\92ستÙ\81ادٱ Ø¨Ù±Ú©Ø½Øª",
+       "action-delete": "اؽ بٱلگٱ ناْ پاکسا کو",
        "action-deleterevision": "ای وانئری نه پاک کو",
        "action-deletedhistory": "ویرگار پاکسا بیه ای بلگه نه بوینیت",
-       "action-browsearchive": "بÙ\84Ú¯Ù\87 Û\8cا Ù¾Ø§Ú© Ø¨Û\8cÙ\87 Ù\86Ù\87 Ù¾Û\8c Ø¬Ù\88رÛ\8c Ø¨Ú©Û\8cد",
+       "action-browsearchive": "بٱÙ\84Ú¯Ù±Û\8cا Ù¾Ø§Ú© Ø¨Û\8cÙ± Ù\86اÙ\92 Ù¾Ø§Ù\9bجÛ\8aرÛ\8c Ø¨Ù±Ú©Ø½Øª",
        "action-undelete": "ای بلگه نه پاک نکو",
        "action-suppressrevision": "وانئری و زنه کردن وانئریا پاک بیه",
-       "action-suppressionlog": "ای پهرستنومه خصوصی نه بوینیت",
-       "action-block": "ای کاریار نه د ویرایشت کردن نهاگری کو",
-       "action-protect": "ریترازیا پر و پیم کاری د ای بلگه نه آلشت بکید",
-       "action-rollback": "Ú\86Ù\88اشÙ\87 Ú©Ø±Ø¯Ù\86 Ø³Ø±Û\8cع Ù\88Û\8cراÛ\8cشتÛ\8cا Ø¢Ø®Ø±Û\8c Ú©Ø§Ø±Û\8cارÛ\8c Ú©Ù\87 Û\8cÙ\87 Ø¨Ù\84Ú¯Ù\87 Ù\88Û\8cجÙ\87 Ù\86Ù\87 Ù\88Û\8cراÛ\8cشت Ø¯Ø¦Ù\87",
-       "action-import": "بÙ\84Ú¯Ù\87 Û\8cا Ù\86Ù\87 Ø¯ Ù\88Û\8cÚ©Û\8c Ù\87Ù\86Û\8c Ù\88ارد Ø¨Ú©Û\8cد",
-       "action-importupload": "بÙ\84Ú¯Ù\87 Û\8cا Ù\86Ù\87 Ø¯ Ø¬Ø§Ù\86Û\8cا Ø³Ù\88ار Ø¨Û\8cÙ\87 Ù\88ارد Ø¨Ú©Û\8cد",
-       "action-patrol": "سردیاری کردن د ویرایشتیا کسونا تر",
-       "action-autopatrol": "سردیاری کردن د ویرایشتیا خوتو",
-       "action-unwatchedpages": "دیئن نوم گه بلگه یا دیئه نبیه",
-       "action-mergehistory": "وه یک شیوسن ویرگار ای بلگه",
-       "action-userrights": "حقوق همه کاریاریا نه ویرایشت بکید",
-       "action-userrights-interwiki": "حقوق همه کاریاریانه د ویکی یا هنی ویرایشت بکید",
-       "action-siteadmin": "رسینه جا نه قلف بکید یا نکید",
-       "action-sendemail": "انجومانامه یا نه کل کو",
-       "action-editmywatchlist": "سیل برگ خوتونه ویرایشت بکید",
-       "action-viewmywatchlist": "سیل برگ خوتونه بوینیت",
-       "action-viewmyprivateinfo": "دÙ\88Ù\86سÙ\85Ù\86Û\8cا Ø®Ù\88تÙ\88Ù\86Ù\87 Ø¨Ù\88Û\8cÙ\86Û\8cت",
-       "action-editmyprivateinfo": "دÙ\88Ù\86سÙ\85Ù\86Û\8cا Ø´ØµÙ\82Û\8c Ø®Ù\88تÙ\88Ù\86Ù\87 Ù\88Û\8cراÛ\8cشت Ø¨Ú©Û\8cد",
-       "action-editcontentmodel": "ویرایشت مدل مینونه یه گل بلگه",
+       "action-suppressionlog": "اؽ پهرستنومٱ خسوسی ناْ باٛینؽت",
+       "action-block": "اؽ کاریار ناْ د ڤیرایش کردن نهاگیری کو",
+       "action-protect": "ریترازؽا پر ۉ پیم کاری د اؽ بٱلگٱ ناْ آلشت بٱکؽت",
+       "action-rollback": "Ú\86Ù\88ئارشٱ Ú©Ø±Ø¯Ù\86 Ø³Ø±Û\8cع Ú¤Û\8cراÛ\8cشؽا Ø¢Ø®Ø±Û\8c Ú©Ø§Ø±Û\8cارؽ Ú©Ø§Ù\92 Û\8cاÙ\9b Ø¨Ù±Ù\84Ú¯Ù± Ú¤Û\8cÚ\98Ù± Ù\86اÙ\92 Ú¤Û\8cراÛ\8cØ´ Ø¯Ø§Ù\9bئٱ",
+       "action-import": "بٱÙ\84Ú¯Ù±Û\8cا Ù\86اÙ\92 Ø¯ Ú¤Û\8cÚ©Û\8c Ù\87Ù\86Û\8c Ú¤Ø§Ø±Ø¯ Ø¨Ù±Ú©Ø½Øª",
+       "action-importupload": "بٱÙ\84Ú¯Ù±Û\8cا Ù\86اÙ\92 Ø¯ Ø¬Ø§Ù\86ؽا Ø³Ú¤Ø§Ø± Ø¨Û\8cÙ± Ú¤Ø§Ø±Ø¯ Ø¨Ù±Ú©Ø½Øª",
+       "action-patrol": "سٱردؽاری کردن د ڤیرایشؽا کٱسونؽا تر",
+       "action-autopatrol": "سٱردؽاری کردن د ڤیرایشؽا خوتو",
+       "action-unwatchedpages": "دیئن نومگٱ بٱلگٱیا دیئٱ ناٛیٱ",
+       "action-mergehistory": "ڤ یٱک شؽڤسن ڤیرگار اؽ بٱلگٱ",
+       "action-userrights": "هقوق هٱمٱ کاریارؽا ناْ ڤیرایش بٱکؽت",
+       "action-userrights-interwiki": "هقوق هٱمٱ کاریارؽا ناْ د ڤیکی یا هنی ڤیرایش بٱکؽت",
+       "action-siteadmin": "قلف کردن ۉ ڤاز کردن پایگا دونسمٱنی",
+       "action-sendemail": "ٱنجومانامٱیا ناْ کلٛ- کو",
+       "action-editmywatchlist": "ساٛل بٱرگ خوتو ناْ ڤیرایش بٱکؽت",
+       "action-viewmywatchlist": "ساٛل بٱرگ خوتو ناْ باٛینؽت",
+       "action-viewmyprivateinfo": "دÙ\88Ù\86سÙ\85Ù±Ù\86Û\8cا Ø®Ù\88تÙ\88 Ù\86اÙ\92 Ø¨Ø§Ù\9bÛ\8cÙ\86ؽت",
+       "action-editmyprivateinfo": "دÙ\88Ù\86سÙ\85Ù±Ù\86Û\8cا Ø´Ù±Ø®Ø³Û\8c Ø®Ù\88تÙ\88 Ù\86اÙ\92Ú¤Û\8cراÛ\8cØ´ Ø¨Ù±Ú©Ø½Øª",
+       "action-editcontentmodel": "ڤیرایش مودل میپؽنونٱ یاٛ بٱلگٱ",
        "action-managechangetags": "راس کردن و پاکسا کردن سردیسیا د رسینه جا",
-       "action-applychangetags": "سردیسیا نه واگرد آلشتیایی که خوتو دئیته وه کار بیئریت",
-       "action-changetags": "اضاف کردن یا جا وه جاکاری سردیسیا دل وه حایی د وانئریا و پهرستنومه یا شخصی",
-       "nchanges": "$1 {{PLURAL:$1|آلشت|آلشتیا}}",
+       "action-applychangetags": "سٱردیسیا ناْ ڤاگرد آلشتؽایؽ کاْ خوتو داٛئؽتٱ ڤ کار باٛیرؽت",
+       "action-changetags": "اْزاف کردن یا جا ڤ جاکاری سٱردیسؽا دل بهایی د ڤانریا ۉ پهرستنومٱیا شٱخسی",
+       "nchanges": "$1 {{PLURAL:$1|آلشت|آلشتؽا}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|د آخری دیئن}}",
        "enhancedrc-history": "ڤیرگار",
        "recentchanges": "آلشتؽا ایسنی",
        "recentchanges-noresult": "هیچ آلشتؽ د درازا دۉرٱ دؽار بیٱ ڤا اؽ ماٛعیارؽا یٱکی ناٛی.",
        "recentchanges-feed-description": "دۏ بؽشتر آلشتؽا تازباو ناْ د ڤیکی کا ها د هڤال هون پاٛگیری کو.",
        "recentchanges-label-newpage": "اؽ ڤیرایش یاٛ بٱلگٱ تازٱ دۏرس کردٱ.",
-       "recentchanges-label-minor": "یٱ یاٛ ڤیرایش کوچکٱ",
+       "recentchanges-label-minor": "یٱ یاٛ ڤیرایش کوچک ئٱ",
        "recentchanges-label-bot": "اؽ ڤيرايش ناْ ياٛ بوت ٱنجوم داٛیٱ",
-       "recentchanges-label-unpatrolled": "اؽ ڤيرايش هنی تيٱ ڤاداشت ناٛیئٱ",
-       "recentchanges-label-plusminus": "ٱندازٱ بٱلگٱ ڤ شماراٛ اؽ بایتؽا آلشت کردٱ.",
+       "recentchanges-label-unpatrolled": "اؽ ڤيرايش هنی تيٱ ڤاداشت ناٛییٱ",
+       "recentchanges-label-plusminus": "ٱندازٱ بٱلگٱ ڤ شمارٱ اؽ بایتؽا آلشت کردٱ.",
        "recentchanges-legend-heading": "<strong>میرات:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (همچنو باٛینؽت [[ڤیژٱ:بٱلگٱیا تازٱ|نوم گٱ بٱلگٱیا تازٱ]])",
        "recentchanges-legend-plusminus": "(<em>±123</em>)",
        "rcshowhidebots": "$1 روباتؽا یا بوتؽا",
        "rcshowhidebots-show": "نشوݩ داٛئن",
        "rcshowhidebots-hide": "قایم کردن",
-       "rcshowhideliu": "$1 کاریاریا سٱبت نوم کردٱ",
+       "rcshowhideliu": "$1 کاریارؽا سٱبت نوم کردٱ",
        "rcshowhideliu-show": "نشوݩ داٛئن",
        "rcshowhideliu-hide": "قایم کردن",
        "rcshowhideanons": "کاریار نادؽار $1",
        "rcshowhideanons-show": "نشوݩ داٛئن",
        "rcshowhideanons-hide": "قایم کردن",
        "rcshowhidepatr": "$1 ڤیرایشیا تیٱ پرس بیٱ",
-       "rcshowhidepatr-show": "Ù\86ئشÙ\88Ù\99 Ø¯Ø£ئن",
-       "rcshowhidepatr-hide": "قام کئردئن",
+       "rcshowhidepatr-show": "Ù\86Ø´Ù\88Ý© Ø¯Ø§Ù\9bئن",
+       "rcshowhidepatr-hide": "قایم کردن",
        "rcshowhidemine": "ڤیرایشؽا ماْ $1",
        "rcshowhidemine-show": "نشوݩ داٛئن",
        "rcshowhidemine-hide": "قایم کردن",
-       "rcshowhidecategorization": "جأرغە کاری بألگە $1",
+       "rcshowhidecategorization": "جٱرغٱ کاری بٱلگٱ $1",
        "rcshowhidecategorization-show": "نئشوٙ دأئن",
-       "rcshowhidecategorization-hide": "قام کئردئن",
-       "rclinks": "آخرین آلشتؽا $1 د آخرین رۊزؽا دؽاری بٱک $2",
+       "rcshowhidecategorization-hide": "قایم کردن",
+       "rclinks": "آخرین آلشتؽا $1 د آخری رۊزؽا دؽاری بٱک $2",
        "diff": "فٱرق",
        "hist": "ڤیرگار",
        "hide": "قایم کردن",
        "unpatrolledletter": "!",
        "rc-change-size": "$1",
        "rc-change-size-new": "$1 {{PLURAL:$1|بایت|بایتؽا}} دما آلشتکاری",
-       "newsectionsummary": "/* $1 */ Ø¨Ù\87رجا ØªØ§Ø²Ù\87",
-       "rc-enhanced-expand": "جزيات نشون بيئه",
-       "rc-enhanced-hide": "جزياته قام كو",
+       "newsectionsummary": "/* $1 */ Ø¨Ù±Ø¦Ø±Ø¬Ø§ ØªØ§Ø²Ù±",
+       "rc-enhanced-expand": "نشوݩ داٛئن جۏزئیات",
+       "rc-enhanced-hide": "قایم کردن جۏزئیات",
        "rc-old-title": "کاملٱن چی \"$1\"دۏرس بیٱ",
        "recentchangeslinked": "آلشتؽا تاٛ یٱکؽ",
        "recentchangeslinked-feed": "آلشتؽا تاٛ یٱک",
        "recentchangeslinked-toolbox": "آلشتؽا تاٛ یٱک",
        "recentchangeslinked-title": "آلشتؽا تاٛ یٱکؽ د $1",
-       "recentchangeslinked-summary": "اؽ نوم بٱلگٱ تازٱ د بٱلگٱیایی کاْ ڤا بٱلگٱیا ڤیژٱ هوم پاٛڤٱن بینٱ آلشت بیٱ(یا سی ٱندومؽا دٱسٱ بٱنی بیٱ)\nبٱلگٱیایی کاْ هان د [[Special:Watchlist|your watchlist]]ۉ گٱپ بینٱ",
+       "recentchangeslinked-summary": "اؽ نوم بٱلگٱ تازٱ د بٱلگٱیایؽ کاْ ڤا بٱلگٱیا ڤیژٱ هوم پاٛڤٱن بینٱ آلشت بیٱ(یا سی ٱندومؽا دٱسٱ بٱنی بیٱ)\nبٱلگٱیایی کاْ هان د [[Special:Watchlist|your watchlist]]ۉ گٱپ بینٱ",
        "recentchangeslinked-page": "نوم بٱلگٱ:",
        "recentchangeslinked-to": "آلشتؽایؽ کاْ د بٱلگٱیا هوم پاٛڤٱن بینٱ ڤ جا بٱلگٱ داٛیٱ بیٱ نشوݩ باٛیٱ",
-       "recentchanges-page-added-to-category": "[[:$1]]د دأسە ئضاف بی",
+       "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": "Ø¢Ù\84ئشتکارÛ\8c Ø®Ù\88دأÙ\86جÙ\88Ù\85 Ù\85ئدیاڤیکی",
+       "autochange-username": "Ø¢Ù\84شتکارÛ\8c Ø®Ù\88دٱÙ\86جÙ\88Ù\85 Ù\85دیاڤیکی",
        "upload": "سڤار کردن جانؽا",
-       "uploadbtn": "سوڤار کئردئن جانیا",
-       "reuploaddesc": "سوار کردن نه انجوم شیو بکیت و د ورئردیت جابلگ سوارکرد",
-       "upload-tryagain": "کل کردن توضیحیا آلشت دئیه بیه جانیا",
+       "uploadbtn": "سڤار کردن جانؽا",
+       "reuploaddesc": "سڤار کردن ناْ ٱنجوم شؽڤ بٱکؽت ۉ ڤرگٱردؽد  جا بٱلگٱ سڤارکرد",
+       "upload-tryagain": "کلٛ کردن تۉزیهؽا آلشت داٛئٱ بیٱ جانؽا",
        "uploadnologin": "هنی نۏمایتٱ ڤامیٛن",
        "uploadnologintext": "لطفن $1 سی سوارکرد جانیایا.",
        "upload_directory_missing": "نشونگه سوارکرد ($1) وجود ناره و نبوئه دروسش بکی.",
index 4763645..70c07c3 100644 (file)
        "rcfilters-clear-all-filters": "Noņemt visus filtrus",
        "rcfilters-show-new-changes": "Skatīt jaunās izmaiņas kopš $1",
        "rcfilters-search-placeholder": "Filtrēt pēdējās izmaiņas (pārlūko vai sāc rakstīt)",
+       "rcfilters-search-placeholder-mobile": "Filtri",
        "rcfilters-invalid-filter": "Nederīgs filtrs",
        "rcfilters-empty-filter": "Nav aktīvu filtru. Tiek rādītas visas izmaiņas.",
        "rcfilters-filterlist-title": "Filtri",
index d183ca0..a6b7a5b 100644 (file)
        "changecontentmodel": "Промена на содржинскиот модел на страница",
        "changecontentmodel-legend": "Промена на содржински модел",
        "changecontentmodel-title-label": "Наслов на страницата",
+       "changecontentmodel-current-label": "Тековен содржински модел:",
        "changecontentmodel-model-label": "Нов содржински модел",
        "changecontentmodel-reason-label": "Причина:",
        "changecontentmodel-submit": "Смени",
        "block-log-flags-angry-autoblock": "овозможено проширено автоблокирање",
        "block-log-flags-hiddenname": "сокриено корисничко име",
        "range_block_disabled": "Администраторската можност да блокираат IP групи е исклучена.",
+       "ipb-prevent-user-talk-edit": "Уредувањето на сопствената разговорна страница мора да биде дозволено при делумен блок, освен ако не вклучува ограничување за разговорната страница.",
        "ipb_expiry_invalid": "Погрешен рок на истекување.",
        "ipb_expiry_old": "Времето на истекување е постаро од тековното време.",
        "ipb_expiry_temp": "Скриените блокирања на корисникот мора да бидат перманентни.",
        "move-page-legend": "Премести страница",
        "movepagetext": "Со користењето на овој образец можете да преименувате страница, преместувајќи ја целата нејзина историја под ново име.\nСтариот наслов ќе стане пренасочувачка страница кон новиот наслов.\nАвтоматски можете да ги подновите пренасочувањата кои покажуваат кон првобитниот наслов.\nАко не изберете автоматско подновување, проверете на [[Special:DoubleRedirects|двојни]] или [[Special:BrokenRedirects|прекинати пренасочувања]].\nНа вас е одговорноста да се осигурате дека врските ќе продолжат да насочуваат таму за каде се предвидени.\n\nИмајте предвид дека страницата '''нема''' да биде преместена ако веќе постои страница со новиот наслов, освен ако е не е пренасочување и нема историја на минати уредувања. Тоа значи дека можете да ја преименувате страницата како што била претходно доколку сте направиле грешка без да ја прекриете постоечката страница.\n\n'''Напомена:'''\nОва може да биде драстична и неочекувана промена за популарна страница;\nосигурајте се дека сте ги разбрале последиците од ова пред да продолжите.",
        "movepagetext-noredirectfixer": "Со користењето на овој образец можете да преименувате страница, преместувајќи ја целата нејзина историја под ново име.\nСтариот наслов ќе стане пренасочувачка страница кон новиот наслов.\nАвтоматски можете да ги подновите пренасочувањата кои покажуваат кон првобитниот наслов.\nНе заборавајте да проверите [[Special:DoubleRedirects|двојни]] и [[Special:BrokenRedirects|прекинати пренасочувања]].\nНа вас е одговорноста да се осигурате дека врските ќе продолжат да насочуваат таму за каде се предвидени.\n\nИмајте предвид дека страницата '''НЕМА''' да биде преместена ако веќе постои страница со новиот наслов, освен ако е празна или ако е пренасочување и нема историја на минати уредувања. Тоа значи дека можете да ја преименувате страницата како што била претходно доколку сте направиле грешка без да ја прекриете постоечката страница.\n\n'''Напомена:'''\nОва може да биде драстична и неочекувана промена за популарна страница;\nосигурајте се дека сте ги разбрале последиците од ова пред да продолжите.",
+       "movepagetext-noredirectsupport": "Со користењето на овој образец можете да преименувате страница, преместувајќи ја целата нејзина историја под ново име.\nНа вас е одговорноста да се осигурате дека врските ќе продолжат да насочуваат таму за каде се предвидени.\n\nИмајте предвид дека страницата <strong>нема</strong> да биде преместена ако веќе постои страница со новиот наслов.\nТоа значи дека можете да ја преименувате страницата како што била претходно доколку сте направиле грешка без да ја прекриете постоечката страница.\n\n<strong>Напомена:</strong>\nОва може да биде драстична и неочекувана промена за популарна страница;\nосигурајте се дека сте ги разбрале последиците од ова пред да продолжите.",
        "movepagetalktext": "Ако го штиклирате кутивчево, соодветната разговорна страница ќе биде автоматски преместена на нов наслов, освен ако таму веќе постои разговорна страница  што не е празна.\n\nВо тој случај, ќе треба да ја преместите или споите страницата рачно, доколку сакате.",
        "moveuserpage-warning": "'''Предупредување:''' На пат сте да преместите корисничка страница. Имајте предвид дека само страницата ќе биде преместена, а самиот корисник ''нема'' да биде преименуван.",
        "movecategorypage-warning": "<strong>Предупредување:</strong> Преместувате категориска страница. Имајте предвид дека ќе се премести само страницата, а страниците во старата категорија <em>нема</em> да се прекатегоризираат во новата.",
index dea9329..157b87c 100644 (file)
@@ -25,7 +25,7 @@
        "tog-previewontop": "Show preview before edit box",
        "tog-previewonfirst": "Show preview on first edit",
        "tog-enotifwatchlistpages": "Email me when a page or a file on my watchlist is changed",
-       "tog-enotifusertalkpages": "Email me when my user talk page is changed",
+       "tog-enotifusertalkpages": "ꯏꯃꯦꯜ ꯊꯥꯔꯛꯎ ꯑꯩꯒꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯋꯥ ꯍꯥꯏꯐꯝ ꯂꯃꯥꯏꯗꯨ ꯍꯣꯡꯗꯣꯛꯄꯥ ꯃꯇꯝꯗ",
        "tog-enotifminoredits": "Email me also for minor edits of pages and files",
        "tog-enotifrevealaddr": "Reveal my email address in notification emails",
        "tog-shownumberswatching": " watching users ꯀꯤ ꯃꯁꯤꯡꯗꯨ ꯎꯨꯠꯂꯨ",
        "category-file-count-limited": "The following {{PLURAL:$1|file is|$1 files are}} in the current category.",
        "listingcontinuesabbrev": "ꯆꯠꯊꯕꯥ",
        "index-category": "Indexed ꯂꯥꯃꯥꯏꯁꯤꯡ",
-       "noindex-category": "Noindexed ꯂꯃꯥꯏꯁꯤꯡ",
-       "broken-file-category": " ꯀꯥꯏꯔꯕꯥ file links ꯒꯥ ꯂꯣꯏꯅꯕꯥ ꯂꯥꯃꯥꯏꯁꯤꯡ",
+       "noindex-category": "ꯏꯟꯗꯦꯛꯁ ꯌꯥꯎꯗꯕ ꯂꯃꯥꯏꯁꯤꯡ",
+       "broken-file-category": " ꯀꯥꯏꯔꯕꯥ ꯐꯥꯏꯜꯒꯥ ꯂꯣꯏꯅꯕꯥ ꯂꯃꯥꯏꯁꯤꯡ",
        "about": "ꯄꯣꯠꯇꯨꯗꯤ ꯃꯔꯝꯗꯥ",
        "article": "ꯂꯃꯥꯏꯁꯤꯗꯥ ꯌꯥꯎꯕꯥ ꯄꯨꯝꯅꯃꯛ",
        "newwindow": "(ꯑꯅꯧꯕꯥ ꯊꯣꯡꯅꯥꯎꯗꯥ ꯍꯥꯡꯗꯣꯛ ꯎ)",
        "navigation": "ꯆꯠꯄꯥ",
        "and": "&#32;ꯑꯃꯁꯨꯡ",
        "faq": "ꯇꯍꯋ",
-       "actions": "Actions",
+       "actions": "ꯊꯕꯛ ꯄꯥꯡꯊꯣꯛꯄ",
        "namespaces": "ꯃꯃꯤꯡꯒꯤ ꯃꯐꯝ",
        "variants": "ꯈꯦꯠꯅꯕꯥ",
        "navigation-heading": "ꯆꯠꯅꯕ ꯌꯦꯡꯐꯝ",
        "view": "ꯃꯤꯠꯌꯦꯡ",
        "view-foreign": "$1 ꯗꯥ ꯌꯦꯡꯉꯨ",
        "edit": "ꯁꯦꯝꯒꯠꯄꯥ",
-       "edit-local": "Edit local description",
+       "edit-local": "ꯁꯦꯝꯒꯠꯄꯒꯤ ꯑꯀꯨꯞꯄ ꯋꯥꯔꯣꯜ",
        "create": "ꯁꯥꯕꯥ",
        "create-local": "ꯁꯨꯋꯥꯏꯒꯤ ꯁꯟꯗꯣꯛꯅꯥ ꯇꯥꯛꯄꯗꯨ ꯍꯥꯞꯆꯤꯟꯂꯨ",
        "delete": "ꯀꯛꯊꯠꯄꯥ",
        "undelete_short": "ꯀꯛꯊꯠꯀꯅꯨ {{PLURAL:$1|ꯁꯦꯝꯒꯠꯄ ꯑꯃꯥ|ꯁꯦꯝꯒꯠꯄꯁꯤꯡ $1}}",
-       "viewdeleted_short": "ꯌꯦꯡꯕ {{PLURAL:$1|one deleted edit|$1 deleted edits}}",
+       "viewdeleted_short": "ꯌꯦꯡꯕ {{PLURAL:$1|ꯀꯛꯊꯠꯈꯔꯕ ꯁꯦꯝꯒꯠꯄ ꯱|$1 ꯀꯛꯊꯠꯈꯔꯕ ꯁꯦꯝꯒꯠꯄꯁꯤꯡ}}",
        "protect": "ꯉꯥꯛꯊꯣꯛꯂꯕꯥ",
        "protect_change": "ꯑꯍꯣꯡꯕꯥ",
        "unprotect": "ꯍꯥꯛꯊꯣꯛꯂꯕꯥ ꯗꯨ ꯍꯣꯡꯕꯥ",
        "versionrequiredtext": "ꯃꯦꯗꯤꯌꯥ ꯋꯤꯀꯤꯅ ꯋꯥꯠꯂꯤꯕꯥ $1ꯕꯔꯖꯟ ꯃꯁꯤꯒꯤ ꯂꯥꯃꯥꯏꯁꯤꯗꯥ ꯁꯤꯖꯤꯟꯅꯕꯥ [[Special:Version|version page]].",
        "ok": "ꯌꯥꯔꯦ",
        "retrievedfrom": "\"$1\" ꯃꯐꯝꯗꯨꯗꯒꯤ ꯑꯣꯏꯔꯛꯄꯥ",
-       "youhavenewmessages": "{{PLURAL:$3|ꯅꯪꯉꯣꯟꯗ ꯂꯩ}} $1 ($2).",
+       "youhavenewmessages": "{{PLURAL:$3|ꯅꯪꯉꯣꯟꯗ ꯂꯩ}} $1 ($2) ꯫",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|You have}} $1 from {{PLURAL:$3|another user|$3 users}} ($2).",
        "youhavenewmessagesmanyusers": "ꯅꯪ $1 ꯂꯩꯔꯦ $2 ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥ ꯃꯌꯥꯝꯗꯒꯤ ꯫",
        "newmessageslinkplural": "{{PLURAL:$1|ꯑꯅꯧꯕ ꯄꯥꯎꯖꯦꯜ ꯱|꯹꯹꯹=ꯑꯅꯧꯕ ꯄꯥꯎꯖꯦꯜꯁꯤꯡ}}",
        "restorelink": "{{PLURAL:$1|one deleted edit|$1 deleted edits}}",
        "feedlinks": "ꯐꯤꯗ:",
        "feed-invalid": "ꯌꯥꯎꯗꯕꯥ subscription feed type.",
-       "feed-unavailable": "Syndication feeds are not available",
+       "feed-unavailable": "ꯐꯤꯗꯁ ꯐꯪꯗꯦ",
        "site-rss-feed": "$1 RSS feed",
        "site-atom-feed": "$1 ꯑꯦꯇꯣꯝ ꯇꯥꯛꯄꯥ",
        "page-rss-feed": "\"$1\" RSS feed",
        "page_first": "ꯑꯍꯥꯟꯕ",
        "page_last": "ꯑꯔꯣꯏꯕ",
        "histlegend": "Diff selection: Mark the radio boxes of the revisions to compare and hit enter or the button at the bottom.<br />\nLegend: <strong>({{int:cur}})</strong> = difference with latest revision, <strong>({{int:last}})</strong> = difference with preceding revision, <strong>{{int:minoreditletter}}</strong> = ꯑꯄꯤꯛꯄ ꯁꯦꯝꯒꯠꯄ",
-       "history-fieldset-title": "ê¯\8aꯤê¯\8bꯨ ê¯\91ê¯\83ꯨê¯\9b ê¯\8dê¯\9dê¯\81ê¯\9fê¯\85ꯥ ê¯\8cꯦꯡê¯\85ê¯\95ꯥ",
+       "history-fieldset-title": "ê¯\94ꯤê¯\9aꯤê¯\96ê¯\9fê¯\81ꯤꯡ ê¯\87ꯦꯡê¯\8aꯣê¯\9bê¯\84ꯥ",
        "histfirst": "ꯈꯨꯋꯥꯏꯗꯒꯤ ꯑꯔꯤꯕꯥ",
        "histlast": "ꯑꯅꯧꯕꯥ",
        "historyempty": "(ꯑꯍꯥꯡꯕ)",
        "mergehistory-fail-invalid-dest": "ꯂꯝꯊꯨꯡꯐꯝ ꯂꯃꯥꯏꯁꯤ ꯂꯩꯇꯦ ꯫",
        "mergehistory-reason": "ꯃꯔꯝ:",
        "mergelog": "ꯂꯣꯒ ꯄꯨꯟꯁꯤꯟꯕ",
-       "history-title": "Revision history of \"$1\"",
+       "history-title": "\"$1\" ꯒꯤ ꯔꯤꯚꯤꯖꯟ ꯄꯨꯋꯥꯔꯤ",
        "difference-title": "$1 ꯒꯤ ꯑꯃꯨꯛꯍꯟꯕꯥ ꯈꯦꯠꯅꯕꯥꯒꯤ ꯃꯔꯛ",
        "lineno": "ꯂꯩ ꯏ $1:",
        "compareselectedversions": "ꯈꯟꯒꯠꯂꯕ ꯁꯤꯡ ꯑꯃꯨꯛ ꯍꯟꯅ ꯌꯦꯡꯕꯗꯨ ꯆꯥꯡꯗꯝꯅꯧ",
index 8b4341b..065918a 100644 (file)
        "rcfilters-clear-all-filters": "Kosongkan semua penapis",
        "rcfilters-show-new-changes": "Lihat perubahan baru sejak $1",
        "rcfilters-search-placeholder": "Penapis perubahan (guna menu atau carian untuk menapis nama)",
+       "rcfilters-search-placeholder-mobile": "Penapis",
        "rcfilters-invalid-filter": "Penapis tidak sah",
        "rcfilters-empty-filter": "Tiada penapis aktif. Semua sumbangan ditunjukkan.",
        "rcfilters-filterlist-title": "Penapis",
        "wlshowhideliu": "pengguna berdaftar",
        "wlshowhideanons": "pengguna awanama",
        "wlshowhidemine": "suntingan saya",
+       "wlshowhidecategorization": "pengkategorian laman",
        "watchlist-options": "Pilihan senarai pantau",
        "watching": "Memantau...",
        "unwatching": "Menyahpantau...",
        "deleteprotected": "Anda tidak boleh menghapuskan laman ini kerana ia telah dilindungi.",
        "deleting-backlinks-warning": "'''Amaran:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Terdapat laman lain]] yang berpaut atau bertransklusi dengan laman yang hendak anda hapus ini.",
        "rollback": "Undurkan suntingan.",
+       "rollback-confirmation-no": "Batal",
        "rollbacklink": "undur",
        "rollbacklinkcount": "mengundurkan $1 {{PLURAL:$1|suntingan}}",
        "rollbacklinkcount-morethan": "mengundurkan lebih daripada $1 {{PLURAL:$1|suntingan}}",
        "ipb-disableusertalk": "Halang pengguna ini daripada menyunting laman perbincangan sendiri apabila disekat",
        "ipb-change-block": "Sekat semula pengguna tersebut dengan tetapan ini",
        "ipb-confirm": "Sahkan sekatan",
+       "ipb-namespaces-label": "Ruang nama",
        "badipaddress": "Alamat IP tidak sah",
        "blockipsuccesssub": "Sekatan berjaya",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] telah disekat.\n<br />Sila lihat [[Special:BlockList|senarai sekatan]] untuk menyemak sekatan.",
        "ipb-unblock": "Nyahsekat nama pengguna atau alamat IP",
        "ipb-blocklist": "Lihat sekatan sedia ada",
        "ipb-blocklist-contribs": "Sumbangan oleh {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 tinggal",
        "block-expiry": "Tamat:",
+       "block-target": "Nama pengguna atau alamat IP:",
        "unblockip": "Nyahsekat pengguna",
        "unblockiptext": "Gunakan borang di bawah untuk membuang sekatan bagialamat IP atau nama pengguna yang telah disekat.",
        "ipusubmit": "Tarik balik sekatan ini",
        "blocklist-userblocks": "Sorokkan sekatan akaun",
        "blocklist-tempblocks": "Sorokkan sekatan sementara",
        "blocklist-addressblocks": "Sorokkan sekatan IP tunggal",
+       "blocklist-type": "Jenis:",
+       "blocklist-type-opt-all": "Semua",
        "blocklist-rangeblocks": "Sorokkan sekatan julat",
        "blocklist-timestamp": "Cop masa",
        "blocklist-target": "Sasaran",
        "createaccountblock": "pembukaan akaun baru disekat",
        "emailblock": "e-mail disekat",
        "blocklist-nousertalk": "tidak boleh menyunting laman perbincangan sendiri",
+       "blocklist-editing": "menyunting",
+       "blocklist-editing-page": "laman",
+       "blocklist-editing-ns": "ruang nama",
        "ipblocklist-empty": "Senarai sekatan adalah kosong.",
        "ipblocklist-no-results": "Alamat IP atau nama pengguna tersebut tidak disekat.",
        "blocklink": "sekat",
        "cant-move-to-user-page": "Anda tidak mempunyai keizinan untuk memindahkan sesebuah laman ke mana-mana laman pengguna (kecuali sebagai sublamannya sahaja).",
        "cant-move-category-page": "Anda tidak mempunyai kebenaran untuk memindah laman-laman kategori.",
        "cant-move-to-category-page": "Anda tidak mempunyai kebenaran untuk memindah sebuah laman ke sebuah laman kategori.",
+       "namespace-nosubpages": "Ruang nama \"$1\" tidak membenarkan sublaman.",
        "newtitle": "Tajuk baru:",
        "move-watch": "Pantau laman ini",
        "movepagebtn": "Pindahkan laman",
        "pagemovedsub": "Pemindahan berjaya",
+       "cannotmove": "Laman tidak dapat dipindahkan, atas {{PLURAL:$1|sebab|sebab-sebab}} berikut:",
        "movepage-moved": "'''\"$1\" telah dipindahkan ke \"$2\"'''",
        "movepage-moved-redirect": "Satu lencongan telah diwujudkan.",
        "movepage-moved-noredirect": "Penciptaan lencongan telah dihalang.",
        "export-download": "Simpan sebagai fail",
        "export-templates": "Sertakan templat",
        "export-pagelinks": "Sertakan laman-laman yang dipaut sedalam:",
+       "export-manual": "Tambah laman secara manual:",
        "allmessages": "Pesanan sistem",
        "allmessagesname": "Nama",
        "allmessagesdefault": "Teks mesej asal",
        "import-mapping-namespace": "Import ke ruang nama:",
        "import-mapping-subpage": "Import sebagai sublaman bagi laman berikut:",
        "import-upload-filename": "Nama fail:",
+       "import-upload-username-prefix": "Awalan interwiki:",
        "import-comment": "Komen:",
        "importtext": "Sila eksport fail daripada sumber wiki dengan menggunakan [[Special:Export|utiliti eksport]].\nSimpan dalam komputer anda dan muat naiknya di sini.",
        "importstart": "Mengimport laman...",
        "pageinfo-display-title": "Tajuk paparan",
        "pageinfo-default-sort": "Kunci isih azali",
        "pageinfo-length": "Kepanjangan halaman (bait)",
+       "pageinfo-namespace": "Ruang nama",
        "pageinfo-article-id": "ID halaman",
        "pageinfo-language": "Bahasa isi kandungan halaman",
        "pageinfo-content-model": "Model kandungan halaman",
        "pageinfo-category-pages": "Bilangan halaman",
        "pageinfo-category-subcats": "Bilangan subkategori",
        "pageinfo-category-files": "Bilangan fail",
+       "pageinfo-user-id": "ID pengguna",
+       "pageinfo-file-hash": "Nilai cincangan",
        "markaspatrolleddiff": "Tanda ronda",
        "markaspatrolledtext": "Tanda ronda laman ini",
        "markedaspatrolled": "Tanda ronda",
        "newimages-summary": "Laman khas ini memaparkan senarai fail muat naik terakhir.",
        "newimages-legend": "Penapis",
        "newimages-label": "Nama fail (atau sebahagian daripadanya):",
+       "newimages-user": "Alamat IP atau nama pengguna",
        "newimages-showbots": "Paparkan muat naik oleh bot",
+       "newimages-mediatype": "Jenis media:",
        "noimages": "Tiada imej.",
        "ilsubmit": "Cari",
        "bydate": "mengikut tarikh",
        "version-specialpages": "Laman khas",
        "version-parserhooks": "Penyangkuk penghurai",
        "version-variables": "Pemboleh ubah",
+       "version-editors": "Penyunting",
        "version-antispam": "Pencegahan spam",
        "version-other": "Lain-lain",
        "version-mediahandlers": "Pengelola media",
        "htmlform-cloner-create": "Tambah lebih",
        "htmlform-cloner-delete": "Buang",
        "htmlform-cloner-required": "Sekurang-kurangnya satu nilai diperlukan.",
+       "htmlform-date-placeholder": "TTTT-BB-HH",
+       "htmlform-time-placeholder": "JJ:MM:SS",
+       "htmlform-datetime-placeholder": "TTTT-BB-HH JJ:MM:SS",
+       "htmlform-title-not-exists": "$1 tidak wujud.",
+       "htmlform-user-not-exists": "<strong>$1</strong> tidak wujud.",
+       "htmlform-user-not-valid": "<strong>$1</strong> bukan nama pengguna yang sah.",
        "logentry-delete-delete": "$1 telah {{GENDER:$2|menghapuskan}} laman $3",
        "logentry-delete-restore": "$1 telah {{GENDER:$2|memulihkan}} laman $3 ($4)",
        "logentry-delete-event": "$1 telah {{GENDER:$2|mengubah}} keterlihatan $5 peristiwa log di $3: $4",
        "pagelang-language": "Bahasa",
        "pagelang-use-default": "Gunakan bahasa asli",
        "pagelang-select-lang": "Pilih bahasa",
+       "pagelang-reason": "Sebab",
        "right-pagelang": "Mengubah bahasa laman",
        "action-pagelang": "mengubah bahasa laman",
        "log-name-pagelang": "Log perubahan bahasa",
        "mediastatistics-header-text": "Tekstual",
        "mediastatistics-header-executable": "Fail boleh laksana",
        "mediastatistics-header-archive": "Format mampat",
+       "mediastatistics-header-total": "Semua fail",
        "json-warn-trailing-comma": "$1 koma pengekor telah digugurkan dari JSON",
        "json-error-unknown": "Terdapat masalah dengan JSON. Ralat: $1",
        "json-error-depth": "Kedalaman tindakan maksimum telah dicecah",
        "mw-widgets-dateinput-no-date": "Tarik belum dipilih",
        "mw-widgets-titleinput-description-new-page": "laman belum wujud",
        "mw-widgets-titleinput-description-redirect": "melencong ke $1",
-       "randomrootpage": "Laman akar rawak"
+       "date-range-from": "Dari tarikh:",
+       "randomrootpage": "Laman akar rawak",
+       "log-action-filter-import": "Jenis import:",
+       "log-action-filter-all": "Semua",
+       "log-action-filter-protect-protect": "Perlindungan",
+       "log-action-filter-upload-upload": "Muat naik baru",
+       "authmanager-email-label": "E-mel",
+       "authmanager-email-help": "Alamat e-mel",
+       "authmanager-realname-label": "Nama sebenar",
+       "authmanager-realname-help": "Nama sebenar pengguna",
+       "credentialsform-account": "Nama akaun:",
+       "edit-error-short": "Ralat: $1",
+       "edit-error-long": "Ralat:\n\n$1",
+       "pagedata-title": "Data laman",
+       "pagedata-bad-title": "Tajuk tidak sah: $1.",
+       "passwordpolicies-group": "Kumpulan",
+       "passwordpolicies-policies": "Dasar-dasar"
 }
index f0eeaa4..033be0b 100644 (file)
        "changecontentmodel": "Title of the change content model special page",
        "changecontentmodel-legend": "Legend of the fieldset on the change content model special page",
        "changecontentmodel-title-label": "Label for the input field where the target page title should be entered\n{{Identical|Page title}}",
+       "changecontentmodel-current-label": "Label for the current content model",
        "changecontentmodel-model-label": "Label of the dropdown listing available content model types the user can change a page to",
        "changecontentmodel-reason-label": "{{Identical|Reason}}",
        "changecontentmodel-submit": "Label of the form \"submit\" button for [[Special:ChangeContentModel]]\n{{Identical|Change}}",
        "move-page-legend": "Legend of the fieldset around the input form of [[Special:MovePage/testpage]].\n\nSee also:\n* {{msg-mw|newtitle|label for new title}}\n* {{msg-mw|movereason|label for textarea}}\n* {{msg-mw|movetalk|label for checkbox}}\n* {{msg-mw|move-leave-redirect|label for checkbox}}\n* {{msg-mw|fix-double-redirects|label for checkbox}}\n* {{msg-mw|move-subpages|label for checkbox}}\n* {{msg-mw|move-talk-subpages|label for checkbox}}\n* {{msg-mw|move-watch|label for checkbox}}\n{{Identical|Move page}}",
        "movepagetext": "Introduction shown when moving a page ([[Special:MovePage]]).\n\nSpecial pages mentioned: {{msg-mw|Doubleredirects}}, {{msg-mw|Brokenredirects}}\n\nSee also:\n* {{msg-mw|Movepagetext-noredirectfixer}}",
        "movepagetext-noredirectfixer": "A variant of the following message ''Movepagetext'' displayed when the automatic redirect fixer is not enabled.\n\nSpecial pages mentioned: {{msg-mw|Doubleredirects}}, {{msg-mw|Brokenredirects}}\n\nSee also:\n* {{msg-mw|Movepagetext}}",
+       "movepagetext-noredirectsupport": "A variant of the following message ''Movepagetext'' displayed when the content model of the page being moved does not support redirects.\n\nSee also:\n* {{msg-mw|Movepagetext}}",
        "movepagetalktext": "Text on the special 'Move page'. This text only appears if the talk page is not empty.",
        "moveuserpage-warning": "Used as warning in [[Special:MovePage]], when moving a user page.",
        "movecategorypage-warning": "Used as warning in [[Special:MovePage]], when moving a category page.",
index 0263425..c3644ee 100644 (file)
@@ -1366,28 +1366,34 @@ abstract class Maintenance {
        }
 
        /**
-        * Returns a database to be used by current maintenance script. It can be set by setDB().
-        * If not set, wfGetDB() will be used.
-        * This function has the same parameters as wfGetDB()
+        * Returns a database to be used by current maintenance script.
+        *
+        * This uses the main LBFactory instance by default unless overriden via setDB().
+        *
+        * This function has the same parameters as LoadBalancer::getConnection().
         *
         * @param int $db DB index (DB_REPLICA/DB_MASTER)
         * @param string|string[] $groups default: empty array
-        * @param string|bool $wiki default: current wiki
+        * @param string|bool $dbDomain default: current wiki
         * @return IMaintainableDatabase
         */
-       protected function getDB( $db, $groups = [], $wiki = false ) {
+       protected function getDB( $db, $groups = [], $dbDomain = false ) {
                if ( $this->mDb === null ) {
-                       return wfGetDB( $db, $groups, $wiki );
+                       return MediaWikiServices::getInstance()
+                               ->getDBLoadBalancerFactory()
+                               ->getMainLB( $dbDomain )
+                               ->getMaintenanceConnectionRef( $db, $groups, $dbDomain );
                }
+
                return $this->mDb;
        }
 
        /**
         * Sets database object to be returned by getDB().
         *
-        * @param IDatabase $db
+        * @param IMaintainableDatabase $db
         */
-       public function setDB( IDatabase $db ) {
+       public function setDB( IMaintainableDatabase $db ) {
                $this->mDb = $db;
        }
 
@@ -1529,7 +1535,7 @@ abstract class Maintenance {
                        $title = $titleObj->getPrefixedDBkey();
                        $this->output( "$title..." );
                        # Update searchindex
-                       $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
+                       $u = new SearchUpdate( $pageId, $titleObj, $rev->getContent() );
                        $u->doUpdate();
                        $this->output( "\n" );
                }
index df3b4a1..08eade9 100644 (file)
@@ -30,7 +30,7 @@ require_once __DIR__ . '/../../includes/export/WikiExporter.php';
 
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\LoadBalancer;
-use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IMaintainableDatabase;
 
 /**
  * @ingroup Dump
@@ -68,7 +68,7 @@ abstract class BackupDumper extends Maintenance {
        /**
         * The dependency-injected database to use.
         *
-        * @var IDatabase|null
+        * @var IMaintainableDatabase|null
         *
         * @see self::setDB
         */
@@ -328,7 +328,7 @@ abstract class BackupDumper extends Maintenance {
         * @todo Fixme: the --server parameter is currently not respected, as it
         * doesn't seem terribly easy to ask the load balancer for a particular
         * connection by name.
-        * @return IDatabase
+        * @return IMaintainableDatabase
         */
        function backupDb() {
                if ( $this->forcedDb !== null ) {
@@ -337,7 +337,7 @@ abstract class BackupDumper extends Maintenance {
 
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $this->lb = $lbFactory->newMainLB();
-               $db = $this->lb->getConnection( DB_REPLICA, 'dump' );
+               $db = $this->lb->getMaintenanceConnectionRef( DB_REPLICA, 'dump' );
 
                // Discourage the server from disconnecting us if it takes a long time
                // to read out the big ol' batch query.
@@ -350,10 +350,9 @@ abstract class BackupDumper extends Maintenance {
         * Force the dump to use the provided database connection for database
         * operations, wherever possible.
         *
-        * @param IDatabase|null $db (Optional) the database connection to use. If null, resort to
-        *   use the globally provided ways to get database connections.
+        * @param IMaintainableDatabase $db The database connection to use
         */
-       function setDB( IDatabase $db = null ) {
+       function setDB( IMaintainableDatabase $db ) {
                parent::setDB( $db );
                $this->forcedDb = $db;
        }
index f9b3951..21b92c5 100644 (file)
@@ -240,7 +240,7 @@ TEXT
                }
 
                try {
-                       $this->db = $this->lb->getConnection( DB_REPLICA, 'dump' );
+                       $this->db = $this->lb->getMaintenanceConnectionRef( DB_REPLICA, 'dump' );
                } catch ( Exception $e ) {
                        throw new MWException( __METHOD__
                                . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" );
index 1dd1909..a71bb74 100644 (file)
@@ -115,7 +115,12 @@ class CommandLineInstaller extends Maintenance {
                        $this->setPassOption();
                }
 
-               $installer = InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions );
+               try {
+                       $installer = InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions );
+               } catch ( \MediaWiki\Installer\InstallException $e ) {
+                       $this->output( $e->getStatus()->getMessage()->parse() . "\n" );
+                       return false;
+               }
 
                $status = $installer->doEnvironmentChecks();
                if ( $status->isGood() ) {
@@ -123,17 +128,21 @@ class CommandLineInstaller extends Maintenance {
                } else {
                        $installer->showStatusMessage( $status );
 
-                       return;
+                       return false;
                }
                if ( !$envChecksOnly ) {
-                       $installer->execute();
+                       $status = $installer->execute();
+                       if ( !$status->isGood() ) {
+                               return false;
+                       }
                        $installer->writeConfigurationFile( $this->getOption( 'confpath', $IP ) );
+                       $installer->showMessage(
+                               'config-install-success',
+                               $installer->getVar( 'wgServer' ),
+                               $installer->getVar( 'wgScriptPath' )
+                       );
                }
-               $installer->showMessage(
-                       'config-install-success',
-                       $installer->getVar( 'wgServer' ),
-                       $installer->getVar( 'wgScriptPath' )
-               );
+               return true;
        }
 
        private function setDbPassOption() {
index a67417f..f5ebc02 100644 (file)
@@ -93,8 +93,8 @@ class MediaWikiShell extends Maintenance {
                }
                if ( $d > 1 ) {
                        # Set DBO_DEBUG (equivalent of $wgDebugDumpSql)
-                       wfGetDB( DB_MASTER )->setFlag( DBO_DEBUG );
-                       wfGetDB( DB_REPLICA )->setFlag( DBO_DEBUG );
+                       $this->getDB( DB_MASTER )->setFlag( DBO_DEBUG );
+                       $this->getDB( DB_REPLICA )->setFlag( DBO_DEBUG );
                }
        }
 
index 3b0607f..21d8b2d 100644 (file)
@@ -83,8 +83,7 @@ class MwSql extends Maintenance {
                        $index = DB_MASTER;
                }
 
-               /** @var IDatabase $db DB handle for the appropriate cluster/wiki */
-               $db = $lb->getConnection( $index, [], $wiki );
+               $db = $lb->getMaintenanceConnectionRef( $index, [], $wiki );
                if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
                        $this->fatalError( "The server selected ({$db->getServer()}) is not a replica DB." );
                }
index 3866be7..60f88ba 100644 (file)
@@ -42,7 +42,7 @@ class OrphanStats extends Maintenance {
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lb = $lbFactory->getExternalLB( $cluster );
 
-               return $lb->getConnection( DB_REPLICA );
+               return $lb->getMaintenanceConnectionRef( DB_REPLICA );
        }
 
        public function execute() {
index 6fd53cc..92b6679 100644 (file)
  * @ingroup Maintenance ExternalStorage
  */
 
+use Wikimedia\Rdbms\IMaintainableDatabase;
 use MediaWiki\Logger\LegacyLogger;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Shell\Shell;
-use Wikimedia\Rdbms\IDatabase;
 
 $optionsWithArgs = RecompressTracked::getOptionsWithArgs();
 require __DIR__ . '/../commandLine.inc';
@@ -275,6 +275,7 @@ class RecompressTracked {
        /**
         * Dispatch a command to the next available replica DB.
         * This may block until a replica DB finishes its work and becomes available.
+        * @param array ...$args
         */
        function dispatch( ...$args ) {
                $pipes = $this->replicaPipes;
@@ -647,13 +648,13 @@ class RecompressTracked {
        /**
         * Gets a DB master connection for the given external cluster name
         * @param string $cluster
-        * @return IDatabase
+        * @return IMaintainableDatabase
         */
        function getExtDB( $cluster ) {
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lb = $lbFactory->getExternalLB( $cluster );
 
-               return $lb->getConnection( DB_MASTER );
+               return $lb->getMaintenanceConnectionRef( DB_MASTER );
        }
 
        /**
index 385ae6a..d9793b4 100644 (file)
@@ -232,7 +232,7 @@ class TrackBlobs {
                $pos = $dbw->getMasterPos();
                $dbr->masterPosWait( $pos, 100000 );
 
-               $textClause = $this->getTextClause( $this->clusters );
+               $textClause = $this->getTextClause();
                $startId = 0;
                $endId = $dbr->selectField( 'text', 'MAX(old_id)', '', __METHOD__ );
                $rowsInserted = 0;
@@ -325,9 +325,9 @@ class TrackBlobs {
                        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                        $lb = $lbFactory->getExternalLB( $cluster );
                        try {
-                               $extDB = $lb->getConnection( DB_REPLICA );
+                               $extDB = $lb->getMaintenanceConnectionRef( DB_REPLICA );
                        } catch ( DBConnectionError $e ) {
-                               if ( strpos( $e->error, 'Unknown database' ) !== false ) {
+                               if ( strpos( $e->getMessage(), 'Unknown database' ) !== false ) {
                                        echo "No database on $cluster\n";
                                } else {
                                        echo "Error on $cluster: " . $e->getMessage() . "\n";
@@ -362,8 +362,8 @@ class TrackBlobs {
 
                                foreach ( $res as $row ) {
                                        gmp_setbit( $actualBlobs, $row->blob_id );
+                                       $startId = $row->blob_id;
                                }
-                               $startId = $row->blob_id;
 
                                ++$batchesDone;
                                if ( $batchesDone >= $this->reportingInterval ) {
index e29d658..68188f0 100644 (file)
@@ -763,10 +763,6 @@ return [
        ],
 
        /* MediaWiki */
-       'mediawiki.apihelp' => [
-               'styles' => 'resources/src/mediawiki.apihelp.css',
-               'targets' => [ 'desktop' ],
-       ],
        'mediawiki.template' => [
                'scripts' => 'resources/src/mediawiki.template.js',
                'targets' => [ 'desktop', 'mobile' ],
@@ -785,7 +781,10 @@ return [
                'dependencies' => 'mediawiki.template',
        ],
        'mediawiki.apipretty' => [
-               'styles' => 'resources/src/mediawiki.apipretty.css',
+               'styles' => [
+                       'resources/src/mediawiki.apipretty/apipretty.css',
+                       'resources/src/mediawiki.apipretty/apihelp.css',
+               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.api' => [
@@ -1154,6 +1153,7 @@ return [
                        'upload-form-label-usage-filename',
                        'action-upload',
                        'apierror-mustbeloggedin',
+                       'apierror-permissiondenied',
                        'badaccess-groups',
                        'apierror-timeout',
                        'apierror-offline',
@@ -2820,13 +2820,6 @@ return [
                ],
        ],
 
-       // @todo FIXME: Remove 7 days after Ib0020b6bd0156 is deployed to all wikis.
-       'html5shiv' => [
-               'scripts' => [
-                       'resources/lib/html5shiv/html5shiv.js'
-               ],
-       ],
-
        /* EasyDeflate */
 
        'easy-deflate.core' => [
diff --git a/resources/src/mediawiki.apihelp.css b/resources/src/mediawiki.apihelp.css
deleted file mode 100644 (file)
index d1f32ab..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/* stylelint-disable selector-class-pattern */
-
-.apihelp-header {
-       clear: both;
-       margin-bottom: 0.1em;
-}
-
-.apihelp-header.apihelp-module-name {
-       /*
-        * This element is explicitly set to dir="ltr" in HTML.
-        * Set explicit alignment so that CSSJanus will flip it to "right";
-        * otherwise the alignment will be automatically set to "left" according
-        * to the element's direction, and this will have an inconsistent look.
-        */
-       text-align: left;
-}
-
-div.apihelp-linktrail {
-       font-size: smaller;
-}
-
-.apihelp-block {
-       margin-top: 0.5em;
-}
-
-.apihelp-block-head {
-       font-weight: bold;
-}
-
-.apihelp-flags {
-       font-size: smaller;
-       float: right;
-       border: 1px solid #000;
-       padding: 0.25em;
-       width: 20em;
-}
-
-.apihelp-deprecated,
-.apihelp-flag-deprecated,
-.apihelp-flag-internal strong {
-       font-weight: bold;
-       color: #d33;
-}
-
-.apihelp-deprecated-value {
-       text-decoration: line-through;
-}
-
-.apihelp-unknown {
-       color: #72777d;
-}
-
-.apihelp-empty {
-       color: #72777d;
-}
-
-.apihelp-help-urls ul {
-       list-style-image: none;
-       list-style-type: none;
-       margin-left: 0;
-}
-
-.apihelp-parameters dl,
-.apihelp-examples dl,
-.apihelp-permissions dl {
-       margin-left: 2em;
-}
-
-.apihelp-parameters dt {
-       float: left;
-       clear: left;
-       min-width: 10em;
-       white-space: nowrap;
-       line-height: 1.5em;
-}
-
-.apihelp-parameters dt:after {
-       content: ':\A0';
-}
-
-.apihelp-parameters dd {
-       margin: 0 0 0.5em 10em;
-       line-height: 1.5em;
-}
-
-.apihelp-parameters dd p:first-child {
-       margin-top: 0;
-}
-
-.apihelp-parameters dd.info {
-       margin-left: 12em;
-       text-indent: -2em;
-}
-
-.apihelp-examples dt {
-       font-weight: normal;
-}
-
-.api-main-links {
-       text-align: center;
-}
-
-.api-main-links ul:before {
-       content: '[';
-}
-
-.api-main-links ul:after {
-       content: ']';
-}
diff --git a/resources/src/mediawiki.apipretty.css b/resources/src/mediawiki.apipretty.css
deleted file mode 100644 (file)
index 3e921f4..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/* stylelint-disable selector-class-pattern */
-
-.mw-special-ApiHelp h1.firstHeading {
-       display: none;
-}
-
-.api-pretty-header {
-       font-size: small;
-}
-
-.api-pretty-content {
-       white-space: pre-wrap;
-}
diff --git a/resources/src/mediawiki.apipretty/apihelp.css b/resources/src/mediawiki.apipretty/apihelp.css
new file mode 100644 (file)
index 0000000..d1f32ab
--- /dev/null
@@ -0,0 +1,109 @@
+/* stylelint-disable selector-class-pattern */
+
+.apihelp-header {
+       clear: both;
+       margin-bottom: 0.1em;
+}
+
+.apihelp-header.apihelp-module-name {
+       /*
+        * This element is explicitly set to dir="ltr" in HTML.
+        * Set explicit alignment so that CSSJanus will flip it to "right";
+        * otherwise the alignment will be automatically set to "left" according
+        * to the element's direction, and this will have an inconsistent look.
+        */
+       text-align: left;
+}
+
+div.apihelp-linktrail {
+       font-size: smaller;
+}
+
+.apihelp-block {
+       margin-top: 0.5em;
+}
+
+.apihelp-block-head {
+       font-weight: bold;
+}
+
+.apihelp-flags {
+       font-size: smaller;
+       float: right;
+       border: 1px solid #000;
+       padding: 0.25em;
+       width: 20em;
+}
+
+.apihelp-deprecated,
+.apihelp-flag-deprecated,
+.apihelp-flag-internal strong {
+       font-weight: bold;
+       color: #d33;
+}
+
+.apihelp-deprecated-value {
+       text-decoration: line-through;
+}
+
+.apihelp-unknown {
+       color: #72777d;
+}
+
+.apihelp-empty {
+       color: #72777d;
+}
+
+.apihelp-help-urls ul {
+       list-style-image: none;
+       list-style-type: none;
+       margin-left: 0;
+}
+
+.apihelp-parameters dl,
+.apihelp-examples dl,
+.apihelp-permissions dl {
+       margin-left: 2em;
+}
+
+.apihelp-parameters dt {
+       float: left;
+       clear: left;
+       min-width: 10em;
+       white-space: nowrap;
+       line-height: 1.5em;
+}
+
+.apihelp-parameters dt:after {
+       content: ':\A0';
+}
+
+.apihelp-parameters dd {
+       margin: 0 0 0.5em 10em;
+       line-height: 1.5em;
+}
+
+.apihelp-parameters dd p:first-child {
+       margin-top: 0;
+}
+
+.apihelp-parameters dd.info {
+       margin-left: 12em;
+       text-indent: -2em;
+}
+
+.apihelp-examples dt {
+       font-weight: normal;
+}
+
+.api-main-links {
+       text-align: center;
+}
+
+.api-main-links ul:before {
+       content: '[';
+}
+
+.api-main-links ul:after {
+       content: ']';
+}
diff --git a/resources/src/mediawiki.apipretty/apipretty.css b/resources/src/mediawiki.apipretty/apipretty.css
new file mode 100644 (file)
index 0000000..3e921f4
--- /dev/null
@@ -0,0 +1,13 @@
+/* stylelint-disable selector-class-pattern */
+
+.mw-special-ApiHelp h1.firstHeading {
+       display: none;
+}
+
+.api-pretty-header {
+       font-size: small;
+}
+
+.api-pretty-content {
+       white-space: pre-wrap;
+}
index a500226..7f4af5b 100644 (file)
                        if ( stats.enabled ) {
                                $.extend( stats, mw.loader.store.stats );
                                try {
-                                       raw = localStorage.getItem( mw.loader.store.getStoreKey() );
+                                       raw = localStorage.getItem( mw.loader.store.key );
                                        stats.totalSizeInBytes = byteLength( raw );
                                        stats.totalSize = humanSize( byteLength( raw ) );
                                } catch ( e ) {}
index b0355b0..ace92f0 100644 (file)
                                         * @return {Object} Module store contents.
                                         */
                                        toJSON: function () {
-                                               return { items: mw.loader.store.items, vary: mw.loader.store.getVary() };
+                                               return { items: mw.loader.store.items, vary: mw.loader.store.vary };
                                        },
 
                                        /**
-                                        * Get the localStorage key for the entire module store. The key references
+                                        * The localStorage key for the entire module store. The key references
                                         * $wgDBname to prevent clashes between wikis which share a common host.
                                         *
-                                        * @return {string} localStorage item key
+                                        * @property {string}
                                         */
-                                       getStoreKey: function () {
-                                               return $VARS.storeKey;
-                                       },
+                                       key: $VARS.storeKey,
 
                                        /**
-                                        * Get a key on which to vary the module cache.
+                                        * A string containing various factors on which to the module cache should vary.
                                         *
-                                        * @return {string} String of concatenated vary conditions.
+                                        * @property {string}
                                         */
-                                       getVary: function () {
-                                               return $VARS.storeVary;
-                                       },
+                                       vary: $VARS.storeVary,
 
                                        /**
                                         * Initialize the store.
 
                                                try {
                                                        // This a string we stored, or `null` if the key does not (yet) exist.
-                                                       raw = localStorage.getItem( this.getStoreKey() );
+                                                       raw = localStorage.getItem( this.key );
                                                        // If we get here, localStorage is available; mark enabled
                                                        this.enabled = true;
                                                        // If null, JSON.parse() will cast to string and re-parse, still null.
                                                        data = JSON.parse( raw );
-                                                       if ( data && typeof data.items === 'object' && data.vary === this.getVary() ) {
+                                                       if ( data && typeof data.items === 'object' && data.vary === this.vary ) {
                                                                this.items = data.items;
                                                                return;
                                                        }
                                                //    The store was enabled, and `items` starts fresh.
                                                //
                                                // 2. localStorage contained parseable data under our store key,
-                                               //    but it's not applicable to our current context (see getVary).
+                                               //    but it's not applicable to our current context (see #vary).
                                                //    The store was enabled, and `items` starts fresh.
                                                //
                                                // 3. JSON.parse threw (localStorage contained corrupt data).
                                        clear: function () {
                                                this.items = {};
                                                try {
-                                                       localStorage.removeItem( this.getStoreKey() );
+                                                       localStorage.removeItem( this.key );
                                                } catch ( e ) {}
                                        },
 
                                                                mw.loader.store.set( mw.loader.store.queue.shift() );
                                                        }
 
-                                                       key = mw.loader.store.getStoreKey();
+                                                       key = mw.loader.store.key;
                                                        try {
                                                                // Replacing the content of the module store might fail if the new
                                                                // contents would exceed the browser's localStorage size limit. To
index 8faaeda..43fbee8 100644 (file)
@@ -12,8 +12,7 @@ class SearchUpdateTest extends MediaWikiTestCase {
 
        protected function setUp() {
                parent::setUp();
-               $this->setMwGlobals( 'wgSearchType', 'MockSearch' );
-               $this->su = new SearchUpdate( 0, "" );
+               $this->su = new SearchUpdate( 0, Title::newMainPage() );
        }
 
        public function updateText( $text ) {
index 4f9664f..acd8a19 100644 (file)
@@ -36,14 +36,6 @@ class ResourcesTest extends MediaWikiTestCase {
                );
        }
 
-       public function testVersionHash() {
-               $data = self::getAllModules();
-               foreach ( $data['modules'] as $moduleName => $module ) {
-                       $version = $module->getVersionHash( $data['context'] );
-                       $this->assertEquals( 7, strlen( $version ), "$moduleName must use ResourceLoader::makeHash" );
-               }
-       }
-
        /**
         * Verify that all modules specified as dependencies of other modules actually
         * exist and are not illegal.
index 214c25a..662224c 100644 (file)
@@ -52,23 +52,10 @@ exports.config = {
 
        // ==================
        // Test Files
-       // FIXME: The non-core patterns to be removed once T199116 is fixed.
        // ==================
        specs: [
                relPath( './tests/selenium/wdio-mediawiki/specs/*.js' ),
-               relPath( './tests/selenium/specs/**/*.js' ),
-               relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
-               relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' ),
-               relPath( './extensions/Wikibase/repo/tests/selenium/specs/**/*.js' ),
-               relPath( './skins/*/tests/selenium/specs/**/*.js' )
-       ],
-       // Patterns to exclude
-       exclude: [
-               relPath( './extensions/CirrusSearch/tests/selenium/specs/**/*.js' ),
-               // Disabled because these tests modify LocalSettings.php, see T199116 for the long-term fix.
-               relPath( './extensions/FileImporter/tests/selenium/specs/**/*.js' ),
-               // Disabled per T222517
-               relPath( './skins/MinervaNeue/tests/selenium/specs/**/*.js' )
+               relPath( './tests/selenium/specs/**/*.js' )
        ],
 
        // ============