Merge "Make rebuildFileCache cover ?action=history"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 20 Sep 2016 08:10:56 +0000 (08:10 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 20 Sep 2016 08:10:56 +0000 (08:10 +0000)
144 files changed:
autoload.php
includes/DefaultSettings.php
includes/Defines.php
includes/Html.php
includes/ServiceWiring.php
includes/api/ApiMain.php
includes/api/i18n/diq.json
includes/changes/RecentChange.php
includes/db/DatabaseMssql.php
includes/db/DatabaseOracle.php
includes/db/DatabasePostgres.php [deleted file]
includes/db/DatabaseSqlite.php [deleted file]
includes/db/loadbalancer/LBFactoryMW.php
includes/db/loadbalancer/LBFactorySingle.php [deleted file]
includes/debug/logger/LegacyLogger.php
includes/deferred/LinksDeletionUpdate.php
includes/exception/MWExceptionRenderer.php
includes/filebackend/FileBackend.php
includes/filebackend/lockmanager/DBLockManager.php
includes/filebackend/lockmanager/FSLockManager.php [deleted file]
includes/filebackend/lockmanager/LockManager.php [deleted file]
includes/filebackend/lockmanager/MemcLockManager.php
includes/filebackend/lockmanager/MySqlLockManager.php
includes/filebackend/lockmanager/PostgreSqlLockManager.php
includes/filebackend/lockmanager/QuorumLockManager.php [deleted file]
includes/filebackend/lockmanager/RedisLockManager.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignDBViaLBRepo.php
includes/filerepo/RepoGroup.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/ForeignAPIFile.php
includes/filerepo/file/LocalFile.php
includes/gallery/TraditionalImageGallery.php
includes/installer/DatabaseInstaller.php
includes/installer/SqliteInstaller.php
includes/installer/i18n/it.json
includes/libs/StatusValue.php
includes/libs/lockmanager/FSLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/LockManager.php [new file with mode: 0644]
includes/libs/lockmanager/NullLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/QuorumLockManager.php [new file with mode: 0644]
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseBase.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabasePostgres.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseSqlite.php [new file with mode: 0644]
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
includes/libs/rdbms/database/utils/SavepointPostgres.php [new file with mode: 0644]
includes/libs/rdbms/defines.php
includes/libs/rdbms/field/PostgresField.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/libs/rdbms/lbfactory/LBFactorySingle.php [new file with mode: 0644]
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php [new file with mode: 0644]
includes/objectcache/ObjectCache.php
includes/page/WikiPage.php
index.php
languages/Language.php
languages/i18n/an.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/cdo.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gsw.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/ia.json
languages/i18n/ilo.json
languages/i18n/it.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/kiu.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/lij.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sa.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/ta.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/rebuildrecentchanges.php
maintenance/sql.php
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/src/mediawiki.less/mediawiki.ui/mixins.less
resources/src/mediawiki.less/mediawiki.ui/variables.less
resources/src/mediawiki.special/mediawiki.special.userlogin.login.css
resources/src/mediawiki.widgets.datetime/mediawiki.widgets.datetime.definitions.less
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.less
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less
resources/src/mediawiki/mediawiki.Upload.BookletLayout.css
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/db/DatabaseTestHelper.php

index eb43d2f..198e477 100644 (file)
@@ -327,8 +327,8 @@ $wgAutoloadLocalClasses = [
        'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
        'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
        'DatabaseOracle' => __DIR__ . '/includes/db/DatabaseOracle.php',
-       'DatabasePostgres' => __DIR__ . '/includes/db/DatabasePostgres.php',
-       'DatabaseSqlite' => __DIR__ . '/includes/db/DatabaseSqlite.php',
+       'DatabasePostgres' => __DIR__ . '/includes/libs/rdbms/database/DatabasePostgres.php',
+       'DatabaseSqlite' => __DIR__ . '/includes/libs/rdbms/database/DatabaseSqlite.php',
        'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
        'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
        'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
@@ -438,7 +438,7 @@ $wgAutoloadLocalClasses = [
        'FSFileBackendFileList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileOpHandle' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
-       'FSLockManager' => __DIR__ . '/includes/filebackend/lockmanager/FSLockManager.php',
+       'FSLockManager' => __DIR__ . '/includes/libs/lockmanager/FSLockManager.php',
        'FSRepo' => __DIR__ . '/includes/filerepo/FSRepo.php',
        'FakeAuthTemplate' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
        'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
@@ -661,7 +661,7 @@ $wgAutoloadLocalClasses = [
        'LBFactoryMW' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMW.php',
        'LBFactoryMulti' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactoryMulti.php',
        'LBFactorySimple' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySimple.php',
-       'LBFactorySingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
+       'LBFactorySingle' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySingle.php',
        'LCStore' => __DIR__ . '/includes/cache/localisation/LCStore.php',
        'LCStoreCDB' => __DIR__ . '/includes/cache/localisation/LCStoreCDB.php',
        'LCStoreDB' => __DIR__ . '/includes/cache/localisation/LCStoreDB.php',
@@ -733,7 +733,7 @@ $wgAutoloadLocalClasses = [
        'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
        'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
        'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
-       'LoadBalancerSingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
+       'LoadBalancerSingle' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php',
        'LoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitor.php',
        'LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php',
        'LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php',
@@ -747,7 +747,7 @@ $wgAutoloadLocalClasses = [
        'LocalSettingsGenerator' => __DIR__ . '/includes/installer/LocalSettingsGenerator.php',
        'LocalisationCache' => __DIR__ . '/includes/cache/localisation/LocalisationCache.php',
        'LocalisationCacheBulkLoad' => __DIR__ . '/includes/cache/localisation/LocalisationCacheBulkLoad.php',
-       'LockManager' => __DIR__ . '/includes/filebackend/lockmanager/LockManager.php',
+       'LockManager' => __DIR__ . '/includes/libs/lockmanager/LockManager.php',
        'LockManagerGroup' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroup.php',
        'LogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
        'LogEntryBase' => __DIR__ . '/includes/logging/LogEntry.php',
@@ -979,7 +979,7 @@ $wgAutoloadLocalClasses = [
        'NullFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'NullIndexField' => __DIR__ . '/includes/search/NullIndexField.php',
        'NullJob' => __DIR__ . '/includes/jobqueue/jobs/NullJob.php',
-       'NullLockManager' => __DIR__ . '/includes/filebackend/lockmanager/LockManager.php',
+       'NullLockManager' => __DIR__ . '/includes/libs/lockmanager/NullLockManager.php',
        'NullRepo' => __DIR__ . '/includes/filerepo/NullRepo.php',
        'NullStatsdDataFactory' => __DIR__ . '/includes/libs/stats/NullStatsdDataFactory.php',
        'NumericUppercaseCollation' => __DIR__ . '/includes/collation/NumericUppercaseCollation.php',
@@ -1071,7 +1071,7 @@ $wgAutoloadLocalClasses = [
        'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
        'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
        'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
-       'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
+       'PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
        'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
        'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
        'Preferences' => __DIR__ . '/includes/Preferences.php',
@@ -1110,7 +1110,7 @@ $wgAutoloadLocalClasses = [
        'PurgeParserCache' => __DIR__ . '/maintenance/purgeParserCache.php',
        'QueryPage' => __DIR__ . '/includes/specialpage/QueryPage.php',
        'QuickTemplate' => __DIR__ . '/includes/skins/QuickTemplate.php',
-       'QuorumLockManager' => __DIR__ . '/includes/filebackend/lockmanager/QuorumLockManager.php',
+       'QuorumLockManager' => __DIR__ . '/includes/libs/lockmanager/QuorumLockManager.php',
        'RCCacheEntry' => __DIR__ . '/includes/changes/RCCacheEntry.php',
        'RCCacheEntryFactory' => __DIR__ . '/includes/changes/RCCacheEntryFactory.php',
        'RCDatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
@@ -1226,7 +1226,7 @@ $wgAutoloadLocalClasses = [
        'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
        'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
        'Sanitizer' => __DIR__ . '/includes/Sanitizer.php',
-       'SavepointPostgres' => __DIR__ . '/includes/db/DatabasePostgres.php',
+       'SavepointPostgres' => __DIR__ . '/includes/libs/rdbms/database/utils/SavepointPostgres.php',
        'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php',
        'ScopedLock' => __DIR__ . '/includes/filebackend/lockmanager/ScopedLock.php',
        'SearchApi' => __DIR__ . '/includes/api/SearchApi.php',
index 135c3e5..f0e9e83 100644 (file)
@@ -2085,7 +2085,7 @@ $wgExternalStores = [];
  * Create a cluster named 'cluster1' containing three servers:
  * @code
  * $wgExternalServers = [
- *     'cluster1' => [ 'srv28', 'srv29', 'srv30' ]
+ *     'cluster1' => <array in the same format as $wgDBservers>
  * ];
  * @endcode
  *
index 077f39a..529dfb3 100644 (file)
 # Obsolete aliases
 define( 'DB_SLAVE', -1 );
 
+/**@{
+ * Obsolete IDatabase::makeList() constants
+ * These are also available as Database class constants
+ */
+define( 'LIST_COMMA', IDatabase::LIST_COMMA );
+define( 'LIST_AND', IDatabase::LIST_AND );
+define( 'LIST_SET', IDatabase::LIST_SET );
+define( 'LIST_NAMES', IDatabase::LIST_NAMES );
+define( 'LIST_OR', IDatabase::LIST_OR );
+/**@}*/
+
 /**@{
  * Virtual namespaces; don't appear in the page database
  */
index 8c01448..2ef891d 100644 (file)
  * @since 1.16
  */
 class Html {
-       // List of void elements from HTML5, section 8.1.2 as of 2011-08-12
+       // List of void elements from HTML5, section 8.1.2 as of 2016-09-19
        private static $voidElements = [
                'area',
                'base',
                'br',
                'col',
-               'command',
                'embed',
                'hr',
                'img',
@@ -339,7 +338,6 @@ class Html {
                                'height' => '150',
                                'width' => '300',
                        ],
-                       'command' => [ 'type' => 'command' ],
                        'form' => [
                                'action' => 'GET',
                                'autocomplete' => 'on',
index 7a34b3a..8c7d802 100644 (file)
@@ -45,51 +45,13 @@ return [
        'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
                $mainConfig = $services->getMainConfig();
 
-               $lbConf = $mainConfig->get( 'LBFactoryConf' );
-               $lbConf += [
-                       'localDomain' => new DatabaseDomain(
-                               $mainConfig->get( 'DBname' ), null, $mainConfig->get( 'DBprefix' ) ),
-                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
-                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
-               ];
-
+               $lbConf = LBFactoryMW::applyDefaultConfig(
+                       $mainConfig->get( 'LBFactoryConf' ),
+                       $mainConfig
+               );
                $class = LBFactoryMW::getLBFactoryClass( $lbConf );
-               if ( $class === 'LBFactorySimple' ) {
-                       if ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
-                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
-                                       $lbConf['servers'][$i] = $server + [
-                                               'schema' => $mainConfig->get( 'DBmwschema' ),
-                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                               'flags' => DBO_DEFAULT,
-                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
-                                       ];
-                               }
-                       } else {
-                               $flags = DBO_DEFAULT;
-                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
-                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
-                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
-                               $lbConf['servers'] = [
-                                       [
-                                               'host' => $mainConfig->get( 'DBserver' ),
-                                               'user' => $mainConfig->get( 'DBuser' ),
-                                               'password' => $mainConfig->get( 'DBpassword' ),
-                                               'dbname' => $mainConfig->get( 'DBname' ),
-                                               'schema' => $mainConfig->get( 'DBmwschema' ),
-                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                               'type' => $mainConfig->get( 'DBtype' ),
-                                               'load' => 1,
-                                               'flags' => $flags,
-                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
-                                       ]
-                               ];
-                       }
-                       $lbConf['externalServers'] = $mainConfig->get( 'ExternalServers' );
-               }
 
-               return new $class( LBFactoryMW::applyDefaultConfig( $lbConf ) );
+               return new $class( $lbConf );
        },
 
        'DBLoadBalancer' => function( MediaWikiServices $services ) {
index 1f3c76a..ae3f3f2 100644 (file)
@@ -1299,7 +1299,7 @@ class ApiMain extends ApiBase {
                }
 
                if ( $module->isWriteMode()
-                       && in_array( 'bot', $this->getUser()->getGroups() )
+                       && $this->getUser()->isBot()
                        && wfGetLB()->getServerCount() > 1
                ) {
                        $this->checkBotReadOnly();
index 0b86f10..31bb5fa 100644 (file)
@@ -35,7 +35,7 @@
        "apihelp-feedcontributions-param-feedformat": "Formata warikerdışi",
        "apihelp-feedcontributions-param-hideminor": "Vuryayışanê werdiyan bınımne",
        "apihelp-feedcontributions-param-showsizediff": "Goreyê ebati ferqê versiyoni bıasne.",
-       "apihelp-feedrecentchanges-param-hideminor": "Vurnayışanê qıckekan bınımne.",
+       "apihelp-feedrecentchanges-param-hideminor": "Vurriyayışanê werdiyan bınımne.",
        "apihelp-feedrecentchanges-param-hidebots": "Vurnayışanê botan bınımne.",
        "apihelp-feedrecentchanges-param-hideanons": "Vurnayışanê karberanê anoniman bınımne.",
        "apihelp-feedrecentchanges-param-hideliu": "Vurnayışanê karberanê qeydınan bınımne.",
index 590fd37..794865e 100644 (file)
@@ -297,7 +297,8 @@ class RecentChange {
                }
 
                # If our database is strict about IP addresses, use NULL instead of an empty string
-               if ( $dbw->strictIPs() && $this->mAttribs['rc_ip'] == '' ) {
+               $strictIPs = in_array( $dbw->getType(), [ 'oracle', 'postgres' ] ); // legacy
+               if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
                        unset( $this->mAttribs['rc_ip'] );
                }
 
@@ -312,7 +313,7 @@ class RecentChange {
                $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' );
 
                # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
-               if ( $dbw->cascadingDeletes() && $this->mAttribs['rc_cur_id'] == 0 ) {
+               if ( $this->mAttribs['rc_cur_id'] == 0 ) {
                        unset( $this->mAttribs['rc_cur_id'] );
                }
 
index 1c58a9a..00fb800 100644 (file)
@@ -42,22 +42,6 @@ class DatabaseMssql extends DatabaseBase {
 
        protected $mPort;
 
-       public function cascadingDeletes() {
-               return true;
-       }
-
-       public function cleanupTriggers() {
-               return false;
-       }
-
-       public function strictIPs() {
-               return false;
-       }
-
-       public function realTimestamps() {
-               return false;
-       }
-
        public function implicitGroupby() {
                return false;
        }
@@ -66,10 +50,6 @@ class DatabaseMssql extends DatabaseBase {
                return false;
        }
 
-       public function functionalIndexes() {
-               return true;
-       }
-
        public function unionSupportsOrderAndLimit() {
                return false;
        }
@@ -1265,13 +1245,6 @@ class DatabaseMssql extends DatabaseBase {
                return $sql;
        }
 
-       /**
-        * @return string
-        */
-       public function getSearchEngine() {
-               return "SearchMssql";
-       }
-
        /**
         * Returns an associative array for fields that are of type varbinary, binary, or image
         * $table can be either a raw table name or passed through tableName() first
index ec3336a..561dadb 100644 (file)
@@ -176,22 +176,6 @@ class DatabaseOracle extends DatabaseBase {
                return 'oracle';
        }
 
-       function cascadingDeletes() {
-               return true;
-       }
-
-       function cleanupTriggers() {
-               return true;
-       }
-
-       function strictIPs() {
-               return true;
-       }
-
-       function realTimestamps() {
-               return true;
-       }
-
        function implicitGroupby() {
                return false;
        }
@@ -200,10 +184,6 @@ class DatabaseOracle extends DatabaseBase {
                return false;
        }
 
-       function searchableIPs() {
-               return true;
-       }
-
        /**
         * Usually aborts on failure
         * @param string $server
@@ -1517,10 +1497,6 @@ class DatabaseOracle extends DatabaseBase {
                return 'CAST ( ' . $field . ' AS VARCHAR2 )';
        }
 
-       public function getSearchEngine() {
-               return 'SearchOracle';
-       }
-
        public function getInfinity() {
                return '31-12-2030 12:00:00.000000';
        }
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
deleted file mode 100644 (file)
index cb95304..0000000
+++ /dev/null
@@ -1,1638 +0,0 @@
-<?php
-/**
- * This is the Postgres database abstraction layer.
- *
- * 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 Database
- */
-
-class PostgresField implements Field {
-       private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
-               $has_default, $default;
-
-       /**
-        * @param IDatabase $db
-        * @param string $table
-        * @param string $field
-        * @return null|PostgresField
-        */
-       static function fromText( $db, $table, $field ) {
-               $q = <<<SQL
-SELECT
- attnotnull, attlen, conname AS conname,
- atthasdef,
- adsrc,
- COALESCE(condeferred, 'f') AS deferred,
- COALESCE(condeferrable, 'f') AS deferrable,
- CASE WHEN typname = 'int2' THEN 'smallint'
-  WHEN typname = 'int4' THEN 'integer'
-  WHEN typname = 'int8' THEN 'bigint'
-  WHEN typname = 'bpchar' THEN 'char'
- ELSE typname END AS typname
-FROM pg_class c
-JOIN pg_namespace n ON (n.oid = c.relnamespace)
-JOIN pg_attribute a ON (a.attrelid = c.oid)
-JOIN pg_type t ON (t.oid = a.atttypid)
-LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
-LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
-WHERE relkind = 'r'
-AND nspname=%s
-AND relname=%s
-AND attname=%s;
-SQL;
-
-               $table = $db->tableName( $table, 'raw' );
-               $res = $db->query(
-                       sprintf( $q,
-                               $db->addQuotes( $db->getCoreSchema() ),
-                               $db->addQuotes( $table ),
-                               $db->addQuotes( $field )
-                       )
-               );
-               $row = $db->fetchObject( $res );
-               if ( !$row ) {
-                       return null;
-               }
-               $n = new PostgresField;
-               $n->type = $row->typname;
-               $n->nullable = ( $row->attnotnull == 'f' );
-               $n->name = $field;
-               $n->tablename = $table;
-               $n->max_length = $row->attlen;
-               $n->deferrable = ( $row->deferrable == 't' );
-               $n->deferred = ( $row->deferred == 't' );
-               $n->conname = $row->conname;
-               $n->has_default = ( $row->atthasdef === 't' );
-               $n->default = $row->adsrc;
-
-               return $n;
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tablename;
-       }
-
-       function type() {
-               return $this->type;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function is_deferrable() {
-               return $this->deferrable;
-       }
-
-       function is_deferred() {
-               return $this->deferred;
-       }
-
-       function conname() {
-               return $this->conname;
-       }
-
-       /**
-        * @since 1.19
-        * @return bool|mixed
-        */
-       function defaultValue() {
-               if ( $this->has_default ) {
-                       return $this->default;
-               } else {
-                       return false;
-               }
-       }
-}
-
-/**
- * Manage savepoints within a transaction
- * @ingroup Database
- * @since 1.19
- */
-class SavepointPostgres {
-       /** @var DatabasePostgres Establish a savepoint within a transaction */
-       protected $dbw;
-       protected $id;
-       protected $didbegin;
-
-       /**
-        * @param IDatabase $dbw
-        * @param int $id
-        */
-       public function __construct( $dbw, $id ) {
-               $this->dbw = $dbw;
-               $this->id = $id;
-               $this->didbegin = false;
-               /* If we are not in a transaction, we need to be for savepoint trickery */
-               if ( !$dbw->trxLevel() ) {
-                       $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
-                       $this->didbegin = true;
-               }
-       }
-
-       public function __destruct() {
-               if ( $this->didbegin ) {
-                       $this->dbw->rollback();
-                       $this->didbegin = false;
-               }
-       }
-
-       public function commit() {
-               if ( $this->didbegin ) {
-                       $this->dbw->commit();
-                       $this->didbegin = false;
-               }
-       }
-
-       protected function query( $keyword, $msg_ok, $msg_failed ) {
-               if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
-               } else {
-                       wfDebug( sprintf( $msg_failed, $this->id ) );
-               }
-       }
-
-       public function savepoint() {
-               $this->query( "SAVEPOINT",
-                       "Transaction state: savepoint \"%s\" established.\n",
-                       "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function release() {
-               $this->query( "RELEASE",
-                       "Transaction state: savepoint \"%s\" released.\n",
-                       "Transaction state: release of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function rollback() {
-               $this->query( "ROLLBACK TO",
-                       "Transaction state: savepoint \"%s\" rolled back.\n",
-                       "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function __toString() {
-               return (string)$this->id;
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DatabasePostgres extends DatabaseBase {
-       /** @var resource */
-       protected $mLastResult = null;
-
-       /** @var int The number of rows affected as an integer */
-       protected $mAffectedRows = null;
-
-       /** @var int */
-       private $mInsertId = null;
-
-       /** @var float|string */
-       private $numericVersion = null;
-
-       /** @var string Connect string to open a PostgreSQL connection */
-       private $connectString;
-
-       /** @var string */
-       private $mCoreSchema;
-
-       function getType() {
-               return 'postgres';
-       }
-
-       function cascadingDeletes() {
-               return true;
-       }
-
-       function cleanupTriggers() {
-               return true;
-       }
-
-       function strictIPs() {
-               return true;
-       }
-
-       function realTimestamps() {
-               return true;
-       }
-
-       function implicitGroupby() {
-               return false;
-       }
-
-       function implicitOrderby() {
-               return false;
-       }
-
-       function searchableIPs() {
-               return true;
-       }
-
-       function functionalIndexes() {
-               return true;
-       }
-
-       function hasConstraint( $name ) {
-               $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
-                       "WHERE c.connamespace = n.oid AND conname = '" .
-                       pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
-                       pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
-               $res = $this->doQuery( $sql );
-
-               return $this->numRows( $res );
-       }
-
-       /**
-        * Usually aborts on failure
-        * @param string $server
-        * @param string $user
-        * @param string $password
-        * @param string $dbName
-        * @throws DBConnectionError|Exception
-        * @return resource|bool|null
-        */
-       function open( $server, $user, $password, $dbName ) {
-               # Test for Postgres support, to avoid suppressed fatal error
-               if ( !function_exists( 'pg_connect' ) ) {
-                       throw new DBConnectionError(
-                               $this,
-                               "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
-                               "option? (Note: if you recently installed PHP, you may need to restart your\n" .
-                               "webserver and database)\n"
-                       );
-               }
-
-               global $wgDBport;
-
-               if ( !strlen( $user ) ) { # e.g. the class is being loaded
-                       return null;
-               }
-
-               $this->mServer = $server;
-               $port = $wgDBport;
-               $this->mUser = $user;
-               $this->mPassword = $password;
-               $this->mDBname = $dbName;
-
-               $connectVars = [
-                       'dbname' => $dbName,
-                       'user' => $user,
-                       'password' => $password
-               ];
-               if ( $server != false && $server != '' ) {
-                       $connectVars['host'] = $server;
-               }
-               if ( $port != false && $port != '' ) {
-                       $connectVars['port'] = $port;
-               }
-               if ( $this->mFlags & DBO_SSL ) {
-                       $connectVars['sslmode'] = 1;
-               }
-
-               $this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
-               $this->close();
-               $this->installErrorHandler();
-
-               try {
-                       $this->mConn = pg_connect( $this->connectString );
-               } catch ( Exception $ex ) {
-                       $this->restoreErrorHandler();
-                       throw $ex;
-               }
-
-               $phpError = $this->restoreErrorHandler();
-
-               if ( !$this->mConn ) {
-                       wfDebug( "DB connection error\n" );
-                       wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " .
-                               substr( $password, 0, 3 ) . "...\n" );
-                       wfDebug( $this->lastError() . "\n" );
-                       throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
-               }
-
-               $this->mOpened = true;
-
-               global $wgCommandLineMode;
-               # If called from the command-line (e.g. importDump), only show errors
-               if ( $wgCommandLineMode ) {
-                       $this->doQuery( "SET client_min_messages = 'ERROR'" );
-               }
-
-               $this->query( "SET client_encoding='UTF8'", __METHOD__ );
-               $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
-               $this->query( "SET timezone = 'GMT'", __METHOD__ );
-               $this->query( "SET standard_conforming_strings = on", __METHOD__ );
-               if ( $this->getServerVersion() >= 9.0 ) {
-                       $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
-               }
-
-               global $wgDBmwschema;
-               $this->determineCoreSchema( $wgDBmwschema );
-
-               return $this->mConn;
-       }
-
-       /**
-        * Postgres doesn't support selectDB in the same way MySQL does. So if the
-        * DB name doesn't match the open connection, open a new one
-        * @param string $db
-        * @return bool
-        */
-       function selectDB( $db ) {
-               if ( $this->mDBname !== $db ) {
-                       return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
-               } else {
-                       return true;
-               }
-       }
-
-       function makeConnectionString( $vars ) {
-               $s = '';
-               foreach ( $vars as $name => $value ) {
-                       $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
-               }
-
-               return $s;
-       }
-
-       /**
-        * Closes a database connection, if it is open
-        * Returns success, true if already closed
-        * @return bool
-        */
-       protected function closeConnection() {
-               return pg_close( $this->mConn );
-       }
-
-       public function doQuery( $sql ) {
-               $sql = mb_convert_encoding( $sql, 'UTF-8' );
-               // Clear previously left over PQresult
-               while ( $res = pg_get_result( $this->mConn ) ) {
-                       pg_free_result( $res );
-               }
-               if ( pg_send_query( $this->mConn, $sql ) === false ) {
-                       throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
-               }
-               $this->mLastResult = pg_get_result( $this->mConn );
-               $this->mAffectedRows = null;
-               if ( pg_result_error( $this->mLastResult ) ) {
-                       return false;
-               }
-
-               return $this->mLastResult;
-       }
-
-       protected function dumpError() {
-               $diags = [
-                       PGSQL_DIAG_SEVERITY,
-                       PGSQL_DIAG_SQLSTATE,
-                       PGSQL_DIAG_MESSAGE_PRIMARY,
-                       PGSQL_DIAG_MESSAGE_DETAIL,
-                       PGSQL_DIAG_MESSAGE_HINT,
-                       PGSQL_DIAG_STATEMENT_POSITION,
-                       PGSQL_DIAG_INTERNAL_POSITION,
-                       PGSQL_DIAG_INTERNAL_QUERY,
-                       PGSQL_DIAG_CONTEXT,
-                       PGSQL_DIAG_SOURCE_FILE,
-                       PGSQL_DIAG_SOURCE_LINE,
-                       PGSQL_DIAG_SOURCE_FUNCTION
-               ];
-               foreach ( $diags as $d ) {
-                       wfDebug( sprintf( "PgSQL ERROR(%d): %s\n",
-                               $d, pg_result_error_field( $this->mLastResult, $d ) ) );
-               }
-       }
-
-       function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
-               if ( $tempIgnore ) {
-                       /* Check for constraint violation */
-                       if ( $errno === '23505' ) {
-                               parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
-
-                               return;
-                       }
-               }
-               /* Transaction stays in the ERROR state until rolled back */
-               if ( $this->mTrxLevel ) {
-                       $ignore = $this->ignoreErrors( true );
-                       $this->rollback( __METHOD__ );
-                       $this->ignoreErrors( $ignore );
-               }
-               parent::reportQueryError( $error, $errno, $sql, $fname, false );
-       }
-
-       function queryIgnore( $sql, $fname = __METHOD__ ) {
-               return $this->query( $sql, $fname, true );
-       }
-
-       /**
-        * @param stdClass|ResultWrapper $res
-        * @throws DBUnexpectedError
-        */
-       function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $ok = pg_free_result( $res );
-               MediaWiki\restoreWarnings();
-               if ( !$ok ) {
-                       throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
-               }
-       }
-
-       /**
-        * @param ResultWrapper|stdClass $res
-        * @return stdClass
-        * @throws DBUnexpectedError
-        */
-       function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = pg_fetch_object( $res );
-               MediaWiki\restoreWarnings();
-               # @todo FIXME: HACK HACK HACK HACK debug
-
-               # @todo hashar: not sure if the following test really trigger if the object
-               #          fetching failed.
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $row;
-       }
-
-       function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = pg_fetch_array( $res );
-               MediaWiki\restoreWarnings();
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $row;
-       }
-
-       function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $n = pg_num_rows( $res );
-               MediaWiki\restoreWarnings();
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $n;
-       }
-
-       function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_num_fields( $res );
-       }
-
-       function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_name( $res, $n );
-       }
-
-       /**
-        * Return the result of the last call to nextSequenceValue();
-        * This must be called after nextSequenceValue().
-        *
-        * @return int|null
-        */
-       function insertId() {
-               return $this->mInsertId;
-       }
-
-       /**
-        * @param mixed $res
-        * @param int $row
-        * @return bool
-        */
-       function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_result_seek( $res, $row );
-       }
-
-       function lastError() {
-               if ( $this->mConn ) {
-                       if ( $this->mLastResult ) {
-                               return pg_result_error( $this->mLastResult );
-                       } else {
-                               return pg_last_error();
-                       }
-               } else {
-                       return 'No database connection';
-               }
-       }
-
-       function lastErrno() {
-               if ( $this->mLastResult ) {
-                       return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
-               } else {
-                       return false;
-               }
-       }
-
-       function affectedRows() {
-               if ( !is_null( $this->mAffectedRows ) ) {
-                       // Forced result for simulated queries
-                       return $this->mAffectedRows;
-               }
-               if ( empty( $this->mLastResult ) ) {
-                       return 0;
-               }
-
-               return pg_affected_rows( $this->mLastResult );
-       }
-
-       /**
-        * Estimate rows in dataset
-        * Returns estimated count, based on EXPLAIN output
-        * This is not necessarily an accurate estimate, so use sparingly
-        * Returns -1 if count cannot be found
-        * Takes same arguments as Database::select()
-        *
-        * @param string $table
-        * @param string $vars
-        * @param string $conds
-        * @param string $fname
-        * @param array $options
-        * @return int
-        */
-       function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
-       ) {
-               $options['EXPLAIN'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
-               $rows = -1;
-               if ( $res ) {
-                       $row = $this->fetchRow( $res );
-                       $count = [];
-                       if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
-                               $rows = (int)$count[1];
-                       }
-               }
-
-               return $rows;
-       }
-
-       /**
-        * Returns information about an index
-        * If errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-               foreach ( $res as $row ) {
-                       if ( $row->indexname == $this->indexName( $index ) ) {
-                               return $row;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Returns is of attributes used in index
-        *
-        * @since 1.19
-        * @param string $index
-        * @param bool|string $schema
-        * @return array
-        */
-       function indexAttributes( $index, $schema = false ) {
-               if ( $schema === false ) {
-                       $schema = $this->getCoreSchema();
-               }
-               /*
-                * A subquery would be not needed if we didn't care about the order
-                * of attributes, but we do
-                */
-               $sql = <<<__INDEXATTR__
-
-                       SELECT opcname,
-                               attname,
-                               i.indoption[s.g] as option,
-                               pg_am.amname
-                       FROM
-                               (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
-                                       FROM
-                                               pg_index isub
-                                       JOIN pg_class cis
-                                               ON cis.oid=isub.indexrelid
-                                       JOIN pg_namespace ns
-                                               ON cis.relnamespace = ns.oid
-                                       WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
-                               pg_attribute,
-                               pg_opclass opcls,
-                               pg_am,
-                               pg_class ci
-                               JOIN pg_index i
-                                       ON ci.oid=i.indexrelid
-                               JOIN pg_class ct
-                                       ON ct.oid = i.indrelid
-                               JOIN pg_namespace n
-                                       ON ci.relnamespace = n.oid
-                               WHERE
-                                       ci.relname='$index' AND n.nspname='$schema'
-                                       AND     attrelid = ct.oid
-                                       AND     i.indkey[s.g] = attnum
-                                       AND     i.indclass[s.g] = opcls.oid
-                                       AND     pg_am.oid = opcls.opcmethod
-__INDEXATTR__;
-               $res = $this->query( $sql, __METHOD__ );
-               $a = [];
-               if ( $res ) {
-                       foreach ( $res as $row ) {
-                               $a[] = [
-                                       $row->attname,
-                                       $row->opcname,
-                                       $row->amname,
-                                       $row->option ];
-                       }
-               } else {
-                       return null;
-               }
-
-               return $a;
-       }
-
-       function indexUnique( $table, $index, $fname = __METHOD__ ) {
-               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
-                       " AND indexdef LIKE 'CREATE UNIQUE%(" .
-                       $this->strencode( $this->indexName( $index ) ) .
-                       ")'";
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-
-               return $res->numRows() > 0;
-       }
-
-       /**
-        * Change the FOR UPDATE option as necessary based on the join conditions. Then pass
-        * to the parent function to get the actual SQL text.
-        *
-        * In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
-        * can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do
-        * so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly.
-        *
-        * MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL.
-        * @see DatabaseBase::selectSQLText
-        */
-       function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               if ( is_array( $options ) ) {
-                       $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
-                       if ( $forUpdateKey !== false && $join_conds ) {
-                               unset( $options[$forUpdateKey] );
-
-                               foreach ( $join_conds as $table_cond => $join_cond ) {
-                                       if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
-                                               $options['FOR UPDATE'][] = $table_cond;
-                                       }
-                               }
-                       }
-
-                       if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
-                               unset( $options['ORDER BY'] );
-                       }
-               }
-
-               return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-       }
-
-       /**
-        * INSERT wrapper, inserts an array into a table
-        *
-        * $args may be a single associative array, or an array of these with numeric keys,
-        * for multi-row insert (Postgres version 8.2 and above only).
-        *
-        * @param string $table Name of the table to insert to.
-        * @param array $args Items to insert into the table.
-        * @param string $fname Name of the function, for profiling
-        * @param array|string $options String or array. Valid options: IGNORE
-        * @return bool Success of insert operation. IGNORE always returns true.
-        */
-       function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
-               if ( !count( $args ) ) {
-                       return true;
-               }
-
-               $table = $this->tableName( $table );
-               if ( !isset( $this->numericVersion ) ) {
-                       $this->getServerVersion();
-               }
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
-                       $multi = true;
-                       $keys = array_keys( $args[0] );
-               } else {
-                       $multi = false;
-                       $keys = array_keys( $args );
-               }
-
-               // If IGNORE is set, we use savepoints to emulate mysql's behavior
-               $savepoint = null;
-               if ( in_array( 'IGNORE', $options ) ) {
-                       $savepoint = new SavepointPostgres( $this, 'mw' );
-                       $olde = error_reporting( 0 );
-                       // For future use, we may want to track the number of actual inserts
-                       // Right now, insert (all writes) simply return true/false
-                       $numrowsinserted = 0;
-               }
-
-               $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
-
-               if ( $multi ) {
-                       if ( $this->numericVersion >= 8.2 && !$savepoint ) {
-                               $first = true;
-                               foreach ( $args as $row ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                       } else {
-                                               $sql .= ',';
-                                       }
-                                       $sql .= '(' . $this->makeList( $row ) . ')';
-                               }
-                               $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       } else {
-                               $res = true;
-                               $origsql = $sql;
-                               foreach ( $args as $row ) {
-                                       $tempsql = $origsql;
-                                       $tempsql .= '(' . $this->makeList( $row ) . ')';
-
-                                       if ( $savepoint ) {
-                                               $savepoint->savepoint();
-                                       }
-
-                                       $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
-
-                                       if ( $savepoint ) {
-                                               $bar = pg_result_error( $this->mLastResult );
-                                               if ( $bar != false ) {
-                                                       $savepoint->rollback();
-                                               } else {
-                                                       $savepoint->release();
-                                                       $numrowsinserted++;
-                                               }
-                                       }
-
-                                       // If any of them fail, we fail overall for this function call
-                                       // Note that this will be ignored if IGNORE is set
-                                       if ( !$tempres ) {
-                                               $res = false;
-                                       }
-                               }
-                       }
-               } else {
-                       // Not multi, just a lone insert
-                       if ( $savepoint ) {
-                               $savepoint->savepoint();
-                       }
-
-                       $sql .= '(' . $this->makeList( $args ) . ')';
-                       $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       if ( $savepoint ) {
-                               $bar = pg_result_error( $this->mLastResult );
-                               if ( $bar != false ) {
-                                       $savepoint->rollback();
-                               } else {
-                                       $savepoint->release();
-                                       $numrowsinserted++;
-                               }
-                       }
-               }
-               if ( $savepoint ) {
-                       error_reporting( $olde );
-                       $savepoint->commit();
-
-                       // Set the affected row count for the whole operation
-                       $this->mAffectedRows = $numrowsinserted;
-
-                       // IGNORE always returns true
-                       return true;
-               }
-
-               return $res;
-       }
-
-       /**
-        * INSERT SELECT wrapper
-        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
-        * Source items may be literals rather then field names, but strings should
-        * be quoted with Database::addQuotes()
-        * $conds may be "*" to copy the whole table
-        * srcTable may be an array of tables.
-        * @todo FIXME: Implement this a little better (seperate select/insert)?
-        *
-        * @param string $destTable
-        * @param array|string $srcTable
-        * @param array $varMap
-        * @param array $conds
-        * @param string $fname
-        * @param array $insertOptions
-        * @param array $selectOptions
-        * @return bool
-        */
-       function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = [] ) {
-               $destTable = $this->tableName( $destTable );
-
-               if ( !is_array( $insertOptions ) ) {
-                       $insertOptions = [ $insertOptions ];
-               }
-
-               /*
-                * If IGNORE is set, we use savepoints to emulate mysql's behavior
-                * Ignore LOW PRIORITY option, since it is MySQL-specific
-                */
-               $savepoint = null;
-               if ( in_array( 'IGNORE', $insertOptions ) ) {
-                       $savepoint = new SavepointPostgres( $this, 'mw' );
-                       $olde = error_reporting( 0 );
-                       $numrowsinserted = 0;
-                       $savepoint->savepoint();
-               }
-
-               if ( !is_array( $selectOptions ) ) {
-                       $selectOptions = [ $selectOptions ];
-               }
-               list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
-                       $this->makeSelectOptions( $selectOptions );
-               if ( is_array( $srcTable ) ) {
-                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
-               } else {
-                       $srcTable = $this->tableName( $srcTable );
-               }
-
-               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
-                       " SELECT $startOpts " . implode( ',', $varMap ) .
-                       " FROM $srcTable $useIndex $ignoreIndex ";
-
-               if ( $conds != '*' ) {
-                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
-               }
-
-               $sql .= " $tailOpts";
-
-               $res = (bool)$this->query( $sql, $fname, $savepoint );
-               if ( $savepoint ) {
-                       $bar = pg_result_error( $this->mLastResult );
-                       if ( $bar != false ) {
-                               $savepoint->rollback();
-                       } else {
-                               $savepoint->release();
-                               $numrowsinserted++;
-                       }
-                       error_reporting( $olde );
-                       $savepoint->commit();
-
-                       // Set the affected row count for the whole operation
-                       $this->mAffectedRows = $numrowsinserted;
-
-                       // IGNORE always returns true
-                       return true;
-               }
-
-               return $res;
-       }
-
-       function tableName( $name, $format = 'quoted' ) {
-               # Replace reserved words with better ones
-               switch ( $name ) {
-                       case 'user':
-                               return $this->realTableName( 'mwuser', $format );
-                       case 'text':
-                               return $this->realTableName( 'pagecontent', $format );
-                       default:
-                               return $this->realTableName( $name, $format );
-               }
-       }
-
-       /* Don't cheat on installer */
-       function realTableName( $name, $format = 'quoted' ) {
-               return parent::tableName( $name, $format );
-       }
-
-       /**
-        * Return the next in a sequence, save the value for retrieval via insertId()
-        *
-        * @param string $seqName
-        * @return int|null
-        */
-       function nextSequenceValue( $seqName ) {
-               $safeseq = str_replace( "'", "''", $seqName );
-               $res = $this->query( "SELECT nextval('$safeseq')" );
-               $row = $this->fetchRow( $res );
-               $this->mInsertId = $row[0];
-
-               return $this->mInsertId;
-       }
-
-       /**
-        * Return the current value of a sequence. Assumes it has been nextval'ed in this session.
-        *
-        * @param string $seqName
-        * @return int
-        */
-       function currentSequenceValue( $seqName ) {
-               $safeseq = str_replace( "'", "''", $seqName );
-               $res = $this->query( "SELECT currval('$safeseq')" );
-               $row = $this->fetchRow( $res );
-               $currval = $row[0];
-
-               return $currval;
-       }
-
-       # Returns the size of a text field, or -1 for "unlimited"
-       function textFieldSize( $table, $field ) {
-               $table = $this->tableName( $table );
-               $sql = "SELECT t.typname as ftype,a.atttypmod as size
-                       FROM pg_class c, pg_attribute a, pg_type t
-                       WHERE relname='$table' AND a.attrelid=c.oid AND
-                               a.atttypid=t.oid and a.attname='$field'";
-               $res = $this->query( $sql );
-               $row = $this->fetchObject( $res );
-               if ( $row->ftype == 'varchar' ) {
-                       $size = $row->size - 4;
-               } else {
-                       $size = $row->size;
-               }
-
-               return $size;
-       }
-
-       function limitResult( $sql, $limit, $offset = false ) {
-               return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
-       }
-
-       function wasDeadlock() {
-               return $this->lastErrno() == '40P01';
-       }
-
-       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
-               $newName = $this->addIdentifierQuotes( $newName );
-               $oldName = $this->addIdentifierQuotes( $oldName );
-
-               return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
-                       "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
-       }
-
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $eschema = $this->addQuotes( $this->getCoreSchema() );
-               $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
-               $endArray = [];
-
-               foreach ( $result as $table ) {
-                       $vars = get_object_vars( $table );
-                       $table = array_pop( $vars );
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
-                               $endArray[] = $table;
-                       }
-               }
-
-               return $endArray;
-       }
-
-       function timestamp( $ts = 0 ) {
-               return wfTimestamp( TS_POSTGRES, $ts );
-       }
-
-       /**
-        * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
-        * to http://www.php.net/manual/en/ref.pgsql.php
-        *
-        * Parsing a postgres array can be a tricky problem, he's my
-        * take on this, it handles multi-dimensional arrays plus
-        * escaping using a nasty regexp to determine the limits of each
-        * data-item.
-        *
-        * This should really be handled by PHP PostgreSQL module
-        *
-        * @since 1.19
-        * @param string $text Postgreql array returned in a text form like {a,b}
-        * @param string $output
-        * @param int $limit
-        * @param int $offset
-        * @return string
-        */
-       function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
-               if ( false === $limit ) {
-                       $limit = strlen( $text ) - 1;
-                       $output = [];
-               }
-               if ( '{}' == $text ) {
-                       return $output;
-               }
-               do {
-                       if ( '{' != $text[$offset] ) {
-                               preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
-                                       $text, $match, 0, $offset );
-                               $offset += strlen( $match[0] );
-                               $output[] = ( '"' != $match[1][0]
-                                       ? $match[1]
-                                       : stripcslashes( substr( $match[1], 1, -1 ) ) );
-                               if ( '},' == $match[3] ) {
-                                       return $output;
-                               }
-                       } else {
-                               $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
-                       }
-               } while ( $limit > $offset );
-
-               return $output;
-       }
-
-       /**
-        * Return aggregated value function call
-        * @param array $valuedata
-        * @param string $valuename
-        * @return array
-        */
-       public function aggregateValue( $valuedata, $valuename = 'value' ) {
-               return $valuedata;
-       }
-
-       /**
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink() {
-               return '[{{int:version-db-postgres-url}} PostgreSQL]';
-       }
-
-       /**
-        * Return current schema (executes SELECT current_schema())
-        * Needs transaction
-        *
-        * @since 1.19
-        * @return string Default schema for the current session
-        */
-       function getCurrentSchema() {
-               $res = $this->query( "SELECT current_schema()", __METHOD__ );
-               $row = $this->fetchRow( $res );
-
-               return $row[0];
-       }
-
-       /**
-        * Return list of schemas which are accessible without schema name
-        * This is list does not contain magic keywords like "$user"
-        * Needs transaction
-        *
-        * @see getSearchPath()
-        * @see setSearchPath()
-        * @since 1.19
-        * @return array List of actual schemas for the current sesson
-        */
-       function getSchemas() {
-               $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
-               $row = $this->fetchRow( $res );
-               $schemas = [];
-
-               /* PHP pgsql support does not support array type, "{a,b}" string is returned */
-
-               return $this->pg_array_parse( $row[0], $schemas );
-       }
-
-       /**
-        * Return search patch for schemas
-        * This is different from getSchemas() since it contain magic keywords
-        * (like "$user").
-        * Needs transaction
-        *
-        * @since 1.19
-        * @return array How to search for table names schemas for the current user
-        */
-       function getSearchPath() {
-               $res = $this->query( "SHOW search_path", __METHOD__ );
-               $row = $this->fetchRow( $res );
-
-               /* PostgreSQL returns SHOW values as strings */
-
-               return explode( ",", $row[0] );
-       }
-
-       /**
-        * Update search_path, values should already be sanitized
-        * Values may contain magic keywords like "$user"
-        * @since 1.19
-        *
-        * @param array $search_path List of schemas to be searched by default
-        */
-       function setSearchPath( $search_path ) {
-               $this->query( "SET search_path = " . implode( ", ", $search_path ) );
-       }
-
-       /**
-        * Determine default schema for MediaWiki core
-        * Adjust this session schema search path if desired schema exists
-        * and is not alread there.
-        *
-        * We need to have name of the core schema stored to be able
-        * to query database metadata.
-        *
-        * This will be also called by the installer after the schema is created
-        *
-        * @since 1.19
-        *
-        * @param string $desiredSchema
-        */
-       function determineCoreSchema( $desiredSchema ) {
-               $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
-               if ( $this->schemaExists( $desiredSchema ) ) {
-                       if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
-                               $this->mCoreSchema = $desiredSchema;
-                               wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" );
-                       } else {
-                               /**
-                                * Prepend our schema (e.g. 'mediawiki') in front
-                                * of the search path
-                                * Fixes bug 15816
-                                */
-                               $search_path = $this->getSearchPath();
-                               array_unshift( $search_path,
-                                       $this->addIdentifierQuotes( $desiredSchema ) );
-                               $this->setSearchPath( $search_path );
-                               $this->mCoreSchema = $desiredSchema;
-                               wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" );
-                       }
-               } else {
-                       $this->mCoreSchema = $this->getCurrentSchema();
-                       wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" .
-                               $this->mCoreSchema . "\"\n" );
-               }
-               /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
-               $this->commit( __METHOD__ );
-       }
-
-       /**
-        * Return schema name fore core MediaWiki tables
-        *
-        * @since 1.19
-        * @return string Core schema name
-        */
-       function getCoreSchema() {
-               return $this->mCoreSchema;
-       }
-
-       /**
-        * @return string Version information from the database
-        */
-       function getServerVersion() {
-               if ( !isset( $this->numericVersion ) ) {
-                       $versionInfo = pg_version( $this->mConn );
-                       if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
-                               // Old client, abort install
-                               $this->numericVersion = '7.3 or earlier';
-                       } elseif ( isset( $versionInfo['server'] ) ) {
-                               // Normal client
-                               $this->numericVersion = $versionInfo['server'];
-                       } else {
-                               // Bug 16937: broken pgsql extension from PHP<5.3
-                               $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
-                       }
-               }
-
-               return $this->numericVersion;
-       }
-
-       /**
-        * Query whether a given relation exists (in the given schema, or the
-        * default mw one if not given)
-        * @param string $table
-        * @param array|string $types
-        * @param bool|string $schema
-        * @return bool
-        */
-       function relationExists( $table, $types, $schema = false ) {
-               if ( !is_array( $types ) ) {
-                       $types = [ $types ];
-               }
-               if ( !$schema ) {
-                       $schema = $this->getCoreSchema();
-               }
-               $table = $this->realTableName( $table, 'raw' );
-               $etable = $this->addQuotes( $table );
-               $eschema = $this->addQuotes( $schema );
-               $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
-                       . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
-                       . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
-               $res = $this->query( $sql );
-               $count = $res ? $res->numRows() : 0;
-
-               return (bool)$count;
-       }
-
-       /**
-        * For backward compatibility, this function checks both tables and
-        * views.
-        * @param string $table
-        * @param string $fname
-        * @param bool|string $schema
-        * @return bool
-        */
-       function tableExists( $table, $fname = __METHOD__, $schema = false ) {
-               return $this->relationExists( $table, [ 'r', 'v' ], $schema );
-       }
-
-       function sequenceExists( $sequence, $schema = false ) {
-               return $this->relationExists( $sequence, 'S', $schema );
-       }
-
-       function triggerExists( $table, $trigger ) {
-               $q = <<<SQL
-       SELECT 1 FROM pg_class, pg_namespace, pg_trigger
-               WHERE relnamespace=pg_namespace.oid AND relkind='r'
-                         AND tgrelid=pg_class.oid
-                         AND nspname=%s AND relname=%s AND tgname=%s
-SQL;
-               $res = $this->query(
-                       sprintf(
-                               $q,
-                               $this->addQuotes( $this->getCoreSchema() ),
-                               $this->addQuotes( $table ),
-                               $this->addQuotes( $trigger )
-                       )
-               );
-               if ( !$res ) {
-                       return null;
-               }
-               $rows = $res->numRows();
-
-               return $rows;
-       }
-
-       function ruleExists( $table, $rule ) {
-               $exists = $this->selectField( 'pg_rules', 'rulename',
-                       [
-                               'rulename' => $rule,
-                               'tablename' => $table,
-                               'schemaname' => $this->getCoreSchema()
-                       ]
-               );
-
-               return $exists === $rule;
-       }
-
-       function constraintExists( $table, $constraint ) {
-               $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
-                       "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
-                       $this->addQuotes( $this->getCoreSchema() ),
-                       $this->addQuotes( $table ),
-                       $this->addQuotes( $constraint )
-               );
-               $res = $this->query( $sql );
-               if ( !$res ) {
-                       return null;
-               }
-               $rows = $res->numRows();
-
-               return $rows;
-       }
-
-       /**
-        * Query whether a given schema exists. Returns true if it does, false if it doesn't.
-        * @param string $schema
-        * @return bool
-        */
-       function schemaExists( $schema ) {
-               $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
-                       [ 'nspname' => $schema ], __METHOD__ );
-
-               return (bool)$exists;
-       }
-
-       /**
-        * Returns true if a given role (i.e. user) exists, false otherwise.
-        * @param string $roleName
-        * @return bool
-        */
-       function roleExists( $roleName ) {
-               $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
-                       [ 'rolname' => $roleName ], __METHOD__ );
-
-               return (bool)$exists;
-       }
-
-       /**
-        * @var string $table
-        * @var string $field
-        * @return PostgresField|null
-        */
-       function fieldInfo( $table, $field ) {
-               return PostgresField::fromText( $this, $table, $field );
-       }
-
-       /**
-        * pg_field_type() wrapper
-        * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
-        * @param int $index Field number, starting from 0
-        * @return string
-        */
-       function fieldType( $res, $index ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_type( $res, $index );
-       }
-
-       /**
-        * @param string $b
-        * @return Blob
-        */
-       function encodeBlob( $b ) {
-               return new PostgresBlob( pg_escape_bytea( $b ) );
-       }
-
-       function decodeBlob( $b ) {
-               if ( $b instanceof PostgresBlob ) {
-                       $b = $b->fetch();
-               } elseif ( $b instanceof Blob ) {
-                       return $b->fetch();
-               }
-
-               return pg_unescape_bytea( $b );
-       }
-
-       function strencode( $s ) {
-               // Should not be called by us
-
-               return pg_escape_string( $this->mConn, $s );
-       }
-
-       /**
-        * @param null|bool|Blob $s
-        * @return int|string
-        */
-       function addQuotes( $s ) {
-               if ( is_null( $s ) ) {
-                       return 'NULL';
-               } elseif ( is_bool( $s ) ) {
-                       return intval( $s );
-               } elseif ( $s instanceof Blob ) {
-                       if ( $s instanceof PostgresBlob ) {
-                               $s = $s->fetch();
-                       } else {
-                               $s = pg_escape_bytea( $this->mConn, $s->fetch() );
-                       }
-                       return "'$s'";
-               }
-
-               return "'" . pg_escape_string( $this->mConn, $s ) . "'";
-       }
-
-       /**
-        * Postgres specific version of replaceVars.
-        * Calls the parent version in Database.php
-        *
-        * @param string $ins SQL string, read from a stream (usually tables.sql)
-        * @return string SQL string
-        */
-       protected function replaceVars( $ins ) {
-               $ins = parent::replaceVars( $ins );
-
-               if ( $this->numericVersion >= 8.3 ) {
-                       // Thanks for not providing backwards-compatibility, 8.3
-                       $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
-               }
-
-               if ( $this->numericVersion <= 8.1 ) { // Our minimum version
-                       $ins = str_replace( 'USING gin', 'USING gist', $ins );
-               }
-
-               return $ins;
-       }
-
-       /**
-        * Various select options
-        *
-        * @param array $options An associative array of options to be turned into
-        *   an SQL query, valid keys are listed in the function.
-        * @return array
-        */
-       function makeSelectOptions( $options ) {
-               $preLimitTail = $postLimitTail = '';
-               $startOpts = $useIndex = $ignoreIndex = '';
-
-               $noKeyOptions = [];
-               foreach ( $options as $key => $option ) {
-                       if ( is_numeric( $key ) ) {
-                               $noKeyOptions[$option] = true;
-                       }
-               }
-
-               $preLimitTail .= $this->makeGroupByWithHaving( $options );
-
-               $preLimitTail .= $this->makeOrderBy( $options );
-
-               // if ( isset( $options['LIMIT'] ) ) {
-               //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
-               //              isset( $options['OFFSET'] ) ? $options['OFFSET']
-               //              : false );
-               // }
-
-               if ( isset( $options['FOR UPDATE'] ) ) {
-                       $postLimitTail .= ' FOR UPDATE OF ' .
-                               implode( ', ', array_map( [ &$this, 'tableName' ], $options['FOR UPDATE'] ) );
-               } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
-                       $postLimitTail .= ' FOR UPDATE';
-               }
-
-               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
-                       $startOpts .= 'DISTINCT';
-               }
-
-               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
-       }
-
-       function getDBname() {
-               return $this->mDBname;
-       }
-
-       function getServer() {
-               return $this->mServer;
-       }
-
-       function buildConcat( $stringList ) {
-               return implode( ' || ', $stringList );
-       }
-
-       public function buildGroupConcatField(
-               $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
-       ) {
-               $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
-
-               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
-       }
-
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
-       public function buildStringCast( $field ) {
-               return $field . '::text';
-       }
-
-       public function getSearchEngine() {
-               return 'SearchPostgres';
-       }
-
-       public function streamStatementEnd( &$sql, &$newLine ) {
-               # Allow dollar quoting for function declarations
-               if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
-                       if ( $this->delimiter ) {
-                               $this->delimiter = false;
-                       } else {
-                               $this->delimiter = ';';
-                       }
-               }
-
-               return parent::streamStatementEnd( $sql, $newLine );
-       }
-
-       /**
-        * Check to see if a named lock is available. This is non-blocking.
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        *
-        * @param string $lockName Name of lock to poll
-        * @param string $method Name of method calling us
-        * @return bool
-        * @since 1.20
-        */
-       public function lockIsFree( $lockName, $method ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
-                       WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               return ( $row->lockstatus === 't' );
-       }
-
-       /**
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        * @param string $lockName
-        * @param string $method
-        * @param int $timeout
-        * @return bool
-        */
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $loop = new WaitConditionLoop(
-                       function () use ( $lockName, $key, $timeout, $method ) {
-                               $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
-                               $row = $this->fetchObject( $res );
-                               if ( $row->lockstatus === 't' ) {
-                                       parent::lock( $lockName, $method, $timeout ); // record
-                                       return true;
-                               }
-
-                               return WaitConditionLoop::CONDITION_CONTINUE;
-                       },
-                       $timeout
-               );
-
-               return ( $loop->invoke() === $loop::CONDITION_REACHED );
-       }
-
-       /**
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
-        * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        * @param string $lockName
-        * @param string $method
-        * @return bool
-        */
-       public function unlock( $lockName, $method ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               if ( $row->lockstatus === 't' ) {
-                       parent::unlock( $lockName, $method ); // record
-                       return true;
-               }
-
-               wfDebug( __METHOD__ . " failed to release lock\n" );
-
-               return false;
-       }
-
-       /**
-        * @param string $lockName
-        * @return string Integer
-        */
-       private function bigintFromLockName( $lockName ) {
-               return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
-       }
-} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
deleted file mode 100644 (file)
index 28fb5b5..0000000
+++ /dev/null
@@ -1,1068 +0,0 @@
-<?php
-/**
- * This is the SQLite database abstraction layer.
- * See maintenance/sqlite/README for development notes and other specific information
- *
- * 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 Database
- */
-
-/**
- * @ingroup Database
- */
-class DatabaseSqlite extends DatabaseBase {
-       /** @var bool Whether full text is enabled */
-       private static $fulltextEnabled = null;
-
-       /** @var string Directory */
-       protected $dbDir;
-
-       /** @var string File name for SQLite database file */
-       protected $dbPath;
-
-       /** @var string Transaction mode */
-       protected $trxMode;
-
-       /** @var int The number of rows affected as an integer */
-       protected $mAffectedRows;
-
-       /** @var resource */
-       protected $mLastResult;
-
-       /** @var PDO */
-       protected $mConn;
-
-       /** @var FSLockManager (hopefully on the same server as the DB) */
-       protected $lockMgr;
-
-       /**
-        * Additional params include:
-        *   - dbDirectory : directory containing the DB and the lock file directory
-        *                   [defaults to $wgSQLiteDataDir]
-        *   - dbFilePath  : use this to force the path of the DB file
-        *   - trxMode     : one of (deferred, immediate, exclusive)
-        * @param array $p
-        */
-       function __construct( array $p ) {
-               global $wgSQLiteDataDir;
-
-               $this->dbDir = isset( $p['dbDirectory'] ) ? $p['dbDirectory'] : $wgSQLiteDataDir;
-
-               if ( isset( $p['dbFilePath'] ) ) {
-                       parent::__construct( $p );
-                       // Standalone .sqlite file mode.
-                       // Super doesn't open when $user is false, but we can work with $dbName,
-                       // which is derived from the file path in this case.
-                       $this->openFile( $p['dbFilePath'] );
-               } else {
-                       $this->mDBname = $p['dbname'];
-                       // Stock wiki mode using standard file names per DB.
-                       parent::__construct( $p );
-                       // Super doesn't open when $user is false, but we can work with $dbName
-                       if ( $p['dbname'] && !$this->isOpen() ) {
-                               if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
-                                       $done = [];
-                                       foreach ( $this->tableAliases as $params ) {
-                                               if ( isset( $done[$params['dbname']] ) ) {
-                                                       continue;
-                                               }
-                                               $this->attachDatabase( $params['dbname'] );
-                                               $done[$params['dbname']] = 1;
-                                       }
-                               }
-                       }
-               }
-
-               $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
-               if ( $this->trxMode &&
-                       !in_array( $this->trxMode, [ 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ] )
-               ) {
-                       $this->trxMode = null;
-                       wfWarn( "Invalid SQLite transaction mode provided." );
-               }
-
-               $this->lockMgr = new FSLockManager( [ 'lockDirectory' => "{$this->dbDir}/locks" ] );
-       }
-
-       /**
-        * @param string $filename
-        * @param array $p Options map; supports:
-        *   - flags       : (same as __construct counterpart)
-        *   - trxMode     : (same as __construct counterpart)
-        *   - dbDirectory : (same as __construct counterpart)
-        * @return DatabaseSqlite
-        * @since 1.25
-        */
-       public static function newStandaloneInstance( $filename, array $p = [] ) {
-               $p['dbFilePath'] = $filename;
-               $p['schema'] = false;
-               $p['tablePrefix'] = '';
-
-               return DatabaseBase::factory( 'sqlite', $p );
-       }
-
-       /**
-        * @return string
-        */
-       function getType() {
-               return 'sqlite';
-       }
-
-       /**
-        * @todo Check if it should be true like parent class
-        *
-        * @return bool
-        */
-       function implicitGroupby() {
-               return false;
-       }
-
-       /** Open an SQLite database and return a resource handle to it
-        *  NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
-        *
-        * @param string $server
-        * @param string $user
-        * @param string $pass
-        * @param string $dbName
-        *
-        * @throws DBConnectionError
-        * @return PDO
-        */
-       function open( $server, $user, $pass, $dbName ) {
-               $this->close();
-               $fileName = self::generateFileName( $this->dbDir, $dbName );
-               if ( !is_readable( $fileName ) ) {
-                       $this->mConn = false;
-                       throw new DBConnectionError( $this, "SQLite database not accessible" );
-               }
-               $this->openFile( $fileName );
-
-               return $this->mConn;
-       }
-
-       /**
-        * Opens a database file
-        *
-        * @param string $fileName
-        * @throws DBConnectionError
-        * @return PDO|bool SQL connection or false if failed
-        */
-       protected function openFile( $fileName ) {
-               $err = false;
-
-               $this->dbPath = $fileName;
-               try {
-                       if ( $this->mFlags & DBO_PERSISTENT ) {
-                               $this->mConn = new PDO( "sqlite:$fileName", '', '',
-                                       [ PDO::ATTR_PERSISTENT => true ] );
-                       } else {
-                               $this->mConn = new PDO( "sqlite:$fileName", '', '' );
-                       }
-               } catch ( PDOException $e ) {
-                       $err = $e->getMessage();
-               }
-
-               if ( !$this->mConn ) {
-                       wfDebug( "DB connection error: $err\n" );
-                       throw new DBConnectionError( $this, $err );
-               }
-
-               $this->mOpened = !!$this->mConn;
-               if ( $this->mOpened ) {
-                       # Set error codes only, don't raise exceptions
-                       $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
-                       # Enforce LIKE to be case sensitive, just like MySQL
-                       $this->query( 'PRAGMA case_sensitive_like = 1' );
-
-                       return $this->mConn;
-               }
-
-               return false;
-       }
-
-       /**
-        * @return string SQLite DB file path
-        * @since 1.25
-        */
-       public function getDbFilePath() {
-               return $this->dbPath;
-       }
-
-       /**
-        * Does not actually close the connection, just destroys the reference for GC to do its work
-        * @return bool
-        */
-       protected function closeConnection() {
-               $this->mConn = null;
-
-               return true;
-       }
-
-       /**
-        * Generates a database file name. Explicitly public for installer.
-        * @param string $dir Directory where database resides
-        * @param string $dbName Database name
-        * @return string
-        */
-       public static function generateFileName( $dir, $dbName ) {
-               return "$dir/$dbName.sqlite";
-       }
-
-       /**
-        * Check if the searchindext table is FTS enabled.
-        * @return bool False if not enabled.
-        */
-       function checkForEnabledSearch() {
-               if ( self::$fulltextEnabled === null ) {
-                       self::$fulltextEnabled = false;
-                       $table = $this->tableName( 'searchindex' );
-                       $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
-                       if ( $res ) {
-                               $row = $res->fetchRow();
-                               self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
-                       }
-               }
-
-               return self::$fulltextEnabled;
-       }
-
-       /**
-        * Returns version of currently supported SQLite fulltext search module or false if none present.
-        * @return string
-        */
-       static function getFulltextSearchModule() {
-               static $cachedResult = null;
-               if ( $cachedResult !== null ) {
-                       return $cachedResult;
-               }
-               $cachedResult = false;
-               $table = 'dummy_search_test';
-
-               $db = self::newStandaloneInstance( ':memory:' );
-               if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
-                       $cachedResult = 'FTS3';
-               }
-               $db->close();
-
-               return $cachedResult;
-       }
-
-       /**
-        * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
-        * for details.
-        *
-        * @param string $name Database name to be used in queries like
-        *   SELECT foo FROM dbname.table
-        * @param bool|string $file Database file name. If omitted, will be generated
-        *   using $name and configured data directory
-        * @param string $fname Calling function name
-        * @return ResultWrapper
-        */
-       function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
-               if ( !$file ) {
-                       $file = self::generateFileName( $this->dbDir, $name );
-               }
-               $file = $this->addQuotes( $file );
-
-               return $this->query( "ATTACH DATABASE $file AS $name", $fname );
-       }
-
-       function isWriteQuery( $sql ) {
-               return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
-       }
-
-       /**
-        * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
-        *
-        * @param string $sql
-        * @return bool|ResultWrapper
-        */
-       protected function doQuery( $sql ) {
-               $res = $this->mConn->query( $sql );
-               if ( $res === false ) {
-                       return false;
-               } else {
-                       $r = $res instanceof ResultWrapper ? $res->result : $res;
-                       $this->mAffectedRows = $r->rowCount();
-                       $res = new ResultWrapper( $this, $r->fetchAll() );
-               }
-
-               return $res;
-       }
-
-       /**
-        * @param ResultWrapper|mixed $res
-        */
-       function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res->result = null;
-               } else {
-                       $res = null;
-               }
-       }
-
-       /**
-        * @param ResultWrapper|array $res
-        * @return stdClass|bool
-        */
-       function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-
-               $cur = current( $r );
-               if ( is_array( $cur ) ) {
-                       next( $r );
-                       $obj = new stdClass;
-                       foreach ( $cur as $k => $v ) {
-                               if ( !is_numeric( $k ) ) {
-                                       $obj->$k = $v;
-                               }
-                       }
-
-                       return $obj;
-               }
-
-               return false;
-       }
-
-       /**
-        * @param ResultWrapper|mixed $res
-        * @return array|bool
-        */
-       function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               $cur = current( $r );
-               if ( is_array( $cur ) ) {
-                       next( $r );
-
-                       return $cur;
-               }
-
-               return false;
-       }
-
-       /**
-        * The PDO::Statement class implements the array interface so count() will work
-        *
-        * @param ResultWrapper|array $res
-        * @return int
-        */
-       function numRows( $res ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-
-               return count( $r );
-       }
-
-       /**
-        * @param ResultWrapper $res
-        * @return int
-        */
-       function numFields( $res ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) && count( $r ) > 0 ) {
-                       // The size of the result array is twice the number of fields. (Bug: 65578)
-                       return count( $r[0] ) / 2;
-               } else {
-                       // If the result is empty return 0
-                       return 0;
-               }
-       }
-
-       /**
-        * @param ResultWrapper $res
-        * @param int $n
-        * @return bool
-        */
-       function fieldName( $res, $n ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) ) {
-                       $keys = array_keys( $r[0] );
-
-                       return $keys[$n];
-               }
-
-               return false;
-       }
-
-       /**
-        * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
-        *
-        * @param string $name
-        * @param string $format
-        * @return string
-        */
-       function tableName( $name, $format = 'quoted' ) {
-               // table names starting with sqlite_ are reserved
-               if ( strpos( $name, 'sqlite_' ) === 0 ) {
-                       return $name;
-               }
-
-               return str_replace( '"', '', parent::tableName( $name, $format ) );
-       }
-
-       /**
-        * Index names have DB scope
-        *
-        * @param string $index
-        * @return string
-        */
-       protected function indexName( $index ) {
-               return $index;
-       }
-
-       /**
-        * This must be called after nextSequenceVal
-        *
-        * @return int
-        */
-       function insertId() {
-               // PDO::lastInsertId yields a string :(
-               return intval( $this->mConn->lastInsertId() );
-       }
-
-       /**
-        * @param ResultWrapper|array $res
-        * @param int $row
-        */
-       function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               reset( $r );
-               if ( $row > 0 ) {
-                       for ( $i = 0; $i < $row; $i++ ) {
-                               next( $r );
-                       }
-               }
-       }
-
-       /**
-        * @return string
-        */
-       function lastError() {
-               if ( !is_object( $this->mConn ) ) {
-                       return "Cannot return last error, no db connection";
-               }
-               $e = $this->mConn->errorInfo();
-
-               return isset( $e[2] ) ? $e[2] : '';
-       }
-
-       /**
-        * @return string
-        */
-       function lastErrno() {
-               if ( !is_object( $this->mConn ) ) {
-                       return "Cannot return last error, no db connection";
-               } else {
-                       $info = $this->mConn->errorInfo();
-
-                       return $info[1];
-               }
-       }
-
-       /**
-        * @return int
-        */
-       function affectedRows() {
-               return $this->mAffectedRows;
-       }
-
-       /**
-        * Returns information about an index
-        * Returns false if the index does not exist
-        * - if errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return array
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-               if ( $res->numRows() == 0 ) {
-                       return false;
-               }
-               $info = [];
-               foreach ( $res as $row ) {
-                       $info[] = $row->name;
-               }
-
-               return $info;
-       }
-
-       /**
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
-       function indexUnique( $table, $index, $fname = __METHOD__ ) {
-               $row = $this->selectRow( 'sqlite_master', '*',
-                       [
-                               'type' => 'index',
-                               'name' => $this->indexName( $index ),
-                       ], $fname );
-               if ( !$row || !isset( $row->sql ) ) {
-                       return null;
-               }
-
-               // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
-               $indexPos = strpos( $row->sql, 'INDEX' );
-               if ( $indexPos === false ) {
-                       return null;
-               }
-               $firstPart = substr( $row->sql, 0, $indexPos );
-               $options = explode( ' ', $firstPart );
-
-               return in_array( 'UNIQUE', $options );
-       }
-
-       /**
-        * Filter the options used in SELECT statements
-        *
-        * @param array $options
-        * @return array
-        */
-       function makeSelectOptions( $options ) {
-               foreach ( $options as $k => $v ) {
-                       if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
-                               $options[$k] = '';
-                       }
-               }
-
-               return parent::makeSelectOptions( $options );
-       }
-
-       /**
-        * @param array $options
-        * @return string
-        */
-       protected function makeUpdateOptionsArray( $options ) {
-               $options = parent::makeUpdateOptionsArray( $options );
-               $options = self::fixIgnore( $options );
-
-               return $options;
-       }
-
-       /**
-        * @param array $options
-        * @return array
-        */
-       static function fixIgnore( $options ) {
-               # SQLite uses OR IGNORE not just IGNORE
-               foreach ( $options as $k => $v ) {
-                       if ( $v == 'IGNORE' ) {
-                               $options[$k] = 'OR IGNORE';
-                       }
-               }
-
-               return $options;
-       }
-
-       /**
-        * @param array $options
-        * @return string
-        */
-       function makeInsertOptions( $options ) {
-               $options = self::fixIgnore( $options );
-
-               return parent::makeInsertOptions( $options );
-       }
-
-       /**
-        * Based on generic method (parent) with some prior SQLite-sepcific adjustments
-        * @param string $table
-        * @param array $a
-        * @param string $fname
-        * @param array $options
-        * @return bool
-        */
-       function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
-               if ( !count( $a ) ) {
-                       return true;
-               }
-
-               # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
-               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
-                       $ret = true;
-                       foreach ( $a as $v ) {
-                               if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
-                                       $ret = false;
-                               }
-                       }
-               } else {
-                       $ret = parent::insert( $table, $a, "$fname/single-row", $options );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * @param string $table
-        * @param array $uniqueIndexes Unused
-        * @param string|array $rows
-        * @param string $fname
-        * @return bool|ResultWrapper
-        */
-       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
-               if ( !count( $rows ) ) {
-                       return true;
-               }
-
-               # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
-               if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
-                       $ret = true;
-                       foreach ( $rows as $v ) {
-                               if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
-                                       $ret = false;
-                               }
-                       }
-               } else {
-                       $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * Returns the size of a text field, or -1 for "unlimited"
-        * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
-        *
-        * @param string $table
-        * @param string $field
-        * @return int
-        */
-       function textFieldSize( $table, $field ) {
-               return -1;
-       }
-
-       /**
-        * @return bool
-        */
-       function unionSupportsOrderAndLimit() {
-               return false;
-       }
-
-       /**
-        * @param string $sqls
-        * @param bool $all Whether to "UNION ALL" or not
-        * @return string
-        */
-       function unionQueries( $sqls, $all ) {
-               $glue = $all ? ' UNION ALL ' : ' UNION ';
-
-               return implode( $glue, $sqls );
-       }
-
-       /**
-        * @return bool
-        */
-       function wasDeadlock() {
-               return $this->lastErrno() == 5; // SQLITE_BUSY
-       }
-
-       /**
-        * @return bool
-        */
-       function wasErrorReissuable() {
-               return $this->lastErrno() == 17; // SQLITE_SCHEMA;
-       }
-
-       /**
-        * @return bool
-        */
-       function wasReadOnlyError() {
-               return $this->lastErrno() == 8; // SQLITE_READONLY;
-       }
-
-       /**
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink() {
-               return "[{{int:version-db-sqlite-url}} SQLite]";
-       }
-
-       /**
-        * @return string Version information from the database
-        */
-       function getServerVersion() {
-               $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
-
-               return $ver;
-       }
-
-       /**
-        * @return string User-friendly database information
-        */
-       public function getServerInfo() {
-               return wfMessage( self::getFulltextSearchModule()
-                       ? 'sqlite-has-fts'
-                       : 'sqlite-no-fts', $this->getServerVersion() )->text();
-       }
-
-       /**
-        * Get information about a given field
-        * Returns false if the field does not exist.
-        *
-        * @param string $table
-        * @param string $field
-        * @return SQLiteField|bool False on failure
-        */
-       function fieldInfo( $table, $field ) {
-               $tableName = $this->tableName( $table );
-               $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
-               $res = $this->query( $sql, __METHOD__ );
-               foreach ( $res as $row ) {
-                       if ( $row->name == $field ) {
-                               return new SQLiteField( $row, $tableName );
-                       }
-               }
-
-               return false;
-       }
-
-       protected function doBegin( $fname = '' ) {
-               if ( $this->trxMode ) {
-                       $this->query( "BEGIN {$this->trxMode}", $fname );
-               } else {
-                       $this->query( 'BEGIN', $fname );
-               }
-               $this->mTrxLevel = 1;
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       function strencode( $s ) {
-               return substr( $this->addQuotes( $s ), 1, -1 );
-       }
-
-       /**
-        * @param string $b
-        * @return Blob
-        */
-       function encodeBlob( $b ) {
-               return new Blob( $b );
-       }
-
-       /**
-        * @param Blob|string $b
-        * @return string
-        */
-       function decodeBlob( $b ) {
-               if ( $b instanceof Blob ) {
-                       $b = $b->fetch();
-               }
-
-               return $b;
-       }
-
-       /**
-        * @param Blob|string $s
-        * @return string
-        */
-       function addQuotes( $s ) {
-               if ( $s instanceof Blob ) {
-                       return "x'" . bin2hex( $s->fetch() ) . "'";
-               } elseif ( is_bool( $s ) ) {
-                       return (int)$s;
-               } elseif ( strpos( $s, "\0" ) !== false ) {
-                       // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
-                       // This is a known limitation of SQLite's mprintf function which PDO
-                       // should work around, but doesn't. I have reported this to php.net as bug #63419:
-                       // https://bugs.php.net/bug.php?id=63419
-                       // There was already a similar report for SQLite3::escapeString, bug #62361:
-                       // https://bugs.php.net/bug.php?id=62361
-                       // There is an additional bug regarding sorting this data after insert
-                       // on older versions of sqlite shipped with ubuntu 12.04
-                       // https://phabricator.wikimedia.org/T74367
-                       wfDebugLog(
-                               __CLASS__,
-                               __FUNCTION__ .
-                                       ': Quoting value containing null byte. ' .
-                                       'For consistency all binary data should have been ' .
-                                       'first processed with self::encodeBlob()'
-                       );
-                       return "x'" . bin2hex( $s ) . "'";
-               } else {
-                       return $this->mConn->quote( $s );
-               }
-       }
-
-       /**
-        * @return string
-        */
-       function buildLike() {
-               $params = func_get_args();
-               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-
-               return parent::buildLike( $params ) . "ESCAPE '\' ";
-       }
-
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
-       public function buildStringCast( $field ) {
-               return 'CAST ( ' . $field . ' AS TEXT )';
-       }
-
-       /**
-        * @return string
-        */
-       public function getSearchEngine() {
-               return "SearchSqlite";
-       }
-
-       /**
-        * No-op version of deadlockLoop
-        *
-        * @return mixed
-        */
-       public function deadlockLoop( /*...*/ ) {
-               $args = func_get_args();
-               $function = array_shift( $args );
-
-               return call_user_func_array( $function, $args );
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       protected function replaceVars( $s ) {
-               $s = parent::replaceVars( $s );
-               if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
-                       // CREATE TABLE hacks to allow schema file sharing with MySQL
-
-                       // binary/varbinary column type -> blob
-                       $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
-                       // no such thing as unsigned
-                       $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
-                       // INT -> INTEGER
-                       $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
-                       // floating point types -> REAL
-                       $s = preg_replace(
-                               '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
-                               'REAL',
-                               $s
-                       );
-                       // varchar -> TEXT
-                       $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
-                       // TEXT normalization
-                       $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
-                       // BLOB normalization
-                       $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
-                       // BOOL -> INTEGER
-                       $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
-                       // DATETIME -> TEXT
-                       $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
-                       // No ENUM type
-                       $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
-                       // binary collation type -> nothing
-                       $s = preg_replace( '/\bbinary\b/i', '', $s );
-                       // auto_increment -> autoincrement
-                       $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
-                       // No explicit options
-                       $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
-                       // AUTOINCREMENT should immedidately follow PRIMARY KEY
-                       $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
-               } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
-                       // No truncated indexes
-                       $s = preg_replace( '/\(\d+\)/', '', $s );
-                       // No FULLTEXT
-                       $s = preg_replace( '/\bfulltext\b/i', '', $s );
-               } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
-                       // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
-                       $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
-               } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
-                       // INSERT IGNORE --> INSERT OR IGNORE
-                       $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
-               }
-
-               return $s;
-       }
-
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
-                       if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
-                               throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
-                       }
-               }
-
-               return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
-       }
-
-       public function unlock( $lockName, $method ) {
-               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
-       }
-
-       /**
-        * Build a concatenation list to feed into a SQL query
-        *
-        * @param string[] $stringList
-        * @return string
-        */
-       function buildConcat( $stringList ) {
-               return '(' . implode( ') || (', $stringList ) . ')';
-       }
-
-       public function buildGroupConcatField(
-               $delim, $table, $field, $conds = '', $join_conds = []
-       ) {
-               $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
-
-               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
-       }
-
-       /**
-        * @param string $oldName
-        * @param string $newName
-        * @param bool $temporary
-        * @param string $fname
-        * @return bool|ResultWrapper
-        * @throws RuntimeException
-        */
-       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
-               $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
-                       $this->addQuotes( $oldName ) . " AND type='table'", $fname );
-               $obj = $this->fetchObject( $res );
-               if ( !$obj ) {
-                       throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
-               }
-               $sql = $obj->sql;
-               $sql = preg_replace(
-                       '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
-                       $this->addIdentifierQuotes( $newName ),
-                       $sql,
-                       1
-               );
-               if ( $temporary ) {
-                       if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
-                               wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
-                       } else {
-                               $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
-                       }
-               }
-
-               $res = $this->query( $sql, $fname );
-
-               // Take over indexes
-               $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
-               foreach ( $indexList as $index ) {
-                       if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
-                               continue;
-                       }
-
-                       if ( $index->unique ) {
-                               $sql = 'CREATE UNIQUE INDEX';
-                       } else {
-                               $sql = 'CREATE INDEX';
-                       }
-                       // Try to come up with a new index name, given indexes have database scope in SQLite
-                       $indexName = $newName . '_' . $index->name;
-                       $sql .= ' ' . $indexName . ' ON ' . $newName;
-
-                       $indexInfo = $this->query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')' );
-                       $fields = [];
-                       foreach ( $indexInfo as $indexInfoRow ) {
-                               $fields[$indexInfoRow->seqno] = $indexInfoRow->name;
-                       }
-
-                       $sql .= '(' . implode( ',', $fields ) . ')';
-
-                       $this->query( $sql );
-               }
-
-               return $res;
-       }
-
-       /**
-        * List all tables on the database
-        *
-        * @param string $prefix Only show tables with this prefix, e.g. mw_
-        * @param string $fname Calling function name
-        *
-        * @return array
-        */
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $result = $this->select(
-                       'sqlite_master',
-                       'name',
-                       "type='table'"
-               );
-
-               $endArray = [];
-
-               foreach ( $result as $table ) {
-                       $vars = get_object_vars( $table );
-                       $table = array_pop( $vars );
-
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
-                               if ( strpos( $table, 'sqlite_' ) !== 0 ) {
-                                       $endArray[] = $table;
-                               }
-                       }
-               }
-
-               return $endArray;
-       }
-
-       /**
-        * Override due to no CASCADE support
-        *
-        * @param string $tableName
-        * @param string $fName
-        * @return bool|ResultWrapper
-        * @throws DBReadOnlyError
-        */
-       public function dropTable( $tableName, $fName = __METHOD__ ) {
-               if ( !$this->tableExists( $tableName, $fName ) ) {
-                       return false;
-               }
-               $sql = "DROP TABLE " . $this->tableName( $tableName );
-
-               return $this->query( $sql, $fName );
-       }
-
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
-       }
-
-} // end DatabaseSqlite class
index 69fd21d..9821da1 100644 (file)
@@ -27,69 +27,103 @@ use MediaWiki\Logger\LoggerFactory;
  * Legacy MediaWiki-specific class for generating database load balancers
  * @ingroup Database
  */
-abstract class LBFactoryMW extends LBFactory {
+abstract class LBFactoryMW {
        /**
-        * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
-        * @param array $conf
-        * @TODO: inject objects via dependency framework
-        */
-       public function __construct( array $conf ) {
-               parent::__construct( self::applyDefaultConfig( $conf ) );
-       }
-
-       /**
-        * @param array $conf
+        * @param array $lbConf Config for LBFactory::__construct()
+        * @param Config $mainConfig Main config object from MediaWikiServices
         * @return array
-        * @TODO: inject objects via dependency framework
         */
-       public static function applyDefaultConfig( array $conf ) {
-               global $wgDBtype, $wgSQLMode, $wgDBmysql5, $wgDBname, $wgDBprefix, $wgDBmwschema;
+       public static function applyDefaultConfig( array $lbConf, Config $mainConfig ) {
                global $wgCommandLineMode;
 
-               $defaults = [
-                       'localDomain' => new DatabaseDomain( $wgDBname, null, $wgDBprefix ),
-                       'hostname' => wfHostname(),
+               $lbConf += [
+                       'localDomain' => new DatabaseDomain(
+                               $mainConfig->get( 'DBname' ),
+                               null,
+                               $mainConfig->get( 'DBprefix' )
+                       ),
                        'profiler' => Profiler::instance(),
                        'trxProfiler' => Profiler::instance()->getTransactionProfiler(),
                        'replLogger' => LoggerFactory::getInstance( 'DBReplication' ),
-                       'queryLogger' => LoggerFactory::getInstance( 'wfLogDBError' ),
-                       'connLogger' => LoggerFactory::getInstance( 'wfLogDBError' ),
+                       'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ),
+                       'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
                        'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
                        'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
                        'cliMode' => $wgCommandLineMode,
-                       'agent' => ''
+                       'hostname' => wfHostname(),
+                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
+                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
                ];
+
+               if ( $lbConf['class'] === 'LBFactorySimple' ) {
+                       if ( isset( $lbConf['servers'] ) ) {
+                               // Server array is already explicitly configured; leave alone
+                       } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
+                                       if ( $server['type'] === 'sqlite' ) {
+                                               $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
+                                       } elseif ( $server['type'] === 'postgres' ) {
+                                               $server += [ 'port' => $mainConfig->get( 'DBport' ) ];
+                                       }
+                                       $lbConf['servers'][$i] = $server + [
+                                               'schema' => $mainConfig->get( 'DBmwschema' ),
+                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                               'flags' => DBO_DEFAULT,
+                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                                       ];
+                               }
+                       } else {
+                               $flags = DBO_DEFAULT;
+                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
+                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+                               $server = [
+                                       'host' => $mainConfig->get( 'DBserver' ),
+                                       'user' => $mainConfig->get( 'DBuser' ),
+                                       'password' => $mainConfig->get( 'DBpassword' ),
+                                       'dbname' => $mainConfig->get( 'DBname' ),
+                                       'schema' => $mainConfig->get( 'DBmwschema' ),
+                                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                       'type' => $mainConfig->get( 'DBtype' ),
+                                       'load' => 1,
+                                       'flags' => $flags,
+                                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                       'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                               ];
+                               if ( $server['type'] === 'sqlite' ) {
+                                       $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
+                               } elseif ( $server['type'] === 'postgres' ) {
+                                       $server['port'] = $mainConfig->get( 'DBport' );
+                               }
+                               $lbConf['servers'] = [ $server ];
+                       }
+                       if ( !isset( $lbConf['externalClusters'] ) ) {
+                               $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
+                       }
+               } elseif ( $lbConf['class'] === 'LBFactoryMulti' ) {
+                       if ( isset( $lbConf['serverTemplate'] ) ) {
+                               $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' );
+                               $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
+                               $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get( 'DBmysql5' );
+                       }
+               }
+
                // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
                $sCache = ObjectCache::getLocalServerInstance();
                if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
-                       $defaults['srvCache'] = $sCache;
+                       $lbConf['srvCache'] = $sCache;
                }
                $cCache = ObjectCache::getLocalClusterInstance();
                if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) {
-                       $defaults['memCache'] = $cCache;
+                       $lbConf['memCache'] = $cCache;
                }
                $wCache = ObjectCache::getMainWANInstance();
                if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
-                       $defaults['wanCache'] = $wCache;
-               }
-
-               // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
-               // and everything else doesn't use a schema (e.g. null)
-               // Although postgres and oracle support schemas, we don't use them (yet)
-               // to maintain backwards compatibility
-               $schema = ( $wgDBtype === 'mssql' ) ? $wgDBmwschema : null;
-
-               if ( isset( $conf['serverTemplate'] ) ) { // LBFactoryMulti
-                       $conf['serverTemplate']['schema'] = $schema;
-                       $conf['serverTemplate']['sqlMode'] = $wgSQLMode;
-                       $conf['serverTemplate']['utf8Mode'] = $wgDBmysql5;
-               } elseif ( isset( $conf['servers'] ) ) { // LBFactorySimple
-                       foreach ( $conf['servers'] as $i => $server ) {
-                               $conf['servers'][$i]['schema'] = $schema;
-                       }
+                       $lbConf['wanCache'] = $wCache;
                }
 
-               return $conf + $defaults;
+               return $lbConf;
        }
 
        /**
diff --git a/includes/db/loadbalancer/LBFactorySingle.php b/includes/db/loadbalancer/LBFactorySingle.php
deleted file mode 100644 (file)
index 3937dfd..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-/**
- * Simple generator of database connections that always returns the same object.
- *
- * 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 Database
- */
-
-/**
- * An LBFactory class that always returns a single database object.
- */
-class LBFactorySingle extends LBFactory {
-       /** @var LoadBalancerSingle */
-       private $lb;
-
-       /**
-        * @param array $conf An associative array with one member:
-        *  - connection: The IDatabase connection object
-        */
-       public function __construct( array $conf ) {
-               parent::__construct( $conf );
-
-               $this->lb = new LoadBalancerSingle( array_merge( $this->baseLoadBalancerParams(), $conf ) );
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancerSingle
-        */
-       public function newMainLB( $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancerSingle
-        */
-       public function getMainLB( $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancerSingle
-        */
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancerSingle
-        */
-       public function getExternalLB( $cluster, $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string|callable $callback
-        * @param array $params
-        */
-       public function forEachLB( $callback, array $params = [] ) {
-               call_user_func_array( $callback, array_merge( [ $this->lb ], $params ) );
-       }
-}
-
-/**
- * Helper class for LBFactorySingle.
- */
-class LoadBalancerSingle extends LoadBalancer {
-       /** @var IDatabase */
-       private $db;
-
-       /**
-        * @param array $params
-        */
-       public function __construct( array $params ) {
-               $this->db = $params['connection'];
-
-               parent::__construct( [
-                       'servers' => [
-                               [
-                                       'type' => $this->db->getType(),
-                                       'host' => $this->db->getServer(),
-                                       'dbname' => $this->db->getDBname(),
-                                       'load' => 1,
-                               ]
-                       ],
-                       'trxProfiler' => isset( $params['trxProfiler'] ) ? $params['trxProfiler'] : null,
-                       'srvCache' => isset( $params['srvCache'] ) ? $params['srvCache'] : null,
-                       'wanCache' => isset( $params['wanCache'] ) ? $params['wanCache'] : null
-               ] );
-
-               if ( isset( $params['readOnlyReason'] ) ) {
-                       $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
-               }
-       }
-
-       /**
-        *
-        * @param string $server
-        * @param bool $dbNameOverride
-        *
-        * @return IDatabase
-        */
-       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
-               return $this->db;
-       }
-}
index 526b4ab..ef7a994 100644 (file)
@@ -70,6 +70,14 @@ class LegacyLogger extends AbstractLogger {
                LogLevel::EMERGENCY => 600,
        ];
 
+       /**
+        * @var array
+        */
+       protected static $dbChannels = [
+               'DBQuery' => true,
+               'DBConnection' => true
+       ];
+
        /**
         * @param string $channel
         */
@@ -83,14 +91,29 @@ class LegacyLogger extends AbstractLogger {
         * @param string|int $level
         * @param string $message
         * @param array $context
+        * @return null
         */
        public function log( $level, $message, array $context = [] ) {
-               if ( self::shouldEmit( $this->channel, $message, $level, $context ) ) {
-                       $text = self::format( $this->channel, $message, $context );
-                       $destination = self::destination( $this->channel, $message, $context );
+               if ( isset( self::$dbChannels[$this->channel] )
+                       && isset( self::$levelMapping[$level] )
+                       && self::$levelMapping[$level] >= LogLevel::ERROR
+               ) {
+                       // Format and write DB errors to the legacy locations
+                       $effectiveChannel = 'wfLogDBError';
+               } else {
+                       $effectiveChannel = $this->channel;
+               }
+
+               if ( self::shouldEmit( $effectiveChannel, $message, $level, $context ) ) {
+                       $text = self::format( $effectiveChannel, $message, $context );
+                       $destination = self::destination( $effectiveChannel, $message, $context );
                        self::emit( $text, $destination );
                }
-               if ( !isset( $context['private'] ) || !$context['private'] ) {
+               if ( $this->channel === 'DBQuery' && isset( $context['method'] )
+                       && isset( $context['master'] ) && isset( $context['runtime'] )
+               ) {
+                       MWDebug::query( $message, $context['method'], $context['master'], $context['runtime'] );
+               } elseif ( !isset( $context['private'] ) || !$context['private'] ) {
                        // Add to debug toolbar if not marked as "private"
                        MWDebug::debugMsg( $message, [ 'channel' => $this->channel ] + $context );
                }
@@ -298,6 +321,7 @@ class LegacyLogger extends AbstractLogger {
         * @param string $channel
         * @param string $message
         * @param array $context
+        * @return null
         */
        protected static function formatAsWfDebugLog( $channel, $message, $context ) {
                $time = wfTimestamp( TS_DB );
@@ -432,7 +456,6 @@ class LegacyLogger extends AbstractLogger {
        *
        * @param string $text
        * @param string $file Filename
-       * @throws MWException
        */
        public static function emit( $text, $file ) {
                if ( substr( $file, 0, 4 ) == 'udp:' ) {
index 4159166..93b3ef6 100644 (file)
@@ -108,87 +108,81 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate {
                        }
                }
 
-               // If using cascading deletes, we can skip some explicit deletes
-               if ( !$dbw->cascadingDeletes() ) {
-                       // Delete outgoing links
-                       $this->batchDeleteByPK(
-                               'pagelinks',
-                               [ 'pl_from' => $id ],
-                               [ 'pl_from', 'pl_namespace', 'pl_title' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'imagelinks',
-                               [ 'il_from' => $id ],
-                               [ 'il_from', 'il_to' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'categorylinks',
-                               [ 'cl_from' => $id ],
-                               [ 'cl_from', 'cl_to' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'templatelinks',
-                               [ 'tl_from' => $id ],
-                               [ 'tl_from', 'tl_namespace', 'tl_title' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'externallinks',
-                               [ 'el_from' => $id ],
-                               [ 'el_id' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'langlinks',
-                               [ 'll_from' => $id ],
-                               [ 'll_from', 'll_lang' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'iwlinks',
-                               [ 'iwl_from' => $id ],
-                               [ 'iwl_from', 'iwl_prefix', 'iwl_title' ],
-                               $batchSize
-                       );
-                       // Delete any redirect entry or page props entries
-                       $dbw->delete( 'redirect', [ 'rd_from' => $id ], __METHOD__ );
-                       $dbw->delete( 'page_props', [ 'pp_page' => $id ], __METHOD__ );
-               }
+               $this->batchDeleteByPK(
+                       'pagelinks',
+                       [ 'pl_from' => $id ],
+                       [ 'pl_from', 'pl_namespace', 'pl_title' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'imagelinks',
+                       [ 'il_from' => $id ],
+                       [ 'il_from', 'il_to' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'categorylinks',
+                       [ 'cl_from' => $id ],
+                       [ 'cl_from', 'cl_to' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'templatelinks',
+                       [ 'tl_from' => $id ],
+                       [ 'tl_from', 'tl_namespace', 'tl_title' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'externallinks',
+                       [ 'el_from' => $id ],
+                       [ 'el_id' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'langlinks',
+                       [ 'll_from' => $id ],
+                       [ 'll_from', 'll_lang' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'iwlinks',
+                       [ 'iwl_from' => $id ],
+                       [ 'iwl_from', 'iwl_prefix', 'iwl_title' ],
+                       $batchSize
+               );
 
-               // If using cleanup triggers, we can skip some manual deletes
-               if ( !$dbw->cleanupTriggers() ) {
-                       // Find recentchanges entries to clean up...
-                       $rcIdsForTitle = $dbw->selectFieldValues(
-                               'recentchanges',
-                               'rc_id',
-                               [
-                                       'rc_type != ' . RC_LOG,
-                                       'rc_namespace' => $title->getNamespace(),
-                                       'rc_title' => $title->getDBkey(),
-                                       'rc_timestamp < ' .
-                                               $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) )
-                               ],
-                               __METHOD__
-                       );
-                       $rcIdsForPage = $dbw->selectFieldValues(
-                               'recentchanges',
-                               'rc_id',
-                               [ 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ],
-                               __METHOD__
-                       );
+               // Delete any redirect entry or page props entries
+               $dbw->delete( 'redirect', [ 'rd_from' => $id ], __METHOD__ );
+               $dbw->delete( 'page_props', [ 'pp_page' => $id ], __METHOD__ );
+
+               // Find recentchanges entries to clean up...
+               $rcIdsForTitle = $dbw->selectFieldValues(
+                       'recentchanges',
+                       'rc_id',
+                       [
+                               'rc_type != ' . RC_LOG,
+                               'rc_namespace' => $title->getNamespace(),
+                               'rc_title' => $title->getDBkey(),
+                               'rc_timestamp < ' .
+                                       $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) )
+                       ],
+                       __METHOD__
+               );
+               $rcIdsForPage = $dbw->selectFieldValues(
+                       'recentchanges',
+                       'rc_id',
+                       [ 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ],
+                       __METHOD__
+               );
 
-                       // T98706: delete by PK to avoid lock contention with RC delete log insertions
-                       $rcIdBatches = array_chunk( array_merge( $rcIdsForTitle, $rcIdsForPage ), $batchSize );
-                       foreach ( $rcIdBatches as $rcIdBatch ) {
-                               $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
-                               if ( count( $rcIdBatches ) > 1 ) {
-                                       $lbFactory->commitAndWaitForReplication(
-                                               __METHOD__, $this->ticket, [ 'wiki' => $dbw->getWikiID() ]
-                                       );
-                               }
+               // T98706: delete by PK to avoid lock contention with RC delete log insertions
+               $rcIdBatches = array_chunk( array_merge( $rcIdsForTitle, $rcIdsForPage ), $batchSize );
+               foreach ( $rcIdBatches as $rcIdBatch ) {
+                       $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
+                       if ( count( $rcIdBatches ) > 1 ) {
+                               $lbFactory->commitAndWaitForReplication(
+                                       __METHOD__, $this->ticket, [ 'wiki' => $dbw->getWikiID() ]
+                               );
                        }
                }
 
index bb7a01f..e242da3 100644 (file)
@@ -28,11 +28,11 @@ class MWExceptionRenderer {
        const AS_PRETTY = 2; // show as HTML
 
        /**
-        * @param Exception $e Original exception
+        * @param Exception|Throwable $e Original exception
         * @param integer $mode MWExceptionExposer::AS_* constant
-        * @param Exception|null $eNew New exception from attempting to show the first
+        * @param Exception|Throwable|null $eNew New exception from attempting to show the first
         */
-       public static function output( Exception $e, $mode, Exception $eNew = null ) {
+       public static function output( $e, $mode, $eNew = null ) {
                global $wgMimeType;
 
                if ( $e instanceof DBConnectionError ) {
@@ -88,12 +88,12 @@ class MWExceptionRenderer {
         *
         * Called by MWException for b/c
         *
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @param string $name Class name of the exception
         * @param array $args Arguments to pass to the callback functions
         * @return string|null String to output or null if any hook has been called
         */
-       public static function runHooks( Exception $e, $name, $args = [] ) {
+       public static function runHooks( $e, $name, $args = [] ) {
                global $wgExceptionHooks;
 
                if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
@@ -129,10 +129,10 @@ class MWExceptionRenderer {
        }
 
        /**
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @return bool Should the exception use $wgOut to output the error?
         */
-       private static function useOutputPage( Exception $e ) {
+       private static function useOutputPage( $e ) {
                // Can the extension use the Message class/wfMessage to get i18n-ed messages?
                foreach ( $e->getTrace() as $frame ) {
                        if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
@@ -150,9 +150,9 @@ class MWExceptionRenderer {
        /**
         * Output the exception report using HTML
         *
-        * @param Exception $e
+        * @param Exception|Throwable $e
         */
-       private static function reportHTML( Exception $e ) {
+       private static function reportHTML( $e ) {
                global $wgOut, $wgSitename;
 
                if ( self::useOutputPage( $e ) ) {
@@ -206,10 +206,10 @@ class MWExceptionRenderer {
         * backtrace to the error, otherwise show a message to ask to set it to true
         * to show that information.
         *
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @return string Html to output
         */
-       public static function getHTML( Exception $e ) {
+       public static function getHTML( $e ) {
                if ( self::showBackTrace( $e ) ) {
                        $html = "<div class=\"errorbox\"><p>" .
                                nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
@@ -254,10 +254,10 @@ class MWExceptionRenderer {
        }
 
        /**
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @return string
         */
-       private function getText( Exception $e ) {
+       private static function getText( $e ) {
                if ( self::showBackTrace( $e ) ) {
                        return MWExceptionHandler::getLogMessage( $e ) .
                                "\nBacktrace:\n" .
@@ -269,10 +269,10 @@ class MWExceptionRenderer {
        }
 
        /**
-        * @param Exception $e
+        * @param Exception|Throwable $e
         * @return bool
         */
-       private static function showBackTrace( Exception $e ) {
+       private static function showBackTrace( $e ) {
                global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
 
                return (
@@ -324,9 +324,9 @@ class MWExceptionRenderer {
        }
 
        /**
-        * @param Exception $e
+        * @param Exception|Throwable $e
         */
-       private static function reportOutageHTML( Exception $e ) {
+       private static function reportOutageHTML( $e ) {
                global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
 
                $sorry = htmlspecialchars( self::msg(
index 1f91b3f..ed2bdcc 100644 (file)
@@ -1260,7 +1260,7 @@ abstract class FileBackend {
        final public function lockFiles( array $paths, $type, $timeout = 0 ) {
                $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
 
-               return $this->lockManager->lock( $paths, $type, $timeout );
+               return $this->wrapStatus( $this->lockManager->lock( $paths, $type, $timeout ) );
        }
 
        /**
@@ -1273,7 +1273,7 @@ abstract class FileBackend {
        final public function unlockFiles( array $paths, $type ) {
                $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
 
-               return $this->lockManager->unlock( $paths, $type );
+               return $this->wrapStatus( $this->lockManager->unlock( $paths, $type ) );
        }
 
        /**
index cccf71a..4667dde 100644 (file)
@@ -104,7 +104,7 @@ abstract class DBLockManager extends QuorumLockManager {
 
        // @todo change this code to work in one batch
        protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                foreach ( $pathsByType as $type => $paths ) {
                        $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
                }
@@ -115,7 +115,7 @@ abstract class DBLockManager extends QuorumLockManager {
        abstract protected function doGetLocksOnServer( $lockSrv, array $paths, $type );
 
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               return Status::newGood();
+               return StatusValue::newGood();
        }
 
        /**
diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php
deleted file mode 100644 (file)
index 8e149d6..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-/**
- * Simple version of LockManager based on using FS lock files.
- *
- * 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 LockManager
- */
-
-/**
- * Simple version of LockManager based on using FS lock files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * This should work fine for small sites running off one server.
- * Do not use this with 'lockDirectory' set to an NFS mount unless the
- * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
- * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class FSLockManager extends LockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       protected $lockDir; // global dir for all servers
-
-       /** @var array Map of (locked key => lock file handle) */
-       protected $handles = [];
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * @param array $config Includes:
-        *   - lockDirectory : Directory containing the lock files
-        */
-       function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->lockDir = $config['lockDirectory'];
-       }
-
-       /**
-        * @see LockManager::doLock()
-        * @param array $paths
-        * @param int $type
-        * @return StatusValue
-        */
-       protected function doLock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               $lockedPaths = []; // files locked in this attempt
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleLock( $path, $type ) );
-                       if ( $status->isOK() ) {
-                               $lockedPaths[] = $path;
-                       } else {
-                               // Abort and unlock everything
-                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
-
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see LockManager::doUnlock()
-        * @param array $paths
-        * @param int $type
-        * @return StatusValue
-        */
-       protected function doUnlock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleUnlock( $path, $type ) );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lock a single resource key
-        *
-        * @param string $path
-        * @param int $type
-        * @return StatusValue
-        */
-       protected function doSingleLock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( isset( $this->locksHeld[$path][$type] ) ) {
-                       ++$this->locksHeld[$path][$type];
-               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
-                       $this->locksHeld[$path][$type] = 1;
-               } else {
-                       if ( isset( $this->handles[$path] ) ) {
-                               $handle = $this->handles[$path];
-                       } else {
-                               MediaWiki\suppressWarnings();
-                               $handle = fopen( $this->getLockPath( $path ), 'a+' );
-                               MediaWiki\restoreWarnings();
-                               if ( !$handle ) { // lock dir missing?
-                                       wfMkdirParents( $this->lockDir );
-                                       $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
-                               }
-                       }
-                       if ( $handle ) {
-                               // Either a shared or exclusive lock
-                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
-                               if ( flock( $handle, $lock | LOCK_NB ) ) {
-                                       // Record this lock as active
-                                       $this->locksHeld[$path][$type] = 1;
-                                       $this->handles[$path] = $handle;
-                               } else {
-                                       fclose( $handle );
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                               }
-                       } else {
-                               $status->fatal( 'lockmanager-fail-openlock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Unlock a single resource key
-        *
-        * @param string $path
-        * @param int $type
-        * @return StatusValue
-        */
-       protected function doSingleUnlock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } else {
-                       $handlesToClose = [];
-                       --$this->locksHeld[$path][$type];
-                       if ( $this->locksHeld[$path][$type] <= 0 ) {
-                               unset( $this->locksHeld[$path][$type] );
-                       }
-                       if ( !count( $this->locksHeld[$path] ) ) {
-                               unset( $this->locksHeld[$path] ); // no locks on this path
-                               if ( isset( $this->handles[$path] ) ) {
-                                       $handlesToClose[] = $this->handles[$path];
-                                       unset( $this->handles[$path] );
-                               }
-                       }
-                       // Unlock handles to release locks and delete
-                       // any lock files that end up with no locks on them...
-                       if ( wfIsWindows() ) {
-                               // Windows: for any process, including this one,
-                               // calling unlink() on a locked file will fail
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                       } else {
-                               // Unix: unlink() can be used on files currently open by this
-                               // process and we must do so in order to avoid race conditions
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @param string $path
-        * @param array $handlesToClose
-        * @return StatusValue
-        */
-       private function closeLockHandles( $path, array $handlesToClose ) {
-               $status = Status::newGood();
-               foreach ( $handlesToClose as $handle ) {
-                       if ( !flock( $handle, LOCK_UN ) ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-                       if ( !fclose( $handle ) ) {
-                               $status->warning( 'lockmanager-fail-closelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @param string $path
-        * @return StatusValue
-        */
-       private function pruneKeyLockFiles( $path ) {
-               $status = Status::newGood();
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       # No locks are held for the lock file anymore
-                       if ( !unlink( $this->getLockPath( $path ) ) ) {
-                               $status->warning( 'lockmanager-fail-deletelock', $path );
-                       }
-                       unset( $this->handles[$path] );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Get the path to the lock file for a key
-        * @param string $path
-        * @return string
-        */
-       protected function getLockPath( $path ) {
-               return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               while ( count( $this->locksHeld ) ) {
-                       foreach ( $this->locksHeld as $path => $locks ) {
-                               $this->doSingleUnlock( $path, self::LOCK_EX );
-                               $this->doSingleUnlock( $path, self::LOCK_SH );
-                       }
-               }
-       }
-}
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
deleted file mode 100644 (file)
index eff031b..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-<?php
-/**
- * @defgroup LockManager Lock management
- * @ingroup FileBackend
- */
-
-/**
- * Resource locking handling.
- *
- * 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 LockManager
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for handling resource locking.
- *
- * Locks on resource keys can either be shared or exclusive.
- *
- * Implementations must keep track of what is locked by this proccess
- * in-memory and support nested locking calls (using reference counting).
- * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
- * Locks should either be non-blocking or have low wait timeouts.
- *
- * Subclasses should avoid throwing exceptions at all costs.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-abstract class LockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       /** @var array Map of (resource path => lock type => count) */
-       protected $locksHeld = [];
-
-       protected $domain; // string; domain (usually wiki ID)
-       protected $lockTTL; // integer; maximum time locks can be held
-
-       /** Lock types; stronger locks have higher values */
-       const LOCK_SH = 1; // shared lock (for reads)
-       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
-       const LOCK_EX = 3; // exclusive lock (for writes)
-
-       /**
-        * Construct a new instance from configuration
-        *
-        * @param array $config Parameters include:
-        *   - domain  : Domain (usually wiki ID) that all resources are relative to [optional]
-        *   - lockTTL : Age (in seconds) at which resource locks should expire.
-        *               This only applies if locks are not tied to a connection/process.
-        */
-       public function __construct( array $config ) {
-               $this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
-               if ( isset( $config['lockTTL'] ) ) {
-                       $this->lockTTL = max( 5, $config['lockTTL'] );
-               } elseif ( PHP_SAPI === 'cli' ) {
-                       $this->lockTTL = 3600;
-               } else {
-                       $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
-                       $this->lockTTL = max( 5 * 60, 2 * (int)$met );
-               }
-       }
-
-       /**
-        * Lock the resources at the given abstract paths
-        *
-        * @param array $paths List of resource names
-        * @param int $type LockManager::LOCK_* constant
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
-        * @return StatusValue
-        */
-       final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
-               return $this->lockByType( [ $type => $paths ], $timeout );
-       }
-
-       /**
-        * Lock the resources at the given abstract paths
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
-        * @return StatusValue
-        * @since 1.22
-        */
-       final public function lockByType( array $pathsByType, $timeout = 0 ) {
-               $pathsByType = $this->normalizePathsByType( $pathsByType );
-
-               $status = null;
-               $loop = new WaitConditionLoop(
-                       function () use ( &$status, $pathsByType ) {
-                               $status = $this->doLockByType( $pathsByType );
-
-                               return $status->isOK() ?: WaitConditionLoop::CONDITION_CONTINUE;
-                       },
-                       $timeout
-               );
-               $loop->invoke();
-
-               return $status;
-       }
-
-       /**
-        * Unlock the resources at the given abstract paths
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return StatusValue
-        */
-       final public function unlock( array $paths, $type = self::LOCK_EX ) {
-               return $this->unlockByType( [ $type => $paths ] );
-       }
-
-       /**
-        * Unlock the resources at the given abstract paths
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        * @since 1.22
-        */
-       final public function unlockByType( array $pathsByType ) {
-               $pathsByType = $this->normalizePathsByType( $pathsByType );
-               $status = $this->doUnlockByType( $pathsByType );
-
-               return $status;
-       }
-
-       /**
-        * Get the base 36 SHA-1 of a string, padded to 31 digits.
-        * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
-        *
-        * @param string $path
-        * @return string
-        */
-       final protected function sha1Base36Absolute( $path ) {
-               return Wikimedia\base_convert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
-       }
-
-       /**
-        * Get the base 16 SHA-1 of a string, padded to 31 digits.
-        * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
-        *
-        * @param string $path
-        * @return string
-        */
-       final protected function sha1Base16Absolute( $path ) {
-               return sha1( "{$this->domain}:{$path}" );
-       }
-
-       /**
-        * Normalize the $paths array by converting LOCK_UW locks into the
-        * appropriate type and removing any duplicated paths for each lock type.
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return array
-        * @since 1.22
-        */
-       final protected function normalizePathsByType( array $pathsByType ) {
-               $res = [];
-               foreach ( $pathsByType as $type => $paths ) {
-                       $res[$this->lockTypeMap[$type]] = array_unique( $paths );
-               }
-
-               return $res;
-       }
-
-       /**
-        * @see LockManager::lockByType()
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        * @since 1.22
-        */
-       protected function doLockByType( array $pathsByType ) {
-               $status = Status::newGood();
-               $lockedByType = []; // map of (type => paths)
-               foreach ( $pathsByType as $type => $paths ) {
-                       $status->merge( $this->doLock( $paths, $type ) );
-                       if ( $status->isOK() ) {
-                               $lockedByType[$type] = $paths;
-                       } else {
-                               // Release the subset of locks that were acquired
-                               foreach ( $lockedByType as $lType => $lPaths ) {
-                                       $status->merge( $this->doUnlock( $lPaths, $lType ) );
-                               }
-                               break;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lock resources with the given keys and lock type
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return StatusValue
-        */
-       abstract protected function doLock( array $paths, $type );
-
-       /**
-        * @see LockManager::unlockByType()
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        * @since 1.22
-        */
-       protected function doUnlockByType( array $pathsByType ) {
-               $status = Status::newGood();
-               foreach ( $pathsByType as $type => $paths ) {
-                       $status->merge( $this->doUnlock( $paths, $type ) );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Unlock resources with the given keys and lock type
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return StatusValue
-        */
-       abstract protected function doUnlock( array $paths, $type );
-}
-
-/**
- * Simple version of LockManager that does nothing
- * @since 1.19
- */
-class NullLockManager extends LockManager {
-       protected function doLock( array $paths, $type ) {
-               return Status::newGood();
-       }
-
-       protected function doUnlock( array $paths, $type ) {
-               return Status::newGood();
-       }
-}
index 2e2d0a3..81ce424 100644 (file)
@@ -90,7 +90,7 @@ class MemcLockManager extends QuorumLockManager {
 
        // @todo Change this code to work in one batch
        protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $lockedPaths = [];
                foreach ( $pathsByType as $type => $paths ) {
@@ -112,7 +112,7 @@ class MemcLockManager extends QuorumLockManager {
 
        // @todo Change this code to work in one batch
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                foreach ( $pathsByType as $type => $paths ) {
                        $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
@@ -129,7 +129,7 @@ class MemcLockManager extends QuorumLockManager {
         * @return StatusValue
         */
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $memc = $this->getCache( $lockSrv );
                $keys = array_map( [ $this, 'recordKeyForPath' ], $paths ); // lock records
@@ -205,7 +205,7 @@ class MemcLockManager extends QuorumLockManager {
         * @return StatusValue
         */
        protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $memc = $this->getCache( $lockSrv );
                $keys = array_map( [ $this, 'recordKeyForPath' ], $paths ); // lock records
@@ -257,7 +257,7 @@ class MemcLockManager extends QuorumLockManager {
         * @return StatusValue
         */
        protected function releaseAllLocks() {
-               return Status::newGood(); // not supported
+               return StatusValue::newGood(); // not supported
        }
 
        /**
index 896e0ff..124d410 100644 (file)
@@ -38,7 +38,7 @@ class MySqlLockManager extends DBLockManager {
         * @return StatusValue
         */
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
 
@@ -108,7 +108,7 @@ class MySqlLockManager extends DBLockManager {
         * @return StatusValue
         */
        protected function releaseAllLocks() {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                foreach ( $this->conns as $lockDb => $db ) {
                        if ( $db->trxLevel() ) { // in transaction
index 307c164..d6b1ce8 100644 (file)
@@ -14,7 +14,7 @@ class PostgreSqlLockManager extends DBLockManager {
        ];
 
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                if ( !count( $paths ) ) {
                        return $status; // nothing to lock
                }
@@ -64,7 +64,7 @@ class PostgreSqlLockManager extends DBLockManager {
         * @return StatusValue
         */
        protected function releaseAllLocks() {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                foreach ( $this->conns as $lockDb => $db ) {
                        try {
diff --git a/includes/filebackend/lockmanager/QuorumLockManager.php b/includes/filebackend/lockmanager/QuorumLockManager.php
deleted file mode 100644 (file)
index 0db9e81..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- *
- * 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 LockManager
- */
-
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- * The resource space can also be sharded into separate peer groups.
- *
- * @ingroup LockManager
- * @since 1.20
- */
-abstract class QuorumLockManager extends LockManager {
-       /** @var array Map of bucket indexes to peer server lists */
-       protected $srvsByBucket = []; // (bucket index => (lsrv1, lsrv2, ...))
-
-       /** @var array Map of degraded buckets */
-       protected $degradedBuckets = []; // (buckey index => UNIX timestamp)
-
-       final protected function doLock( array $paths, $type ) {
-               return $this->doLockByType( [ $type => $paths ] );
-       }
-
-       final protected function doUnlock( array $paths, $type ) {
-               return $this->doUnlockByType( [ $type => $paths ] );
-       }
-
-       protected function doLockByType( array $pathsByType ) {
-               $status = Status::newGood();
-
-               $pathsToLock = []; // (bucket => type => paths)
-               // Get locks that need to be acquired (buckets => locks)...
-               foreach ( $pathsByType as $type => $paths ) {
-                       foreach ( $paths as $path ) {
-                               if ( isset( $this->locksHeld[$path][$type] ) ) {
-                                       ++$this->locksHeld[$path][$type];
-                               } else {
-                                       $bucket = $this->getBucketFromPath( $path );
-                                       $pathsToLock[$bucket][$type][] = $path;
-                               }
-                       }
-               }
-
-               $lockedPaths = []; // files locked in this attempt (type => paths)
-               // Attempt to acquire these locks...
-               foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
-                       // Try to acquire the locks for this bucket
-                       $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
-                       if ( !$status->isOK() ) {
-                               $status->merge( $this->doUnlockByType( $lockedPaths ) );
-
-                               return $status;
-                       }
-                       // Record these locks as active
-                       foreach ( $pathsToLockByType as $type => $paths ) {
-                               foreach ( $paths as $path ) {
-                                       $this->locksHeld[$path][$type] = 1; // locked
-                                       // Keep track of what locks were made in this attempt
-                                       $lockedPaths[$type][] = $path;
-                               }
-                       }
-               }
-
-               return $status;
-       }
-
-       protected function doUnlockByType( array $pathsByType ) {
-               $status = Status::newGood();
-
-               $pathsToUnlock = []; // (bucket => type => paths)
-               foreach ( $pathsByType as $type => $paths ) {
-                       foreach ( $paths as $path ) {
-                               if ( !isset( $this->locksHeld[$path][$type] ) ) {
-                                       $status->warning( 'lockmanager-notlocked', $path );
-                               } else {
-                                       --$this->locksHeld[$path][$type];
-                                       // Reference count the locks held and release locks when zero
-                                       if ( $this->locksHeld[$path][$type] <= 0 ) {
-                                               unset( $this->locksHeld[$path][$type] );
-                                               $bucket = $this->getBucketFromPath( $path );
-                                               $pathsToUnlock[$bucket][$type][] = $path;
-                                       }
-                                       if ( !count( $this->locksHeld[$path] ) ) {
-                                               unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
-                                       }
-                               }
-                       }
-               }
-
-               // Remove these specific locks if possible, or at least release
-               // all locks once this process is currently not holding any locks.
-               foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
-                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
-               }
-               if ( !count( $this->locksHeld ) ) {
-                       $status->merge( $this->releaseAllLocks() );
-                       $this->degradedBuckets = []; // safe to retry the normal quorum
-               }
-
-               return $status;
-       }
-
-       /**
-        * Attempt to acquire locks with the peers for a bucket.
-        * This is all or nothing; if any key is locked then this totally fails.
-        *
-        * @param int $bucket
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        */
-       final protected function doLockingRequestBucket( $bucket, array $pathsByType ) {
-               $status = Status::newGood();
-
-               $yesVotes = 0; // locks made on trustable servers
-               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
-               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
-               // Get votes for each peer, in order, until we have enough...
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               --$votesLeft;
-                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
-                               $this->degradedBuckets[$bucket] = time();
-                               continue; // server down?
-                       }
-                       // Attempt to acquire the lock on this peer
-                       $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) );
-                       if ( !$status->isOK() ) {
-                               return $status; // vetoed; resource locked
-                       }
-                       ++$yesVotes; // success for this peer
-                       if ( $yesVotes >= $quorum ) {
-                               return $status; // lock obtained
-                       }
-                       --$votesLeft;
-                       $votesNeeded = $quorum - $yesVotes;
-                       if ( $votesNeeded > $votesLeft ) {
-                               break; // short-circuit
-                       }
-               }
-               // At this point, we must not have met the quorum
-               $status->setResult( false );
-
-               return $status;
-       }
-
-       /**
-        * Attempt to release locks with the peers for a bucket
-        *
-        * @param int $bucket
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        */
-       final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) {
-               $status = Status::newGood();
-
-               $yesVotes = 0; // locks freed on trustable servers
-               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
-               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
-               $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum?
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               $status->warning( 'lockmanager-fail-svr-release', $lockSrv );
-                       } else {
-                               // Attempt to release the lock on this peer
-                               $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
-                               ++$yesVotes; // success for this peer
-                               // Normally the first peers form the quorum, and the others are ignored.
-                               // Ignore them in this case, but not when an alternative quorum was used.
-                               if ( $yesVotes >= $quorum && !$isDegraded ) {
-                                       break; // lock released
-                               }
-                       }
-               }
-               // Set a bad StatusValue if the quorum was not met.
-               // Assumes the same "up" servers as during the acquire step.
-               $status->setResult( $yesVotes >= $quorum );
-
-               return $status;
-       }
-
-       /**
-        * Get the bucket for resource path.
-        * This should avoid throwing any exceptions.
-        *
-        * @param string $path
-        * @return int
-        */
-       protected function getBucketFromPath( $path ) {
-               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
-               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
-       }
-
-       /**
-        * Check if a lock server is up.
-        * This should process cache results to reduce RTT.
-        *
-        * @param string $lockSrv
-        * @return bool
-        */
-       abstract protected function isServerUp( $lockSrv );
-
-       /**
-        * Get a connection to a lock server and acquire locks
-        *
-        * @param string $lockSrv
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        */
-       abstract protected function getLocksOnServer( $lockSrv, array $pathsByType );
-
-       /**
-        * Get a connection to a lock server and release locks on $paths.
-        *
-        * Subclasses must effectively implement this or releaseAllLocks().
-        *
-        * @param string $lockSrv
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return StatusValue
-        */
-       abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType );
-
-       /**
-        * Release all locks that this session is holding.
-        *
-        * Subclasses must effectively implement this or freeLocksOnServer().
-        *
-        * @return StatusValue
-        */
-       abstract protected function releaseAllLocks();
-}
index 4121ecb..6fd819d 100644 (file)
@@ -79,7 +79,7 @@ class RedisLockManager extends QuorumLockManager {
        }
 
        protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $pathList = call_user_func_array( 'array_merge', array_values( $pathsByType ) );
 
@@ -172,7 +172,7 @@ LUA;
        }
 
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $pathList = call_user_func_array( 'array_merge', array_values( $pathsByType ) );
 
@@ -242,7 +242,7 @@ LUA;
        }
 
        protected function releaseAllLocks() {
-               return Status::newGood(); // not supported
+               return StatusValue::newGood(); // not supported
        }
 
        protected function isServerUp( $lockSrv ) {
index b8b1cf6..8fee3bf 100644 (file)
@@ -825,7 +825,7 @@ class FileRepo {
 
                $status = $this->storeBatch( [ [ $srcPath, $dstZone, $dstRel ] ], $flags );
                if ( $status->successCount == 0 ) {
-                       $status->ok = false;
+                       $status->setOK( false );
                }
 
                return $status;
@@ -1166,7 +1166,7 @@ class FileRepo {
                $status = $this->publishBatch(
                        [ [ $src, $dstRel, $archiveRel, $options ] ], $flags );
                if ( $status->successCount == 0 ) {
-                       $status->ok = false;
+                       $status->setOK( false );
                }
                if ( isset( $status->value[0] ) ) {
                        $status->value = $status->value[0];
index f8b1ed9..55df1af 100644 (file)
@@ -42,6 +42,9 @@ class ForeignDBViaLBRepo extends LocalRepo {
        /** @var array */
        protected $fileFromRowFactory = [ 'ForeignDBFile', 'newFromRow' ];
 
+       /** @var bool */
+       protected $hasSharedCache;
+
        /**
         * @param array|null $info
         */
index d515b05..bd32de0 100644 (file)
@@ -135,17 +135,18 @@ class RepoGroup {
                }
 
                # Check the cache
+               $dbkey = $title->getDBkey();
                if ( empty( $options['ignoreRedirect'] )
                        && empty( $options['private'] )
                        && empty( $options['bypassCache'] )
                ) {
                        $time = isset( $options['time'] ) ? $options['time'] : '';
-                       $dbkey = $title->getDBkey();
                        if ( $this->cache->has( $dbkey, $time, 60 ) ) {
                                return $this->cache->get( $dbkey, $time );
                        }
                        $useCache = true;
                } else {
+                       $time = false;
                        $useCache = false;
                }
 
index d1e683a..921e129 100644 (file)
@@ -425,6 +425,7 @@ class ArchivedFile {
         */
        function pageCount() {
                if ( !isset( $this->pageCount ) ) {
+                       // @FIXME: callers expect File objects
                        if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
                                $this->pageCount = $this->handler->pageCount( $this );
                        } else {
index f6752d8..43b6855 100644 (file)
  * @ingroup FileAbstraction
  */
 class ForeignAPIFile extends File {
+       /** @var bool */
        private $mExists;
+       /** @var array */
+       private $mInfo = [];
 
        protected $repoClass = 'ForeignApiRepo';
 
@@ -244,7 +247,7 @@ class ForeignAPIFile extends File {
        public function getUser( $type = 'text' ) {
                if ( $type == 'text' ) {
                        return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
-               } elseif ( $type == 'id' ) {
+               } else {
                        return 0; // What makes sense here, for a remote user?
                }
        }
@@ -344,9 +347,6 @@ class ForeignAPIFile extends File {
                return $files;
        }
 
-       /**
-        * @see File::purgeCache()
-        */
        function purgeCache( $options = [] ) {
                $this->purgeThumbnails( $options );
                $this->purgeDescriptionPage();
index 618272c..396b47c 100644 (file)
@@ -1480,8 +1480,10 @@ class LocalFile extends File {
                                                );
 
                                                if ( isset( $status->value['revision'] ) ) {
+                                                       /** @var $rev Revision */
+                                                       $rev = $status->value['revision'];
                                                        // Associate new page revision id
-                                                       $logEntry->setAssociatedRevId( $status->value['revision']->getId() );
+                                                       $logEntry->setAssociatedRevId( $rev->getId() );
                                                }
                                                // This relies on the resetArticleID() call in WikiPage::insertOn(),
                                                // which is triggered on $descTitle by doEditContent() above.
@@ -2692,7 +2694,7 @@ class LocalFileRestoreBatch {
                                // Even if some files could be copied, fail entirely as that is the
                                // easiest thing to do without data loss
                                $this->cleanupFailedBatch( $storeStatus, $storeBatch );
-                               $status->ok = false;
+                               $status->setOK( false );
                                $this->file->unlock();
 
                                return $status;
@@ -2952,7 +2954,7 @@ class LocalFileMoveBatch {
                if ( !$statusDb->isGood() ) {
                        $destFile->unlock();
                        $this->file->unlock();
-                       $statusDb->ok = false;
+                       $statusDb->setOK( false );
 
                        return $statusDb;
                }
@@ -2971,7 +2973,7 @@ class LocalFileMoveBatch {
                                $this->file->unlock();
                                wfDebugLog( 'imagemove', "Error in moving files: "
                                        . $statusMove->getWikiText( false, false, 'en' ) );
-                               $statusMove->ok = false;
+                               $statusMove->setOK( false );
 
                                return $statusMove;
                        }
index f6527b8..0f889da 100644 (file)
@@ -238,8 +238,8 @@ class TraditionalImageGallery extends ImageGalleryBase {
        }
 
        /**
-        * How much padding such the thumb have between image and inner div that
-        * that contains the border. This is both for verical and horizontal
+        * How much padding the thumb has between the image and the inner div
+        * that contains the border. This is for both vertical and horizontal
         * padding. (However, it is cut in half in the vertical direction).
         * @return int
         */
index ded2bd8..4f10367 100644 (file)
@@ -334,8 +334,7 @@ abstract class DatabaseInstaller {
 
                $connection = $status->value;
                $services->redefineService( 'DBLoadBalancerFactory', function() use ( $connection ) {
-                       return new LBFactorySingle( [
-                               'connection' => $connection ] );
+                       return LBFactorySingle::newFromConnection( $connection );
                } );
 
        }
index d59c162..9bf87f4 100644 (file)
@@ -179,16 +179,12 @@ class SqliteInstaller extends DatabaseInstaller {
         * @return Status
         */
        public function openConnection() {
-               global $wgSQLiteDataDir;
-
                $status = Status::newGood();
                $dir = $this->getVar( 'wgSQLiteDataDir' );
                $dbName = $this->getVar( 'wgDBname' );
                try {
                        # @todo FIXME: Need more sensible constructor parameters, e.g. single associative array
-                       # Setting globals kind of sucks
-                       $wgSQLiteDataDir = $dir;
-                       $db = DatabaseBase::factory( 'sqlite', [ 'dbname' => $dbName ] );
+                       $db = DatabaseBase::factory( 'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ] );
                        $status->value = $db;
                } catch ( DBConnectionError $e ) {
                        $status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
@@ -243,10 +239,7 @@ class SqliteInstaller extends DatabaseInstaller {
 
                # Create the global cache DB
                try {
-                       global $wgSQLiteDataDir;
-                       # @todo FIXME: setting globals kind of sucks
-                       $wgSQLiteDataDir = $dir;
-                       $conn = DatabaseBase::factory( 'sqlite', [ 'dbname' => "wikicache" ] );
+                       $conn = DatabaseBase::factory( 'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
                        # @todo: don't duplicate objectcache definition, though it's very simple
                        $sql =
 <<<EOT
@@ -326,6 +319,7 @@ EOT;
                'type' => 'sqlite',
                'dbname' => 'wikicache',
                'tablePrefix' => '',
+               'dbDirectory' => \$wgSQLiteDataDir,
                'flags' => 0
        ]
 ];";
index 6ed2722..dc24830 100644 (file)
@@ -18,7 +18,8 @@
                        "C.R.",
                        "Macofe",
                        "Matteocng",
-                       "Einreiher"
+                       "Einreiher",
+                       "Tosky"
                ]
        },
        "config-desc": "Programma di installazione per MediaWiki",
        "config-subscribe-help": "Si tratta di una mailing list a basso traffico dedicata agli annunci di nuove versioni, compresi importanti segnalazioni riguardanti la sicurezza.\nÈ consigliato iscriversi e aggiornare la propria installazione di MediaWiki quando una nuova versione viene resa pubblica.",
        "config-subscribe-noemail": "Hai provato ad iscriverti alla mailing list dedicata agli annunci delle nuove versioni senza fornire un indirizzo email.\nInserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
        "config-pingback": "Condividi i dati su questa installazione con gli sviluppatori di MediaWiki.",
-       "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare la wiki.",
+       "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare il wiki.",
        "config-optional-continue": "Fammi altre domande.",
        "config-optional-skip": "Sono già stanco, installa solo il wiki.",
        "config-profile": "Profilo dei diritti utente:",
index 45185c5..bff9abd 100644 (file)
@@ -58,7 +58,7 @@ class StatusValue {
         * Factory function for fatal errors
         *
         * @param string|MessageSpecifier $message Message key or object
-        * @return StatusValue
+        * @return static
         */
        public static function newFatal( $message /*, parameters...*/ ) {
                $params = func_get_args();
@@ -71,7 +71,7 @@ class StatusValue {
         * Factory function for good results
         *
         * @param mixed $value
-        * @return StatusValue
+        * @return static
         */
        public static function newGood( $value = null ) {
                $result = new static();
diff --git a/includes/libs/lockmanager/FSLockManager.php b/includes/libs/lockmanager/FSLockManager.php
new file mode 100644 (file)
index 0000000..7f33a0a
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+/**
+ * Simple version of LockManager based on using FS lock files.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Simple version of LockManager based on using FS lock files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * This should work fine for small sites running off one server.
+ * Do not use this with 'lockDirectory' set to an NFS mount unless the
+ * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
+ * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class FSLockManager extends LockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       /** @var string Global dir for all servers */
+       protected $lockDir;
+
+       /** @var array Map of (locked key => lock file handle) */
+       protected $handles = [];
+
+       /** @var bool */
+       protected $isWindows;
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * @param array $config Includes:
+        *   - lockDirectory : Directory containing the lock files
+        */
+       function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->lockDir = $config['lockDirectory'];
+               $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
+       }
+
+       /**
+        * @see LockManager::doLock()
+        * @param array $paths
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doLock( array $paths, $type ) {
+               $status = StatusValue::newGood();
+
+               $lockedPaths = []; // files locked in this attempt
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleLock( $path, $type ) );
+                       if ( $status->isOK() ) {
+                               $lockedPaths[] = $path;
+                       } else {
+                               // Abort and unlock everything
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see LockManager::doUnlock()
+        * @param array $paths
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doUnlock( array $paths, $type ) {
+               $status = StatusValue::newGood();
+
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleUnlock( $path, $type ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lock a single resource key
+        *
+        * @param string $path
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doSingleLock( $path, $type ) {
+               $status = StatusValue::newGood();
+
+               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                       ++$this->locksHeld[$path][$type];
+               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                       $this->locksHeld[$path][$type] = 1;
+               } else {
+                       if ( isset( $this->handles[$path] ) ) {
+                               $handle = $this->handles[$path];
+                       } else {
+                               MediaWiki\suppressWarnings();
+                               $handle = fopen( $this->getLockPath( $path ), 'a+' );
+                               if ( !$handle ) { // lock dir missing?
+                                       mkdir( $this->lockDir, 0777, true );
+                                       $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+                               }
+                               MediaWiki\restoreWarnings();
+                       }
+                       if ( $handle ) {
+                               // Either a shared or exclusive lock
+                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
+                               if ( flock( $handle, $lock | LOCK_NB ) ) {
+                                       // Record this lock as active
+                                       $this->locksHeld[$path][$type] = 1;
+                                       $this->handles[$path] = $handle;
+                               } else {
+                                       fclose( $handle );
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                               }
+                       } else {
+                               $status->fatal( 'lockmanager-fail-openlock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Unlock a single resource key
+        *
+        * @param string $path
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doSingleUnlock( $path, $type ) {
+               $status = StatusValue::newGood();
+
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } else {
+                       $handlesToClose = [];
+                       --$this->locksHeld[$path][$type];
+                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                               unset( $this->locksHeld[$path][$type] );
+                       }
+                       if ( !count( $this->locksHeld[$path] ) ) {
+                               unset( $this->locksHeld[$path] ); // no locks on this path
+                               if ( isset( $this->handles[$path] ) ) {
+                                       $handlesToClose[] = $this->handles[$path];
+                                       unset( $this->handles[$path] );
+                               }
+                       }
+                       // Unlock handles to release locks and delete
+                       // any lock files that end up with no locks on them...
+                       if ( $this->isWindows ) {
+                               // Windows: for any process, including this one,
+                               // calling unlink() on a locked file will fail
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                       } else {
+                               // Unix: unlink() can be used on files currently open by this
+                               // process and we must do so in order to avoid race conditions
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @param string $path
+        * @param array $handlesToClose
+        * @return StatusValue
+        */
+       private function closeLockHandles( $path, array $handlesToClose ) {
+               $status = StatusValue::newGood();
+               foreach ( $handlesToClose as $handle ) {
+                       if ( !flock( $handle, LOCK_UN ) ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+                       if ( !fclose( $handle ) ) {
+                               $status->warning( 'lockmanager-fail-closelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @param string $path
+        * @return StatusValue
+        */
+       private function pruneKeyLockFiles( $path ) {
+               $status = StatusValue::newGood();
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       # No locks are held for the lock file anymore
+                       if ( !unlink( $this->getLockPath( $path ) ) ) {
+                               $status->warning( 'lockmanager-fail-deletelock', $path );
+                       }
+                       unset( $this->handles[$path] );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get the path to the lock file for a key
+        * @param string $path
+        * @return string
+        */
+       protected function getLockPath( $path ) {
+               return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               while ( count( $this->locksHeld ) ) {
+                       foreach ( $this->locksHeld as $path => $locks ) {
+                               $this->doSingleUnlock( $path, self::LOCK_EX );
+                               $this->doSingleUnlock( $path, self::LOCK_SH );
+                       }
+               }
+       }
+}
diff --git a/includes/libs/lockmanager/LockManager.php b/includes/libs/lockmanager/LockManager.php
new file mode 100644 (file)
index 0000000..80add5b
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+/**
+ * @defgroup LockManager Lock management
+ * @ingroup FileBackend
+ */
+
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for handling resource locking.
+ *
+ * Locks on resource keys can either be shared or exclusive.
+ *
+ * Implementations must keep track of what is locked by this proccess
+ * in-memory and support nested locking calls (using reference counting).
+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
+ * Locks should either be non-blocking or have low wait timeouts.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+abstract class LockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       /** @var array Map of (resource path => lock type => count) */
+       protected $locksHeld = [];
+
+       protected $domain; // string; domain (usually wiki ID)
+       protected $lockTTL; // integer; maximum time locks can be held
+
+       /** Lock types; stronger locks have higher values */
+       const LOCK_SH = 1; // shared lock (for reads)
+       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+       const LOCK_EX = 3; // exclusive lock (for writes)
+
+       /**
+        * Construct a new instance from configuration
+        *
+        * @param array $config Parameters include:
+        *   - domain  : Domain (usually wiki ID) that all resources are relative to [optional]
+        *   - lockTTL : Age (in seconds) at which resource locks should expire.
+        *               This only applies if locks are not tied to a connection/process.
+        */
+       public function __construct( array $config ) {
+               $this->domain = isset( $config['domain'] ) ? $config['domain'] : 'global';
+               if ( isset( $config['lockTTL'] ) ) {
+                       $this->lockTTL = max( 5, $config['lockTTL'] );
+               } elseif ( PHP_SAPI === 'cli' ) {
+                       $this->lockTTL = 3600;
+               } else {
+                       $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
+                       $this->lockTTL = max( 5 * 60, 2 * (int)$met );
+               }
+       }
+
+       /**
+        * Lock the resources at the given abstract paths
+        *
+        * @param array $paths List of resource names
+        * @param int $type LockManager::LOCK_* constant
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+        * @return StatusValue
+        */
+       final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
+               return $this->lockByType( [ $type => $paths ], $timeout );
+       }
+
+       /**
+        * Lock the resources at the given abstract paths
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+        * @return StatusValue
+        * @since 1.22
+        */
+       final public function lockByType( array $pathsByType, $timeout = 0 ) {
+               $pathsByType = $this->normalizePathsByType( $pathsByType );
+
+               $status = null;
+               $loop = new WaitConditionLoop(
+                       function () use ( &$status, $pathsByType ) {
+                               $status = $this->doLockByType( $pathsByType );
+
+                               return $status->isOK() ?: WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+               $loop->invoke();
+
+               return $status;
+       }
+
+       /**
+        * Unlock the resources at the given abstract paths
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       final public function unlock( array $paths, $type = self::LOCK_EX ) {
+               return $this->unlockByType( [ $type => $paths ] );
+       }
+
+       /**
+        * Unlock the resources at the given abstract paths
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       final public function unlockByType( array $pathsByType ) {
+               $pathsByType = $this->normalizePathsByType( $pathsByType );
+               $status = $this->doUnlockByType( $pathsByType );
+
+               return $status;
+       }
+
+       /**
+        * Get the base 36 SHA-1 of a string, padded to 31 digits.
+        * Before hashing, the path will be prefixed with the domain ID.
+        * This should be used interally for lock key or file names.
+        *
+        * @param string $path
+        * @return string
+        */
+       final protected function sha1Base36Absolute( $path ) {
+               return Wikimedia\base_convert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
+       }
+
+       /**
+        * Get the base 16 SHA-1 of a string, padded to 31 digits.
+        * Before hashing, the path will be prefixed with the domain ID.
+        * This should be used interally for lock key or file names.
+        *
+        * @param string $path
+        * @return string
+        */
+       final protected function sha1Base16Absolute( $path ) {
+               return sha1( "{$this->domain}:{$path}" );
+       }
+
+       /**
+        * Normalize the $paths array by converting LOCK_UW locks into the
+        * appropriate type and removing any duplicated paths for each lock type.
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return array
+        * @since 1.22
+        */
+       final protected function normalizePathsByType( array $pathsByType ) {
+               $res = [];
+               foreach ( $pathsByType as $type => $paths ) {
+                       $res[$this->lockTypeMap[$type]] = array_unique( $paths );
+               }
+
+               return $res;
+       }
+
+       /**
+        * @see LockManager::lockByType()
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       protected function doLockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+               $lockedByType = []; // map of (type => paths)
+               foreach ( $pathsByType as $type => $paths ) {
+                       $status->merge( $this->doLock( $paths, $type ) );
+                       if ( $status->isOK() ) {
+                               $lockedByType[$type] = $paths;
+                       } else {
+                               // Release the subset of locks that were acquired
+                               foreach ( $lockedByType as $lType => $lPaths ) {
+                                       $status->merge( $this->doUnlock( $lPaths, $lType ) );
+                               }
+                               break;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lock resources with the given keys and lock type
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       abstract protected function doLock( array $paths, $type );
+
+       /**
+        * @see LockManager::unlockByType()
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       protected function doUnlockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+               foreach ( $pathsByType as $type => $paths ) {
+                       $status->merge( $this->doUnlock( $paths, $type ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Unlock resources with the given keys and lock type
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       abstract protected function doUnlock( array $paths, $type );
+}
diff --git a/includes/libs/lockmanager/NullLockManager.php b/includes/libs/lockmanager/NullLockManager.php
new file mode 100644 (file)
index 0000000..5ad558f
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * Simple version of LockManager that does nothing
+ * @since 1.19
+ */
+class NullLockManager extends LockManager {
+       protected function doLock( array $paths, $type ) {
+               return StatusValue::newGood();
+       }
+
+       protected function doUnlock( array $paths, $type ) {
+               return StatusValue::newGood();
+       }
+}
diff --git a/includes/libs/lockmanager/QuorumLockManager.php b/includes/libs/lockmanager/QuorumLockManager.php
new file mode 100644 (file)
index 0000000..8b5e7fd
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ * The resource space can also be sharded into separate peer groups.
+ *
+ * @ingroup LockManager
+ * @since 1.20
+ */
+abstract class QuorumLockManager extends LockManager {
+       /** @var array Map of bucket indexes to peer server lists */
+       protected $srvsByBucket = []; // (bucket index => (lsrv1, lsrv2, ...))
+
+       /** @var array Map of degraded buckets */
+       protected $degradedBuckets = []; // (buckey index => UNIX timestamp)
+
+       final protected function doLock( array $paths, $type ) {
+               return $this->doLockByType( [ $type => $paths ] );
+       }
+
+       final protected function doUnlock( array $paths, $type ) {
+               return $this->doUnlockByType( [ $type => $paths ] );
+       }
+
+       protected function doLockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $pathsToLock = []; // (bucket => type => paths)
+               // Get locks that need to be acquired (buckets => locks)...
+               foreach ( $pathsByType as $type => $paths ) {
+                       foreach ( $paths as $path ) {
+                               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                                       ++$this->locksHeld[$path][$type];
+                               } else {
+                                       $bucket = $this->getBucketFromPath( $path );
+                                       $pathsToLock[$bucket][$type][] = $path;
+                               }
+                       }
+               }
+
+               $lockedPaths = []; // files locked in this attempt (type => paths)
+               // Attempt to acquire these locks...
+               foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
+                       // Try to acquire the locks for this bucket
+                       $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
+                       if ( !$status->isOK() ) {
+                               $status->merge( $this->doUnlockByType( $lockedPaths ) );
+
+                               return $status;
+                       }
+                       // Record these locks as active
+                       foreach ( $pathsToLockByType as $type => $paths ) {
+                               foreach ( $paths as $path ) {
+                                       $this->locksHeld[$path][$type] = 1; // locked
+                                       // Keep track of what locks were made in this attempt
+                                       $lockedPaths[$type][] = $path;
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
+       protected function doUnlockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $pathsToUnlock = []; // (bucket => type => paths)
+               foreach ( $pathsByType as $type => $paths ) {
+                       foreach ( $paths as $path ) {
+                               if ( !isset( $this->locksHeld[$path][$type] ) ) {
+                                       $status->warning( 'lockmanager-notlocked', $path );
+                               } else {
+                                       --$this->locksHeld[$path][$type];
+                                       // Reference count the locks held and release locks when zero
+                                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                                               unset( $this->locksHeld[$path][$type] );
+                                               $bucket = $this->getBucketFromPath( $path );
+                                               $pathsToUnlock[$bucket][$type][] = $path;
+                                       }
+                                       if ( !count( $this->locksHeld[$path] ) ) {
+                                               unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+                                       }
+                               }
+                       }
+               }
+
+               // Remove these specific locks if possible, or at least release
+               // all locks once this process is currently not holding any locks.
+               foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
+                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
+               }
+               if ( !count( $this->locksHeld ) ) {
+                       $status->merge( $this->releaseAllLocks() );
+                       $this->degradedBuckets = []; // safe to retry the normal quorum
+               }
+
+               return $status;
+       }
+
+       /**
+        * Attempt to acquire locks with the peers for a bucket.
+        * This is all or nothing; if any key is locked then this totally fails.
+        *
+        * @param int $bucket
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       final protected function doLockingRequestBucket( $bucket, array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $yesVotes = 0; // locks made on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
+               // Get votes for each peer, in order, until we have enough...
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               --$votesLeft;
+                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
+                               $this->degradedBuckets[$bucket] = time();
+                               continue; // server down?
+                       }
+                       // Attempt to acquire the lock on this peer
+                       $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) );
+                       if ( !$status->isOK() ) {
+                               return $status; // vetoed; resource locked
+                       }
+                       ++$yesVotes; // success for this peer
+                       if ( $yesVotes >= $quorum ) {
+                               return $status; // lock obtained
+                       }
+                       --$votesLeft;
+                       $votesNeeded = $quorum - $yesVotes;
+                       if ( $votesNeeded > $votesLeft ) {
+                               break; // short-circuit
+                       }
+               }
+               // At this point, we must not have met the quorum
+               $status->setResult( false );
+
+               return $status;
+       }
+
+       /**
+        * Attempt to release locks with the peers for a bucket
+        *
+        * @param int $bucket
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $yesVotes = 0; // locks freed on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
+               $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum?
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               $status->warning( 'lockmanager-fail-svr-release', $lockSrv );
+                       } else {
+                               // Attempt to release the lock on this peer
+                               $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
+                               ++$yesVotes; // success for this peer
+                               // Normally the first peers form the quorum, and the others are ignored.
+                               // Ignore them in this case, but not when an alternative quorum was used.
+                               if ( $yesVotes >= $quorum && !$isDegraded ) {
+                                       break; // lock released
+                               }
+                       }
+               }
+               // Set a bad StatusValue if the quorum was not met.
+               // Assumes the same "up" servers as during the acquire step.
+               $status->setResult( $yesVotes >= $quorum );
+
+               return $status;
+       }
+
+       /**
+        * Get the bucket for resource path.
+        * This should avoid throwing any exceptions.
+        *
+        * @param string $path
+        * @return int
+        */
+       protected function getBucketFromPath( $path ) {
+               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
+       }
+
+       /**
+        * Check if a lock server is up.
+        * This should process cache results to reduce RTT.
+        *
+        * @param string $lockSrv
+        * @return bool
+        */
+       abstract protected function isServerUp( $lockSrv );
+
+       /**
+        * Get a connection to a lock server and acquire locks
+        *
+        * @param string $lockSrv
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       abstract protected function getLocksOnServer( $lockSrv, array $pathsByType );
+
+       /**
+        * Get a connection to a lock server and release locks on $paths.
+        *
+        * Subclasses must effectively implement this or releaseAllLocks().
+        *
+        * @param string $lockSrv
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType );
+
+       /**
+        * Release all locks that this session is holding.
+        *
+        * Subclasses must effectively implement this or freeLocksOnServer().
+        *
+        * @return StatusValue
+        */
+       abstract protected function releaseAllLocks();
+}
index 0d9b692..2375678 100644 (file)
@@ -308,7 +308,7 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function makeList( $a, $mode = LIST_COMMA ) {
+       public function makeList( $a, $mode = self::LIST_COMMA ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
index de0de6e..a5b9284 100644 (file)
@@ -231,16 +231,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        protected $trxProfiler;
 
        /**
-        * Constructor.
-        *
-        * FIXME: It is possible to construct a Database object with no associated
-        * connection object, by specifying no parameters to __construct(). This
-        * feature is deprecated and should be removed.
+        * Constructor and database handle and attempt to connect to the DB server
         *
         * IDatabase classes should not be constructed directly in external
-        * code. DatabaseBase::factory() should be used instead.
+        * code. Database::factory() should be used instead.
         *
-        * @param array $params Parameters passed from DatabaseBase::factory()
+        * @param array $params Parameters passed from Database::factory()
         */
        function __construct( array $params ) {
                $server = $params['host'];
@@ -287,37 +283,59 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
                if ( $user ) {
                        $this->open( $server, $user, $password, $dbName );
+               } elseif ( $this->requiresDatabaseUser() ) {
+                       throw new InvalidArgumentException( "No database user provided." );
                }
 
+               // Set the domain object after open() sets the relevant fields
                $this->currentDomain = ( $this->mDBname != '' )
                        ? new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix )
                        : DatabaseDomain::newUnspecified();
        }
 
        /**
-        * Given a DB type, construct the name of the appropriate child class of
-        * IDatabase. This is designed to replace all of the manual stuff like:
-        *    $class = 'Database' . ucfirst( strtolower( $dbType ) );
-        * as well as validate against the canonical list of DB types we have
-        *
-        * This factory function is mostly useful for when you need to connect to a
-        * database other than the MediaWiki default (such as for external auth,
-        * an extension, et cetera). Do not use this to connect to the MediaWiki
-        * database. Example uses in core:
-        * @see LoadBalancer::reallyOpenConnection()
-        * @see ForeignDBRepo::getMasterDB()
-        * @see WebInstallerDBConnect::execute()
-        *
-        * @since 1.18
-        *
-        * @param string $dbType A possible DB type
-        * @param array $p An array of options to pass to the constructor.
-        *    Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
-        * @return IDatabase|null If the database driver or extension cannot be found
+        * Construct a Database subclass instance given a database type and parameters
+        *
+        * This also connects to the database immediately upon object construction
+        *
+        * @param string $dbType A possible DB type (sqlite, mysql, postgres)
+        * @param array $p Parameter map with keys:
+        *   - host : The hostname of the DB server
+        *   - user : The name of the database user the client operates under
+        *   - password : The password for the database user
+        *   - dbname : The name of the database to use where queries do not specify one.
+        *      The database must exist or an error might be thrown. Setting this to the empty string
+        *      will avoid any such errors and make the handle have no implicit database scope. This is
+        *      useful for queries like SHOW STATUS, CREATE DATABASE, or DROP DATABASE. Note that a
+        *      "database" in Postgres is rougly equivalent to an entire MySQL server. This the domain
+        *      in which user names and such are defined, e.g. users are database-specific in Postgres.
+        *   - schema : The database schema to use (if supported). A "schema" in Postgres is roughly
+        *      equivalent to a "database" in MySQL. Note that MySQL and SQLite do not use schemas.
+        *   - tablePrefix : Optional table prefix that is implicitly added on to all table names
+        *      recognized in queries. This can be used in place of schemas for handle site farms.
+        *   - flags : Optional bitfield of DBO_* constants that define connection, protocol,
+        *      buffering, and transaction behavior. It is STRONGLY adviced to leave the DBO_DEFAULT
+        *      flag in place UNLESS this this database simply acts as a key/value store.
+        *   - driver: Optional name of a specific DB client driver. For MySQL, there is the old
+        *      'mysql' driver and the newer 'mysqli' driver.
+        *   - variables: Optional map of session variables to set after connecting. This can be
+        *      used to adjust lock timeouts or encoding modes and the like.
+        *   - connLogger: Optional PSR-3 logger interface instance.
+        *   - queryLogger: Optional PSR-3 logger interface instance.
+        *   - profiler: Optional class name or object with profileIn()/profileOut() methods.
+        *      These will be called in query(), using a simplified version of the SQL that also
+        *      includes the agent as a SQL comment.
+        *   - trxProfiler: Optional TransactionProfiler instance.
+        *   - errorLogger: Optional callback that takes an Exception and logs it.
+        *   - cliMode: Whether to consider the execution context that of a CLI script.
+        *   - agent: Optional name used to identify the end-user in query profiling/logging.
+        *   - srvCache: Optional BagOStuff instance to an APC-style cache.
+        * @return Database|null If the database driver or extension cannot be found
         * @throws InvalidArgumentException If the database driver or extension cannot be found
+        * @since 1.18
         */
        final public static function factory( $dbType, $p = [] ) {
-               $canonicalDBTypes = [
+               static $canonicalDBTypes = [
                        'mysql' => [ 'mysqli', 'mysql' ],
                        'postgres' => [],
                        'sqlite' => [],
@@ -667,7 +685,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
-        * Create a log context to pass to PSR logging functions.
+        * Create a log context to pass to PSR-3 logger functions.
         *
         * @param array $extras Additional data to add to context
         * @return array
@@ -903,7 +921,11 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $this->trxProfiler->recordQueryCompletion(
                        $queryProf, $startTime, $isWrite, $this->affectedRows()
                );
-               MWDebug::query( $sql, $fname, $isMaster, $queryRuntime );
+               $this->queryLogger->debug( $sql, [
+                       'method' => $fname,
+                       'master' => $isMaster,
+                       'runtime' => $queryRuntime,
+               ] );
 
                return $ret;
        }
@@ -1159,7 +1181,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                }
                if ( isset( $options['HAVING'] ) ) {
                        $having = is_array( $options['HAVING'] )
-                               ? $this->makeList( $options['HAVING'], LIST_AND )
+                               ? $this->makeList( $options['HAVING'], self::LIST_AND )
                                : $options['HAVING'];
                        $sql .= ' HAVING ' . $having;
                }
@@ -1229,7 +1251,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
                if ( !empty( $conds ) ) {
                        if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
+                               $conds = $this->makeList( $conds, self::LIST_AND );
                        }
                        $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail";
                } else {
@@ -1463,16 +1485,16 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
                $table = $this->tableName( $table );
                $opts = $this->makeUpdateOptions( $options );
-               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
+               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
 
                if ( $conds !== [] && $conds !== '*' ) {
-                       $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
+                       $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
                }
 
                return $this->query( $sql, $fname );
        }
 
-       public function makeList( $a, $mode = LIST_COMMA ) {
+       public function makeList( $a, $mode = self::LIST_COMMA ) {
                if ( !is_array( $a ) ) {
                        throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
                }
@@ -1482,9 +1504,9 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
                foreach ( $a as $field => $value ) {
                        if ( !$first ) {
-                               if ( $mode == LIST_AND ) {
+                               if ( $mode == self::LIST_AND ) {
                                        $list .= ' AND ';
-                               } elseif ( $mode == LIST_OR ) {
+                               } elseif ( $mode == self::LIST_OR ) {
                                        $list .= ' OR ';
                                } else {
                                        $list .= ',';
@@ -1493,11 +1515,13 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                $first = false;
                        }
 
-                       if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
+                       if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
                                $list .= "($value)";
-                       } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
+                       } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
                                $list .= "$value";
-                       } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
+                       } elseif (
+                               ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
+                       ) {
                                // Remove null from array to be handled separately if found
                                $includeNull = false;
                                foreach ( array_keys( $value, null, true ) as $nullKey ) {
@@ -1505,7 +1529,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        unset( $value[$nullKey] );
                                }
                                if ( count( $value ) == 0 && !$includeNull ) {
-                                       throw new InvalidArgumentException( __METHOD__ . ": empty input for field $field" );
+                                       throw new InvalidArgumentException(
+                                               __METHOD__ . ": empty input for field $field" );
                                } elseif ( count( $value ) == 0 ) {
                                        // only check if $field is null
                                        $list .= "$field IS NULL";
@@ -1530,17 +1555,19 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        }
                                }
                        } elseif ( $value === null ) {
-                               if ( $mode == LIST_AND || $mode == LIST_OR ) {
+                               if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
                                        $list .= "$field IS ";
-                               } elseif ( $mode == LIST_SET ) {
+                               } elseif ( $mode == self::LIST_SET ) {
                                        $list .= "$field = ";
                                }
                                $list .= 'NULL';
                        } else {
-                               if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
+                               if (
+                                       $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
+                               ) {
                                        $list .= "$field = ";
                                }
-                               $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
+                               $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
                        }
                }
 
@@ -1554,12 +1581,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        if ( count( $sub ) ) {
                                $conds[] = $this->makeList(
                                        [ $baseKey => $base, $subKey => array_keys( $sub ) ],
-                                       LIST_AND );
+                                       self::LIST_AND );
                        }
                }
 
                if ( $conds ) {
-                       return $this->makeList( $conds, LIST_OR );
+                       return $this->makeList( $conds, self::LIST_OR );
                } else {
                        // Nothing to search for...
                        return false;
@@ -1880,7 +1907,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                                $tableClause .= ' ' . $ignore;
                                        }
                                }
-                               $on = $this->makeList( (array)$conds, LIST_AND );
+                               $on = $this->makeList( (array)$conds, self::LIST_AND );
                                if ( $on != '' ) {
                                        $tableClause .= ' ON (' . $on . ')';
                                }
@@ -2149,10 +2176,10 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        foreach ( $index as $column ) {
                                                $rowKey[$column] = $row[$column];
                                        }
-                                       $clauses[] = $this->makeList( $rowKey, LIST_AND );
+                                       $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
                                }
                        }
-                       $where = [ $this->makeList( $clauses, LIST_OR ) ];
+                       $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
                } else {
                        $where = false;
                }
@@ -2194,7 +2221,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $joinTable = $this->tableName( $joinTable );
                $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
                if ( $conds != '*' ) {
-                       $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
+                       $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
                }
                $sql .= ')';
 
@@ -2235,7 +2262,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
                if ( $conds != '*' ) {
                        if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
+                               $conds = $this->makeList( $conds, self::LIST_AND );
                        }
                        $sql .= ' WHERE ' . $conds;
                }
@@ -2313,7 +2340,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
                if ( $conds != '*' ) {
                        if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
+                               $conds = $this->makeList( $conds, self::LIST_AND );
                        }
                        $sql .= " WHERE $conds";
                }
@@ -2364,7 +2391,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
        public function conditional( $cond, $trueVal, $falseVal ) {
                if ( is_array( $cond ) ) {
-                       $cond = $this->makeList( $cond, LIST_AND );
+                       $cond = $this->makeList( $cond, self::LIST_AND );
                }
 
                return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
@@ -3447,6 +3474,14 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $this->tableAliases = $aliases;
        }
 
+       /**
+        * @return bool Whether a DB user is required to access the DB
+        * @since 1.28
+        */
+       protected function requiresDatabaseUser() {
+               return true;
+       }
+
        /**
         * @since 1.19
         * @return string
index 4f823b5..e008705 100644 (file)
  * @ingroup Database
  */
 abstract class DatabaseBase extends Database {
-       /**
-        * Boolean, controls output of large amounts of debug information.
-        * @param bool|null $debug
-        *   - true to enable debugging
-        *   - false to disable debugging
-        *   - omitted or null to do nothing
-        *
-        * @return bool Previous value of the flag
-        * @deprecated since 1.28; use setFlag()
-        */
-       public function debug( $debug = null ) {
-               $res = $this->getFlag( DBO_DEBUG );
-               if ( $debug !== null ) {
-                       $debug ? $this->setFlag( DBO_DEBUG ) : $this->clearFlag( DBO_DEBUG );
-               }
-
-               return $res;
-       }
-
-       /**
-        * @return string Command delimiter used by this database engine
-        */
-       public function getDelimiter() {
-               return $this->delimiter;
-       }
-
-       /**
-        * Returns true if this database supports (and uses) cascading deletes
-        *
-        * @return bool
-        */
-       public function cascadingDeletes() {
-               return false;
-       }
-       /**
-        * Returns true if this database supports (and uses) triggers (e.g. on the page table)
-        *
-        * @return bool
-        */
-       public function cleanupTriggers() {
-               return false;
-       }
-       /**
-        * Returns true if this database is strict about what can be put into an IP field.
-        * Specifically, it uses a NULL value instead of an empty string.
-        *
-        * @return bool
-        */
-       public function strictIPs() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database can do a native search on IP columns
-        * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
-        *
-        * @return bool
-        */
-       public function searchableIPs() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database uses timestamps rather than integers
-        *
-        * @return bool
-        */
-       public function realTimestamps() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database can use functional indexes
-        *
-        * @return bool
-        */
-       public function functionalIndexes() {
-               return false;
-       }
-
-       /**
-        * Intended to be compatible with the PEAR::DB wrapper functions.
-        * http://pear.php.net/manual/en/package.database.db.intro-execute.php
-        *
-        * ? = scalar value, quoted as necessary
-        * ! = raw SQL bit (a function for instance)
-        * & = filename; reads the file and inserts as a blob
-        *     (we don't use this though...)
-        *
-        * @param string $sql
-        * @param string $func
-        *
-        * @return array
-        */
-       protected function prepare( $sql, $func = __METHOD__ ) {
-               /* MySQL doesn't support prepared statements (yet), so just
-                * pack up the query for reference. We'll manually replace
-                * the bits later.
-                */
-               return [ 'query' => $sql, 'func' => $func ];
-       }
-
-       /**
-        * Free a prepared query, generated by prepare().
-        * @param string $prepared
-        */
-       protected function freePrepared( $prepared ) {
-               /* No-op by default */
-       }
-
-       /**
-        * Execute a prepared query with the various arguments
-        * @param string $prepared The prepared sql
-        * @param mixed $args Either an array here, or put scalars as varargs
-        *
-        * @return ResultWrapper
-        */
-       public function execute( $prepared, $args = null ) {
-               if ( !is_array( $args ) ) {
-                       # Pull the var args
-                       $args = func_get_args();
-                       array_shift( $args );
-               }
-
-               $sql = $this->fillPrepared( $prepared['query'], $args );
-
-               return $this->query( $sql, $prepared['func'] );
-       }
-
-       /**
-        * For faking prepared SQL statements on DBs that don't support it directly.
-        *
-        * @param string $preparedQuery A 'preparable' SQL statement
-        * @param array $args Array of Arguments to fill it with
-        * @return string Executable SQL
-        */
-       public function fillPrepared( $preparedQuery, $args ) {
-               reset( $args );
-               $this->preparedArgs =& $args;
-
-               return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
-                       [ &$this, 'fillPreparedArg' ], $preparedQuery );
-       }
-
-       /**
-        * preg_callback func for fillPrepared()
-        * The arguments should be in $this->preparedArgs and must not be touched
-        * while we're doing this.
-        *
-        * @param array $matches
-        * @throws DBUnexpectedError
-        * @return string
-        */
-       protected function fillPreparedArg( $matches ) {
-               switch ( $matches[1] ) {
-                       case '\\?':
-                               return '?';
-                       case '\\!':
-                               return '!';
-                       case '\\&':
-                               return '&';
-               }
-
-               list( /* $n */, $arg ) = each( $this->preparedArgs );
-
-               switch ( $matches[1] ) {
-                       case '?':
-                               return $this->addQuotes( $arg );
-                       case '!':
-                               return $arg;
-                       case '&':
-                               # return $this->addQuotes( file_get_contents( $arg ) );
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       '& mode is not implemented. If it\'s really needed, uncomment the line above.'
-                               );
-                       default:
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       'Received invalid match. This should never happen!'
-                               );
-               }
-       }
-
        /**
         * Get search engine class. All subclasses of this need to implement this
         * if they wish to use searching.
         *
         * @return string
+        * @deprecated since 1.27; use SearchEngineFactory::getSearchEngineClass()
         */
        public function getSearchEngine() {
                return 'SearchEngineDummy';
index 46c6678..2d19081 100644 (file)
@@ -735,7 +735,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @see https://www.percona.com/doc/percona-toolkit/2.1/pt-heartbeat.html
         */
        protected function getHeartbeatData( array $conds ) {
-               $whereSQL = $this->makeList( $conds, LIST_AND );
+               $whereSQL = $this->makeList( $conds, self::LIST_AND );
                // Use ORDER BY for channel based queries since that field might not be UNIQUE.
                // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
                // percision field is not supported in MySQL <= 5.5.
@@ -1057,16 +1057,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                return true;
        }
 
-       /**
-        * Get search engine class. All subclasses of this
-        * need to implement this if they wish to use searching.
-        *
-        * @return string
-        */
-       public function getSearchEngine() {
-               return 'SearchMySQL';
-       }
-
        /**
         * @param bool $value
         */
@@ -1107,7 +1097,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
 
                if ( $conds != '*' ) {
-                       $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
+                       $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
                }
 
                return $this->query( $sql, $fname );
@@ -1141,7 +1131,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                        $rowTuples[] = '(' . $this->makeList( $row ) . ')';
                }
                $sql .= implode( ',', $rowTuples );
-               $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
+               $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET );
 
                return (bool)$this->query( $sql, $fname );
        }
diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php
new file mode 100644 (file)
index 0000000..e79b28b
--- /dev/null
@@ -0,0 +1,1434 @@
+<?php
+/**
+ * This is the Postgres database abstraction layer.
+ *
+ * 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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DatabasePostgres extends DatabaseBase {
+       /** @var int|bool */
+       protected $port;
+
+       /** @var resource */
+       protected $mLastResult = null;
+       /** @var int The number of rows affected as an integer */
+       protected $mAffectedRows = null;
+
+       /** @var int */
+       private $mInsertId = null;
+       /** @var float|string */
+       private $numericVersion = null;
+       /** @var string Connect string to open a PostgreSQL connection */
+       private $connectString;
+       /** @var string */
+       private $mCoreSchema;
+
+       public function __construct( array $params ) {
+               $this->port = isset( $params['port'] ) ? $params['port'] : false;
+               parent::__construct( $params );
+       }
+
+       function getType() {
+               return 'postgres';
+       }
+
+       function implicitGroupby() {
+               return false;
+       }
+
+       function implicitOrderby() {
+               return false;
+       }
+
+       function hasConstraint( $name ) {
+               $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
+                       "WHERE c.connamespace = n.oid AND conname = '" .
+                       pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
+                       pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
+               $res = $this->doQuery( $sql );
+
+               return $this->numRows( $res );
+       }
+
+       /**
+        * Usually aborts on failure
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws DBConnectionError|Exception
+        * @return resource|bool|null
+        */
+       function open( $server, $user, $password, $dbName ) {
+               # Test for Postgres support, to avoid suppressed fatal error
+               if ( !function_exists( 'pg_connect' ) ) {
+                       throw new DBConnectionError(
+                               $this,
+                               "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
+                               "option? (Note: if you recently installed PHP, you may need to restart your\n" .
+                               "webserver and database)\n"
+                       );
+               }
+
+               if ( !strlen( $user ) ) { # e.g. the class is being loaded
+                       return null;
+               }
+
+               $this->mServer = $server;
+               $this->mUser = $user;
+               $this->mPassword = $password;
+               $this->mDBname = $dbName;
+
+               $connectVars = [
+                       'dbname' => $dbName,
+                       'user' => $user,
+                       'password' => $password
+               ];
+               if ( $server != false && $server != '' ) {
+                       $connectVars['host'] = $server;
+               }
+               if ( (int)$this->port > 0 ) {
+                       $connectVars['port'] = (int)$this->port;
+               }
+               if ( $this->mFlags & DBO_SSL ) {
+                       $connectVars['sslmode'] = 1;
+               }
+
+               $this->connectString = $this->makeConnectionString( $connectVars );
+               $this->close();
+               $this->installErrorHandler();
+
+               try {
+                       $this->mConn = pg_connect( $this->connectString );
+               } catch ( Exception $ex ) {
+                       $this->restoreErrorHandler();
+                       throw $ex;
+               }
+
+               $phpError = $this->restoreErrorHandler();
+
+               if ( !$this->mConn ) {
+                       $this->queryLogger->debug( "DB connection error\n" );
+                       $this->queryLogger->debug(
+                               "Server: $server, Database: $dbName, User: $user, Password: " .
+                               substr( $password, 0, 3 ) . "...\n" );
+                       $this->queryLogger->debug( $this->lastError() . "\n" );
+                       throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
+               }
+
+               $this->mOpened = true;
+
+               # If called from the command-line (e.g. importDump), only show errors
+               if ( $this->cliMode ) {
+                       $this->doQuery( "SET client_min_messages = 'ERROR'" );
+               }
+
+               $this->query( "SET client_encoding='UTF8'", __METHOD__ );
+               $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
+               $this->query( "SET timezone = 'GMT'", __METHOD__ );
+               $this->query( "SET standard_conforming_strings = on", __METHOD__ );
+               if ( $this->getServerVersion() >= 9.0 ) {
+                       $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
+               }
+
+               $this->determineCoreSchema( $this->mSchema );
+
+               return $this->mConn;
+       }
+
+       /**
+        * Postgres doesn't support selectDB in the same way MySQL does. So if the
+        * DB name doesn't match the open connection, open a new one
+        * @param string $db
+        * @return bool
+        */
+       function selectDB( $db ) {
+               if ( $this->mDBname !== $db ) {
+                       return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
+               } else {
+                       return true;
+               }
+       }
+
+       function makeConnectionString( $vars ) {
+               $s = '';
+               foreach ( $vars as $name => $value ) {
+                       $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
+               }
+
+               return $s;
+       }
+
+       /**
+        * Closes a database connection, if it is open
+        * Returns success, true if already closed
+        * @return bool
+        */
+       protected function closeConnection() {
+               return pg_close( $this->mConn );
+       }
+
+       public function doQuery( $sql ) {
+               $sql = mb_convert_encoding( $sql, 'UTF-8' );
+               // Clear previously left over PQresult
+               while ( $res = pg_get_result( $this->mConn ) ) {
+                       pg_free_result( $res );
+               }
+               if ( pg_send_query( $this->mConn, $sql ) === false ) {
+                       throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
+               }
+               $this->mLastResult = pg_get_result( $this->mConn );
+               $this->mAffectedRows = null;
+               if ( pg_result_error( $this->mLastResult ) ) {
+                       return false;
+               }
+
+               return $this->mLastResult;
+       }
+
+       protected function dumpError() {
+               $diags = [
+                       PGSQL_DIAG_SEVERITY,
+                       PGSQL_DIAG_SQLSTATE,
+                       PGSQL_DIAG_MESSAGE_PRIMARY,
+                       PGSQL_DIAG_MESSAGE_DETAIL,
+                       PGSQL_DIAG_MESSAGE_HINT,
+                       PGSQL_DIAG_STATEMENT_POSITION,
+                       PGSQL_DIAG_INTERNAL_POSITION,
+                       PGSQL_DIAG_INTERNAL_QUERY,
+                       PGSQL_DIAG_CONTEXT,
+                       PGSQL_DIAG_SOURCE_FILE,
+                       PGSQL_DIAG_SOURCE_LINE,
+                       PGSQL_DIAG_SOURCE_FUNCTION
+               ];
+               foreach ( $diags as $d ) {
+                       $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
+                               $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+               }
+       }
+
+       function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+               if ( $tempIgnore ) {
+                       /* Check for constraint violation */
+                       if ( $errno === '23505' ) {
+                               parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
+
+                               return;
+                       }
+               }
+               /* Transaction stays in the ERROR state until rolled back */
+               if ( $this->mTrxLevel ) {
+                       $ignore = $this->ignoreErrors( true );
+                       $this->rollback( __METHOD__ );
+                       $this->ignoreErrors( $ignore );
+               }
+               parent::reportQueryError( $error, $errno, $sql, $fname, false );
+       }
+
+       function queryIgnore( $sql, $fname = __METHOD__ ) {
+               return $this->query( $sql, $fname, true );
+       }
+
+       /**
+        * @param stdClass|ResultWrapper $res
+        * @throws DBUnexpectedError
+        */
+       function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $ok = pg_free_result( $res );
+               MediaWiki\restoreWarnings();
+               if ( !$ok ) {
+                       throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
+               }
+       }
+
+       /**
+        * @param ResultWrapper|stdClass $res
+        * @return stdClass
+        * @throws DBUnexpectedError
+        */
+       function fetchObject( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = pg_fetch_object( $res );
+               MediaWiki\restoreWarnings();
+               # @todo FIXME: HACK HACK HACK HACK debug
+
+               # @todo hashar: not sure if the following test really trigger if the object
+               #          fetching failed.
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $row;
+       }
+
+       function fetchRow( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = pg_fetch_array( $res );
+               MediaWiki\restoreWarnings();
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $row;
+       }
+
+       function numRows( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $n = pg_num_rows( $res );
+               MediaWiki\restoreWarnings();
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $n;
+       }
+
+       function numFields( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_num_fields( $res );
+       }
+
+       function fieldName( $res, $n ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_field_name( $res, $n );
+       }
+
+       /**
+        * Return the result of the last call to nextSequenceValue();
+        * This must be called after nextSequenceValue().
+        *
+        * @return int|null
+        */
+       function insertId() {
+               return $this->mInsertId;
+       }
+
+       /**
+        * @param mixed $res
+        * @param int $row
+        * @return bool
+        */
+       function dataSeek( $res, $row ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_result_seek( $res, $row );
+       }
+
+       function lastError() {
+               if ( $this->mConn ) {
+                       if ( $this->mLastResult ) {
+                               return pg_result_error( $this->mLastResult );
+                       } else {
+                               return pg_last_error();
+                       }
+               } else {
+                       return 'No database connection';
+               }
+       }
+
+       function lastErrno() {
+               if ( $this->mLastResult ) {
+                       return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
+               } else {
+                       return false;
+               }
+       }
+
+       function affectedRows() {
+               if ( !is_null( $this->mAffectedRows ) ) {
+                       // Forced result for simulated queries
+                       return $this->mAffectedRows;
+               }
+               if ( empty( $this->mLastResult ) ) {
+                       return 0;
+               }
+
+               return pg_affected_rows( $this->mLastResult );
+       }
+
+       /**
+        * Estimate rows in dataset
+        * Returns estimated count, based on EXPLAIN output
+        * This is not necessarily an accurate estimate, so use sparingly
+        * Returns -1 if count cannot be found
+        * Takes same arguments as Database::select()
+        *
+        * @param string $table
+        * @param string $vars
+        * @param string $conds
+        * @param string $fname
+        * @param array $options
+        * @return int
+        */
+       function estimateRowCount( $table, $vars = '*', $conds = '',
+               $fname = __METHOD__, $options = []
+       ) {
+               $options['EXPLAIN'] = true;
+               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               $rows = -1;
+               if ( $res ) {
+                       $row = $this->fetchRow( $res );
+                       $count = [];
+                       if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
+                               $rows = (int)$count[1];
+                       }
+               }
+
+               return $rows;
+       }
+
+       /**
+        * Returns information about an index
+        * If errors are explicitly ignored, returns NULL on failure
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|null
+        */
+       function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+               foreach ( $res as $row ) {
+                       if ( $row->indexname == $this->indexName( $index ) ) {
+                               return $row;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Returns is of attributes used in index
+        *
+        * @since 1.19
+        * @param string $index
+        * @param bool|string $schema
+        * @return array
+        */
+       function indexAttributes( $index, $schema = false ) {
+               if ( $schema === false ) {
+                       $schema = $this->getCoreSchema();
+               }
+               /*
+                * A subquery would be not needed if we didn't care about the order
+                * of attributes, but we do
+                */
+               $sql = <<<__INDEXATTR__
+
+                       SELECT opcname,
+                               attname,
+                               i.indoption[s.g] as option,
+                               pg_am.amname
+                       FROM
+                               (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
+                                       FROM
+                                               pg_index isub
+                                       JOIN pg_class cis
+                                               ON cis.oid=isub.indexrelid
+                                       JOIN pg_namespace ns
+                                               ON cis.relnamespace = ns.oid
+                                       WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
+                               pg_attribute,
+                               pg_opclass opcls,
+                               pg_am,
+                               pg_class ci
+                               JOIN pg_index i
+                                       ON ci.oid=i.indexrelid
+                               JOIN pg_class ct
+                                       ON ct.oid = i.indrelid
+                               JOIN pg_namespace n
+                                       ON ci.relnamespace = n.oid
+                               WHERE
+                                       ci.relname='$index' AND n.nspname='$schema'
+                                       AND     attrelid = ct.oid
+                                       AND     i.indkey[s.g] = attnum
+                                       AND     i.indclass[s.g] = opcls.oid
+                                       AND     pg_am.oid = opcls.opcmethod
+__INDEXATTR__;
+               $res = $this->query( $sql, __METHOD__ );
+               $a = [];
+               if ( $res ) {
+                       foreach ( $res as $row ) {
+                               $a[] = [
+                                       $row->attname,
+                                       $row->opcname,
+                                       $row->amname,
+                                       $row->option ];
+                       }
+               } else {
+                       return null;
+               }
+
+               return $a;
+       }
+
+       function indexUnique( $table, $index, $fname = __METHOD__ ) {
+               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
+                       " AND indexdef LIKE 'CREATE UNIQUE%(" .
+                       $this->strencode( $this->indexName( $index ) ) .
+                       ")'";
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+
+               return $res->numRows() > 0;
+       }
+
+       function selectSQLText(
+               $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+       ) {
+               // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
+               // to the parent function to get the actual SQL text.
+               // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
+               // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
+               // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
+               // accordingly.
+               // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
+               if ( is_array( $options ) ) {
+                       $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
+                       if ( $forUpdateKey !== false && $join_conds ) {
+                               unset( $options[$forUpdateKey] );
+
+                               foreach ( $join_conds as $table_cond => $join_cond ) {
+                                       if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
+                                               $options['FOR UPDATE'][] = $table_cond;
+                                       }
+                               }
+                       }
+
+                       if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
+                               unset( $options['ORDER BY'] );
+                       }
+               }
+
+               return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+       }
+
+       /**
+        * INSERT wrapper, inserts an array into a table
+        *
+        * $args may be a single associative array, or an array of these with numeric keys,
+        * for multi-row insert (Postgres version 8.2 and above only).
+        *
+        * @param string $table Name of the table to insert to.
+        * @param array $args Items to insert into the table.
+        * @param string $fname Name of the function, for profiling
+        * @param array|string $options String or array. Valid options: IGNORE
+        * @return bool Success of insert operation. IGNORE always returns true.
+        */
+       function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
+               if ( !count( $args ) ) {
+                       return true;
+               }
+
+               $table = $this->tableName( $table );
+               if ( !isset( $this->numericVersion ) ) {
+                       $this->getServerVersion();
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
+                       $multi = true;
+                       $keys = array_keys( $args[0] );
+               } else {
+                       $multi = false;
+                       $keys = array_keys( $args );
+               }
+
+               // If IGNORE is set, we use savepoints to emulate mysql's behavior
+               $savepoint = $olde = null;
+               $numrowsinserted = 0;
+               if ( in_array( 'IGNORE', $options ) ) {
+                       $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
+                       $olde = error_reporting( 0 );
+                       // For future use, we may want to track the number of actual inserts
+                       // Right now, insert (all writes) simply return true/false
+               }
+
+               $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
+
+               if ( $multi ) {
+                       if ( $this->numericVersion >= 8.2 && !$savepoint ) {
+                               $first = true;
+                               foreach ( $args as $row ) {
+                                       if ( $first ) {
+                                               $first = false;
+                                       } else {
+                                               $sql .= ',';
+                                       }
+                                       $sql .= '(' . $this->makeList( $row ) . ')';
+                               }
+                               $res = (bool)$this->query( $sql, $fname, $savepoint );
+                       } else {
+                               $res = true;
+                               $origsql = $sql;
+                               foreach ( $args as $row ) {
+                                       $tempsql = $origsql;
+                                       $tempsql .= '(' . $this->makeList( $row ) . ')';
+
+                                       if ( $savepoint ) {
+                                               $savepoint->savepoint();
+                                       }
+
+                                       $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
+
+                                       if ( $savepoint ) {
+                                               $bar = pg_result_error( $this->mLastResult );
+                                               if ( $bar != false ) {
+                                                       $savepoint->rollback();
+                                               } else {
+                                                       $savepoint->release();
+                                                       $numrowsinserted++;
+                                               }
+                                       }
+
+                                       // If any of them fail, we fail overall for this function call
+                                       // Note that this will be ignored if IGNORE is set
+                                       if ( !$tempres ) {
+                                               $res = false;
+                                       }
+                               }
+                       }
+               } else {
+                       // Not multi, just a lone insert
+                       if ( $savepoint ) {
+                               $savepoint->savepoint();
+                       }
+
+                       $sql .= '(' . $this->makeList( $args ) . ')';
+                       $res = (bool)$this->query( $sql, $fname, $savepoint );
+                       if ( $savepoint ) {
+                               $bar = pg_result_error( $this->mLastResult );
+                               if ( $bar != false ) {
+                                       $savepoint->rollback();
+                               } else {
+                                       $savepoint->release();
+                                       $numrowsinserted++;
+                               }
+                       }
+               }
+               if ( $savepoint ) {
+                       error_reporting( $olde );
+                       $savepoint->commit();
+
+                       // Set the affected row count for the whole operation
+                       $this->mAffectedRows = $numrowsinserted;
+
+                       // IGNORE always returns true
+                       return true;
+               }
+
+               return $res;
+       }
+
+       /**
+        * INSERT SELECT wrapper
+        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
+        * Source items may be literals rather then field names, but strings should
+        * be quoted with Database::addQuotes()
+        * $conds may be "*" to copy the whole table
+        * srcTable may be an array of tables.
+        * @todo FIXME: Implement this a little better (seperate select/insert)?
+        *
+        * @param string $destTable
+        * @param array|string $srcTable
+        * @param array $varMap
+        * @param array $conds
+        * @param string $fname
+        * @param array $insertOptions
+        * @param array $selectOptions
+        * @return bool
+        */
+       function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
+               $insertOptions = [], $selectOptions = [] ) {
+               $destTable = $this->tableName( $destTable );
+
+               if ( !is_array( $insertOptions ) ) {
+                       $insertOptions = [ $insertOptions ];
+               }
+
+               /*
+                * If IGNORE is set, we use savepoints to emulate mysql's behavior
+                * Ignore LOW PRIORITY option, since it is MySQL-specific
+                */
+               $savepoint = $olde = null;
+               $numrowsinserted = 0;
+               if ( in_array( 'IGNORE', $insertOptions ) ) {
+                       $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
+                       $olde = error_reporting( 0 );
+                       $savepoint->savepoint();
+               }
+
+               if ( !is_array( $selectOptions ) ) {
+                       $selectOptions = [ $selectOptions ];
+               }
+               list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
+                       $this->makeSelectOptions( $selectOptions );
+               if ( is_array( $srcTable ) ) {
+                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
+               } else {
+                       $srcTable = $this->tableName( $srcTable );
+               }
+
+               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+                       " SELECT $startOpts " . implode( ',', $varMap ) .
+                       " FROM $srcTable $useIndex $ignoreIndex ";
+
+               if ( $conds != '*' ) {
+                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+               }
+
+               $sql .= " $tailOpts";
+
+               $res = (bool)$this->query( $sql, $fname, $savepoint );
+               if ( $savepoint ) {
+                       $bar = pg_result_error( $this->mLastResult );
+                       if ( $bar != false ) {
+                               $savepoint->rollback();
+                       } else {
+                               $savepoint->release();
+                               $numrowsinserted++;
+                       }
+                       error_reporting( $olde );
+                       $savepoint->commit();
+
+                       // Set the affected row count for the whole operation
+                       $this->mAffectedRows = $numrowsinserted;
+
+                       // IGNORE always returns true
+                       return true;
+               }
+
+               return $res;
+       }
+
+       function tableName( $name, $format = 'quoted' ) {
+               # Replace reserved words with better ones
+               switch ( $name ) {
+                       case 'user':
+                               return $this->realTableName( 'mwuser', $format );
+                       case 'text':
+                               return $this->realTableName( 'pagecontent', $format );
+                       default:
+                               return $this->realTableName( $name, $format );
+               }
+       }
+
+       /* Don't cheat on installer */
+       function realTableName( $name, $format = 'quoted' ) {
+               return parent::tableName( $name, $format );
+       }
+
+       /**
+        * Return the next in a sequence, save the value for retrieval via insertId()
+        *
+        * @param string $seqName
+        * @return int|null
+        */
+       function nextSequenceValue( $seqName ) {
+               $safeseq = str_replace( "'", "''", $seqName );
+               $res = $this->query( "SELECT nextval('$safeseq')" );
+               $row = $this->fetchRow( $res );
+               $this->mInsertId = $row[0];
+
+               return $this->mInsertId;
+       }
+
+       /**
+        * Return the current value of a sequence. Assumes it has been nextval'ed in this session.
+        *
+        * @param string $seqName
+        * @return int
+        */
+       function currentSequenceValue( $seqName ) {
+               $safeseq = str_replace( "'", "''", $seqName );
+               $res = $this->query( "SELECT currval('$safeseq')" );
+               $row = $this->fetchRow( $res );
+               $currval = $row[0];
+
+               return $currval;
+       }
+
+       # Returns the size of a text field, or -1 for "unlimited"
+       function textFieldSize( $table, $field ) {
+               $table = $this->tableName( $table );
+               $sql = "SELECT t.typname as ftype,a.atttypmod as size
+                       FROM pg_class c, pg_attribute a, pg_type t
+                       WHERE relname='$table' AND a.attrelid=c.oid AND
+                               a.atttypid=t.oid and a.attname='$field'";
+               $res = $this->query( $sql );
+               $row = $this->fetchObject( $res );
+               if ( $row->ftype == 'varchar' ) {
+                       $size = $row->size - 4;
+               } else {
+                       $size = $row->size;
+               }
+
+               return $size;
+       }
+
+       function limitResult( $sql, $limit, $offset = false ) {
+               return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
+       }
+
+       function wasDeadlock() {
+               return $this->lastErrno() == '40P01';
+       }
+
+       function duplicateTableStructure(
+               $oldName, $newName, $temporary = false, $fname = __METHOD__
+       ) {
+               $newName = $this->addIdentifierQuotes( $newName );
+               $oldName = $this->addIdentifierQuotes( $oldName );
+
+               return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
+                       "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
+       }
+
+       function listTables( $prefix = null, $fname = __METHOD__ ) {
+               $eschema = $this->addQuotes( $this->getCoreSchema() );
+               $result = $this->query(
+                       "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
+               $endArray = [];
+
+               foreach ( $result as $table ) {
+                       $vars = get_object_vars( $table );
+                       $table = array_pop( $vars );
+                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                               $endArray[] = $table;
+                       }
+               }
+
+               return $endArray;
+       }
+
+       function timestamp( $ts = 0 ) {
+               $ct = new ConvertableTimestamp( $ts );
+
+               return $ct->getTimestamp( TS_POSTGRES );
+       }
+
+       /**
+        * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
+        * to http://www.php.net/manual/en/ref.pgsql.php
+        *
+        * Parsing a postgres array can be a tricky problem, he's my
+        * take on this, it handles multi-dimensional arrays plus
+        * escaping using a nasty regexp to determine the limits of each
+        * data-item.
+        *
+        * This should really be handled by PHP PostgreSQL module
+        *
+        * @since 1.19
+        * @param string $text Postgreql array returned in a text form like {a,b}
+        * @param string $output
+        * @param int|bool $limit
+        * @param int $offset
+        * @return string
+        */
+       function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
+               if ( false === $limit ) {
+                       $limit = strlen( $text ) - 1;
+                       $output = [];
+               }
+               if ( '{}' == $text ) {
+                       return $output;
+               }
+               do {
+                       if ( '{' != $text[$offset] ) {
+                               preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
+                                       $text, $match, 0, $offset );
+                               $offset += strlen( $match[0] );
+                               $output[] = ( '"' != $match[1][0]
+                                       ? $match[1]
+                                       : stripcslashes( substr( $match[1], 1, -1 ) ) );
+                               if ( '},' == $match[3] ) {
+                                       return $output;
+                               }
+                       } else {
+                               $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
+                       }
+               } while ( $limit > $offset );
+
+               return $output;
+       }
+
+       /**
+        * Return aggregated value function call
+        * @param array $valuedata
+        * @param string $valuename
+        * @return array
+        */
+       public function aggregateValue( $valuedata, $valuename = 'value' ) {
+               return $valuedata;
+       }
+
+       /**
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink() {
+               return '[{{int:version-db-postgres-url}} PostgreSQL]';
+       }
+
+       /**
+        * Return current schema (executes SELECT current_schema())
+        * Needs transaction
+        *
+        * @since 1.19
+        * @return string Default schema for the current session
+        */
+       function getCurrentSchema() {
+               $res = $this->query( "SELECT current_schema()", __METHOD__ );
+               $row = $this->fetchRow( $res );
+
+               return $row[0];
+       }
+
+       /**
+        * Return list of schemas which are accessible without schema name
+        * This is list does not contain magic keywords like "$user"
+        * Needs transaction
+        *
+        * @see getSearchPath()
+        * @see setSearchPath()
+        * @since 1.19
+        * @return array List of actual schemas for the current sesson
+        */
+       function getSchemas() {
+               $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
+               $row = $this->fetchRow( $res );
+               $schemas = [];
+
+               /* PHP pgsql support does not support array type, "{a,b}" string is returned */
+
+               return $this->pg_array_parse( $row[0], $schemas );
+       }
+
+       /**
+        * Return search patch for schemas
+        * This is different from getSchemas() since it contain magic keywords
+        * (like "$user").
+        * Needs transaction
+        *
+        * @since 1.19
+        * @return array How to search for table names schemas for the current user
+        */
+       function getSearchPath() {
+               $res = $this->query( "SHOW search_path", __METHOD__ );
+               $row = $this->fetchRow( $res );
+
+               /* PostgreSQL returns SHOW values as strings */
+
+               return explode( ",", $row[0] );
+       }
+
+       /**
+        * Update search_path, values should already be sanitized
+        * Values may contain magic keywords like "$user"
+        * @since 1.19
+        *
+        * @param array $search_path List of schemas to be searched by default
+        */
+       function setSearchPath( $search_path ) {
+               $this->query( "SET search_path = " . implode( ", ", $search_path ) );
+       }
+
+       /**
+        * Determine default schema for MediaWiki core
+        * Adjust this session schema search path if desired schema exists
+        * and is not alread there.
+        *
+        * We need to have name of the core schema stored to be able
+        * to query database metadata.
+        *
+        * This will be also called by the installer after the schema is created
+        *
+        * @since 1.19
+        *
+        * @param string $desiredSchema
+        */
+       function determineCoreSchema( $desiredSchema ) {
+               $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+               if ( $this->schemaExists( $desiredSchema ) ) {
+                       if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
+                               $this->mCoreSchema = $desiredSchema;
+                               $this->queryLogger->debug(
+                                       "Schema \"" . $desiredSchema . "\" already in the search path\n" );
+                       } else {
+                               /**
+                                * Prepend our schema (e.g. 'mediawiki') in front
+                                * of the search path
+                                * Fixes bug 15816
+                                */
+                               $search_path = $this->getSearchPath();
+                               array_unshift( $search_path,
+                                       $this->addIdentifierQuotes( $desiredSchema ) );
+                               $this->setSearchPath( $search_path );
+                               $this->mCoreSchema = $desiredSchema;
+                               $this->queryLogger->debug(
+                                       "Schema \"" . $desiredSchema . "\" added to the search path\n" );
+                       }
+               } else {
+                       $this->mCoreSchema = $this->getCurrentSchema();
+                       $this->queryLogger->debug(
+                               "Schema \"" . $desiredSchema . "\" not found, using current \"" .
+                               $this->mCoreSchema . "\"\n" );
+               }
+               /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
+               $this->commit( __METHOD__ );
+       }
+
+       /**
+        * Return schema name fore core MediaWiki tables
+        *
+        * @since 1.19
+        * @return string Core schema name
+        */
+       function getCoreSchema() {
+               return $this->mCoreSchema;
+       }
+
+       /**
+        * @return string Version information from the database
+        */
+       function getServerVersion() {
+               if ( !isset( $this->numericVersion ) ) {
+                       $versionInfo = pg_version( $this->mConn );
+                       if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
+                               // Old client, abort install
+                               $this->numericVersion = '7.3 or earlier';
+                       } elseif ( isset( $versionInfo['server'] ) ) {
+                               // Normal client
+                               $this->numericVersion = $versionInfo['server'];
+                       } else {
+                               // Bug 16937: broken pgsql extension from PHP<5.3
+                               $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
+                       }
+               }
+
+               return $this->numericVersion;
+       }
+
+       /**
+        * Query whether a given relation exists (in the given schema, or the
+        * default mw one if not given)
+        * @param string $table
+        * @param array|string $types
+        * @param bool|string $schema
+        * @return bool
+        */
+       function relationExists( $table, $types, $schema = false ) {
+               if ( !is_array( $types ) ) {
+                       $types = [ $types ];
+               }
+               if ( !$schema ) {
+                       $schema = $this->getCoreSchema();
+               }
+               $table = $this->realTableName( $table, 'raw' );
+               $etable = $this->addQuotes( $table );
+               $eschema = $this->addQuotes( $schema );
+               $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
+                       . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
+                       . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
+               $res = $this->query( $sql );
+               $count = $res ? $res->numRows() : 0;
+
+               return (bool)$count;
+       }
+
+       /**
+        * For backward compatibility, this function checks both tables and
+        * views.
+        * @param string $table
+        * @param string $fname
+        * @param bool|string $schema
+        * @return bool
+        */
+       function tableExists( $table, $fname = __METHOD__, $schema = false ) {
+               return $this->relationExists( $table, [ 'r', 'v' ], $schema );
+       }
+
+       function sequenceExists( $sequence, $schema = false ) {
+               return $this->relationExists( $sequence, 'S', $schema );
+       }
+
+       function triggerExists( $table, $trigger ) {
+               $q = <<<SQL
+       SELECT 1 FROM pg_class, pg_namespace, pg_trigger
+               WHERE relnamespace=pg_namespace.oid AND relkind='r'
+                         AND tgrelid=pg_class.oid
+                         AND nspname=%s AND relname=%s AND tgname=%s
+SQL;
+               $res = $this->query(
+                       sprintf(
+                               $q,
+                               $this->addQuotes( $this->getCoreSchema() ),
+                               $this->addQuotes( $table ),
+                               $this->addQuotes( $trigger )
+                       )
+               );
+               if ( !$res ) {
+                       return null;
+               }
+               $rows = $res->numRows();
+
+               return $rows;
+       }
+
+       function ruleExists( $table, $rule ) {
+               $exists = $this->selectField( 'pg_rules', 'rulename',
+                       [
+                               'rulename' => $rule,
+                               'tablename' => $table,
+                               'schemaname' => $this->getCoreSchema()
+                       ]
+               );
+
+               return $exists === $rule;
+       }
+
+       function constraintExists( $table, $constraint ) {
+               $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+                       "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+                       $this->addQuotes( $this->getCoreSchema() ),
+                       $this->addQuotes( $table ),
+                       $this->addQuotes( $constraint )
+               );
+               $res = $this->query( $sql );
+               if ( !$res ) {
+                       return null;
+               }
+               $rows = $res->numRows();
+
+               return $rows;
+       }
+
+       /**
+        * Query whether a given schema exists. Returns true if it does, false if it doesn't.
+        * @param string $schema
+        * @return bool
+        */
+       function schemaExists( $schema ) {
+               $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
+                       [ 'nspname' => $schema ], __METHOD__ );
+
+               return (bool)$exists;
+       }
+
+       /**
+        * Returns true if a given role (i.e. user) exists, false otherwise.
+        * @param string $roleName
+        * @return bool
+        */
+       function roleExists( $roleName ) {
+               $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
+                       [ 'rolname' => $roleName ], __METHOD__ );
+
+               return (bool)$exists;
+       }
+
+       /**
+        * @var string $table
+        * @var string $field
+        * @return PostgresField|null
+        */
+       function fieldInfo( $table, $field ) {
+               return PostgresField::fromText( $this, $table, $field );
+       }
+
+       /**
+        * pg_field_type() wrapper
+        * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
+        * @param int $index Field number, starting from 0
+        * @return string
+        */
+       function fieldType( $res, $index ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_field_type( $res, $index );
+       }
+
+       /**
+        * @param string $b
+        * @return Blob
+        */
+       function encodeBlob( $b ) {
+               return new PostgresBlob( pg_escape_bytea( $b ) );
+       }
+
+       function decodeBlob( $b ) {
+               if ( $b instanceof PostgresBlob ) {
+                       $b = $b->fetch();
+               } elseif ( $b instanceof Blob ) {
+                       return $b->fetch();
+               }
+
+               return pg_unescape_bytea( $b );
+       }
+
+       function strencode( $s ) {
+               // Should not be called by us
+
+               return pg_escape_string( $this->mConn, $s );
+       }
+
+       /**
+        * @param null|bool|Blob $s
+        * @return int|string
+        */
+       function addQuotes( $s ) {
+               if ( is_null( $s ) ) {
+                       return 'NULL';
+               } elseif ( is_bool( $s ) ) {
+                       return intval( $s );
+               } elseif ( $s instanceof Blob ) {
+                       if ( $s instanceof PostgresBlob ) {
+                               $s = $s->fetch();
+                       } else {
+                               $s = pg_escape_bytea( $this->mConn, $s->fetch() );
+                       }
+                       return "'$s'";
+               }
+
+               return "'" . pg_escape_string( $this->mConn, $s ) . "'";
+       }
+
+       /**
+        * Postgres specific version of replaceVars.
+        * Calls the parent version in Database.php
+        *
+        * @param string $ins SQL string, read from a stream (usually tables.sql)
+        * @return string SQL string
+        */
+       protected function replaceVars( $ins ) {
+               $ins = parent::replaceVars( $ins );
+
+               if ( $this->numericVersion >= 8.3 ) {
+                       // Thanks for not providing backwards-compatibility, 8.3
+                       $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
+               }
+
+               if ( $this->numericVersion <= 8.1 ) { // Our minimum version
+                       $ins = str_replace( 'USING gin', 'USING gist', $ins );
+               }
+
+               return $ins;
+       }
+
+       /**
+        * Various select options
+        *
+        * @param array $options An associative array of options to be turned into
+        *   an SQL query, valid keys are listed in the function.
+        * @return array
+        */
+       function makeSelectOptions( $options ) {
+               $preLimitTail = $postLimitTail = '';
+               $startOpts = $useIndex = $ignoreIndex = '';
+
+               $noKeyOptions = [];
+               foreach ( $options as $key => $option ) {
+                       if ( is_numeric( $key ) ) {
+                               $noKeyOptions[$option] = true;
+                       }
+               }
+
+               $preLimitTail .= $this->makeGroupByWithHaving( $options );
+
+               $preLimitTail .= $this->makeOrderBy( $options );
+
+               // if ( isset( $options['LIMIT'] ) ) {
+               //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
+               //              isset( $options['OFFSET'] ) ? $options['OFFSET']
+               //              : false );
+               // }
+
+               if ( isset( $options['FOR UPDATE'] ) ) {
+                       $postLimitTail .= ' FOR UPDATE OF ' .
+                               implode( ', ', array_map( [ &$this, 'tableName' ], $options['FOR UPDATE'] ) );
+               } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+                       $postLimitTail .= ' FOR UPDATE';
+               }
+
+               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+                       $startOpts .= 'DISTINCT';
+               }
+
+               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
+       }
+
+       function getDBname() {
+               return $this->mDBname;
+       }
+
+       function getServer() {
+               return $this->mServer;
+       }
+
+       function buildConcat( $stringList ) {
+               return implode( ' || ', $stringList );
+       }
+
+       public function buildGroupConcatField(
+               $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
+       ) {
+               $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
+
+               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
+       }
+
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return $field . '::text';
+       }
+
+       public function streamStatementEnd( &$sql, &$newLine ) {
+               # Allow dollar quoting for function declarations
+               if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
+                       if ( $this->delimiter ) {
+                               $this->delimiter = false;
+                       } else {
+                               $this->delimiter = ';';
+                       }
+               }
+
+               return parent::streamStatementEnd( $sql, $newLine );
+       }
+
+       /**
+        * Check to see if a named lock is available. This is non-blocking.
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        *
+        * @param string $lockName Name of lock to poll
+        * @param string $method Name of method calling us
+        * @return bool
+        * @since 1.20
+        */
+       public function lockIsFree( $lockName, $method ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
+                       WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
+               $row = $this->fetchObject( $result );
+
+               return ( $row->lockstatus === 't' );
+       }
+
+       /**
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        * @param string $lockName
+        * @param string $method
+        * @param int $timeout
+        * @return bool
+        */
+       public function lock( $lockName, $method, $timeout = 5 ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $loop = new WaitConditionLoop(
+                       function () use ( $lockName, $key, $timeout, $method ) {
+                               $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
+                               $row = $this->fetchObject( $res );
+                               if ( $row->lockstatus === 't' ) {
+                                       parent::lock( $lockName, $method, $timeout ); // record
+                                       return true;
+                               }
+
+                               return WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+
+               return ( $loop->invoke() === $loop::CONDITION_REACHED );
+       }
+
+       /**
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
+        * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        * @param string $lockName
+        * @param string $method
+        * @return bool
+        */
+       public function unlock( $lockName, $method ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
+               $row = $this->fetchObject( $result );
+
+               if ( $row->lockstatus === 't' ) {
+                       parent::unlock( $lockName, $method ); // record
+                       return true;
+               }
+
+               $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
+
+               return false;
+       }
+
+       /**
+        * @param string $lockName
+        * @return string Integer
+        */
+       private function bigintFromLockName( $lockName ) {
+               return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php
new file mode 100644 (file)
index 0000000..6614898
--- /dev/null
@@ -0,0 +1,1059 @@
+<?php
+/**
+ * This is the SQLite database abstraction layer.
+ * See maintenance/sqlite/README for development notes and other specific information
+ *
+ * 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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DatabaseSqlite extends DatabaseBase {
+       /** @var bool Whether full text is enabled */
+       private static $fulltextEnabled = null;
+
+       /** @var string Directory */
+       protected $dbDir;
+
+       /** @var string File name for SQLite database file */
+       protected $dbPath;
+
+       /** @var string Transaction mode */
+       protected $trxMode;
+
+       /** @var int The number of rows affected as an integer */
+       protected $mAffectedRows;
+
+       /** @var resource */
+       protected $mLastResult;
+
+       /** @var PDO */
+       protected $mConn;
+
+       /** @var FSLockManager (hopefully on the same server as the DB) */
+       protected $lockMgr;
+
+       /**
+        * Additional params include:
+        *   - dbDirectory : directory containing the DB and the lock file directory
+        *                   [defaults to $wgSQLiteDataDir]
+        *   - dbFilePath  : use this to force the path of the DB file
+        *   - trxMode     : one of (deferred, immediate, exclusive)
+        * @param array $p
+        */
+       function __construct( array $p ) {
+               if ( isset( $p['dbFilePath'] ) ) {
+                       parent::__construct( $p );
+                       // Standalone .sqlite file mode.
+                       // Super doesn't open when $user is false, but we can work with $dbName,
+                       // which is derived from the file path in this case.
+                       $this->openFile( $p['dbFilePath'] );
+                       $lockDomain = md5( $p['dbFilePath'] );
+               } elseif ( !isset( $p['dbDirectory'] ) ) {
+                       throw new InvalidArgumentException( "Need 'dbDirectory' or 'dbFilePath' parameter." );
+               } else {
+                       $this->dbDir = $p['dbDirectory'];
+                       $this->mDBname = $p['dbname'];
+                       $lockDomain = $this->mDBname;
+                       // Stock wiki mode using standard file names per DB.
+                       parent::__construct( $p );
+                       // Super doesn't open when $user is false, but we can work with $dbName
+                       if ( $p['dbname'] && !$this->isOpen() ) {
+                               if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
+                                       $done = [];
+                                       foreach ( $this->tableAliases as $params ) {
+                                               if ( isset( $done[$params['dbname']] ) ) {
+                                                       continue;
+                                               }
+                                               $this->attachDatabase( $params['dbname'] );
+                                               $done[$params['dbname']] = 1;
+                                       }
+                               }
+                       }
+               }
+
+               $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
+               if ( $this->trxMode &&
+                       !in_array( $this->trxMode, [ 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ] )
+               ) {
+                       $this->trxMode = null;
+                       $this->queryLogger->warning( "Invalid SQLite transaction mode provided." );
+               }
+
+               $this->lockMgr = new FSLockManager( [
+                       'domain' => $lockDomain,
+                       'lockDirectory' => "{$this->dbDir}/locks"
+               ] );
+       }
+
+       /**
+        * @param string $filename
+        * @param array $p Options map; supports:
+        *   - flags       : (same as __construct counterpart)
+        *   - trxMode     : (same as __construct counterpart)
+        *   - dbDirectory : (same as __construct counterpart)
+        * @return DatabaseSqlite
+        * @since 1.25
+        */
+       public static function newStandaloneInstance( $filename, array $p = [] ) {
+               $p['dbFilePath'] = $filename;
+               $p['schema'] = false;
+               $p['tablePrefix'] = '';
+
+               return DatabaseBase::factory( 'sqlite', $p );
+       }
+
+       /**
+        * @return string
+        */
+       function getType() {
+               return 'sqlite';
+       }
+
+       /**
+        * @todo Check if it should be true like parent class
+        *
+        * @return bool
+        */
+       function implicitGroupby() {
+               return false;
+       }
+
+       /** Open an SQLite database and return a resource handle to it
+        *  NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
+        *
+        * @param string $server
+        * @param string $user
+        * @param string $pass
+        * @param string $dbName
+        *
+        * @throws DBConnectionError
+        * @return PDO
+        */
+       function open( $server, $user, $pass, $dbName ) {
+               $this->close();
+               $fileName = self::generateFileName( $this->dbDir, $dbName );
+               if ( !is_readable( $fileName ) ) {
+                       $this->mConn = false;
+                       throw new DBConnectionError( $this, "SQLite database not accessible" );
+               }
+               $this->openFile( $fileName );
+
+               return $this->mConn;
+       }
+
+       /**
+        * Opens a database file
+        *
+        * @param string $fileName
+        * @throws DBConnectionError
+        * @return PDO|bool SQL connection or false if failed
+        */
+       protected function openFile( $fileName ) {
+               $err = false;
+
+               $this->dbPath = $fileName;
+               try {
+                       if ( $this->mFlags & DBO_PERSISTENT ) {
+                               $this->mConn = new PDO( "sqlite:$fileName", '', '',
+                                       [ PDO::ATTR_PERSISTENT => true ] );
+                       } else {
+                               $this->mConn = new PDO( "sqlite:$fileName", '', '' );
+                       }
+               } catch ( PDOException $e ) {
+                       $err = $e->getMessage();
+               }
+
+               if ( !$this->mConn ) {
+                       $this->queryLogger->debug( "DB connection error: $err\n" );
+                       throw new DBConnectionError( $this, $err );
+               }
+
+               $this->mOpened = !!$this->mConn;
+               if ( $this->mOpened ) {
+                       # Set error codes only, don't raise exceptions
+                       $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+                       # Enforce LIKE to be case sensitive, just like MySQL
+                       $this->query( 'PRAGMA case_sensitive_like = 1' );
+
+                       return $this->mConn;
+               }
+
+               return false;
+       }
+
+       /**
+        * @return string SQLite DB file path
+        * @since 1.25
+        */
+       public function getDbFilePath() {
+               return $this->dbPath;
+       }
+
+       /**
+        * Does not actually close the connection, just destroys the reference for GC to do its work
+        * @return bool
+        */
+       protected function closeConnection() {
+               $this->mConn = null;
+
+               return true;
+       }
+
+       /**
+        * Generates a database file name. Explicitly public for installer.
+        * @param string $dir Directory where database resides
+        * @param string $dbName Database name
+        * @return string
+        */
+       public static function generateFileName( $dir, $dbName ) {
+               return "$dir/$dbName.sqlite";
+       }
+
+       /**
+        * Check if the searchindext table is FTS enabled.
+        * @return bool False if not enabled.
+        */
+       function checkForEnabledSearch() {
+               if ( self::$fulltextEnabled === null ) {
+                       self::$fulltextEnabled = false;
+                       $table = $this->tableName( 'searchindex' );
+                       $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
+                       if ( $res ) {
+                               $row = $res->fetchRow();
+                               self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
+                       }
+               }
+
+               return self::$fulltextEnabled;
+       }
+
+       /**
+        * Returns version of currently supported SQLite fulltext search module or false if none present.
+        * @return string
+        */
+       static function getFulltextSearchModule() {
+               static $cachedResult = null;
+               if ( $cachedResult !== null ) {
+                       return $cachedResult;
+               }
+               $cachedResult = false;
+               $table = 'dummy_search_test';
+
+               $db = self::newStandaloneInstance( ':memory:' );
+               if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
+                       $cachedResult = 'FTS3';
+               }
+               $db->close();
+
+               return $cachedResult;
+       }
+
+       /**
+        * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
+        * for details.
+        *
+        * @param string $name Database name to be used in queries like
+        *   SELECT foo FROM dbname.table
+        * @param bool|string $file Database file name. If omitted, will be generated
+        *   using $name and configured data directory
+        * @param string $fname Calling function name
+        * @return ResultWrapper
+        */
+       function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
+               if ( !$file ) {
+                       $file = self::generateFileName( $this->dbDir, $name );
+               }
+               $file = $this->addQuotes( $file );
+
+               return $this->query( "ATTACH DATABASE $file AS $name", $fname );
+       }
+
+       function isWriteQuery( $sql ) {
+               return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
+       }
+
+       /**
+        * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
+        *
+        * @param string $sql
+        * @return bool|ResultWrapper
+        */
+       protected function doQuery( $sql ) {
+               $res = $this->mConn->query( $sql );
+               if ( $res === false ) {
+                       return false;
+               }
+
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               $this->mAffectedRows = $r->rowCount();
+               $res = new ResultWrapper( $this, $r->fetchAll() );
+
+               return $res;
+       }
+
+       /**
+        * @param ResultWrapper|mixed $res
+        */
+       function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res->result = null;
+               } else {
+                       $res = null;
+               }
+       }
+
+       /**
+        * @param ResultWrapper|array $res
+        * @return stdClass|bool
+        */
+       function fetchObject( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+
+               $cur = current( $r );
+               if ( is_array( $cur ) ) {
+                       next( $r );
+                       $obj = new stdClass;
+                       foreach ( $cur as $k => $v ) {
+                               if ( !is_numeric( $k ) ) {
+                                       $obj->$k = $v;
+                               }
+                       }
+
+                       return $obj;
+               }
+
+               return false;
+       }
+
+       /**
+        * @param ResultWrapper|mixed $res
+        * @return array|bool
+        */
+       function fetchRow( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+               $cur = current( $r );
+               if ( is_array( $cur ) ) {
+                       next( $r );
+
+                       return $cur;
+               }
+
+               return false;
+       }
+
+       /**
+        * The PDO::Statement class implements the array interface so count() will work
+        *
+        * @param ResultWrapper|array $res
+        * @return int
+        */
+       function numRows( $res ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+
+               return count( $r );
+       }
+
+       /**
+        * @param ResultWrapper $res
+        * @return int
+        */
+       function numFields( $res ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               if ( is_array( $r ) && count( $r ) > 0 ) {
+                       // The size of the result array is twice the number of fields. (Bug: 65578)
+                       return count( $r[0] ) / 2;
+               } else {
+                       // If the result is empty return 0
+                       return 0;
+               }
+       }
+
+       /**
+        * @param ResultWrapper $res
+        * @param int $n
+        * @return bool
+        */
+       function fieldName( $res, $n ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               if ( is_array( $r ) ) {
+                       $keys = array_keys( $r[0] );
+
+                       return $keys[$n];
+               }
+
+               return false;
+       }
+
+       /**
+        * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
+        *
+        * @param string $name
+        * @param string $format
+        * @return string
+        */
+       function tableName( $name, $format = 'quoted' ) {
+               // table names starting with sqlite_ are reserved
+               if ( strpos( $name, 'sqlite_' ) === 0 ) {
+                       return $name;
+               }
+
+               return str_replace( '"', '', parent::tableName( $name, $format ) );
+       }
+
+       /**
+        * Index names have DB scope
+        *
+        * @param string $index
+        * @return string
+        */
+       protected function indexName( $index ) {
+               return $index;
+       }
+
+       /**
+        * This must be called after nextSequenceVal
+        *
+        * @return int
+        */
+       function insertId() {
+               // PDO::lastInsertId yields a string :(
+               return intval( $this->mConn->lastInsertId() );
+       }
+
+       /**
+        * @param ResultWrapper|array $res
+        * @param int $row
+        */
+       function dataSeek( $res, $row ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+               reset( $r );
+               if ( $row > 0 ) {
+                       for ( $i = 0; $i < $row; $i++ ) {
+                               next( $r );
+                       }
+               }
+       }
+
+       /**
+        * @return string
+        */
+       function lastError() {
+               if ( !is_object( $this->mConn ) ) {
+                       return "Cannot return last error, no db connection";
+               }
+               $e = $this->mConn->errorInfo();
+
+               return isset( $e[2] ) ? $e[2] : '';
+       }
+
+       /**
+        * @return string
+        */
+       function lastErrno() {
+               if ( !is_object( $this->mConn ) ) {
+                       return "Cannot return last error, no db connection";
+               } else {
+                       $info = $this->mConn->errorInfo();
+
+                       return $info[1];
+               }
+       }
+
+       /**
+        * @return int
+        */
+       function affectedRows() {
+               return $this->mAffectedRows;
+       }
+
+       /**
+        * Returns information about an index
+        * Returns false if the index does not exist
+        * - if errors are explicitly ignored, returns NULL on failure
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return array
+        */
+       function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+               if ( $res->numRows() == 0 ) {
+                       return false;
+               }
+               $info = [];
+               foreach ( $res as $row ) {
+                       $info[] = $row->name;
+               }
+
+               return $info;
+       }
+
+       /**
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|null
+        */
+       function indexUnique( $table, $index, $fname = __METHOD__ ) {
+               $row = $this->selectRow( 'sqlite_master', '*',
+                       [
+                               'type' => 'index',
+                               'name' => $this->indexName( $index ),
+                       ], $fname );
+               if ( !$row || !isset( $row->sql ) ) {
+                       return null;
+               }
+
+               // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
+               $indexPos = strpos( $row->sql, 'INDEX' );
+               if ( $indexPos === false ) {
+                       return null;
+               }
+               $firstPart = substr( $row->sql, 0, $indexPos );
+               $options = explode( ' ', $firstPart );
+
+               return in_array( 'UNIQUE', $options );
+       }
+
+       /**
+        * Filter the options used in SELECT statements
+        *
+        * @param array $options
+        * @return array
+        */
+       function makeSelectOptions( $options ) {
+               foreach ( $options as $k => $v ) {
+                       if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
+                               $options[$k] = '';
+                       }
+               }
+
+               return parent::makeSelectOptions( $options );
+       }
+
+       /**
+        * @param array $options
+        * @return string
+        */
+       protected function makeUpdateOptionsArray( $options ) {
+               $options = parent::makeUpdateOptionsArray( $options );
+               $options = self::fixIgnore( $options );
+
+               return $options;
+       }
+
+       /**
+        * @param array $options
+        * @return array
+        */
+       static function fixIgnore( $options ) {
+               # SQLite uses OR IGNORE not just IGNORE
+               foreach ( $options as $k => $v ) {
+                       if ( $v == 'IGNORE' ) {
+                               $options[$k] = 'OR IGNORE';
+                       }
+               }
+
+               return $options;
+       }
+
+       /**
+        * @param array $options
+        * @return string
+        */
+       function makeInsertOptions( $options ) {
+               $options = self::fixIgnore( $options );
+
+               return parent::makeInsertOptions( $options );
+       }
+
+       /**
+        * Based on generic method (parent) with some prior SQLite-sepcific adjustments
+        * @param string $table
+        * @param array $a
+        * @param string $fname
+        * @param array $options
+        * @return bool
+        */
+       function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+               if ( !count( $a ) ) {
+                       return true;
+               }
+
+               # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
+               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
+                       $ret = true;
+                       foreach ( $a as $v ) {
+                               if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
+                                       $ret = false;
+                               }
+                       }
+               } else {
+                       $ret = parent::insert( $table, $a, "$fname/single-row", $options );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param string $table
+        * @param array $uniqueIndexes Unused
+        * @param string|array $rows
+        * @param string $fname
+        * @return bool|ResultWrapper
+        */
+       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+               if ( !count( $rows ) ) {
+                       return true;
+               }
+
+               # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
+               if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
+                       $ret = true;
+                       foreach ( $rows as $v ) {
+                               if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
+                                       $ret = false;
+                               }
+                       }
+               } else {
+                       $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Returns the size of a text field, or -1 for "unlimited"
+        * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
+        *
+        * @param string $table
+        * @param string $field
+        * @return int
+        */
+       function textFieldSize( $table, $field ) {
+               return -1;
+       }
+
+       /**
+        * @return bool
+        */
+       function unionSupportsOrderAndLimit() {
+               return false;
+       }
+
+       /**
+        * @param string $sqls
+        * @param bool $all Whether to "UNION ALL" or not
+        * @return string
+        */
+       function unionQueries( $sqls, $all ) {
+               $glue = $all ? ' UNION ALL ' : ' UNION ';
+
+               return implode( $glue, $sqls );
+       }
+
+       /**
+        * @return bool
+        */
+       function wasDeadlock() {
+               return $this->lastErrno() == 5; // SQLITE_BUSY
+       }
+
+       /**
+        * @return bool
+        */
+       function wasErrorReissuable() {
+               return $this->lastErrno() == 17; // SQLITE_SCHEMA;
+       }
+
+       /**
+        * @return bool
+        */
+       function wasReadOnlyError() {
+               return $this->lastErrno() == 8; // SQLITE_READONLY;
+       }
+
+       /**
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink() {
+               return "[{{int:version-db-sqlite-url}} SQLite]";
+       }
+
+       /**
+        * @return string Version information from the database
+        */
+       function getServerVersion() {
+               $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+
+               return $ver;
+       }
+
+       /**
+        * Get information about a given field
+        * Returns false if the field does not exist.
+        *
+        * @param string $table
+        * @param string $field
+        * @return SQLiteField|bool False on failure
+        */
+       function fieldInfo( $table, $field ) {
+               $tableName = $this->tableName( $table );
+               $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
+               $res = $this->query( $sql, __METHOD__ );
+               foreach ( $res as $row ) {
+                       if ( $row->name == $field ) {
+                               return new SQLiteField( $row, $tableName );
+                       }
+               }
+
+               return false;
+       }
+
+       protected function doBegin( $fname = '' ) {
+               if ( $this->trxMode ) {
+                       $this->query( "BEGIN {$this->trxMode}", $fname );
+               } else {
+                       $this->query( 'BEGIN', $fname );
+               }
+               $this->mTrxLevel = 1;
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       function strencode( $s ) {
+               return substr( $this->addQuotes( $s ), 1, -1 );
+       }
+
+       /**
+        * @param string $b
+        * @return Blob
+        */
+       function encodeBlob( $b ) {
+               return new Blob( $b );
+       }
+
+       /**
+        * @param Blob|string $b
+        * @return string
+        */
+       function decodeBlob( $b ) {
+               if ( $b instanceof Blob ) {
+                       $b = $b->fetch();
+               }
+
+               return $b;
+       }
+
+       /**
+        * @param Blob|string $s
+        * @return string
+        */
+       function addQuotes( $s ) {
+               if ( $s instanceof Blob ) {
+                       return "x'" . bin2hex( $s->fetch() ) . "'";
+               } elseif ( is_bool( $s ) ) {
+                       return (int)$s;
+               } elseif ( strpos( $s, "\0" ) !== false ) {
+                       // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
+                       // This is a known limitation of SQLite's mprintf function which PDO
+                       // should work around, but doesn't. I have reported this to php.net as bug #63419:
+                       // https://bugs.php.net/bug.php?id=63419
+                       // There was already a similar report for SQLite3::escapeString, bug #62361:
+                       // https://bugs.php.net/bug.php?id=62361
+                       // There is an additional bug regarding sorting this data after insert
+                       // on older versions of sqlite shipped with ubuntu 12.04
+                       // https://phabricator.wikimedia.org/T74367
+                       $this->queryLogger->debug(
+                               __FUNCTION__ .
+                               ': Quoting value containing null byte. ' .
+                               'For consistency all binary data should have been ' .
+                               'first processed with self::encodeBlob()'
+                       );
+                       return "x'" . bin2hex( $s ) . "'";
+               } else {
+                       return $this->mConn->quote( $s );
+               }
+       }
+
+       /**
+        * @return string
+        */
+       function buildLike() {
+               $params = func_get_args();
+               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+
+               return parent::buildLike( $params ) . "ESCAPE '\' ";
+       }
+
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return 'CAST ( ' . $field . ' AS TEXT )';
+       }
+
+       /**
+        * No-op version of deadlockLoop
+        *
+        * @return mixed
+        */
+       public function deadlockLoop( /*...*/ ) {
+               $args = func_get_args();
+               $function = array_shift( $args );
+
+               return call_user_func_array( $function, $args );
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       protected function replaceVars( $s ) {
+               $s = parent::replaceVars( $s );
+               if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
+                       // CREATE TABLE hacks to allow schema file sharing with MySQL
+
+                       // binary/varbinary column type -> blob
+                       $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
+                       // no such thing as unsigned
+                       $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
+                       // INT -> INTEGER
+                       $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
+                       // floating point types -> REAL
+                       $s = preg_replace(
+                               '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
+                               'REAL',
+                               $s
+                       );
+                       // varchar -> TEXT
+                       $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
+                       // TEXT normalization
+                       $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
+                       // BLOB normalization
+                       $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
+                       // BOOL -> INTEGER
+                       $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
+                       // DATETIME -> TEXT
+                       $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
+                       // No ENUM type
+                       $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
+                       // binary collation type -> nothing
+                       $s = preg_replace( '/\bbinary\b/i', '', $s );
+                       // auto_increment -> autoincrement
+                       $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
+                       // No explicit options
+                       $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
+                       // AUTOINCREMENT should immedidately follow PRIMARY KEY
+                       $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
+               } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
+                       // No truncated indexes
+                       $s = preg_replace( '/\(\d+\)/', '', $s );
+                       // No FULLTEXT
+                       $s = preg_replace( '/\bfulltext\b/i', '', $s );
+               } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
+                       // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
+                       $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
+               } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
+                       // INSERT IGNORE --> INSERT OR IGNORE
+                       $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
+               }
+
+               return $s;
+       }
+
+       public function lock( $lockName, $method, $timeout = 5 ) {
+               if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
+                       if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
+                               throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
+                       }
+               }
+
+               return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
+       }
+
+       public function unlock( $lockName, $method ) {
+               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
+       }
+
+       /**
+        * Build a concatenation list to feed into a SQL query
+        *
+        * @param string[] $stringList
+        * @return string
+        */
+       function buildConcat( $stringList ) {
+               return '(' . implode( ') || (', $stringList ) . ')';
+       }
+
+       public function buildGroupConcatField(
+               $delim, $table, $field, $conds = '', $join_conds = []
+       ) {
+               $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
+
+               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
+       }
+
+       /**
+        * @param string $oldName
+        * @param string $newName
+        * @param bool $temporary
+        * @param string $fname
+        * @return bool|ResultWrapper
+        * @throws RuntimeException
+        */
+       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+               $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
+                       $this->addQuotes( $oldName ) . " AND type='table'", $fname );
+               $obj = $this->fetchObject( $res );
+               if ( !$obj ) {
+                       throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
+               }
+               $sql = $obj->sql;
+               $sql = preg_replace(
+                       '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
+                       $this->addIdentifierQuotes( $newName ),
+                       $sql,
+                       1
+               );
+               if ( $temporary ) {
+                       if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
+                               $this->queryLogger->debug(
+                                       "Table $oldName is virtual, can't create a temporary duplicate.\n" );
+                       } else {
+                               $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
+                       }
+               }
+
+               $res = $this->query( $sql, $fname );
+
+               // Take over indexes
+               $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
+               foreach ( $indexList as $index ) {
+                       if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
+                               continue;
+                       }
+
+                       if ( $index->unique ) {
+                               $sql = 'CREATE UNIQUE INDEX';
+                       } else {
+                               $sql = 'CREATE INDEX';
+                       }
+                       // Try to come up with a new index name, given indexes have database scope in SQLite
+                       $indexName = $newName . '_' . $index->name;
+                       $sql .= ' ' . $indexName . ' ON ' . $newName;
+
+                       $indexInfo = $this->query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')' );
+                       $fields = [];
+                       foreach ( $indexInfo as $indexInfoRow ) {
+                               $fields[$indexInfoRow->seqno] = $indexInfoRow->name;
+                       }
+
+                       $sql .= '(' . implode( ',', $fields ) . ')';
+
+                       $this->query( $sql );
+               }
+
+               return $res;
+       }
+
+       /**
+        * List all tables on the database
+        *
+        * @param string $prefix Only show tables with this prefix, e.g. mw_
+        * @param string $fname Calling function name
+        *
+        * @return array
+        */
+       function listTables( $prefix = null, $fname = __METHOD__ ) {
+               $result = $this->select(
+                       'sqlite_master',
+                       'name',
+                       "type='table'"
+               );
+
+               $endArray = [];
+
+               foreach ( $result as $table ) {
+                       $vars = get_object_vars( $table );
+                       $table = array_pop( $vars );
+
+                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                               if ( strpos( $table, 'sqlite_' ) !== 0 ) {
+                                       $endArray[] = $table;
+                               }
+                       }
+               }
+
+               return $endArray;
+       }
+
+       /**
+        * Override due to no CASCADE support
+        *
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        * @throws DBReadOnlyError
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+               $sql = "DROP TABLE " . $this->tableName( $tableName );
+
+               return $this->query( $sql, $fName );
+       }
+
+       protected function requiresDatabaseUser() {
+               return false; // just a file
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+       }
+}
index 495816f..25e5912 100644 (file)
@@ -63,6 +63,17 @@ interface IDatabase {
        /** @var string Estimate time to apply (scanning, applying) */
        const ESTIMATE_DB_APPLY = 'apply';
 
+       /** @var int Combine list with comma delimeters */
+       const LIST_COMMA = 0;
+       /** @var int Combine list with AND clauses */
+       const LIST_AND = 1;
+       /** @var int Convert map into a SET clause */
+       const LIST_SET = 2;
+       /** @var int Treat as field name and do not apply value escaping */
+       const LIST_NAMES = 3;
+       /** @var int Combine list with OR clauses */
+       const LIST_OR = 4;
+
        /**
         * A string describing the current software version, and possibly
         * other details in a user-friendly way. Will be listed on Special:Version, etc.
@@ -897,18 +908,29 @@ interface IDatabase {
        /**
         * Makes an encoded list of strings from an array
         *
+        * These can be used to make conjunctions or disjunctions on SQL condition strings
+        * derived from an array (see IDatabase::select() $conds documentation).
+        *
+        * Example usage:
+        * @code
+        *     $sql = $db->makeList( [
+        *         'rev_user' => $id,
+        *         $db->makeList( [ 'rev_minor' => 1, 'rev_len' < 500 ], $db::LIST_OR ] )
+        *     ], $db::LIST_AND );
+        * @endcode
+        * This would set $sql to "rev_user = '$id' AND (rev_minor = '1' OR rev_len < '500')"
+        *
         * @param array $a Containing the data
-        * @param int $mode Constant
-        *    - LIST_COMMA: Comma separated, no field names
-        *    - LIST_AND:   ANDed WHERE clause (without the WHERE). See the
-        *      documentation for $conds in IDatabase::select().
-        *    - LIST_OR:    ORed WHERE clause (without the WHERE)
-        *    - LIST_SET:   Comma separated with field names, like a SET clause
-        *    - LIST_NAMES: Comma separated field names
+        * @param int $mode IDatabase class constant:
+        *    - IDatabase::LIST_COMMA: Comma separated, no field names
+        *    - IDatabase::LIST_AND:   ANDed WHERE clause (without the WHERE).
+        *    - IDatabase::LIST_OR:    ORed WHERE clause (without the WHERE)
+        *    - IDatabase::LIST_SET:   Comma separated with field names, like a SET clause
+        *    - IDatabase::LIST_NAMES: Comma separated field names
         * @throws DBError
         * @return string
         */
-       public function makeList( $a, $mode = LIST_COMMA );
+       public function makeList( $a, $mode = self::LIST_COMMA );
 
        /**
         * Build a partial where clause from a 2-d array such as used for LinkBatch.
index 774def8..1a046cf 100644 (file)
@@ -4,35 +4,19 @@
  * doesn't go anywhere near an actual database.
  */
 class FakeResultWrapper extends ResultWrapper {
-       /** @var array */
-       public $result = [];
-
-       /** @var null And it's going to stay that way :D */
-       protected $db = null;
-
-       /** @var int */
-       protected $pos = 0;
-
-       /** @var array|stdClass|bool */
-       protected $currentRow = null;
+       /** @var $result stdClass[] */
 
        /**
-        * @param array $array
+        * @param stdClass[] $rows
         */
-       function __construct( $array ) {
-               $this->result = $array;
+       function __construct( array $rows ) {
+               parent::__construct( null, $rows );
        }
 
-       /**
-        * @return int
-        */
        function numRows() {
                return count( $this->result );
        }
 
-       /**
-        * @return array|bool
-        */
        function fetchRow() {
                if ( $this->pos < count( $this->result ) ) {
                        $this->currentRow = $this->result[$this->pos];
@@ -54,10 +38,6 @@ class FakeResultWrapper extends ResultWrapper {
        function free() {
        }
 
-       /**
-        * Callers want to be able to access fields with $this->fieldName
-        * @return bool|stdClass
-        */
        function fetchObject() {
                $this->fetchRow();
                if ( $this->currentRow ) {
@@ -72,9 +52,6 @@ class FakeResultWrapper extends ResultWrapper {
                $this->currentRow = null;
        }
 
-       /**
-        * @return bool|stdClass
-        */
        function next() {
                return $this->fetchObject();
        }
index cccb8f1..768511b 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 class MssqlResultWrapper extends ResultWrapper {
+       /** @var integer|null */
        private $mSeekTo = null;
 
        /**
index 4843d02..53109c8 100644 (file)
@@ -1,30 +1,43 @@
 <?php
 /**
- * Result wrapper for grabbing data queried by someone else
+ * Result wrapper for grabbing data queried from an IDatabase object
+ *
+ * Note that using the Iterator methods in combination with the non-Iterator
+ * DB result iteration functions may cause rows to be skipped or repeated.
+ *
+ * By default, this will use the iteration methods of the IDatabase handle if provided.
+ * Subclasses can override methods to make it solely work on the result resource instead.
+ * If no database is provided, and the subclass does not override the DB iteration methods,
+ * then a RuntimeException will be thrown when iteration is attempted.
+ *
+ * The result resource field should not be accessed from non-Database related classes.
+ * It is database class specific and is stored here to associate iterators with queries.
+ *
  * @ingroup Database
  */
 class ResultWrapper implements Iterator {
-       /** @var resource */
+       /** @var resource|array|null Optional underlying result handle for subclass usage */
        public $result;
 
-       /** @var IDatabase */
+       /** @var IDatabase|null */
        protected $db;
 
        /** @var int */
        protected $pos = 0;
-
-       /** @var object|null */
+       /** @var stdClass|null */
        protected $currentRow = null;
 
        /**
-        * Create a new result object from a result resource and a Database object
+        * Create a row iterator from a result resource and an optional Database object
+        *
+        * Only Database-related classes should construct ResultWrapper. Other code may
+        * use the FakeResultWrapper subclass for convenience or compatibility shims, however.
         *
-        * @param IDatabase $database
-        * @param resource|ResultWrapper $result
+        * @param IDatabase|null $db Optional database handle
+        * @param ResultWrapper|array|resource $result Optional underlying result handle
         */
-       function __construct( $database, $result ) {
-               $this->db = $database;
-
+       public function __construct( IDatabase $db = null, $result ) {
+               $this->db = $db;
                if ( $result instanceof ResultWrapper ) {
                        $this->result = $result->result;
                } else {
@@ -37,8 +50,8 @@ class ResultWrapper implements Iterator {
         *
         * @return int
         */
-       function numRows() {
-               return $this->db->numRows( $this );
+       public function numRows() {
+               return $this->getDB()->numRows( $this );
        }
 
        /**
@@ -49,8 +62,8 @@ class ResultWrapper implements Iterator {
         * @return stdClass|bool
         * @throws DBUnexpectedError Thrown if the database returns an error
         */
-       function fetchObject() {
-               return $this->db->fetchObject( $this );
+       public function fetchObject() {
+               return $this->getDB()->fetchObject( $this );
        }
 
        /**
@@ -60,38 +73,49 @@ class ResultWrapper implements Iterator {
         * @return array|bool
         * @throws DBUnexpectedError Thrown if the database returns an error
         */
-       function fetchRow() {
-               return $this->db->fetchRow( $this );
+       public function fetchRow() {
+               return $this->getDB()->fetchRow( $this );
        }
 
        /**
-        * Free a result object
+        * Change the position of the cursor in a result object.
+        * See mysql_data_seek()
+        *
+        * @param int $row
         */
-       function free() {
-               $this->db->freeResult( $this );
-               unset( $this->result );
-               unset( $this->db );
+       public function seek( $row ) {
+               $this->getDB()->dataSeek( $this, $row );
        }
 
        /**
-        * Change the position of the cursor in a result object.
-        * See mysql_data_seek()
+        * Free a result object
         *
-        * @param int $row
+        * This either saves memory in PHP (buffered queries) or on the server (unbuffered queries).
+        * In general, queries are not large enough in result sets for this to be worth calling.
         */
-       function seek( $row ) {
-               $this->db->dataSeek( $this, $row );
+       public function free() {
+               if ( $this->db ) {
+                       $this->db->freeResult( $this );
+                       $this->db = null;
+               }
+               $this->result = null;
        }
 
-       /*
-        * ======= Iterator functions =======
-        * Note that using these in combination with the non-iterator functions
-        * above may cause rows to be skipped or repeated.
+       /**
+        * @return IDatabase
+        * @throws RuntimeException
         */
+       private function getDB() {
+               if ( !$this->db ) {
+                       throw new RuntimeException( get_class( $this ) . ' needs a DB handle for iteration.' );
+               }
+
+               return $this->db;
+       }
 
        function rewind() {
                if ( $this->numRows() ) {
-                       $this->db->dataSeek( $this, 0 );
+                       $this->getDB()->dataSeek( $this, 0 );
                }
                $this->pos = 0;
                $this->currentRow = null;
@@ -125,9 +149,6 @@ class ResultWrapper implements Iterator {
                return $this->currentRow;
        }
 
-       /**
-        * @return bool
-        */
        function valid() {
                return $this->current() !== false;
        }
diff --git a/includes/libs/rdbms/database/utils/SavepointPostgres.php b/includes/libs/rdbms/database/utils/SavepointPostgres.php
new file mode 100644 (file)
index 0000000..ec4d09f
--- /dev/null
@@ -0,0 +1,101 @@
+<?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 Database
+ */
+use Psr\Log\LoggerInterface;
+
+/**
+ * Manage savepoints within a transaction
+ * @ingroup Database
+ * @since 1.19
+ */
+class SavepointPostgres {
+       /** @var DatabasePostgres Establish a savepoint within a transaction */
+       protected $dbw;
+       /** @var LoggerInterface */
+       protected $logger;
+       /** @var int */
+       protected $id;
+       /** @var bool */
+       protected $didbegin;
+
+       /**
+        * @param DatabasePostgres $dbw
+        * @param int $id
+        * @param LoggerInterface $logger
+        */
+       public function __construct( DatabasePostgres $dbw, $id, LoggerInterface $logger ) {
+               $this->dbw = $dbw;
+               $this->logger = $logger;
+               $this->id = $id;
+               $this->didbegin = false;
+               /* If we are not in a transaction, we need to be for savepoint trickery */
+               if ( !$dbw->trxLevel() ) {
+                       $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
+                       $this->didbegin = true;
+               }
+       }
+
+       public function __destruct() {
+               if ( $this->didbegin ) {
+                       $this->dbw->rollback();
+                       $this->didbegin = false;
+               }
+       }
+
+       public function commit() {
+               if ( $this->didbegin ) {
+                       $this->dbw->commit();
+                       $this->didbegin = false;
+               }
+       }
+
+       protected function query( $keyword, $msg_ok, $msg_failed ) {
+               if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
+                       $this->logger->debug( sprintf( $msg_ok, $this->id ) );
+               } else {
+                       $this->logger->debug( sprintf( $msg_failed, $this->id ) );
+               }
+       }
+
+       public function savepoint() {
+               $this->query( "SAVEPOINT",
+                       "Transaction state: savepoint \"%s\" established.\n",
+                       "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function release() {
+               $this->query( "RELEASE",
+                       "Transaction state: savepoint \"%s\" released.\n",
+                       "Transaction state: release of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function rollback() {
+               $this->query( "ROLLBACK TO",
+                       "Transaction state: savepoint \"%s\" rolled back.\n",
+                       "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function __toString() {
+               return (string)$this->id;
+       }
+}
index 48baa3c..b420ca1 100644 (file)
@@ -22,14 +22,3 @@ define( 'DBO_COMPRESS', 512 );
 define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
 define( 'DB_MASTER', -2 );    # Write to master (or only server)
 /**@}*/
-
-/**@{
- * Flags for IDatabase::makeList()
- * These are also available as Database class constants
- */
-define( 'LIST_COMMA', 0 );
-define( 'LIST_AND', 1 );
-define( 'LIST_SET', 2 );
-define( 'LIST_NAMES', 3 );
-define( 'LIST_OR', 4 );
-/**@}*/
diff --git a/includes/libs/rdbms/field/PostgresField.php b/includes/libs/rdbms/field/PostgresField.php
new file mode 100644 (file)
index 0000000..36337e2
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+class PostgresField implements Field {
+       private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
+               $has_default, $default;
+
+       /**
+        * @param DatabasePostgres $db
+        * @param string $table
+        * @param string $field
+        * @return null|PostgresField
+        */
+       static function fromText( $db, $table, $field ) {
+               $q = <<<SQL
+SELECT
+ attnotnull, attlen, conname AS conname,
+ atthasdef,
+ adsrc,
+ COALESCE(condeferred, 'f') AS deferred,
+ COALESCE(condeferrable, 'f') AS deferrable,
+ CASE WHEN typname = 'int2' THEN 'smallint'
+  WHEN typname = 'int4' THEN 'integer'
+  WHEN typname = 'int8' THEN 'bigint'
+  WHEN typname = 'bpchar' THEN 'char'
+ ELSE typname END AS typname
+FROM pg_class c
+JOIN pg_namespace n ON (n.oid = c.relnamespace)
+JOIN pg_attribute a ON (a.attrelid = c.oid)
+JOIN pg_type t ON (t.oid = a.atttypid)
+LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
+LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
+WHERE relkind = 'r'
+AND nspname=%s
+AND relname=%s
+AND attname=%s;
+SQL;
+
+               $table = $db->tableName( $table, 'raw' );
+               $res = $db->query(
+                       sprintf( $q,
+                               $db->addQuotes( $db->getCoreSchema() ),
+                               $db->addQuotes( $table ),
+                               $db->addQuotes( $field )
+                       )
+               );
+               $row = $db->fetchObject( $res );
+               if ( !$row ) {
+                       return null;
+               }
+               $n = new PostgresField;
+               $n->type = $row->typname;
+               $n->nullable = ( $row->attnotnull == 'f' );
+               $n->name = $field;
+               $n->tablename = $table;
+               $n->max_length = $row->attlen;
+               $n->deferrable = ( $row->deferrable == 't' );
+               $n->deferred = ( $row->deferred == 't' );
+               $n->conname = $row->conname;
+               $n->has_default = ( $row->atthasdef === 't' );
+               $n->default = $row->adsrc;
+
+               return $n;
+       }
+
+       function name() {
+               return $this->name;
+       }
+
+       function tableName() {
+               return $this->tablename;
+       }
+
+       function type() {
+               return $this->type;
+       }
+
+       function isNullable() {
+               return $this->nullable;
+       }
+
+       function maxLength() {
+               return $this->max_length;
+       }
+
+       function is_deferrable() {
+               return $this->deferrable;
+       }
+
+       function is_deferred() {
+               return $this->deferred;
+       }
+
+       function conname() {
+               return $this->conname;
+       }
+
+       /**
+        * @since 1.19
+        * @return bool|mixed
+        */
+       function defaultValue() {
+               if ( $this->has_default ) {
+                       return $this->default;
+               } else {
+                       return false;
+               }
+       }
+}
index 40ba458..cf9a298 100644 (file)
@@ -80,8 +80,26 @@ abstract class LBFactory {
                [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
 
        /**
-        * @TODO: document base params here
-        * @param array $conf
+        * Construct a manager of ILoadBalancer objects
+        *
+        * Sub-classes will extend the required keys in $conf with additional parameters
+        *
+        * @param $conf $params Array with keys:
+        *  - localDomain: A DatabaseDomain or domain ID string.
+        *  - readOnlyReason : Reason the master DB is read-only if so [optional]
+        *  - srvCache : BagOStuff object for server cache [optional]
+        *  - memCache : BagOStuff object for cluster memory cache [optional]
+        *  - wanCache : WANObjectCache object [optional]
+        *  - hostname : The name of the current server [optional]
+        *  - cliMode: Whether the execution context is a CLI script. [optional]
+        *  - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
+        *  - trxProfiler: TransactionProfiler instance. [optional]
+        *  - replLogger: PSR-3 logger instance. [optional]
+        *  - connLogger: PSR-3 logger instance. [optional]
+        *  - queryLogger: PSR-3 logger instance. [optional]
+        *  - perfLogger: PSR-3 logger instance. [optional]
+        *  - errorLogger : Callback that takes an Exception and logs it. [optional]
+        * @throws InvalidArgumentException
         */
        public function __construct( array $conf ) {
                $this->localDomain = isset( $conf['localDomain'] )
@@ -107,8 +125,6 @@ abstract class LBFactory {
                                trigger_error( E_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
                        };
 
-               $this->chronProt = isset( $conf['chronProt'] ) ? $conf['chronProt'] : null;
-
                $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
                $this->trxProfiler = isset( $conf['trxProfiler'] )
                        ? $conf['trxProfiler']
@@ -130,7 +146,7 @@ abstract class LBFactory {
        /**
         * Disables all load balancers. All connections are closed, and any attempt to
         * open a new connection will result in a DBAccessError.
-        * @see LoadBalancer::disable()
+        * @see ILoadBalancer::disable()
         */
        public function destroy() {
                $this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
@@ -670,7 +686,7 @@ abstract class LBFactory {
                        $prefix
                );
 
-               $this->forEachLB( function( LoadBalancer $lb ) use ( $prefix ) {
+               $this->forEachLB( function( ILoadBalancer $lb ) use ( $prefix ) {
                        $lb->setDomainPrefix( $prefix );
                } );
        }
index 0f1493a..25e1fe0 100644 (file)
  * A multi-database, multi-master factory for Wikimedia and similar installations.
  * Ignores the old configuration globals.
  *
- * Template override precedence (highest => lowest):
- *   - templateOverridesByServer
- *   - masterTemplateOverrides
- *   - templateOverridesBySection/templateOverridesByCluster
- *   - externalTemplateOverrides
- *   - serverTemplate
- * Overrides only work on top level keys (so nested values will not be merged).
- *
- * Configuration:
- *     sectionsByDB                A map of database names to section names.
- *
- *     sectionLoads                A 2-d map. For each section, gives a map of server names to
- *                                 load ratios. For example:
- *                                 [
- *                                     'section1' => [
- *                                         'db1' => 100,
- *                                         'db2' => 100
- *                                     ]
- *                                 ]
- *
- *     serverTemplate              A server info associative array as documented for $wgDBservers.
- *                                 The host, hostName and load entries will be overridden.
- *
- *     groupLoadsBySection         A 3-d map giving server load ratios for each section and group.
- *                                 For example:
- *                                 [
- *                                     'section1' => [
- *                                         'group1' => [
- *                                             'db1' => 100,
- *                                             'db2' => 100
- *                                         ]
- *                                     ]
- *                                 ]
- *
- *     groupLoadsByDB              A 3-d map giving server load ratios by DB name.
- *
- *     hostsByName                 A map of hostname to IP address.
- *
- *     externalLoads               A map of external storage cluster name to server load map.
- *
- *     externalTemplateOverrides   A set of server info keys overriding serverTemplate for external
- *                                 storage.
- *
- *     templateOverridesByServer   A 2-d map overriding serverTemplate and
- *                                 externalTemplateOverrides on a server-by-server basis. Applies
- *                                 to both core and external storage.
- *     templateOverridesBySection  A 2-d map overriding the server info by section.
- *     templateOverridesByCluster  A 2-d map overriding the server info by external storage cluster.
- *
- *     masterTemplateOverrides     An override array for all master servers.
- *
- *     loadMonitorClass            Name of the LoadMonitor class to always use.
- *
- *     readOnlyBySection           A map of section name to read-only message.
- *                                 Missing or false for read/write.
- *
  * @ingroup Database
  */
 class LBFactoryMulti extends LBFactory {
@@ -141,8 +85,6 @@ class LBFactoryMulti extends LBFactory {
         */
        private $readOnlyBySection = [];
 
-       // Other stuff
-
        /** @var array Load balancer factory configuration */
        private $conf;
 
@@ -162,8 +104,60 @@ class LBFactoryMulti extends LBFactory {
        private $lastSection;
 
        /**
-        * @param array $conf
-        * @throws InvalidArgumentException
+        * @see LBFactory::__construct()
+        *
+        * Template override precedence (highest => lowest):
+        *   - templateOverridesByServer
+        *   - masterTemplateOverrides
+        *   - templateOverridesBySection/templateOverridesByCluster
+        *   - externalTemplateOverrides
+        *   - serverTemplate
+        * Overrides only work on top level keys (so nested values will not be merged).
+        *
+        * Server configuration maps should be of the format Database::factory() requires.
+        * Additionally, a 'max lag' key should also be set on server maps, indicating how stale the
+        * data can be before the load balancer tries to avoid using it. The map can have 'is static'
+        * set to disable blocking  replication sync checks (intended for archive servers with
+        * unchanging data).
+        *
+        * @param array $conf Parameters of LBFactory::__construct() as well as:
+        *   - sectionsByDB                Map of database names to section names.
+        *   - sectionLoads                2-d map. For each section, gives a map of server names to
+        *                                 load ratios. For example:
+        *                                 [
+        *                                     'section1' => [
+        *                                         'db1' => 100,
+        *                                         'db2' => 100
+        *                                     ]
+        *                                 ]
+        *   - serverTemplate              Server configuration map intended for Database::factory().
+        *                                 Note that "host", "hostName" and "load" entries will be
+        *                                 overridden by "sectionLoads" and "hostsByName".
+        *   - groupLoadsBySection         3-d map giving server load ratios for each section/group.
+        *                                 For example:
+        *                                 [
+        *                                     'section1' => [
+        *                                         'group1' => [
+        *                                             'db1' => 100,
+        *                                             'db2' => 100
+        *                                         ]
+        *                                     ]
+        *                                 ]
+        *   - groupLoadsByDB              3-d map giving server load ratios by DB name.
+        *   - hostsByName                 Map of hostname to IP address.
+        *   - externalLoads               Map of external storage cluster name to server load map.
+        *   - externalTemplateOverrides   Set of server configuration maps overriding
+        *                                 "serverTemplate" for external storage.
+        *   - templateOverridesByServer   2-d map overriding "serverTemplate" and
+        *                                 "externalTemplateOverrides" on a server-by-server basis.
+        *                                 Applies to both core and external storage.
+        *   - templateOverridesBySection  2-d map overriding the server configuration maps by section.
+        *   - templateOverridesByCluster  2-d map overriding the server configuration maps by external
+        *                                 storage cluster.
+        *   - masterTemplateOverrides     Server configuration map overrides for all master servers.
+        *   - loadMonitorClass            Name of the LoadMonitor class to always use.
+        *   - readOnlyBySection           A map of section name to read-only message.
+        *                                 Missing or false for read/write.
         */
        public function __construct( array $conf ) {
                parent::__construct( $conf );
index 0476cf2..b90afe6 100644 (file)
@@ -38,6 +38,18 @@ class LBFactorySimple extends LBFactory {
        /** @var string */
        private $loadMonitorClass;
 
+       /**
+        * @see LBFactory::__construct()
+        * @param array $conf Parameters of LBFactory::__construct() as well as:
+        *   - servers : list of server configuration maps to Database::factory().
+        *      Additionally, the server maps should have a 'load' key, which is used to decide
+        *      how often clients connect to one server verses the others. A 'max lag' key should
+        *      also be set on server maps, indicating how stale the data can be before the load
+        *      balancer tries to avoid using it. The map can have 'is static' set to disable blocking
+        *      replication sync checks (intended for archive servers with unchanging data).
+        *   - externalClusters : map of cluster names to server arrays. The servers arrays have the
+        *      same format as "servers" above.
+        */
        public function __construct( array $conf ) {
                parent::__construct( $conf );
 
@@ -87,7 +99,7 @@ class LBFactorySimple extends LBFactory {
         */
        protected function newExternalLB( $cluster, $domain = false ) {
                if ( !isset( $this->externalClusters[$cluster] ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"." );
                }
 
                return $this->newLoadBalancer( $this->externalClusters[$cluster] );
diff --git a/includes/libs/rdbms/lbfactory/LBFactorySingle.php b/includes/libs/rdbms/lbfactory/LBFactorySingle.php
new file mode 100644 (file)
index 0000000..4beb5d8
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Simple generator of database connections that always returns the same object.
+ *
+ * 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 Database
+ */
+
+/**
+ * An LBFactory class that always returns a single database object.
+ */
+class LBFactorySingle extends LBFactory {
+       /** @var LoadBalancerSingle */
+       private $lb;
+
+       /**
+        * @param array $conf An associative array with one member:
+        *  - connection: The IDatabase connection object
+        */
+       public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
+               if ( !isset( $conf['connection'] ) ) {
+                       throw new InvalidArgumentException( "Missing 'connection' argument." );
+               }
+
+               $this->lb = new LoadBalancerSingle( array_merge( $this->baseLoadBalancerParams(), $conf ) );
+       }
+
+       /**
+        * @param IDatabase $db Live connection handle
+        * @param array $params Parameter map to LBFactorySingle::__constructs()
+        * @return LBFactorySingle
+        * @since 1.28
+        */
+       public static function newFromConnection( IDatabase $db, array $params = [] ) {
+               return new static( [ 'connection' => $db ] + $params );
+       }
+
+       /**
+        * @param bool|string $wiki
+        * @return LoadBalancerSingle
+        */
+       public function newMainLB( $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param bool|string $wiki
+        * @return LoadBalancerSingle
+        */
+       public function getMainLB( $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $wiki Wiki ID, or false for the current wiki
+        * @return LoadBalancerSingle
+        */
+       protected function newExternalLB( $cluster, $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $wiki Wiki ID, or false for the current wiki
+        * @return LoadBalancerSingle
+        */
+       public function getExternalLB( $cluster, $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string|callable $callback
+        * @param array $params
+        */
+       public function forEachLB( $callback, array $params = [] ) {
+               call_user_func_array( $callback, array_merge( [ $this->lb ], $params ) );
+       }
+}
index 0f6bea3..ca2b327 100644 (file)
  */
 interface ILoadBalancer {
        /**
-        * @param array $params Array with keys:
+        * Construct a manager of IDatabase connection objects
+        *
+        * @param array $params Parameter map with keys:
         *  - servers : Required. Array of server info structures.
+        *  - localDomain: A DatabaseDomain or domain ID string.
         *  - loadMonitor : Name of a class used to fetch server lag and load.
         *  - readOnlyReason : Reason the master DB is read-only if so [optional]
         *  - waitTimeout : Maximum time to wait for replicas for consistency [optional]
         *  - srvCache : BagOStuff object for server cache [optional]
         *  - memCache : BagOStuff object for cluster memory cache [optional]
         *  - wanCache : WANObjectCache object [optional]
-        *  - hostname : the name of the current server [optional]
+        *  - hostname : The name of the current server [optional]
+        *  - cliMode: Whether the execution context is a CLI script. [optional]
+        *  - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
+        *  - trxProfiler: TransactionProfiler instance. [optional]
+        *  - replLogger: PSR-3 logger instance. [optional]
+        *  - connLogger: PSR-3 logger instance. [optional]
+        *  - queryLogger: PSR-3 logger instance. [optional]
+        *  - perfLogger: PSR-3 logger instance. [optional]
+        *  - errorLogger : Callback that takes an Exception and logs it. [optional]
         * @throws InvalidArgumentException
         */
        public function __construct( array $params );
@@ -471,4 +482,25 @@ interface ILoadBalancer {
         * @param callable|null $callback
         */
        public function setTransactionListener( $name, callable $callback = null );
+
+       /**
+        * Set a new table prefix for the existing local domain ID for testing
+        *
+        * @param string $prefix
+        */
+       public function setDomainPrefix( $prefix );
+
+       /**
+        * Make certain table names use their own database, schema, and table prefix
+        * when passed into SQL queries pre-escaped and without a qualified database name
+        *
+        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
+        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
+        *
+        * Calling this twice will completely clear any old table aliases. Also, note that
+        * callers are responsible for making sure the schemas and databases actually exist.
+        *
+        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
+        */
+       public function setTableAliases( array $aliases );
 }
index 75ecd27..c07d38f 100644 (file)
@@ -406,16 +406,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Set the master wait position and wait for a "generic" replica DB to catch up to it
-        *
-        * This can be used a faster proxy for waitForAll()
-        *
-        * @param DBMasterPos $pos
-        * @param int $timeout Max seconds to wait; default is mWaitTimeout
-        * @return bool Success (able to connect and no timeouts reached)
-        * @since 1.26
-        */
        public function waitForOne( $pos, $timeout = null ) {
                $this->mWaitForPos = $pos;
 
@@ -630,38 +620,12 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Get a database connection handle reference
-        *
-        * The handle's methods wrap simply wrap those of a IDatabase handle
-        *
-        * @see LoadBalancer::getConnection() for parameter information
-        *
-        * @param int $db
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
-        * @return DBConnRef
-        * @since 1.22
-        */
        public function getConnectionRef( $db, $groups = [], $domain = false ) {
                $domain = ( $domain !== false ) ? $domain : $this->localDomain;
 
                return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
        }
 
-       /**
-        * Get a database connection handle reference without connecting yet
-        *
-        * The handle's methods wrap simply wrap those of a IDatabase handle
-        *
-        * @see LoadBalancer::getConnection() for parameter information
-        *
-        * @param int $db
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
-        * @return DBConnRef
-        * @since 1.22
-        */
        public function getLazyConnectionRef( $db, $groups = [], $domain = false ) {
                $domain = ( $domain !== false ) ? $domain : $this->localDomain;
 
@@ -848,7 +812,7 @@ class LoadBalancer implements ILoadBalancer {
 
                // Create a live connection object
                try {
-                       $db = DatabaseBase::factory( $server['type'], $server );
+                       $db = Database::factory( $server['type'], $server );
                } catch ( DBConnectionError $e ) {
                        // FIXME: This is probably the ugliest thing I have ever done to
                        // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
@@ -966,12 +930,6 @@ class LoadBalancer implements ILoadBalancer {
                return false;
        }
 
-       /**
-        * Disable this load balancer. All connections are closed, and any attempt to
-        * open a new connection will result in a DBAccessError.
-        *
-        * @since 1.27
-        */
        public function disable() {
                $this->closeAll();
                $this->disabled = true;
@@ -1036,11 +994,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Perform all pre-commit callbacks that remain part of the atomic transactions
-        * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
-        * @since 1.28
-        */
        public function finalizeMasterChanges() {
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
                        // Any error should cause all DB transactions to be rolled back together
@@ -1051,13 +1004,6 @@ class LoadBalancer implements ILoadBalancer {
                } );
        }
 
-       /**
-        * Perform all pre-commit checks for things like replication safety
-        * @param array $options Includes:
-        *   - maxWriteDuration : max write query duration time in seconds
-        * @throws DBTransactionError
-        * @since 1.28
-        */
        public function approveMasterChanges( array $options ) {
                $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
@@ -1091,19 +1037,6 @@ class LoadBalancer implements ILoadBalancer {
                } );
        }
 
-       /**
-        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
-        *
-        * The DBO_TRX setting will be reverted to the default in each of these methods:
-        *   - commitMasterChanges()
-        *   - rollbackMasterChanges()
-        *   - commitAll()
-        * This allows for custom transaction rounds from any outer transaction scope.
-        *
-        * @param string $fname
-        * @throws DBExpectedError
-        * @since 1.28
-        */
        public function beginMasterChanges( $fname = __METHOD__ ) {
                if ( $this->trxRoundId !== false ) {
                        throw new DBTransactionError(
@@ -1167,12 +1100,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Issue all pending post-COMMIT/ROLLBACK callbacks
-        * @param integer $type IDatabase::TRIGGER_* constant
-        * @return Exception|null The first exception or null if there were none
-        * @since 1.28
-        */
        public function runMasterPostTrxCallbacks( $type ) {
                $e = null; // first exception
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
@@ -1206,12 +1133,6 @@ class LoadBalancer implements ILoadBalancer {
                return $e;
        }
 
-       /**
-        * Issue ROLLBACK only on master, only if queries were done on connection
-        * @param string $fname Caller name
-        * @throws DBExpectedError
-        * @since 1.23
-        */
        public function rollbackMasterChanges( $fname = __METHOD__ ) {
                $restore = ( $this->trxRoundId !== false );
                $this->trxRoundId = false;
@@ -1227,11 +1148,6 @@ class LoadBalancer implements ILoadBalancer {
                );
        }
 
-       /**
-        * Suppress all pending post-COMMIT/ROLLBACK callbacks
-        * @return Exception|null The first exception or null if there were none
-        * @since 1.28
-        */
        public function suppressTransactionEndCallbacks() {
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
                        $conn->setTrxEndCallbackSuppression( true );
@@ -1261,31 +1177,16 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
-        *
-        * @param string $fname Caller name
-        * @since 1.28
-        */
        public function flushReplicaSnapshots( $fname = __METHOD__ ) {
                $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
                        $conn->flushSnapshot( __METHOD__ );
                } );
        }
 
-       /**
-        * @return bool Whether a master connection is already open
-        * @since 1.24
-        */
        public function hasMasterConnection() {
                return $this->isOpen( $this->getWriterIndex() );
        }
 
-       /**
-        * Determine if there are pending changes in a transaction by this thread
-        * @since 1.23
-        * @return bool
-        */
        public function hasMasterChanges() {
                $pending = 0;
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$pending ) {
@@ -1295,11 +1196,6 @@ class LoadBalancer implements ILoadBalancer {
                return (bool)$pending;
        }
 
-       /**
-        * Get the timestamp of the latest write query done by this thread
-        * @since 1.25
-        * @return float|bool UNIX timestamp or false
-        */
        public function lastMasterChangeTimestamp() {
                $lastTime = false;
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$lastTime ) {
@@ -1309,14 +1205,6 @@ class LoadBalancer implements ILoadBalancer {
                return $lastTime;
        }
 
-       /**
-        * Check if this load balancer object had any recent or still
-        * pending writes issued against it by this PHP thread
-        *
-        * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
-        * @return bool
-        * @since 1.25
-        */
        public function hasOrMadeRecentMasterChanges( $age = null ) {
                $age = ( $age === null ) ? $this->mWaitTimeout : $age;
 
@@ -1324,12 +1212,6 @@ class LoadBalancer implements ILoadBalancer {
                        || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
        }
 
-       /**
-        * Get the list of callers that have pending master changes
-        *
-        * @return string[] List of method names
-        * @since 1.27
-        */
        public function pendingMasterChangeCallers() {
                $fnames = [];
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$fnames ) {
@@ -1365,11 +1247,6 @@ class LoadBalancer implements ILoadBalancer {
                return $this->getLaggedReplicaMode( $domain );
        }
 
-       /**
-        * @note This method will never cause a new DB connection
-        * @return bool Whether any generic connection used for reads was highly "lagged"
-        * @since 1.28
-        */
        public function laggedReplicaUsed() {
                return $this->laggedReplicaMode;
        }
@@ -1383,13 +1260,6 @@ class LoadBalancer implements ILoadBalancer {
                return $this->laggedReplicaUsed();
        }
 
-       /**
-        * @note This method may trigger a DB connection if not yet done
-        * @param string|bool $domain Domain ID, or false for the current domain
-        * @param IDatabase|null DB master connection; used to avoid loops [optional]
-        * @return string|bool Reason the master is read-only or false if it is not
-        * @since 1.27
-        */
        public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
                if ( $this->readOnlyReason !== false ) {
                        return $this->readOnlyReason;
@@ -1466,12 +1336,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Call a function with each open connection object to a master
-        * @param callable $callback
-        * @param array $params
-        * @since 1.28
-        */
        public function forEachOpenMasterConnection( $callback, array $params = [] ) {
                $masterIndex = $this->getWriterIndex();
                foreach ( $this->mConns as $connsByServer ) {
@@ -1485,12 +1349,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Call a function with each open replica DB connection object
-        * @param callable $callback
-        * @param array $params
-        * @since 1.28
-        */
        public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
                foreach ( $this->mConns as $connsByServer ) {
                        foreach ( $connsByServer as $i => $serverConns ) {
@@ -1543,17 +1401,6 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * Wait for a replica DB to reach a specified master position
-        *
-        * This will connect to the master to get an accurate position if $pos is not given
-        *
-        * @param IDatabase $conn Replica DB
-        * @param DBMasterPos|bool $pos Master position; default: current position
-        * @param integer $timeout Timeout in seconds
-        * @return bool Success
-        * @since 1.27
-        */
        public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
                if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'replica' ) ) {
                        return true; // server is not a replica DB
@@ -1577,24 +1424,10 @@ class LoadBalancer implements ILoadBalancer {
                return $ok;
        }
 
-       /**
-        * Clear the cache for slag lag delay times
-        *
-        * This is only used for testing
-        * @since 1.26
-        */
        public function clearLagTimeCache() {
                $this->getLoadMonitor()->clearCaches();
        }
 
-       /**
-        * Set a callback via IDatabase::setTransactionListener() on
-        * all current and future master connections of this load balancer
-        *
-        * @param string $name Callback name
-        * @param callable|null $callback
-        * @since 1.28
-        */
        public function setTransactionListener( $name, callable $callback = null ) {
                if ( $callback ) {
                        $this->trxRecurringCallbacks[$name] = $callback;
@@ -1608,29 +1441,10 @@ class LoadBalancer implements ILoadBalancer {
                );
        }
 
-       /**
-        * Make certain table names use their own database, schema, and table prefix
-        * when passed into SQL queries pre-escaped and without a qualified database name
-        *
-        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
-        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
-        *
-        * Calling this twice will completely clear any old table aliases. Also, note that
-        * callers are responsible for making sure the schemas and databases actually exist.
-        *
-        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
-        * @since 1.28
-        */
        public function setTableAliases( array $aliases ) {
                $this->tableAliases = $aliases;
        }
 
-       /**
-        * Set a new table prefix for the existing local domain ID for testing
-        *
-        * @param string $prefix
-        * @since 1.28
-        */
        public function setDomainPrefix( $prefix ) {
                $this->localDomain = new DatabaseDomain(
                        $this->localDomain->getDatabase(),
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
new file mode 100644 (file)
index 0000000..9de4850
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Simple generator of database connections that always returns the same object.
+ *
+ * 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 Database
+ */
+
+/**
+ * Trivial LoadBalancer that always returns an injected connection handle
+ */
+class LoadBalancerSingle extends LoadBalancer {
+       /** @var IDatabase */
+       private $db;
+
+       /**
+        * @param array $params An associative array with one member:
+        *   - connection: An IDatabase connection object
+        */
+       public function __construct( array $params ) {
+               if ( !isset( $params['connection'] ) ) {
+                       throw new InvalidArgumentException( "Missing 'connection' argument." );
+               }
+
+               $this->db = $params['connection'];
+
+               parent::__construct( [
+                       'servers' => [
+                               [
+                                       'type' => $this->db->getType(),
+                                       'host' => $this->db->getServer(),
+                                       'dbname' => $this->db->getDBname(),
+                                       'load' => 1,
+                               ]
+                       ],
+                       'trxProfiler' => isset( $params['trxProfiler'] ) ? $params['trxProfiler'] : null,
+                       'srvCache' => isset( $params['srvCache'] ) ? $params['srvCache'] : null,
+                       'wanCache' => isset( $params['wanCache'] ) ? $params['wanCache'] : null
+               ] );
+
+               if ( isset( $params['readOnlyReason'] ) ) {
+                       $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
+               }
+       }
+
+       /**
+        * @param IDatabase $db Live connection handle
+        * @param array $params Parameter map to LoadBalancerSingle::__constructs()
+        * @return LoadBalancerSingle
+        * @since 1.28
+        */
+       public static function newFromConnection( IDatabase $db, array $params = [] ) {
+               return new static( [ 'connection' => $db ] + $params );
+       }
+
+       /**
+        *
+        * @param string $server
+        * @param bool $dbNameOverride
+        *
+        * @return IDatabase
+        */
+       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+               return $this->db;
+       }
+}
index 1e0013f..ea237aa 100644 (file)
@@ -183,8 +183,25 @@ class ObjectCache {
                        $params['reportDupes'] = isset( $params['reportDupes'] )
                                ? $params['reportDupes']
                                : true;
+                       // Do b/c logic for SqlBagOStuff
+                       if ( is_subclass_of( $class, SqlBagOStuff::class ) ) {
+                               if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
+                                       $params['servers'] = [ $params['server'] ];
+                                       unset( $param['server'] );
+                               }
+                               // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
+                               if ( isset( $params['servers'] ) ) {
+                                       foreach ( $params['servers'] as &$server ) {
+                                               if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
+                                                       $server['dbDirectory'] = MediaWikiServices::getInstance()
+                                                               ->getMainConfig()->get( 'SQLiteDataDir' );
+                                               }
+                                       }
+                               }
+                       }
+
                        // Do b/c logic for MemcachedBagOStuff
-                       if ( is_subclass_of( $class, 'MemcachedBagOStuff' ) ) {
+                       if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
                                if ( !isset( $params['servers'] ) ) {
                                        $params['servers'] = $GLOBALS['wgMemCachedServers'];
                                }
index faac26d..2308ef0 100644 (file)
@@ -3017,10 +3017,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Now that it's safely backed up, delete it
                $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
-
-               if ( !$dbw->cascadingDeletes() ) {
-                       $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
-               }
+               $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
 
                // Log the deletion, if the page was suppressed, put it in the suppression log instead
                $logtype = $suppress ? 'suppress' : 'delete';
index 2423092..743f77b 100644 (file)
--- a/index.php
+++ b/index.php
@@ -8,7 +8,7 @@
  * See the README, INSTALL, and UPGRADE files for basic setup instructions
  * and pointers to the online documentation.
  *
- * https://www.mediawiki.org/
+ * https://www.mediawiki.org/wiki/Special:MyLanguage/MediaWiki
  *
  * ----------
  *
index 169e0ff..db71c5c 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 /**
  * Internationalisation code.
+ * See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more information.
  *
  * 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
index ed1e509..092ecc4 100644 (file)
        "htmlform-submit": "Ninviar",
        "htmlform-reset": "Desfer cambios",
        "htmlform-selectorother-other": "Atros",
-       "sqlite-has-fts": "$1, con soporte de busca de texto integro",
-       "sqlite-no-fts": "$1, sin soporte de busca de texto integro",
        "logentry-delete-delete": "$1 borró a pachina $3",
        "logentry-delete-restore": "$1 restauró a pachina $3",
        "logentry-delete-event": "$1 modificó a visibilidat de {{PLURAL:$5|un evento d'o rechistro|$5 eventos d'o rechistro}} en $3: $4",
index 95c7a5d..db567fc 100644 (file)
        "htmlform-title-not-exists": "$1 غير موجود.",
        "htmlform-user-not-exists": "<strong>$1</strong> غير موجود",
        "htmlform-user-not-valid": "اسم المستخدم <strong>$1</strong> غير صالح.",
-       "sqlite-has-fts": "$1 بدعم البحث في كامل النص",
-       "sqlite-no-fts": "$1 بدون دعم البحث في كامل النص",
        "logentry-delete-delete": "{{GENDER:$2|حذف|حذفت}} $1 صفحة $3",
        "logentry-delete-restore": "{{GENDER:$2|استعاد|استعادت}} $1 صفحة $3",
        "logentry-delete-event": "{{GENDER:$2|غيّر|غيّرت}} $1 إمكانية مشاهدة {{PLURAL:$5||حدث|حدثين|$5 أحداث|$5 حدثًا|$5 حدث}} في سجل $3: $4",
index f12c554..cf0ab1b 100644 (file)
        "htmlform-title-not-exists": "$1 nun esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun ye un nome d'usuariu válidu.",
-       "sqlite-has-fts": "$1 con sofitu pa busca de testu completu",
-       "sqlite-no-fts": "$1 ensin sofitu pa busca de testu completu",
        "logentry-delete-delete": "$1 {{GENDER:$2|desanició}} la páxina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restauró}} la páxina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|camudó}} la visibilidá {{PLURAL:$5|d'un socesu del rexistru|de $5 socesos del rexistru}} en $3: $4",
index 451c046..c0e78e6 100644 (file)
        "resettokens-text": "Тут вы можаце скінуць токены, якія даюць вам доступ да пэўных прыватных зьвестак, асацыяваных з вашым рахункам.\n\nКалі вы выпадкова падзяліліся токенамі зь іншымі, або калі ваш рахунак быў скампрамэтаваны, скарыстайцеся гэтай магчымасьцю і скіньце токены.",
        "resettokens-no-tokens": "Няма токенаў для скіданьня.",
        "resettokens-tokens": "Токены:",
-       "resettokens-token-label": "$1 (бягучае значэньне: $2)",
+       "resettokens-token-label": "$1 (цяперашняе значэньне: $2)",
        "resettokens-watchlist-token": "Токен стужкі (Atom/RSS) [[Special:Watchlist|зьменаў у вашым сьпісе назіраньня]]",
        "resettokens-done": "Токены скінутыя.",
        "resettokens-resetbutton": "Скінуць вылучаныя токены",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> — некарэктнае імя карыстальніка.",
-       "sqlite-has-fts": "$1 з падтрымкай поўнатэкстнага пошуку",
-       "sqlite-no-fts": "$1 без падтрымкі поўнатэкстнага пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|выдаліў|выдаліла}} старонку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|зьмяніў|зьмяніла}} бачнасьць $5 {{PLURAL:$5|1=падзеі ў журнале|падзеяў у журнале}} на $3: $4",
        "sessionprovider-nocookies": "Файлы-кукі могуць быць адключаныя. Упэўніцеся, што ў вас уключаныя файлы-кукі і пачніце спачатку.",
        "randomrootpage": "Выпадковая карэнная старонка",
        "log-action-filter-block": "Тып блякаваньня:",
+       "log-action-filter-contentmodel": "Тып мадыфікацыі contentmodel:",
        "log-action-filter-delete": "Тып выдаленьня:",
        "log-action-filter-import": "Тып імпарту:",
+       "log-action-filter-managetags": "Тып дзеяньня кіраваньня меткамі:",
        "log-action-filter-move": "Тып пераносу:",
+       "log-action-filter-newusers": "Тып стварэньня рахунку:",
+       "log-action-filter-patrol": "Тып патруляваньня:",
        "log-action-filter-all": "Усе",
        "log-action-filter-block-block": "Заблякаваць",
        "log-action-filter-block-reblock": "Зьмяненьне блякаваньня",
index 1ab171e..c8a6fe0 100644 (file)
        "rollbacklinkcount": "адкаціць $1 {{PLURAL:$1|праўку|праўкі|правак}}",
        "rollbacklinkcount-morethan": "адкаціць больш за $1 {{PLURAL:$1|праўку|праўкі|правак}}",
        "rollbackfailed": "Не ўдалося адкаціць",
+       "rollback-missingparam": "У запыце адсутнічаюць абавязковыя параметры.",
+       "rollback-missingrevision": "Не ўдалося атрымаць звесткі версіі.",
        "cantrollback": "Немагчыма адкаціць праўку; апошні аўтар гэта адзіны аўтар на гэтай старонцы.",
        "alreadyrolled": "Немагчыма адкаціць апошнюю праўку ў [[:$1]], аўтарства [[User:$2|$2]] ([[User talk:$2|Talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nз таго часу нехта іншы правіў або адкатваў гэтую старонку.\n\nАпошняя праўка старонкі была аўтарства [[User:$3|$3]] ([[User talk:$3|Talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Тлумачэнне праўкі было: <em>$1</em>.",
        "revertpage": "Праўкі аўтарства [[Special:Contributions/$2|$2]] ([[User talk:$2|размова]]) адкочаныя; вернута апошняя версія аўтарства [[User:$1|$1]]",
        "revertpage-nouser": "Праўкі (імя ўдзельніка схавана) адкочаны да версіі {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Адкочаны праўкі $1; вернута апошняя версія $2.",
+       "rollback-success-notify": "Адкочаны праўкі $1;\nвернута апошняя версія $2. [$3 Паказаць змены]",
        "sessionfailure-title": "Памылка сеансу",
        "sessionfailure": "Магчыма, ёсць праблемы з вашым сеансам працы ў сістэме. Таму вам было адмоўлена ў выкананні дзеяння, каб засцерагчыся ад захопу сеанса.\n\nВярніцеся на папярэднюю старонку, перазагрузіце яе і тады паспрабуйце зноў.",
        "changecontentmodel": "Змяніць мадэль змесціва старонкі",
        "changecontentmodel-reason-label": "Прычына:",
        "changecontentmodel-submit": "Змяніць",
        "changecontentmodel-success-title": "Мадэль змесціва была зменена",
+       "changecontentmodel-success-text": "Тып змесціва [[:$1]] быў зменены.",
+       "changecontentmodel-cannot-convert": "Змесціва [[:$1]] не можа быць ператворана ў тып $2.",
+       "changecontentmodel-nodirectediting": "Мадэль змесціва $1 не падтрымлівае наўпростае рэдагаванне",
        "changecontentmodel-emptymodels-title": "Няма даступных мадэляў змесціва",
+       "changecontentmodel-emptymodels-text": "Змесціва [[:$1]] не можа быць ператворана ні ў які тып.",
+       "log-name-contentmodel": "Журнал змен мадэляў змесціва",
+       "log-description-contentmodel": "Падзеі, звязаныя з мадэлямі змесціва старонак",
        "logentry-contentmodel-change-revertlink": "адкаціць",
        "logentry-contentmodel-change-revert": "адкат",
        "protectlogpage": "Журнал аховы",
        "tooltip-ca-nstab-category": "Паказаць старонку катэгорыі",
        "tooltip-minoredit": "Падаць гэтую праўку як дробную",
        "tooltip-save": "Замацаваць свае змяненні",
+       "tooltip-publish": "Апублікаваць вашы змены",
        "tooltip-preview": "Паказаць, якім будзе вынік — ужывайце перад замацоўваннем!",
        "tooltip-diff": "Паказаць, што вы мяняеце ў тэксце.",
        "tooltip-compareselectedversions": "Паказаць розніцу паміж дзвюмя азначанымі версіямі гэтай старонкі.",
        "pageinfo-article-id": "Ідэнтыфікатар старонкі",
        "pageinfo-language": "Мова змесціва старонкі",
        "pageinfo-content-model": "Мадэль змесціва старонкі",
+       "pageinfo-content-model-change": "змяніць",
        "pageinfo-robot-policy": "Індэксаванне робатамі",
        "pageinfo-robot-index": "Дазволена",
        "pageinfo-robot-noindex": "Не дазволена",
        "confirmemail_body_set": "Нехта (магчыма, вы) з IP-адрасам $1\nпаказаў дадзены адрас электроннай пошты для ўліковага запісу «$2» у праекце {{SITENAME}}.\n\nКаб пацвердзіць, што акаўнт сапраўды належыць вам, і ўключыць магчымасць адпраўкі лістоў з сайта {{SITENAME}}, адкрыйце гэтую спасылку ў браўзеры:\n\n$3\n\nКалі рахунак вам *не належыць*, адкрыйце ніжэй паказаную спасылку, каб адмовіцца ад пацверджання адрасу эл.пошты:\n\n$5\n\nКод пацверджання дзейсны да $4.",
        "confirmemail_invalidated": "Пацверджанне эл.пошты скасаванае",
        "invalidateemail": "Адмовіцца ад пацверджання эл.пошты",
+       "notificationemail_subject_changed": "Адрас электроннай пошты на пляцоўцы {{SITENAME}} зменены",
        "scarytranscludedisabled": "[Устаўлянне з іншых вікі не дазволена]",
        "scarytranscludefailed": "[Не ўдалося атрымаць шаблон для $1]",
        "scarytranscludefailed-httpstatus": "[Не ўдалося атрымаць шаблон для $1: HTTP $2]",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> - недапушчальная назва уліковага запісу.",
-       "sqlite-has-fts": "$1 з падтрымкай поўна-тэкставага пошуку",
-       "sqlite-no-fts": "$1 без падтрымкі поўна-тэкставага пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|сцёр|сцёрла}} старонку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|запісу журнала|$5 запісаў журнала}} $3: $4",
index c8eb76b..ac68b88 100644 (file)
@@ -64,7 +64,7 @@
        "tog-enotifminoredits": "Уведомяване по е-пощата при малки промени на страници или файлове",
        "tog-enotifrevealaddr": "Показване на електронния ми адрес в известяващите писма",
        "tog-shownumberswatching": "Показване на броя на потребителите, наблюдаващи дадена страница",
-       "tog-oldsig": "Текущ подпис:",
+       "tog-oldsig": "Ð\92аÑ\88иÑ\8fÑ\82 Ñ\82екущ подпис:",
        "tog-fancysig": "Без превръщане на подписа в препратка към потребителската страница",
        "tog-uselivepreview": "Използване на бърз предварителен преглед",
        "tog-forceeditsummary": "Предупреждаване при празно поле за резюме на редакцията",
@@ -81,7 +81,7 @@
        "tog-showhiddencats": "Показване на скритите категории",
        "tog-norollbackdiff": "Не показвай разликата между редакциите след отмяна на редакции",
        "tog-useeditwarning": "Предупреждаване при опит за напускане на страница, отворена в режим на редактиране, без да са запазени промените",
-       "tog-prefershttps": "Да се използва винаги защитена връзка след влизане",
+       "tog-prefershttps": "Да се използва винаги защитена връзка при влизане",
        "underline-always": "Винаги",
        "underline-never": "Никога",
        "underline-default": "Според настройките на облика или браузъра",
        "newwindow": "(отваря се в нов прозорец)",
        "cancel": "Отказ",
        "moredotdotdot": "Още…",
-       "morenotlisted": "Този Ñ\81пиÑ\81Ñ\8aк Ð½Ðµ Ðµ пълен.",
+       "morenotlisted": "Този Ñ\81пиÑ\81Ñ\8aк Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ðµ Ð½Ðµпълен.",
        "mypage": "Страница",
        "mytalk": "Беседа",
        "anontalk": "Беседа",
        "yourpasswordagain": "Парола (повторно):",
        "createacct-yourpasswordagain": "Потвърждаване на паролата",
        "createacct-yourpasswordagain-ph": "Въвежда се паролата (повторно)",
-       "remembermypassword": "Запомняне на паролата на този компютър (най-много за $1 {{PLURAL:$1|ден|дни}})",
        "userlogin-remembermypassword": "Запомняне",
        "userlogin-signwithsecure": "Използване на защитена връзка",
        "yourdomainname": "Домейн:",
        "tags-actions-header": "Действия",
        "tags-active-yes": "Да",
        "tags-active-no": "Не",
-       "tags-source-extension": "Ð\94еÑ\84иниÑ\80ан Ð¾Ñ\82 Ñ\80азÑ\88иÑ\80ение",
+       "tags-source-extension": "Ð\94еÑ\84иниÑ\80ан Ð¾Ñ\82 Ñ\81оÑ\84Ñ\82Ñ\83еÑ\80а",
        "tags-source-none": "Вече не се използва",
        "tags-edit": "редактиране",
        "tags-delete": "изтриване",
        "htmlform-cloner-create": "Добавяне на още",
        "htmlform-cloner-delete": "Премахване",
        "htmlform-title-not-exists": "$1 не съществува.",
-       "sqlite-has-fts": "$1 с поддръжка на пълнотекстово търсене",
-       "sqlite-no-fts": "$1 без поддръжка на пълнотекстово търсене",
        "logentry-delete-delete": "$1 {{GENDER:$2|изтри}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|възстанови}} страницата $3",
        "logentry-delete-revision": "$1 {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една редакция|$5 редакции}} в страница $3: $4",
index e2fec0d..411ddab 100644 (file)
        "userlogin-remembermypassword": "আমাকে প্রবেশ অবস্থায় রাখো",
        "userlogin-signwithsecure": "নিরাপদ সংযোগ ব্যবহার করুন",
        "cannotlogin-title": "প্রবেশ করতে পারবেন না",
+       "cannotlogin-text": "প্রবেশ করা সম্ভব নয়।",
        "cannotloginnow-title": "এখন প্রবেশ করা যাবে না",
        "cannotloginnow-text": "$1 ব্যবহার করার সময় প্রবেশ করা সম্ভব নয়।",
        "cannotcreateaccount-title": "অ্যাকাউন্ট তৈরি করা যাবে না",
+       "cannotcreateaccount-text": "সরাসরি অ্যাকাউন্ট সৃষ্টিকরণ এই উইকিতে সক্রিয় নয়।",
        "yourdomainname": "আপনার ডোমেইন:",
        "password-change-forbidden": "আপনি এই উইকিতে পাসওয়ার্ড পরিবর্তন করতে পারবেন না।",
        "externaldberror": "হয় কোন বহিঃস্থ যাচাইকরণ ডাটাবেজ ত্রুটি ঘটেছে অথবা আপনার বহিঃস্থ অ্যাকাউন্ট হালনাগাদ করার অনুমতি নেই।",
        "invalid-content-data": "ভুল কন্টেন্ট ডাটা",
        "content-not-allowed-here": "\"$1\" কন্টেন্টটি [[$2]] পাতায় অনুমোদিত নয়",
        "editwarning-warning": "এই পাতাটি ত্যাগ করলে আপনার আপনার করা পরিবর্তনগুলো হারিয়ে যেতে পারে।\nআপনি যদি প্রবেশ করা থাকেন, আপনি এই সতর্কীকরণ বার্তাটি আপনার পছন্দের \"সম্পাদনা\" অনুচ্ছেদ থেকে নিস্ক্রিয় করতে পারেন।",
+       "editpage-invalidcontentmodel-title": "বিষয়বস্তু মডেল সমর্থিত নয়",
        "editpage-notsupportedcontentformat-title": "উল্লেখিত পদ্ধতি সমর্থনযোগ্য নয়।",
        "editpage-notsupportedcontentformat-text": "$1 লেখার ফরম্যাট, $2 কন্টেন্ট মডেলের উপযোগী নয়।",
        "content-model-wikitext": "উইকিটেক্সট",
        "htmlform-title-not-exists": "$1-এর অস্তিত্ব নেই।",
        "htmlform-user-not-exists": "<strong>$1</strong>-এর অস্তিত্ব নেই।",
        "htmlform-user-not-valid": "<strong>$1</strong> একটি বৈধ ব্যবহারকারীর নাম নয়।",
-       "sqlite-has-fts": "$1 সহ পূর্ণ-পাঠ্য অনুসন্ধান সমর্থন",
-       "sqlite-no-fts": "$1 বাদে পূর্ণ-পাঠ্য অনুসন্ধান সমর্থন",
        "logentry-delete-delete": "$1 কর্তৃক $3 পাতাটি অপসারিত হয়েছে",
        "logentry-delete-restore": "$1 কর্তৃক $3 পাতাটি {{GENDER:$2|ফিরিয়ে আনা}} হয়েছে",
        "logentry-delete-event": "$1 {{PLURAL:$5|একটি লগ ইভেন্টের|$5 লগ ইভেন্টসমূহের}} দৃশ্যমানতা {{GENDER:$2|পরিবর্তন}} করেছেন $3: $4",
index 5bea10a..c0c6e29 100644 (file)
        "htmlform-cloner-create": "Ouzhpennañ muioc'h",
        "htmlform-cloner-delete": "Dilemel",
        "htmlform-cloner-required": "Un dalvoudenn a zo ret da vihanañ.",
-       "sqlite-has-fts": "$1 gant enklask eus an destenn a-bezh embreget",
-       "sqlite-no-fts": "$1 hep enklask eus an destenn a-bezh embreget",
        "logentry-delete-delete": "Diverket eo bet ar bajenn $3 gant $1",
        "logentry-delete-restore": "Assavet eo bet ar bajenn $3 gant $1",
        "logentry-delete-event": "Kemmet eo bet gwelusted {{PLURAL:$5|un darvoud eus ar marilh|$5 darvoud eus ar marilh}} d'an $3 gant $1 : $4",
index 159f7e7..bc4466a 100644 (file)
        "htmlform-title-not-exists": "$1 ne postoji.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
-       "sqlite-has-fts": "$1 sa podrškom pretrage cijelog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretrage cijelog teksta",
        "logentry-delete-delete": "$1 {{GENDER:$2|obrisao|obrisala}} je stranicu $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|događaja|$5 događaja}} u evidenciji na $3: $4",
index 2a856d9..82cfba2 100644 (file)
@@ -11,7 +11,8 @@
                        "LNDDYL",
                        "唐吉訶德的侍從",
                        "Ztl8702",
-                       "Macofe"
+                       "Macofe",
+                       "GnuDoyng"
                ]
        },
        "tog-underline": "下劃綫鏈接",
        "editfont-monospace": "蜀様寬其字體",
        "editfont-sansserif": "無襯線其字體",
        "editfont-serif": "有襯線其字體",
-       "sunday": "禮拜",
-       "monday": "拜一",
-       "tuesday": "拜二",
+       "sunday": "Lā̤-bái",
+       "monday": "Bái-ék",
+       "tuesday": "Bái-nê",
        "wednesday": "拜三",
-       "thursday": "拜四",
-       "friday": "拜五",
-       "saturday": "拜六",
+       "thursday": "Bái-sé",
+       "friday": "Bái-ngô",
+       "saturday": "Bái-lĕ̤k",
        "sun": "禮拜",
        "mon": "拜一",
-       "tue": "拜二",
+       "tue": "Bái-nê",
        "wed": "拜三",
        "thu": "拜四",
        "fri": "拜五",
        "sat": "拜六",
-       "january": "一月",
-       "february": "二月",
-       "march": "三月",
-       "april": "四月",
-       "may_long": "五月",
-       "june": "六月",
-       "july": "七月",
-       "august": "八月",
-       "september": "九月",
-       "october": "十月",
-       "november": "十一月",
-       "december": "十二月",
+       "january": "Ék-nguŏk",
+       "february": "Nê-nguŏh",
+       "march": "Săng-nguŏk",
+       "april": "Sé-nguŏk",
+       "may_long": "Ngô-nguŏk",
+       "june": "Lĕ̤k-nguŏk",
+       "july": "Chék-nguŏk",
+       "august": "Báik-nguŏk",
+       "september": "Gāu-nguŏk",
+       "october": "Sĕk-nguŏk",
+       "november": "Sĕk-ék-nguŏk",
+       "december": "Sĕk-nê-nguŏk",
        "january-gen": "一月",
-       "february-gen": "二月",
+       "february-gen": "Nê-nguŏk",
        "march-gen": "三月",
        "april-gen": "四月",
        "may-gen": "五月",
        "october-gen": "十月",
        "november-gen": "十一月",
        "december-gen": "十二月",
-       "jan": "一月",
-       "feb": "二月",
-       "mar": "三月",
-       "apr": "四月",
-       "may": "五月",
-       "jun": "六月",
-       "jul": "七月",
-       "aug": "八月",
-       "sep": "九月",
-       "oct": "十月",
-       "nov": "十一月",
-       "dec": "十二月",
+       "jan": "Ék-nguŏk",
+       "feb": "Nê-nguŏk",
+       "mar": "Săng-nguŏk",
+       "apr": "Sé-nguŏk",
+       "may": "Ngô-nguŏk",
+       "jun": "Lĕ̤k-nguŏk",
+       "jul": "Chék-nguŏk",
+       "aug": "Báik-nguŏk",
+       "sep": "Gāu-nguŏk",
+       "oct": "Sĕk-nguŏk",
+       "nov": "Sĕk-ék-nguŏk",
+       "dec": "Sĕk-nê-nguŏk",
        "january-date": "一月$1號",
        "february-date": "二月$1號",
        "march-date": "三月$1號",
        "october-date": "十月$1號",
        "november-date": "十一月$1號",
        "december-date": "十二月$1號",
-       "pagecategories": "{{PLURAL:$1}}類別",
+       "pagecategories": "{{PLURAL:$1}} Lôi-biék",
        "category_header": "「$1」類別下底其頁面",
        "subcategories": "子類別",
        "category-media-header": "「$1」類別下底其媒體",
        "category-empty": "''茲類別下底現在無文章也無媒體。''",
-       "hidden-categories": "{{PLURAL:$1}}乞囥起其類別",
+       "hidden-categories": "{{PLURAL:$1}} bĭk ké̤ṳk káung kī gì lui-biék",
        "hidden-category-category": "已經囥起其類別",
        "category-subcat-count": "{{PLURAL:$2|茲萆分類僅包括下底蜀萆子分類|茲分類有 {{PLURAL:$1|子分類|$1 萆子分類}},總計 $2 萆。}}",
        "category-subcat-count-limited": "茲蜀萆類別下底有子類別{{PLURAL:$1}}",
        "mypage": "頁面",
        "mytalk": "我其討論",
        "anontalk": "茲隻IP其討論頁",
-       "navigation": "引導",
-       "and": "&#32;",
+       "navigation": "Īng-dô̤:",
+       "and": "&#32;gâe̤ng",
        "qbfind": "討",
        "qbbrowse": "覷蜀覷",
        "qbedit": "修改",
        "faq": "真稠碰著其問題",
        "faqpage": "Project:稠問其問題",
        "actions": "動作",
-       "namespaces": "命名空間",
-       "variants": "變體",
-       "navigation-heading": "導航菜單",
+       "namespaces": "Miàng-kŭng-găng",
+       "variants": "Biéng-tā̤",
+       "navigation-heading": "Dô̤-hòng chái-dăng",
        "errorpagetitle": "鄭咯",
        "returnto": "轉去$1。",
-       "tagline": "來源:{{SITENAME}}",
-       "help": "幫助",
-       "search": "尋討",
-       "searchbutton": "",
+       "tagline": "Lài-nguòng: {{SITENAME}}",
+       "help": "Bŏng-cô",
+       "search": "Sìng-tō̤",
+       "searchbutton": "Tō̤",
        "go": "去",
-       "searcharticle": "",
+       "searcharticle": "Kó̤",
        "history": "頁面歷史",
-       "history_short": "歷史",
+       "history_short": "Lĭk-sṳ̄",
        "updatedmarker": "趁我最後蜀回訪問開始更新",
-       "printableversion": "會拍印其版本",
-       "permalink": "永久鏈接",
+       "printableversion": "Â̤ páh-éng gì bēng-buōng",
+       "permalink": "Īng-giū lièng-giék",
        "print": "拍印",
-       "view": "覷蜀覷",
+       "view": "Ché̤ṳ-siŏh-ché̤ṳ",
        "view-foreign": "敆$1𡅏看",
-       "edit": "修改",
+       "edit": "Siŭ-gāi",
        "edit-local": "編輯當地描述",
        "create": "創建",
        "create-local": "添加當地描述",
        "unprotectthispage": "改變茲蜀頁其保護狀態",
        "newpage": "新頁",
        "talkpage": "討論茲頁",
-       "talkpagelinktext": "討論",
+       "talkpagelinktext": "tō̤-lâung",
        "specialpage": "特殊頁",
-       "personaltools": "個人其傢私花",
+       "personaltools": "Gó̤-ìng gì gă-sĭ-huă",
        "articlepage": "覷蜀覷內容頁面",
-       "talk": "討論",
-       "views": "覷蜀覷",
-       "toolbox": "傢私花",
+       "talk": "Tō̤-lâung",
+       "views": "Ché̤ṳ-siŏh-ché̤ṳ",
+       "toolbox": "Gă-sĭ-huă",
        "userpage": "覷蜀覷用戶頁面",
        "projectpage": "看工程頁",
        "imagepage": "覷蜀覷文件頁面",
        "viewhelppage": "看幫助頁",
        "categorypage": "看分類頁",
        "viewtalkpage": "看討論",
-       "otherlanguages": "其它其語言",
-       "redirectedfrom": "(趁$1重定向過來)",
+       "otherlanguages": "Gì-tă ngṳ̄-ngiòng",
+       "redirectedfrom": "(téng $1 tṳ̀ng-déng-hióng guó-lì)",
        "redirectpagesub": "重定向頁",
        "redirectto": "重定向遘",
-       "lastmodifiedat": "茲蜀頁是着$1 $2其辰候最後修改其。",
+       "lastmodifiedat": "Cī siŏh hiĕh sê diŏh $1 $2 sèng-hâiu có̤i-âu siŭ-gāi gì.",
        "viewcount": "茲蜀頁已經乞訪問$1回了。{{PLURAL:$1}}",
        "protectedpage": "保護頁",
-       "jumpto": "跳遘:",
-       "jumptonavigation": "引導:",
-       "jumptosearch": "尋討",
+       "jumpto": "Tiéu gáu:",
+       "jumptonavigation": "Īng-dô̤:",
+       "jumptosearch": "Sìng-tō̤",
        "view-pool-error": "對不住,服務器茲蜀萆時候已弳過載了。\n過価用戶敆𡅏覷茲蜀頁。\n起動等仂久再來覷茲蜀頁。\n\n$1",
        "generic-pool-error": "對不住,現刻時服務器過載了。\n實在過価用戶敆𡅏訪問茲蜀萆資源。\n起動汝等蜀刻再訪問茲蜀萆資源。",
        "pool-timeout": "等待鎖定其時間遘了",
        "pool-queuefull": "隊列池已經滿了",
        "pool-errorunknown": "𣍐曉什乇綻咯",
-       "aboutsite": "關於{{SITENAME}}",
-       "aboutpage": "Project:關於",
+       "aboutsite": "Guăng-ṳ̀ {{SITENAME}}",
+       "aboutpage": "Project:Guăng-ṳ̀",
        "copyright": "內容會使敆$1下底會使獲得遘,若無會給出其它提示。",
        "copyrightpage": "{{ns:project}}:版權",
-       "currentevents": "大樹下",
-       "currentevents-url": "Project:大樹下",
-       "disclaimers": "無負責聲明",
-       "disclaimerpage": "Project:無負責聲明",
+       "currentevents": "Duâi Ché̤ṳ Â",
+       "currentevents-url": "Project:Duâi Ché̤ṳ Â",
+       "disclaimers": "Mò̤-hô-cáik sĭng-mìng",
+       "disclaimerpage": "Project:Mò̤-hô-cáik sĭng-mìng",
        "edithelp": "修改保護",
-       "mainpage": "頭頁",
-       "mainpage-description": "頭頁",
+       "mainpage": "Tàu Hiĕk",
+       "mainpage-description": "Tàu Hiĕk",
        "policy-url": "Project:政策",
-       "portal": "廳中",
-       "portal-url": "Project:社區門戶",
-       "privacy": "隱私政策",
-       "privacypage": "Project:隱私政策",
+       "portal": "Tiăng-dŏng",
+       "portal-url": "Project:Tiăng-dŏng",
+       "privacy": "Ṳ̄ng-sṳ̆ céng-cháik",
+       "privacypage": "Project:Ṳ̄ng-sŭ céng-cháik",
        "badaccess": "權限錯誤",
        "badaccess-group0": "汝𣍐使做汝要求其茲蜀萆動作。",
        "badaccess-groups": "汝卜做其動作着{{PLURAL:$2|茲蜀群組|茲蜀組裡勢}}其用戶乍有能耐使:$1",
        "versionrequired": "需要版本$1其MediaWiki",
        "versionrequiredtext": "需要MediaWiki其版本$1來使茲蜀頁。\n覷[[Special:Version|版本頁面]]。",
        "ok": "好",
-       "retrievedfrom": "趁「$1」退過來",
+       "retrievedfrom": "Lài-nguòng: \"$1\"",
        "youhavenewmessages": "汝有$1($2)。",
        "youhavenewmessagesfromusers": "汝有趁$3用戶($2)來其$1萆信息{{PLURAL:$3}}",
        "youhavenewmessagesmanyusers": "汝有趁雅価用戶($2)其$1信息",
        "newmessageslinkplural": "{{PLURAL:$1|蜀條新其消息|999=新其消息}}",
        "newmessagesdifflinkplural": "最後{{PLURAL:$1|回改變|999=回改變}}",
        "youhavenewmessagesmulti": "汝有趁$1來其新信息",
-       "editsection": "修改",
+       "editsection": "siŭ-gāi",
        "editold": "修改",
        "viewsourceold": "看源代碼",
-       "editlink": "修改",
-       "viewsourcelink": "看源代碼",
-       "editsectionhint": "修改段:$1",
-       "toc": "目錄",
+       "editlink": "siŭ-gāi",
+       "viewsourcelink": "Káng nguòng-dâi-mā",
+       "editsectionhint": "Siŭ-gāi dâung: $1",
+       "toc": "Mŭk-liŏh",
        "showtoc": "顯示",
        "hidetoc": "囥起",
        "collapsible-collapse": "掩",
        "feed-invalid": "無乇使其下標填充類型",
        "feed-unavailable": "𣍐使聚合訂閱",
        "site-rss-feed": "$1 RSS 訂閱",
-       "site-atom-feed": "$1原子訂閱",
+       "site-atom-feed": "$1 Nguòng-cṳ̄ déng-iŏk",
        "page-rss-feed": "「$1」RSS訂閱",
        "page-atom-feed": "「$1」原子訂閱",
-       "red-link-title": "$1(無許頁)",
+       "red-link-title": "$1 (mò̤ hī hiĕh)",
        "sort-descending": "降序排序",
        "sort-ascending": "升序排序",
-       "nstab-main": "頁面",
+       "nstab-main": "Ùng-ciŏng",
        "nstab-user": "用戶頁",
        "nstab-media": "媒體頁",
-       "nstab-special": "特殊頁面",
+       "nstab-special": "Dĕk-sṳ̀-hiĕk",
        "nstab-project": "工程頁",
-       "nstab-image": "文件",
+       "nstab-image": "Ùng-giông",
        "nstab-mediawiki": "消息",
        "nstab-template": "模板",
        "nstab-help": "幫助頁",
-       "nstab-category": "類別",
+       "nstab-category": "Lôi-biék",
+       "mainpage-nstab": "Tàu Hiĕk",
        "nosuchaction": "無茲蜀種行動",
        "nosuchactiontext": "茲蜀種URL指定其行動是𣍐合法其。",
        "nosuchspecialpage": "無總款其特殊頁",
        "yourpasswordagain": "重新拍囇密碼:",
        "createacct-yourpasswordagain": "確定密碼",
        "createacct-yourpasswordagain-ph": "再拍入蜀回密碼",
-       "remembermypassword": "共我敆茲蜀萆瀏覽器其登錄記錄記定幾日(最価$1日){{PLURAL:$1}}",
        "userlogin-remembermypassword": "記𡅏我躒入其狀態",
        "userlogin-signwithsecure": "使安全其連接",
        "yourdomainname": "汝其域名:",
        "createaccount-title": "{{SITENAME}}其開賬戶",
        "login-abort-generic": "汝其登錄𣍐成功——放棄去了",
        "loginlanguagelabel": "語言:$1",
-       "pt-login": "躒入",
+       "pt-login": "Láuk-diē",
        "pt-login-button": "躒入",
-       "pt-createaccount": "開新賬號",
+       "pt-createaccount": "Kŭi sĭng dióng-hô̤",
        "pt-userlogout": "躒出",
        "php-mail-error-unknown": "PHP其mail()函數,𣍐曉什乇綻去。",
        "changepassword": "改變密碼",
        "passwordreset-domain": "域名:",
        "passwordreset-email": "電批地址:",
        "passwordreset-emailsentemail": "蜀萆密碼重新設置其電批已經寄出去了。",
-       "passwordreset-emailsent-capture": "蜀萆密碼重新設置其電批已經寄出去了,內容就是生下底總款。",
        "changeemail": "修改電批其地址",
        "changeemail-header": "修改賬戶電子郵件地址",
        "changeemail-oldemail": "現刻時其電批地址:",
        "templatesused": "{{PLURAL:$1}}茲頁裏勢使其模板:",
        "templatesusedpreview": "茲萆預覽使其{{PLURAL:$1|模板}}:",
        "templatesusedsection": "茲蜀段使其{{PLURAL:$1|模板}}:",
-       "template-protected": "(保護)",
+       "template-protected": "(bō̤-hô)",
        "template-semiprotected": "(半保護)",
        "permissionserrorstext-withaction": "因為下底其{{PLURAL:$1|原因}},汝無能耐 $2 :",
        "recreate-moveddeleted-warn": "'''注意:汝敆𡅏重新創建舊底已經乞刪唻其頁面。'''\n\n汝應該考慮蜀下繼續去編輯茲蜀頁到底是伓是合適其。茲蜀頁其刪除記錄共移動記錄都敆嚽塊:",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
        "undo-summary": "取消[[Special:Contributions/$2|$2]]([[User talk:$2|Tō̤-lâung]])其$1修改",
-       "cantcreateaccounttitle": "無能獃開賬戶",
        "viewpagelogs": "看茲頁其歷史",
        "nohistory": "茲頁無修改歷史。",
        "currentrev": "最新版本",
-       "revisionasof": "$1其版本",
-       "previousrevision": "←加舊其版本",
+       "revisionasof": "$1 gì bēng-buōng",
+       "previousrevision": "← Gá-gô gì bēng-buōng",
        "nextrevision": "加新其版本→",
        "currentrevisionlink": "最新版本",
        "cur": "仱",
        "difference-title": "「$1」調整以後𣍐蜀樣其地方",
        "difference-title-multipage": "「$1」共「$2」臺中𣍐蜀樣其地方",
        "difference-multipage": "(臺中𣍐蜀様其地方)",
-       "lineno": "第$1行:",
+       "lineno": "Dâ̤ $1 hòng:",
        "compareselectedversions": "比並揀選版本",
        "showhideselectedversions": "顯/藏揀選其調整",
-       "editundo": "取消",
-       "searchresults": "討結果",
-       "searchresults-title": "尋討「$1」其結果",
+       "editundo": "Chṳ̄-siĕu",
+       "searchresults": "Sìng-tō̤ giék-guō",
+       "searchresults-title": "Sìng-tō̤ \"$1\" gì giék-guō",
        "prevn": "前{{PLURAL:$1}}$1萆",
        "nextn": "後{{PLURAL:$1}}$1萆",
-       "shown-title": "每頁顯示$1{{PLURAL:$1|萆結果}}",
+       "shown-title": "Mūi hiĕk hiēng-sê $1{{PLURAL:$1|bĭk giék-guō}}",
        "viewprevnext": "看($1 {{int:pipe-separator}} $2)($3)。",
-       "searchprofile-articles": "內容頁",
-       "searchprofile-images": "多媒體",
-       "searchprofile-everything": "所有乇",
-       "searchprofile-advanced": "高級",
-       "searchprofile-articles-tooltip": "敆$1𡅏尋討",
-       "searchprofile-images-tooltip": "尋討文件",
-       "search-result-size": "$1 ({{PLURAL:$2|$2萆單詞}})",
+       "searchprofile-articles": "Nô̤i-ṳ̀ng hiĕk",
+       "searchprofile-images": "Dŏ̤-mùi-tā̤",
+       "searchprofile-everything": "Sū-iū-nó̤h",
+       "searchprofile-advanced": "Gŏ̤-ngék",
+       "searchprofile-articles-tooltip": "Găk $1 lā̤ sìng-tō̤",
+       "searchprofile-images-tooltip": "Sìng-tō̤ ùng-giông",
+       "search-result-size": "$1 ({{PLURAL:$2|$2 bĭk dăng-sṳ̀}})",
        "search-redirect": "(重定向 $1)",
        "search-suggest": "汝其意思是伓是:$1",
        "searchrelated": "相關其",
        "grouppage-sysop": "{{ns:project}}:管理員",
        "grouppage-bureaucrat": "{{ns:project}}:官僚組",
        "grouppage-suppress": "{{ns:project}}:巡查員",
-       "newuserlogpage": "開賬戶日誌",
+       "newuserlogpage": "Kŭi dióng-hô nĭk-cé",
        "action-edit": "修改茲蜀頁",
-       "recentchanges": "這般其改變",
+       "recentchanges": "Cī-bŏng gì gāi-biéng",
        "recentchanges-summary": "敆維基茲頁跟蹤這般其改變。",
-       "recentchanges-label-newpage": "茲蜀萆修改創建新其蜀頁",
-       "recentchanges-label-minor": "嚽是蜀萆過幼修改",
-       "recentchanges-label-bot": "茲蜀萆修改是機器人做其",
+       "recentchanges-label-newpage": "Cī siŏh bĭk siŭ-gāi cháung-gióng lāu sĭng hiĕk",
+       "recentchanges-label-minor": "Cuòi sê siŏh bĭk guó-éu siŭ-gāi",
+       "recentchanges-label-bot": "Cuòi sê gĭ-ké-nè̤ng siŭ-gāi gì",
        "rclistfrom": "顯示由$3 $2開始其新其改變",
        "rcshowhideminor": "$1過幼修改",
        "rcshowhidebots": "$1機器人",
        "rcshowhideanons": "$1無名用戶",
        "rcshowhidemine": "$1我其修改",
        "rclinks": "顯示$2日以內產生其$1回改變<br />$3",
-       "diff": "",
-       "hist": "",
+       "diff": "chă",
+       "hist": "sṳ̄",
        "hide": "掩",
        "show": "現",
        "minoreditletter": "~",
        "newpageletter": "!",
        "boteditletter": "^",
+       "rc-change-size-new": "Siŭ-gāi ī-hâiu biéng có̤ $1 cê-ciék",
        "rc-enhanced-hide": "囥起細節",
        "recentchangeslinked": "相關其改變",
        "recentchangeslinked-feed": "相關其改變",
-       "recentchangeslinked-toolbox": "相關其改變",
+       "recentchangeslinked-toolbox": "Sŏng-guăng gì gāi-biéng",
        "recentchangeslinked-page": "頁面名:",
-       "upload": "上傳文件",
+       "upload": "Siông-diòng ùng-giông",
        "uploadbtn": "上傳文件",
        "reuploaddesc": "取消上傳,轉去上傳頁面",
        "uploadnologin": "未登錄",
        "listfiles_name": "名",
        "listfiles_user": "用戶",
        "listfiles_size": "尺寸",
-       "file-anchor-link": "文件",
-       "filehist": "文件歷史",
-       "filehist-current": "現刻時",
-       "filehist-datetime": "日期/時間",
-       "filehist-user": "用戶",
-       "filehist-dimensions": "維度",
-       "filehist-comment": "評論",
-       "imagelinks": "文件使用方法",
-       "linkstoimage": "下底{{PLURAL:$1|$1頁鏈接}}遘茲文件:",
+       "file-anchor-link": "Ùng-giông",
+       "filehist": "Ùng-giông lĭk-sṳ̄",
+       "filehist-current": "hiêng-káik-sì",
+       "filehist-datetime": "Nĭk-gĭ/Sì-găng",
+       "filehist-user": "Ê̤ṳng-hô",
+       "filehist-dimensions": "Chióh-cháung",
+       "filehist-comment": "Suók-mìng",
+       "imagelinks": "Ùng-giông sāi-ê̤ṳng cìng-huóng",
+       "linkstoimage": "Â-dā̤ {{PLURAL:$1|$1 hiĕk}} lièng gáu ciā ùng-giông:",
        "nolinkstoimage": "無鏈接遘茲蜀萆文件其頁面。",
        "uploadnewversion-linktext": "上傳蜀萆新版本其茲萆文件。",
        "shared-repo-name-wikimediacommons": "Wikimedia Commons",
        "unwatchedpages": "無監視其頁面",
        "listredirects": "重定向其單單",
        "unusedtemplateswlh": "其它鏈接",
-       "randompage": "隨便罔看",
+       "randompage": "Sùi-biêng muōng ché̤ṳ",
        "randomredirect": "隨便重定向",
        "statistics": "統計",
        "statistics-header-users": "用戶統計",
        "withoutinterwiki": "無跨語言其鏈接",
        "withoutinterwiki-summary": "下底其頁面無鏈接遘其它語言其版本。",
        "fewestrevisions": "修改最少其頁面",
-       "nbytes": "$1{{PLURAL:$1}}å­\97ç¯\80",
+       "nbytes": "$1{{PLURAL:$1}} bÄ­k dÄ\83ng-sá¹³Ì\80",
        "nlinks": "$1隻{{PLURAL:$1|鏈接}}",
        "nmembers": "$1隻成員{{PLURAL:$1}}",
        "wantedcategories": "卜挃其類別",
        "longpages": "長頁",
        "protectedpages": "保護頁",
        "listusers": "用戶單",
-       "newpages": "新頁",
+       "newpages": "Sĭng hiĕk",
        "newpages-username": "用戶名:",
        "ancientpages": "最舊其頁面",
        "move": "移動",
        "allpagesfrom": "使下底其乇開始顯示頁:",
        "allarticles": "所有文章",
        "allinnamespace": "所有頁面($1命名空間)",
-       "allpagessubmit": "",
+       "allpagessubmit": "Kó̤",
        "allpagesprefix": "按頭部顯示頁面:",
        "allpagesbadtitle": "給出其頁面其標題是𣍐合法其,或者有蜀萆跨語言或跨維基其前綴。伊可能包括蜀萆或者価萆𣍐使廮標題裏勢其字符。",
        "categories": "類別",
        "deletionlog": "刪除日誌",
        "deletecomment": "原因:",
        "rollback": "再修改轉去",
-       "rollbacklink": "",
+       "rollbacklink": "duōng",
        "rollbackfailed": "轉𣍐去",
        "cantrollback": "𣍐使恢復修改;最後其貢獻者是茲蜀頁其唯一其作者。",
        "alreadyrolled": "𣍐使回滾最後蜀回[[User:$2|$2]] ([[User talk:$2|討論]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])其[[:$1]]編輯;\n有其他儂已經編輯過了或者茲蜀頁已經乞回滾過了。\n\n最後蜀回茲蜀頁其修改是[[User:$3|$3]] ([[User talk:$3|討論]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])改其。",
        "undeleteviewlink": "看",
        "undeletecomment": "原因:",
        "undelete-search-submit": "尋討",
-       "namespace": "命名空間:",
-       "invert": "反選",
-       "blanknamespace": "(主要)",
+       "namespace": "Miàng-kŭng-găng:",
+       "invert": "Huāng-sōng",
+       "blanknamespace": "(cuō-iéu)",
        "contributions": "{{GENDER:$1|User}}用戶貢獻",
        "contributions-title": "$1其用戶貢獻",
        "mycontris": "我其貢獻",
        "sp-contributions-search": "尋討貢獻",
        "sp-contributions-username": "IP地址或者用戶名:",
        "sp-contributions-submit": "尋討",
-       "whatlinkshere": "甚乇鏈遘嚽塊",
+       "whatlinkshere": "Diē-nē̤ lièng gáu cē̤-nē̤",
        "whatlinkshere-title": "鏈接遘$1其頁面",
        "whatlinkshere-page": "頁面:",
        "linkshere": "下底其頁面鏈接遘'''[[:$1]]''':",
        "anononlyblock": "囇無名用戶",
        "createaccountblock": "防止開賬戶",
        "ipblocklist-empty": "茲張封鎖單單是空其。",
-       "blocklink": "封鎖",
+       "blocklink": "hŭng-sō̤",
        "unblocklink": "開封",
        "change-blocklink": "修改封鎖情況",
-       "contribslink": "貢獻",
+       "contribslink": "góng-hióng",
        "blocklogpage": "封鎖日誌",
        "blocklogentry": "封鎖[[$1]],遘$2時候過時,$3",
        "block-log-flags-anononly": "囇無名用戶",
        "allmessagescurrent": "現時其文字",
        "allmessagestext": "茲是敆MediaWiki命名空間裏勢系統消息其蜀萆單單。\n如果汝卜想貢獻通用其MediaWiki基本地化服務,起動汝訪問[https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation MediaWiki本地化]共[https://translatewiki.net translatewiki.net]。",
        "allmessagesnotsupportedDB": "茲蜀頁𣍐使其,因為'''$wgUseDatabaseMessages'''已經乞禁止去了。",
-       "thumbnail-more": "放大",
+       "thumbnail-more": "Huóng-duâi",
        "tooltip-pt-userpage": "汝其用戶頁",
        "tooltip-pt-mytalk": "汝其討論頁",
        "tooltip-pt-preferences": "汝其設定",
        "tooltip-pt-watchlist": "汝監視其頁面有改過其單單",
        "tooltip-pt-mycontris": "汝其貢獻其單單",
-       "tooltip-pt-login": "希望汝先躒入;不過儂家無逼汝總款做。",
+       "tooltip-pt-login": "Hĭ-uông nṳ̄ sĕng láuk-diē; bók-guó nàng-gă mò̤ ăng nṳ̄ cūng-kuāng có̤.",
        "tooltip-pt-logout": "躒出",
-       "tooltip-ca-talk": "茲蜀頁其討論",
+       "tooltip-ca-talk": "Nô̤i-ṳ̀ng gì tō̤-lâung",
        "tooltip-ca-edit": "汝會使修改茲蜀頁。起動敆保存以前使預覽按鈕",
-       "tooltip-ca-addsection": "開始蜀萆新其部分",
+       "tooltip-ca-addsection": "Gă sĭng dâung",
        "tooltip-ca-viewsource": "茲蜀頁乞保護起去。\n汝會使看伊其源代碼。",
-       "tooltip-ca-history": "覷茲頁舊底其版本",
+       "tooltip-ca-history": "Ché̤ṳ cī hiĕk gó̤-dā̤ gì bēng-buōng",
        "tooltip-ca-protect": "保護茲蜀頁",
        "tooltip-ca-delete": "刪掉茲蜀頁",
        "tooltip-ca-move": "移動茲蜀頁",
-       "tooltip-ca-watch": "將茲蜀頁加遘汝其監視單",
+       "tooltip-ca-watch": "Ciŏng cī siŏh hiĕk gă diē nṳ̄ gì gáng-sê-dăng",
        "tooltip-ca-unwatch": "共茲頁趁監視單𡅏移開去",
-       "tooltip-search": "尋討 {{SITENAME}} [alt-f]",
-       "tooltip-search-fulltext": "敆茲幾頁𡅏尋討茲文字",
-       "tooltip-p-logo": "覷蜀覷頭頁",
-       "tooltip-n-mainpage": "覷蜀覷頭頁",
-       "tooltip-n-mainpage-description": "覷蜀覷頭頁",
-       "tooltip-n-recentchanges": "維基百科最近其改變其單單",
-       "tooltip-n-randompage": "隨便罔看",
-       "tooltip-t-whatlinkshere": "鏈遘嚽塊其所有維基頁面其單單",
-       "tooltip-t-recentchangeslinked": "鏈遘茲頁其頁面其最近修改",
+       "tooltip-search": "Sìng-tō̤ {{SITENAME}} [alt-f]",
+       "tooltip-search-fulltext": "Sìng-tō̤ sāi-ê̤ṳng ciā ùng-cê gì hiĕk-miêng",
+       "tooltip-p-logo": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-mainpage": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-mainpage-description": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-recentchanges": "Cī-bŏng diŏh wiki ô gāi-biéng gì dăng-dăng",
+       "tooltip-n-randompage": "Sùi-biêng muōng ché̤ṳ",
+       "tooltip-t-whatlinkshere": "鏈遘嚽塊其所有維基頁面其單單\nCuòng-buô lièng-gáu cŭ-uái gì wiki hiĕk-miêng dăng-dăng",
+       "tooltip-t-recentchangeslinked": "鏈遘茲頁其頁面其最近修改\nCī hiĕk lièng gáu bĕk hiĕk gì cī-bŏng gì gāi-biéng",
        "tooltip-t-contributions": "茲蜀用戶其貢獻單單",
        "tooltip-t-emailuser": "向茲蜀隻用戶寄電批",
-       "tooltip-t-upload": "上傳文件",
-       "tooltip-t-specialpages": "特殊頁其單單",
-       "tooltip-t-print": "茲蜀頁其會拍印其版本",
-       "tooltip-t-permalink": "茲頁茲版本其永久鏈接",
-       "tooltip-ca-nstab-main": "看蜀看內容頁",
+       "tooltip-t-upload": "Siông-diòng ùng-giông",
+       "tooltip-t-specialpages": "Cuòng-buô dĕk-sṳ̀-hiĕk dăng-dăng",
+       "tooltip-t-print": "Cī hiĕk gì â̤ páh-éng bēng-buōng",
+       "tooltip-t-permalink": "茲頁茲版本其永久鏈接\nCī hiĕk cī bēng-buōng gì īng-giū lièng-giék",
+       "tooltip-ca-nstab-main": "Káng iĕk gì nô̤i-ṳ̀ng",
        "tooltip-ca-nstab-user": "覷蜀覷用戶頁",
        "tooltip-ca-nstab-special": "茲是蜀萆特殊頁,汝𣍐使修改茲蜀頁。",
        "tooltip-ca-nstab-project": "看工程頁",
-       "tooltip-ca-nstab-image": "看文件頁",
+       "tooltip-ca-nstab-image": "Ché̤ṳ ùng-giông hiĕk",
        "tooltip-ca-nstab-template": "覷蜀覷模板",
        "tooltip-minoredit": "共茲標記成過幼修改",
        "tooltip-save": "保存汝其改變 [alt-s]",
        "file-nohires": "無更高決斷",
        "ilsubmit": "尋討",
        "bydate": "按日期",
-       "metadata": "元數據",
+       "metadata": "Nguòng-só-gé̤ṳ",
        "exif-componentsconfiguration-0": "無存在",
        "exif-meteringmode-0": "𣍐八",
        "exif-lightsource-0": "𣍐八",
        "exif-subjectdistancerange-0": "𣍐八",
-       "namespacesall": "所有",
+       "namespacesall": "cuòng-buô",
        "monthsall": "囫圇年",
        "confirmemail": "確定電批地址",
        "confirmemail_invalid": "確認碼無效。\n可能已經過期了。",
        "watchlisttools-view": "看相關改變",
        "watchlisttools-edit": "看共修改監視單",
        "watchlisttools-raw": "修改原始監視單",
-       "specialpages": "特殊頁",
-       "searchsuggest-search": ""
+       "specialpages": "Dĕk-sṳ̀-hiĕk",
+       "searchsuggest-search": "Tō̤"
 }
index 83a72b4..cf16049 100644 (file)
@@ -62,7 +62,7 @@
        "tog-enotifminoredits": "Posílat e-maily i při malých editacích stránek a souborů",
        "tog-enotifrevealaddr": "Prozradit mou e-mailovou adresu v upozorňujících e-mailech",
        "tog-shownumberswatching": "Zobrazovat počet sledujících uživatelů",
-       "tog-oldsig": "Stávající podpis:",
+       "tog-oldsig": "Váš stávající podpis:",
        "tog-fancysig": "Používat v podpisu wikitext (bez automatického odkazu)",
        "tog-uselivepreview": "Používat rychlý náhled",
        "tog-forceeditsummary": "Upozornit, když nevyplním shrnutí editace",
@@ -79,7 +79,7 @@
        "tog-showhiddencats": "Zobrazit skryté kategorie",
        "tog-norollbackdiff": "Po vrácení změny nezobrazovat porovnání rozdílů",
        "tog-useeditwarning": "Upozornit, když budu opouštět editaci bez uložení změn",
-       "tog-prefershttps": "Po přihlášení používat vždy zabezpečené spojení",
+       "tog-prefershttps": "Po přihlášení vždy používat zabezpečené připojení",
        "underline-always": "Vždy",
        "underline-never": "Nikdy",
        "underline-default": "Podle nastavení prohlížeče nebo vzhledu",
        "newwindow": "(otevře se v novém okně)",
        "cancel": "Storno",
        "moredotdotdot": "Další…",
-       "morenotlisted": "Tento seznam není úplný.",
+       "morenotlisted": "Tento seznam může být neúplný.",
        "mypage": "Stránka",
        "mytalk": "Diskuse",
        "anontalk": "Diskuse",
        "htmlform-title-not-exists": "Stránka $1 neexistuje.",
        "htmlform-user-not-exists": "Uživatel <strong>$1</strong> neexistuje.",
        "htmlform-user-not-valid": "<strong>$1</strong> není platné uživatelské jméno.",
-       "sqlite-has-fts": "$1 s podporou plnotextového vyhledávání",
-       "sqlite-no-fts": "$1 bez podpory plnotextového vyhledávání",
        "logentry-delete-delete": "$1 {{GENDER:$2|smazal|smazala}} stránku $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|obnovil|obnovila}} stránku $3",
        "logentry-delete-event": "$1 {{GENDER:$2|změnil|změnila}} viditelnost {{PLURAL:$5|protokolovacího záznamu|$5 protokolovacích záznamů}} ke stránce $3: $4",
index c6bd656..4272464 100644 (file)
        "htmlform-cloner-create": "Tilføj flere",
        "htmlform-cloner-delete": "Fjern",
        "htmlform-cloner-required": "Der kræves mindst en værdi.",
-       "sqlite-has-fts": "$1 med fuld-tekst søgnings support",
-       "sqlite-no-fts": "$1 uden fuld-tekst søgnings support",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettede}} siden $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|gendannede}} siden $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ændrede}} synligheden af {{PLURAL:$5|en loghændelse|$5 loghændelser}} for siden $3: $4",
index d5f52f2..f2c4c01 100644 (file)
        "htmlform-title-not-exists": "$1 ist nicht vorhanden.",
        "htmlform-user-not-exists": "<strong>$1</strong> ist nicht vorhanden.",
        "htmlform-user-not-valid": "<strong>$1</strong> ist kein gültiger Benutzername.",
-       "sqlite-has-fts": "Version $1 mit Unterstützung für die Volltextsuche",
-       "sqlite-no-fts": "Version $1 ohne Unterstützung für die Volltextsuche",
        "logentry-delete-delete": "$1 {{GENDER:$2|löschte}} Seite $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|stellte}} Seite $3 wieder her",
        "logentry-delete-event": "$1 {{GENDER:$2|änderte}} die Sichtbarkeit {{PLURAL:$5|eines Logbucheintrags|von $5 Logbucheinträgen}} auf $3: $4",
index 9e89037..ed030ec 100644 (file)
        "about": "Heqa cı de",
        "article": "Pela zerreki",
        "newwindow": "(pençereyê newey de beno a)",
-       "cancel": "İbtal kı",
+       "cancel": "Bıtexelne",
        "moredotdotdot": "Vêşi...",
        "morenotlisted": "Vêşi lista nêbi...",
        "mypage": "Pele",
        "newpage": "Pela newiye",
        "talkpage": "Ena pele sero werêne",
        "talkpagelinktext": "werênayış",
-       "specialpage": "Pela xısusiye",
+       "specialpage": "Perra bağsi",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pera zerreki bıvin",
-       "talk": "Vatene",
+       "talk": "Werênayış",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "userpage": "Pela karberi bıvêne",
        "newarticle": "(Newe)",
        "newarticletext": "To yew gıre tıkna be ra yew pela ke hewna çıniya.\nSeba afernayışê pele ra, qutiya metnê cêrêni bıgurene (seba melumati qaytê [$1 pela peşti] ke).\nEke be ğeletine ameya tiya, wa gocega <strong>peyser</strong>i programê xo de bıtıkne.",
        "anontalkpagetext": "----''Na per, perêk kı karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres xebetneno û ney IP adresan herkes nêşeno bıvino. Eke şıma qayil niye ina bo xorê [[Special:CreateAccount|yew hesab bıvıraze]] veya xut [[Special:UserLogin|hesab akere]].''",
-       "noarticletext": "Ena pele de hewna theba çıniyo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê  sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
+       "noarticletext": "Ena perrer de hewna theba çıni yo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê  sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
        "noarticletext-nopermission": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê na pele cı geyre]], ya zi <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre]</span>, ema destur çıniyo ke na pele vırazê.",
        "missing-revision": "Rewizyonê name dê pela da #$1 \"{{FULLPAGENAME}}\" dı çıniyo.\n\nNo normal de tarix dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
        "userpage-userdoesnotexist": "Hesabê karberi \"<nowiki>$1</nowiki>\" qeyd nêbiyo.\nKerem ke, tı ke wazenay na pele bafernê/bıvurnê, qontrol ke.",
        "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bıvênên",
        "searchmenu-exists": "''Ena 'Wikipediya de ser \"[[:$1]]\" yew pel esto'''",
        "searchmenu-new": "<strong>Na wiki de pela \"[[:$1]]\" vıraze!</strong> {{PLURAL:$2|0=|Sewbina pela ke şıma geyrayê cı aye bıvênê.|Yew zi neticanê cıgeyrayışê xo bıvênê.}}",
-       "searchprofile-articles": "Pelê zerreki",
-       "searchprofile-images": "Multimedya",
-       "searchprofile-everything": "Heme çi",
-       "searchprofile-advanced": "Raverşiyaye",
+       "searchprofile-articles": "Zerrekê pelan",
+       "searchprofile-images": "Zafınmedya",
+       "searchprofile-everything": "Pêro çi",
+       "searchprofile-advanced": "Herayen",
        "searchprofile-articles-tooltip": "$1 de cı geyre",
        "searchprofile-images-tooltip": "Dosya cı geyre",
        "searchprofile-everything-tooltip": "Tedeesteyan hemine cı geyre (pelanê mınaqeşeyi zi tey)",
        "sp-contributions-search": "Dekerdena cı geyrê",
        "sp-contributions-username": "Adresa IPy ya zi nameyê karberi:",
        "sp-contributions-toponly": "Tenya rewizyonanê tewr peyniyan bimocne",
+       "sp-contributions-hideminor": "Vurriyayışanê werdiyan bınımne",
        "sp-contributions-submit": "Cı geyre",
        "whatlinkshere": "Linkê tedeestey",
        "whatlinkshere-title": "Per da \"$1\" rê perê ke gre danê",
        "htmlform-chosen-placeholder": "Opsiyon weçine",
        "htmlform-cloner-create": "Tayêna cı ke",
        "htmlform-cloner-delete": "Wedare",
-       "sqlite-has-fts": "$1 tam-metn destegê cı geyrayışiya piya",
-       "sqlite-no-fts": "$1 tam-metn bê destegê cı geyrayışi",
        "logentry-delete-delete": "$1 pela $3 {{GENDER:$2|esterıte}}",
        "logentry-delete-restore": "$1 pela $3 {{GENDER:$2|peyser arde}}",
        "logentry-delete-event": "$1 $3: $4 de asayışê {{PLURAL:$5|cıkerdışi|cıkerdışan}} {{GENDER:$2|vurna}}",
index a8dd103..67e6491 100644 (file)
        "htmlform-user-not-exists": "<strong>$1</strong> does not exist.",
        "htmlform-user-not-valid": "<strong>$1</strong> isn't a valid username.",
        "rawmessage": "$1",
-       "sqlite-has-fts": "$1 with full-text search support",
-       "sqlite-no-fts": "$1 without full-text search support",
        "logentry-delete-delete": "$1 {{GENDER:$2|deleted}} page $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restored}} page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4",
index f0a61b3..f7eea23 100644 (file)
        "htmlform-title-not-exists": "$1 ne ekzistas.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne ekzistas.",
        "htmlform-user-not-valid": "<strong>$1</strong> ne estas valida salutnomo.",
-       "sqlite-has-fts": "$1 kun tut-teksta subteno",
-       "sqlite-no-fts": "$1 sen tut-teksta subteno",
        "logentry-delete-delete": "$1 forigis paĝon $3",
        "logentry-delete-restore": "$1 restarigis paĝon $3",
        "logentry-delete-event": "$1 ŝanĝis videblecon de {{PLURAL:$5|protokola evento|$5 protokolaj eventoj}} je $3: $4",
index 74b31c0..6034afb 100644 (file)
        "htmlform-title-not-exists": "$1 no existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> no existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> no es un nombre de usuario válido.",
-       "sqlite-has-fts": "$1 con soporte para búsqueda de texto completo",
-       "sqlite-no-fts": "$1 sin soporte para búsqueda de texto completo",
        "logentry-delete-delete": "$1 {{GENDER:$2|borró}} la página $3",
        "logentry-delete-restore": "$1 restauró la página «$3»",
        "logentry-delete-event": "$1 {{GENDER:$2|modificó}} la visibilidad de {{PLURAL:$5|un evento|$5 eventos}} del registro en $3: $4",
index 523b2ef..476e3f1 100644 (file)
@@ -43,6 +43,7 @@
        "tog-watchdefault": "Aldatzen ditudan orrialdeak eta fitxategiak nire jarraipen-zerrendara gehitu",
        "tog-watchmoves": "Izena aldatutako orrialdeak eta fitxategiak jarraipen-zerrendara gehitu",
        "tog-watchdeletion": "Ezabatzen ditudan orrialdeak eta fitxategiak nire jarraipen-zerrendara gehitu",
+       "tog-watchuploads": "Gehitu igotzen ditudan fitxategiak nire jarraipen zerrendara",
        "tog-watchrollback": "Nire jarraipen zerrendan rollbacka egin dudan orrialdeak erakutsi",
        "tog-minordefault": "Lehenetsi bezala aldaketa txiki bezala markatu guztiak",
        "tog-previewontop": "Aurrebista aldaketa koadroaren aurretik erakutsi",
@@ -52,7 +53,7 @@
        "tog-enotifminoredits": "Orrialde edo fitxategietan aldaketak txikiak direnean ere e-posta jaso",
        "tog-enotifrevealaddr": "Jakinarazpen mezuetan nire e-posta helbidea erakutsi",
        "tog-shownumberswatching": "Jarraitzen duen erabiltzaile kopurua erakutsi",
-       "tog-oldsig": "Egungo sinadura:",
+       "tog-oldsig": "Zure egungo sinadura:",
        "tog-fancysig": "Sinadura wikitestu gisa tratatu (lotura automatikorik gabe)",
        "tog-uselivepreview": "Zuzeneko aurrebista erabili",
        "tog-forceeditsummary": "Aldaketaren laburpena zuri uzterakoan ohartarazi",
        "newwindow": "(leiho berrian irekitzen da)",
        "cancel": "Utzi",
        "moredotdotdot": "Gehiago...",
-       "morenotlisted": "Zerrenda hau ez dago osorik.",
+       "morenotlisted": "Zerrenda hau agian ez dago osorik.",
        "mypage": "Orrialdea",
        "mytalk": "Eztabaida",
        "anontalk": "Eztabaida",
        "createacct-reason-ph": "Zergatik ari zaren beste erabiltzaile kontu bat",
        "createacct-submit": "Kontua sortu",
        "createacct-another-submit": "Kontu bat sortu",
+       "createacct-continue-submit": "Jarraitu kontua sortzen",
+       "createacct-another-continue-submit": "Jarraitu kontua sortzen",
        "createacct-benefit-heading": "{{SITENAME}} zu bezalako pertsonek egiten dute.",
        "createacct-benefit-body1": "{{PLURAL:$1|edizio bat|$1 edizio}}",
        "createacct-benefit-body2": "{{PLURAL:$1|Orrialde 1|$1 orrialde}}",
        "botpasswords-label-update": "Eguneratu",
        "botpasswords-label-cancel": "Utzi",
        "botpasswords-label-delete": "Ezabatu",
+       "botpasswords-label-resetpassword": "Pasahitza berrezarri",
        "resetpass_forbidden": "Ezin dira pasahitzak aldatu",
        "resetpass-no-info": "Orrialde honetara zuzenean sartzeko izena eman behar duzu.",
        "resetpass-submit-loggedin": "Pasahitza aldatu",
        "continue-editing": "Edizio-eremura joan",
        "previewconflict": "Aurreikuspenak aldaketen koadroan idatzitako testua erakusten du, gorde ondoren agertuko den bezala.",
        "session_fail_preview": "'''Sentitzen dugu! Ezin izan da zure aldaketa prozesatu, saioko datu batzuen galera dela-eta. Mesedez, saiatu berriz. Arazoak jarraitzen badu, saiatu [[Special:UserLogout|saioa amaitu]] eta berriz hasten.'''",
-       "session_fail_preview_html": "'''Sentitzen dugu! Ezin izan dugu zure aldaketa burutu, saio datu galera bat medio.'''\n\n''Wiki honek HTML kodea onartzen duenez, aurreikuspena ezgaituta dago JavaScript erasoak saihestu asmoz.''\n\n'''Aldaketa saiakera hau zuzena baldin bada, saiatu berriro mesedez. Arazoak jarraitzen badu, saiatu saioa itxi eta berriz hasten.'''",
+       "session_fail_preview_html": "<strong>Sentitzen dugu! Ezin izan dugu zure aldaketa burutu, saio datu galera bat medio.</strong>\n\n<em>Wiki honek HTML kodea onartzen duenez, aurreikuspena ezgaituta dago JavaScript erasoak saihestu asmoz.</em>\n\n<strong>Aldaketa saiakera hau zuzena baldin bada, saiatu berriro mesedez. Arazoak jarraitzen badu, saiatu  [[Special:UserLogout|saioa itxi]] eta berriz hasten.</strong>",
        "token_suffix_mismatch": "'''Zure aldaketa ezeztatua izan da zure bezeroak puntuazio-karaktereak itxuragabetu dituelako.\nAldaketa ezeztatua izan da testuaren galtzea galarazteko.\nHau batzuetan gertatzen da buggyan oinarritutako web proxy zerbitzua erabiltzean.'''",
        "edit_form_incomplete": "'''Aldaketa formularioaren atal batzuk ez dira iritsi zerbitzarira; bi aldiz ziurtatu zure aldaketak osorik daudela eta berriro saiatu.'''",
        "editing": "«$1» aldatzen",
        "copyrightwarning": "Kontuan izan ezazu {{SITENAME}} webgunean egindako ekarpen guztiak $2 lizentziaren pean argitaratzen direla (xehetasunetarako, ikus $1). Zuk idatzitakoa libreki aldatua eta banatua izatea nahi ez baduzu, ez ezazu hemen jarri.<br />\nEra berean, hitzematen ari zara hau zuk zeuk idatzia dela, edo jabari publikotik nahiz askea den beste ituri batetik kopiatu duzula.\n'''Ez erabili copyright eskubideek babestutako lanik, baimenik gabe!'''",
        "copyrightwarning2": "Mesedez, kontuan izan ezazu {{SITENAME}} webgunean egindako ekarpen guztiak beste erabiltzaileek aldatu edo ezabatu ditzaketela. Zuk idatzitakoa libreki aldatua izatea nahi ez baduzu, ez ezazu hemen jarri.<br />\nEra berean, hitzematen ari zara hau zuk zeuk idatzia dela, edo jabari publikotik nahiz askea den beste ituri batetik kopiatu duzula (xehetasunetarako, ikus $1).\n'''Ez erabili copyright eskubideek babestutako lanik, baimenik gabe!'''",
        "longpageerror": "'''Errorea: Bidali duzun testuak {{PLURAL:$1|kilobyte 1eko|$1 kilobyteko}} luzera du, eta {{PLURAL:$2|kilobyte 1eko|$2 kilobyteko}} maximoa baino luzeagoa da.'''\nEzin da gorde.",
-       "readonlywarning": "'''Oharra: Datu-basea blokeatu egin da mantenu lanak burutzeko, beraz ezingo dituzu orain zure aldaketak gorde.'''\nTestua fitxategi baten kopiatu dezakezu, eta beranduago erabiltzeko gorde.\n\nBlokeatu zuen administratzaileak honako azalpena eman zuen: $1",
+       "readonlywarning": "<strong>Oharra: Datu-basea blokeatu egin da mantenu lanak burutzeko, beraz ezingo dituzu orain zure aldaketak gorde.</strong>I\nTestua fitxategi baten kopiatu dezakezu, eta beranduago erabiltzeko gorde.\n\nBlokeatu zuen administratzaileak honako azalpena eman zuen: $1",
        "protectedpagewarning": "'''Oharra:  Orri hau blokeatua dago administratzaileek soilik eraldatu ahal dezaten.'''\nAzken erregistroa ondoren ikusgai dago erreferentzia gisa:",
        "semiprotectedpagewarning": "'''Oharra''': Orrialde hau erregistratutako erabiltzaileek bakarrik aldatzeko babestuta dago.\nErregistroko azken sarrera azpian jartzen da erreferentzia gisa:",
        "cascadeprotectedwarning": "'''Oharra:''' Orrialde hau blokeatua izan da eta administratzaileek baino ez dute berau aldatzeko ahalmena, honako {{PLURAL:$1|orrialdeko|orrialdeetako}} kaskada-babesean txertatuta dagoelako:",
        "rows": "Lerroak:",
        "columns": "Zutabeak:",
        "searchresultshead": "Bilaketa",
-       "stub-threshold": "<a href=\"#\" class=\"stub\">stub link</a> formaturako atalasea (byteak):",
+       "stub-threshold": "<a href=\"#\" class=\"stub\">stub link</a> formaturako atalasea ($1):",
        "stub-threshold-sample-link": "adibidea",
        "stub-threshold-disabled": "Ezgaitua",
        "recentchangesdays": "Aldaketa berrietan erakutsi beharreko egun kopurua:",
        "apisandbox-helpurls": "Laguntza estekak",
        "apisandbox-examples": "Adibideak",
        "apisandbox-dynamic-parameters": "Parametro gehigarriak",
+       "apisandbox-dynamic-parameters-add-label": "Gehitu parametroa:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Parametroaren izena",
+       "apisandbox-dynamic-error-exists": "$1 parametro izena dagoeneko existitzen da",
        "apisandbox-results": "Emaitzak",
        "booksources": "Iturri liburuak",
        "booksources-search-legend": "Liburuen bilaketa",
        "logempty": "Ez dago emaitzarik erregistroan.",
        "log-title-wildcard": "Testu honekin hasten diren izenburuak bilatu",
        "showhideselectedlogentries": "Erakutsi/ezkutatu aukeratutako log sarrerak",
+       "checkbox-select": "Aukeratu:$1",
        "checkbox-all": "Denak",
        "checkbox-none": "Bat ere ez",
        "allpages": "Orri guztiak",
        "watchlistanontext": "Mesedez saioa hasi zure jarraipen zerrendako orrialdeak ikusi eta aldatu ahal izateko.",
        "watchnologin": "Saioa hasi gabe",
        "addwatch": "Jarraipen zerrendara gehitu",
-       "addedwatchtext": "«[[:$1]]» orria zure [[Special:Watchlist|jarraipen zerrendara]] erantsi da. \n\nOrri honetan aurrerantzean egindako aldaketak zerrenda horretan agertuko dira.",
+       "addedwatchtext": "\"[[:$1]]\" eta haren eztabaida orria zure [[Special:Watchlist|jarraipen zerrendara]] erantsi da. \n\nOrri honetan aurrerantzean egindako aldaketak zerrenda horretan agertuko dira.",
        "addedwatchtext-short": "$1 orria zure jarraipen zerrendara gehitu da.",
        "removewatch": "Kendu zure jarraipen zerrendatik",
        "removedwatchtext": "\"[[:$1]]\" orrialdea zure [[Special:Watchlist|jarraipen zerrendatik]] kendu da.",
        "import-upload": "Igo XML datuak",
        "import-token-mismatch": "Sesio data galdu da. Saia saitez berriro ere, mesedez.",
        "import-invalid-interwiki": "Ezin da esandako wikitik inportatu.",
-       "import-error-edit": "\"$1\" orrialdea ez da inportatu ez duzula baimenik aldatzeko.",
+       "import-error-edit": "\"$1\" orrialdea ez da inportatu aldatzeko baimenik ez duzulako.",
        "import-error-create": "\"$1\" orrialdea ez da inportatu ez duzula baimenik sortzeko.",
        "import-error-interwiki": "\"$1\" orrialdea ez da inportatu bere izena kanpo loturetarako gordeta dagoelako (interwiki).",
        "import-error-special": "\"$1\" orrialdea ez da inportatu izen-tarte berezi bati dagokiolako eta horretan orrialderik ezin delako egon.",
        "tags-actions-header": "Ekintzak",
        "tags-active-yes": "Bai",
        "tags-active-no": "Ez",
-       "tags-source-extension": "Luzapenak definitua",
+       "tags-source-extension": "Softwareak definitua",
        "tags-source-none": "Ez da gehiago erabiltzen",
        "tags-edit": "aldatu",
        "tags-delete": "ezabatu",
        "tags-create-tag-name": "Etiketaren izena:",
        "tags-create-reason": "Arrazoia:",
        "tags-create-submit": "Sortu",
+       "tags-create-no-name": "Etiketatutako izen bat zehaztu behar duzu.",
        "tags-create-already-exists": "\"$1\" etiketa badago.",
        "tags-create-warnings-below": "Etiketaren sorrerarekin jarraitu nahi duzu?",
        "tags-delete-title": "Etiketa ezabatu",
        "htmlform-title-not-exists": "$1 ez da existitzen.",
        "htmlform-user-not-exists": "<strong>$1</strong> ez da existitzen.",
        "htmlform-user-not-valid": "<strong>$1</strong> erabiltzaile izena ezin da erabili.",
-       "sqlite-has-fts": "$1 testu osoan bilatzeko laguntzarekin",
-       "sqlite-no-fts": "$1 testu osoan bilatzeko laguntzarik gabe",
        "logentry-delete-delete": "$1 {{GENDER:$2|wikilariak}} «$3» orria ezabatu du",
        "logentry-delete-restore": "$1(e)k $3 orrialdea {{GENDER:$2|berrezarri}} du",
        "logentry-delete-event": "$1 wikilariak ikusgaitasuna {{{{GENDER:$2|}}|aldatu}} {{PLURAL:$5|dio erregistroko sarrera bati|die erregistroko $5 sarrerari}}, $3 orrian: $4",
        "mw-widgets-titleinput-description-new-page": "orri hori oraindik ez da existitzen",
        "mw-widgets-titleinput-description-redirect": "$1ra birzuzendu",
        "sessionprovider-generic": "$1 sesio",
+       "log-action-filter-block": "Blokeatze mota:",
+       "log-action-filter-delete": "Ezabatze mota:",
+       "log-action-filter-import": "Inportazio mota:",
+       "log-action-filter-move": "Mugimendu mota:",
+       "log-action-filter-newusers": "Kontu sortze-mota:",
+       "log-action-filter-patrol": "Patruilatze mota:",
+       "log-action-filter-protect": "Babes mota:",
+       "log-action-filter-suppress": "Ezabatze mota:",
+       "log-action-filter-upload": "Igoera mota:",
        "log-action-filter-all": "Denak",
        "log-action-filter-block-block": "Blokeatu",
        "log-action-filter-block-reblock": "Blokeoa aldatu",
        "log-action-filter-block-unblock": "blokeoa kendu",
+       "log-action-filter-delete-revision": "Berrikuspen ezabaketa",
+       "log-action-filter-import-interwiki": "Transwiki inportazioa",
+       "log-action-filter-managetags-create": "Etiketa sorkuntza",
+       "log-action-filter-managetags-delete": "Etiketa ezabaketa",
+       "log-action-filter-managetags-activate": "Etiketa aktibazioa",
+       "log-action-filter-managetags-deactivate": "Etiketa desaktibazioa",
        "authmanager-userdoesnotexist": "\"$1\" erabiltzaile kontua ez dago erregistratua.",
        "authmanager-email-label": "Emaila",
        "authmanager-email-help": "Helbide elektronikoa",
        "authmanager-realname-label": "Benetako izena",
        "authmanager-realname-help": "Erabiltzailearen benetako izena",
        "authprovider-resetpass-skip-label": "Utzi",
-       "authform-wrongtoken": "Token okerra"
+       "authform-wrongtoken": "Token okerra",
+       "credentialsform-account": "Kontuaren izena:"
 }
index dbe5d53..0fbbe20 100644 (file)
        "htmlform-title-not-exists": "$1 وجود ندارد.",
        "htmlform-user-not-exists": "<strong>$1</strong> وجود ندارد.",
        "htmlform-user-not-valid": "حساب کاربری <strong>$1</strong> معتبر نیست.",
-       "sqlite-has-fts": "$1 با پشتیبانی از جستجو در متن کامل",
-       "sqlite-no-fts": "$1 بدون پشتیبانی از جستجو در متن کامل",
        "logentry-delete-delete": "$1 صفحهٔ $3 را {{GENDER:$2|حذف کرد}}",
        "logentry-delete-restore": "$1 صفحهٔ $3 را {{GENDER:$2|احیا کرد}}",
        "logentry-delete-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاهه|$5 مورد سیاهه}} را در $3 {{GENDER:$2|تغییر داد}}: $4",
index e417ee4..863e3d7 100644 (file)
        "htmlform-title-not-exists": "Sivua $1 ei ole olemassa.",
        "htmlform-user-not-exists": "Käyttäjää <strong>$1</strong> ei ole olemassa.",
        "htmlform-user-not-valid": "<strong>$1</strong> ei ole kelvollinen käyttäjänimi.",
-       "sqlite-has-fts": "$1, jossa on tuki kokotekstihaulle",
-       "sqlite-no-fts": "$1, jossa ei ole tukea kokotekstihaulle",
        "logentry-delete-delete": "$1 {{GENDER:$2|poisti}} sivun $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|palautti}} sivun $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutti}} {{PLURAL:$5|lokitapahtuman|$5 lokitapahtuman}} näkyvyyttä kohteessa $3: $4",
index 21f2e7c..55df8ff 100644 (file)
        "tog-enotifminoredits": "M’avertir par courriel également lors des modifications mineures des pages ou des fichiers",
        "tog-enotifrevealaddr": "Afficher mon adresse électronique dans les courriels de notification",
        "tog-shownumberswatching": "Afficher le nombre d’utilisateurs en cours",
-       "tog-oldsig": "Signature existante :",
+       "tog-oldsig": "Votre signature existante :",
        "tog-fancysig": "Traiter la signature comme du wikitexte (sans lien automatique)",
        "tog-uselivepreview": "Utiliser l’aperçu rapide",
        "tog-forceeditsummary": "M’avertir lorsque je n’ai pas spécifié de résumé de modification",
        "tog-showhiddencats": "Afficher les catégories cachées",
        "tog-norollbackdiff": "Ne pas afficher le diff après avoir révoqué",
        "tog-useeditwarning": "M’avertir quand je quitte une page en cours de modification sans avoir sauvegardé",
-       "tog-prefershttps": "Toujours utiliser une connexion sécurisée pour se connecter",
+       "tog-prefershttps": "Utilisez toujours une connexion sécurisée pour vous connecter",
        "underline-always": "Toujours",
        "underline-never": "Jamais",
        "underline-default": "Valeur par défaut du thème ou du navigateur",
        "newwindow": "(ouvre dans une nouvelle fenêtre)",
        "cancel": "Annuler",
        "moredotdotdot": "Plus...",
-       "morenotlisted": "Cette liste n’est pas complète.",
+       "morenotlisted": "Cette liste peut être incomplète.",
        "mypage": "Page",
        "mytalk": "Discussion",
        "anontalk": "Discussion",
        "htmlform-title-not-exists": "$1 n’existe pas",
        "htmlform-user-not-exists": "<strong>$1</strong> n’existe pas.",
        "htmlform-user-not-valid": "<strong>$1</strong> n’est pas un nom d’utilisateur valide.",
-       "sqlite-has-fts": "$1 avec recherche en texte intégral prise en charge",
-       "sqlite-no-fts": "$1 sans recherche en texte intégral prise en charge",
        "logentry-delete-delete": "$1 {{GENDER:$2|a supprimé}} la page $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restauré}} la page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a modifié}} la visibilité {{PLURAL:$5|d'un événement du journal|de $5 événements du journal}} sur $3: $4",
index d28d4eb..ab12a38 100644 (file)
        "htmlform-title-not-exists": "\"$1\" non existe.",
        "htmlform-user-not-exists": "\"<strong>$1</strong>\" non existe.",
        "htmlform-user-not-valid": "\"<strong>$1</strong>\" non é un nome de usuario válido.",
-       "sqlite-has-fts": "$1 con soporte para procuras de texto completo",
-       "sqlite-no-fts": "$1 sen soporte para procuras de texto completo",
        "logentry-delete-delete": "$1 {{GENDER:$2|borrou}} a páxina \"$3\"",
        "logentry-delete-restore": "$1 {{GENDER:$2|restaurou}} a páxina \"$3\"",
        "logentry-delete-event": "$1 {{GENDER:$2|mudou}} a visibilidade {{PLURAL:$5|dunha entrada|de $5 entradas}} do rexistro de $3: $4",
index 489479b..7639217 100644 (file)
@@ -21,7 +21,8 @@
                        "80686",
                        "아라",
                        "Macofe",
-                       "Xð"
+                       "Xð",
+                       "Terfili"
                ]
        },
        "tog-underline": "Links unterstryche:",
        "yourpasswordagain": "Passwort no mol yygee:",
        "createacct-yourpasswordagain": "Passwort bstetige",
        "createacct-yourpasswordagain-ph": "Gib s Passwort nomol yy",
-       "remembermypassword": "Uf däm Computer duurhaft aamälde (Maximal fir $1 {{PLURAL:$1|Tag|Täg}})",
        "userlogin-remembermypassword": "Aagmäldet blyybe",
        "userlogin-signwithsecure": "Sicheri Verbindig bruuche",
        "yourdomainname": "Dyyni Domäne",
        "passwordreset-emailtext-user": "Dr Benutzer $1 bi {{SITENAME}} het e Zrucksetzig vu Dym Passwort bi {{SITENAME}} aagforderet ($4). \n\n{{PLURAL:$3|Des Benutzerkonto isch|Die Benutzerkonte sin}} mit däre E-Mail-Adräss verchnipft: \n\n$2 \n\n{{PLURAL:$3|Des temporär Passwort lauft|Die temporäre Passwerter laufe}} in {{PLURAL:$5|eim Tag|$5 Täg}} ab.\nDu sottsch di aamälden un e nej Passwort vergee. Wänn eber ander die Aafrog gstellt het oder Du di wider an Dyy alt Passwort chasch erinnere un s nimi wettsch ändere, chasch die Nochricht ignorieren un alsfurt Dyy alt Passwort bruche.",
        "passwordreset-emailelement": "Benutzername: \n$1\n\nTemporär Passwort: \n$2",
        "passwordreset-emailsentemail": "We das di bestätigti E-Mail-Adrässen vo dym Wiki-Konto isch, de wird es E-Mail verschickt, für ds Passwort zrüggzsetze.",
-       "passwordreset-emailsent-capture": "E Passwort-Zrucksetzigs-Mail isch vergschickt worde, un isch unte aazeigt.",
-       "passwordreset-emailerror-capture": "Die unten angezeigte Passwortzrucksetzigsmail, wu unten aazeigt wird, isch generiert wore, aber dr Versand an {{GENDER:$2|dr Benutzer|d Benutzeri}} het nit funktioniert: $1",
        "changeemail": "E-Mail-Adrässen änderen oder lösche",
        "changeemail-header": "Füll das Formular uus, für dyni E-Mail-Adrässe z ändere. We du möchtisch, das dys Wiki-Konto nümm mit eren E-Mail-Adrässe verbunden isch, de chasch ds Fäld für’ne nöüi E-Mail-Adrässe läär la und ds Formular abschicke.",
-       "changeemail-passwordrequired": "Du muesch dys Passwort agä, für d Änderig z bestätige.",
        "changeemail-no-info": "Du muesch aagmolde sy zum uff die Syte diräkt zuegryfe z chönne.",
        "changeemail-oldemail": "Aktuelli E-Mail-Adräss",
        "changeemail-newemail": "Nöii E-Mail-Adräss:",
        "newarticle": "(Nej)",
        "newarticletext": "Du bisch eme Link nogange zuen ere Syte, wu s nid git.\nZum die Syte aalege, chasch do in däm Chaschte unte aafange schrybe (lueg [$1 Hilfe] fir meh Informatione).\nWänn do nid hesch welle aane goh, no druck in Dyynem Browser uf '''Zruck'''.",
        "anontalkpagetext": "----''Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht. Sälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:CreateAccount|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.''",
-       "noarticletext": "Uf däre Syte het s no kei Täxt. Du chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte bearbeite]</span>.",
+       "noarticletext": "Uf däre Syte het s no kei Täxt. \nDu chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte erstelle]</span>.",
        "noarticletext-nopermission": "In däre Syte het s zur Zyt no kei Text.\nDu chasch dää Titel uf andre Syte [[Special:Search/{{PAGENAME}}|sueche]]\noder <span class=\"plainlinks\">in dr zuegherige [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbiecher sueche].</span> Du derfsch aber die Syte nit aalege.",
        "missing-revision": "D Version $1 vu dr Syte mit Name „{{FULLPAGENAME}}“ git s nit.\n\nDää Fähler chunnt normalerwyys dur e veraltete Link zue dr Versionsgschicht vun ere Syte, wu in dr Zwischezyt glescht woren isch.\nEinzelheite chasch im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lesch-Logbuech] bschaue.",
        "userpage-userdoesnotexist": "S Benutzerkonto „<nowiki>$1</nowiki>“ git s nit. Bitte prief, eb Du die Syte wirkli wit aalege/bearbeite.",
        "undo-nochange": "Schyns isch die Bearbeitig scho rugggängig gmacht wore.",
        "undo-summary": "D Änderig $1 vu [[Special:Contributions/$2|$2]] ([[User talk:$2|Diskussion]]) isch ruckgängig gmacht wore.",
        "undo-summary-username-hidden": "Änderig $1 vun eme versteckte Benutzer ruckgängig gmacht.",
-       "cantcreateaccounttitle": "Benutzerkonto cha nid aagleit wäre.",
        "cantcreateaccount-text": "S Aalege vu me Benutzerkonto vu dr IP-Adräss '''($1)''' isch dur [[User:$3|$3]] gsperrt wore.\n\nGrund vu dr Sperri: ''$2''",
        "cantcreateaccount-range-text": "S Aalege vu Benutzerkonte vu IP-Adrässen im Berych <strong>$1</strong>, wu s Dyni IP-Adräss (<strong>$4</strong>) din het, isch vu [[User:$3|$3]] gsperrt wore.\n\nDr Grund, wu vu $3 aagee woren isch: <em>$2</em>",
        "viewpagelogs": "Logbüecher für die Syten azeige",
        "contributions": "{{GENDER:$1|Benutzer-Byträg}}",
        "contributions-title": "Benutzerbyytreg vu „$1“",
        "mycontris": "Myyni Byyträg",
+       "anoncontribs": "Byyträg",
        "contribsub2": "Vu {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "Ds Benutzerkonto «$1» isch nid registriert.",
        "nocontribs": "S sin keini Benutzerbyytreg mit däne Kriterie gfunde wore.",
        "javascripttest": "JavaScript-Tescht",
        "javascripttest-pagetext-unknownaction": "Unbekannti Aktion «$1».",
        "javascripttest-qunit-intro": "Lueg d [$1 Dokumentation zue Tescht] uf mediawiki.org",
-       "tooltip-pt-userpage": "Dyyni Benutzersyte",
+       "tooltip-pt-userpage": "{{GENDER:|Dyyni}} Benutzersyte",
        "tooltip-pt-anonuserpage": "D Benutzersyte vo der IP-Adress wo du mit schaffsch",
-       "tooltip-pt-mytalk": "Dyyni Diskussionssyte",
+       "tooltip-pt-mytalk": "{{GENDER:|Dyyni}}  Diskussionssyte",
        "tooltip-pt-anontalk": "Diskussione über Änderige vo dere IP-Adress",
-       "tooltip-pt-preferences": "Myni Ystellige",
+       "tooltip-pt-preferences": "{{GENDER:|Dyni}} Ystellige",
        "tooltip-pt-watchlist": "Lischte vo de beobachtete Syte.",
-       "tooltip-pt-mycontris": "Lischt vu Dyyne Byyträg",
+       "tooltip-pt-mycontris": "E Lischt vu {{GENDER:|Dyyne}} Byyträg",
        "tooltip-pt-login": "Aamälde",
        "tooltip-pt-logout": "Abmälde",
        "tooltip-pt-createaccount": "Du chasch gärn e Benutzerkonto aalege un Di aamälde. Du muesch s aber nit",
        "tooltip-t-recentchangeslinked": "Letschti Änderige vo de Syte, wo vo do verlinkt sin",
        "tooltip-feed-rss": "RSS-Feed für selli Syte",
        "tooltip-feed-atom": "Atom-Feed für selli Syte",
-       "tooltip-t-contributions": "Lischte vo de Byträg vo däm Benutzer",
+       "tooltip-t-contributions": "E Lischt vo de Byträg vo {{GENDER:$1|däm Benutzer}}",
        "tooltip-t-emailuser": "Schick däm Benutzer e E-Bost",
        "tooltip-t-info": "Meh Informationen über die Syte",
        "tooltip-t-upload": "Dateien ufelade",
        "htmlform-title-not-exists": "$1 git’s nid.",
        "htmlform-user-not-exists": "<strong>$1</strong> git’s nid.",
        "htmlform-user-not-valid": "<strong>$1</strong> isch ke gültige Name.",
-       "sqlite-has-fts": "$1 mit Unterstitzig vu dr Volltextsuechi",
-       "sqlite-no-fts": "$1 ohni Unterstitzig vu dr Volltextsuechi",
        "logentry-delete-delete": "{{GENDER:$2|Dr|D|}} $1 het d Syte $3 glöscht",
        "logentry-delete-restore": "{{GENDER:$2|Der $1|D $1|$1}} het d Syte $3 wider härgstellt",
        "logentry-delete-event": "{{GENDER:$2|Der $1|D $1|$1}} het d Sichtbarkeit {{PLURAL:$5|vumene Logbuechyytrag|vo $5 Logbuechyyträg}} gänderet uff $3: $4",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
        "mw-widgets-titleinput-description-new-page": "d Syte git’s no nid",
        "mw-widgets-titleinput-description-redirect": "Wyterleitig uf $1",
-       "api-error-blacklisted": "Bitte due en andre, ussagechräftigere Titel usswääle.",
        "randomrootpage": "Zuefelligi Stammsyte"
 }
index 7e0e0fd..adaaab4 100644 (file)
        "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ફેરફાર|ફેરફારો}} કરતાં ઓછું પાછું લાવો",
        "rollbackfailed": "ઉલટાવવું નિષ્ફળ",
        "cantrollback": "આ ફેરફારો ઉલટાવી નહી શકાય\nછેલ્લો ફેરફાર આ પાના ના રચયિતા દ્વારા જ થયો હતો",
-       "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) દ્વારા થયેલ[[:$1]]ના  ફેરફારો ઉલટાવી ન શકાયા;\nકોઇક અન્ય સભ્યએ આ પાનાપર ફેરફાર કરી દીધા છે.\n\nઆ પાના પર ના છેલ્લા ફેરફારો [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) દ્વારા કરવામાં આવ્યાં હતાં.",
+       "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) દ્વારા થયેલ [[:$1]]ના ફેરફારો ઉલટાવી ન શકાયા;\nકોઇક અન્ય સભ્યે આ પાના પર ફેરફાર કર્યો છે અથવા ફેરફારો ઉલ્ટાવ્યા છે.\n\nઆ પાના પરના છેલ્લા ફેરફારો [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) દ્વારા કરવામાં આવ્યા હતા.",
        "editcomment": "ફેરફાર સારાંશ હતી: <em>$1</em>.",
        "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) દ્વારા કરેલ ફેરફારોને  [[User:$1|$1]] દ્વારા કરેલા છેલ્લા સુધારા સુધી ઉલટાવાયા.",
        "revertpage-nouser": "ગુપ્ત સભ્ય વડે કરાયેલ ફેરફારને {{GENDER:$1|[[User:$1|$1]]}} વડે કરેલ છેલ્લા પુનરાવર્તન પર પાછા લઇ જવાયું.",
        "importuploaderrortemp": "આયાતી ફાઈલ ચઢાવવું અસફળ.\nહંગામી ફોલ્ડરા ગાયબ છે.",
        "import-parse-failure": "XML આયાત પદચ્છેદ અસફળ",
        "import-noarticle": "આયાત કરવા માટે કોઇ પાનું નથી!",
-       "import-nonewrevisions": "બધા àª«à«\87રફરà«\8b àªªàª¹à«\87લા àª\86યાત àª\95રાયા àª\9bà«\87.",
+       "import-nonewrevisions": "àª\95à«\8bàª\87 àª«à«\87રફારà«\8b àª\86યાત àª\95રાયા àª¨àª¥à«\80 (બધાàª\82 àªªàª¹à«\87લà«\87થà«\80 àª¹àª¾àª\9cર àª¹àª¤àª¾, àª\85થવા àª\95à«\8dષતિàª\93નà«\87 àª\95ારણà«\87 àª\85વàª\97ણાયા àª\9bà«\87).",
        "xml-error-string": "$1  લીટી ક્ર્માંક $2, સ્તંભ  $3 (બાઇટ  $4): $5",
        "import-upload": "XML માહિતી ચઢાવો",
        "import-token-mismatch": "સત્ર સમાપ્ત\nફરી પ્રયત્ન કરો",
        "htmlform-chosen-placeholder": "વિકલ્પ પસંદ કરો",
        "htmlform-cloner-create": "વધુ ઉમેરો",
        "htmlform-cloner-delete": "હટાવો",
-       "sqlite-has-fts": "$1 પૂર્ણ શબ્દ શોધ સહીત",
-       "sqlite-no-fts": "$1 પૂર્ણ શબ્દ  શોધ વિકલ્પ વગર",
        "logentry-delete-delete": "$1 દ્વારા પાનું $3 {{GENDER:$2|દૂર કરવામાં આવ્યું}}",
        "logentry-delete-restore": "$1 {{GENDER:$2|પુનઃસંગ્રહ}} પાનું $3",
        "logentry-delete-event": "$1 એ {{PLURAL:$5|લૉગ ઘટના|$5 લૉગ ઘટનાઓ}} ની દ્રશ્યતા $3 પર {{GENDER:$2|બદલેલ}} છે: $4",
index e76347f..e9885dd 100644 (file)
        "watcherrortext": "אירעה שגיאה בעת שינוי הגדרות רשימת המעקב של \"$1\".",
        "enotif_reset": "סימון כל הדפים כאילו נצפו",
        "enotif_impersonal_salutation": "משתמש ב{{GRAMMAR:תחילית|{{SITENAME}}}}",
-       "enotif_subject_deleted": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נמחק על־ידי $2",
-       "enotif_subject_created": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נוצר על־ידי $2",
-       "enotif_subject_moved": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} הועבר על־ידי $2",
-       "enotif_subject_restored": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שוחזר על־ידי $2",
-       "enotif_subject_changed": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שונה על־ידי $2",
-       "enotif_body_intro_deleted": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נמחק ב־$PAGEEDITDATE על ידי $2, ראו $3.",
-       "enotif_body_intro_created": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נוצר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_moved": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} הועבר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_restored": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שוחזר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_changed": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שונה ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_lastvisited": "ראו $1 לכל השינויים מאז ביקורכם האחרון.",
+       "enotif_subject_deleted": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} נמחק על־ידי $2",
+       "enotif_subject_created": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} נוצר על־ידי $2",
+       "enotif_subject_moved": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} הועבר על־ידי $2",
+       "enotif_subject_restored": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} שוחזר על־ידי $2",
+       "enotif_subject_changed": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} שוּנה על־ידי $2",
+       "enotif_body_intro_deleted": "הדף \"$1\" באתר {{SITENAME}} נמחק ב־$PAGEEDITDATE על־ידי $2; ראו $3.",
+       "enotif_body_intro_created": "הדף \"$1\" באתר {{SITENAME}} נוצר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_moved": "הדף \"$1\" באתר {{SITENAME}} הועבר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_restored": "הדף \"$1\" באתר {{SITENAME}} שוחזר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_changed": "הדף \"$1\" באתר {{SITENAME}} שוּנה ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_lastvisited": "ראו $1 לכל השינויים מאז ביקורכם האחרון בדף.",
        "enotif_lastdiff": "ראו $1 לשינוי זה.",
        "enotif_anon_editor": "משתמש אנונימי $1",
-       "enotif_body": "×\9c×\9b×\91×\95×\93 $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nתקצ×\99ר ×\94ער×\99×\9b×\94: $PAGESUMMARY $PAGEMINOREDIT\n\n×\91×\90פשר×\95ת×\9b×\9d ×\9c×\99צ×\95ר ×§×©×¨ ×¢×\9d ×\94×¢×\95ר×\9a:\n×\91×\93×\95×\90ר ×\94×\90×\9cק×\98ר×\95× ×\99: $PAGEEDITOR_EMAIL\n×\91×\90תר: $PAGEEDITOR_WIKI\n\n×\9c×\90 ×ª×\94×\99×\99× ×\94 ×\94×\95×\93×¢×\95ת ×¢×\9c ×¤×¢×\95×\9c×\95ת × ×\95ספ×\95ת ×¢×\93 ×©×ª×\91קר×\95 ×\91×\93×£ ×\9bש×\90ת×\9d ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f. ×\91×\90פשר×\95ת×\9b×\9d ×\92×\9d ×\9c×\90פס ×\90ת ×\93×\92×\9c×\99 ×\94×\94×\95×\93×¢×\95ת ×\91×\9b×\9c ×\94×\93פ×\99×\9d ×©×\91רש×\99×\9eת ×\94×\9eעק×\91.\n\n×\9eער×\9bת ×\94×\94×\95×\93×¢×\95ת ×©×\9c {{SITENAME}}\n\n--\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×\94×\95×\93×¢×\95ת ×\94×\93×\95×\90\"×\9c ×\94נש×\9c×\97×\95ת ×\90×\9c×\99×\9b×\9d, ×\91קר×\95 ×\91×\93×£\n{{canonicalurl:{{#special:Preferences}}}}\n\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\92×\93ר×\95ת ×¨×©×\99×\9eת ×\94×\9eעק×\91, ×\91קר×\95 ×\91×\93×£\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\n×\9b×\93×\99 ×\9c×\9e×\97×\95ק ×\90ת ×\94×\93×£ ×\9eרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£\n$UNWATCHURL\n\nלמשוב ולעזרה נוספת:\n$HELPPAGE",
+       "enotif_body": "×\9c×\9b×\91×\95×\93 $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nתקצ×\99ר ×\94ער×\99×\9b×\94: $PAGESUMMARY $PAGEMINOREDIT\n\n×\91×\90פשר×\95ת×\9b×\9d ×\9c×\99צ×\95ר ×§×©×¨ ×¢×\9d ×\94×¢×\95ר×\9a:\n×\91×\93×\95×\90ר ×\90×\9cק×\98ר×\95× ×\99: $PAGEEDITOR_EMAIL\n×\91×\90תר: $PAGEEDITOR_WIKI\n\n×\9c×\90 ×ª×§×\91×\9c×\95 ×\94×\95×\93×¢×\95ת ×¢×\9c ×¤×¢×\95×\9c×\95ת × ×\95ספ×\95ת ×¢×\93 ×©×ª×\91קר×\95 ×\91×\93×£ ×\94×\96×\94 ×\9bש×\90ת×\9d ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f. ×\91×\90פשר×\95ת×\9b×\9d ×\92×\9d ×\9c×\90פס ×\90ת ×\93×\92×\9c×\99 ×\94×\94×\95×\93×¢×\95ת ×¢×\91×\95ר ×\9b×\9c ×\94×\93פ×\99×\9d ×©×\91רש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d.\n\n×\91×\91ר×\9b×\94, ×\9eער×\9bת ×\94×\94×\95×\93×¢×\95ת ×©×\9c {{SITENAME}}.\n\n--\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×\94×\95×\93×¢×\95ת ×\94×\93×\95×\90\"×\9c ×\94נש×\9c×\97×\95ת ×\90×\9c×\99×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n{{canonicalurl:{{#special:Preferences}}}}\n\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×¨×©×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\n×\9b×\93×\99 ×\9c×\94ס×\99ר ×\90ת ×\94×\93×£ ×\94×\96×\94 ×\9eרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n$UNWATCHURL\n\nלמשוב ולעזרה נוספת:\n$HELPPAGE",
        "created": "נוצר",
-       "changed": "שונה",
+       "changed": "שוּנה",
        "deletepage": "מחיקת הדף",
        "confirm": "אישור",
        "excontent": "התוכן היה: \"$1\"",
        "htmlform-title-not-exists": "$1 אינו קיים.",
        "htmlform-user-not-exists": "<strong>$1</strong> אינו קיים.",
        "htmlform-user-not-valid": "<strong>$1</strong> אינו שם משתמש תקין.",
-       "sqlite-has-fts": "$1 עם תמיכה בחיפוש בטקסט מלא",
-       "sqlite-no-fts": "$1 ללא תמיכה בחיפוש בטקסט מלא",
        "logentry-delete-delete": "$1 {{GENDER:$2|מחק|מחקה}} את הדף $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|שחזר|שחזרה}} את הדף $3",
        "logentry-delete-event": "$1 {{GENDER:$2|שינה|שינתה}} את מצב התצוגה של {{PLURAL:$5|פעולת יומן|$5 פעולות יומן}} של $3: $4",
index d8780e1..ba6c84e 100644 (file)
        "diff-multi-sameuser": "({{PLURAL:$1|Egy közbenső módosítás|$1 közbenső módosítás}} ugyanattól a szerkesztőtől nincs mutatva)",
        "diff-multi-otherusers": "({{PLURAL:$1|Egy közbenső módosítás|$1 közbenső módosítás}}, amit {{PLURAL:$2|egy másik szerkesztő végzett|$2 másik szerkesztő végzett}}, nincs mutatva)",
        "diff-multi-manyusers": "({{PLURAL:$1|Egy közbeeső változat|$1 közbeeső változat}} nincs mutatva, amit $2 szerkesztő módosított)",
-       "difference-missing-revision": "A(z) \"{{PAGENAME}}\" nevű oldal #$1 $2 változata nem létezik.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
+       "difference-missing-revision": "Az összehasonlítandó változatok {{PLURAL:$2|egyike ($1) nem található|($1) nem találhatóak}}.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
        "searchresults": "A keresés eredménye",
        "searchresults-title": "Keresési eredmények: „$1”",
        "titlematches": "Címbeli egyezések",
        "htmlform-title-not-exists": "$1 nem létezik.",
        "htmlform-user-not-exists": "<strong>$1</strong> nem létezik.",
        "htmlform-user-not-valid": "<strong>$1</strong> nem egy érvényes felhasználónév.",
-       "sqlite-has-fts": "$1 teljes szöveges keresés támogatással",
-       "sqlite-no-fts": "$1 teljes szöveges keresés támogatása nélkül",
        "logentry-delete-delete": "$1 törölte a következő lapot: $3",
        "logentry-delete-restore": "$1 helyreállította a következő lapot: $3",
        "logentry-delete-event": "$1 megváltoztatta {{PLURAL:$5|egy napló bejegyzés|$5 napló bejegyzés}} láthatóságát a(z) $3 című lapon: $4",
index fa83ac9..3fc5dfe 100644 (file)
        "htmlform-title-not-exists": "$1 non existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> non existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> non es un nomine de usator valide.",
-       "sqlite-has-fts": "$1 con supporto de recerca de texto integre",
-       "sqlite-no-fts": "$1 sin supporto de recerca de texto integre",
        "logentry-delete-delete": "$1 {{GENDER:$2|deleva}} le pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restaurava}} le pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|cambiava}} le visibilitate de {{PLURAL:$5|un entrata|$5 entratas}} de registro in $3: $4",
index be841af..46a2e24 100644 (file)
        "htmlform-title-not-exists": "Awan ti $1.",
        "htmlform-user-not-exists": "Awan ti <strong>$1</strong>.",
        "htmlform-user-not-valid": "Saan nga umiso a nagan ti agar-aramat ti <strong>$1</strong>.",
-       "sqlite-has-fts": "Ti $1 nga addaan iti suporta ti panagbiruk ti napno a teksto",
-       "sqlite-no-fts": "Ti $1 nga awan iti suporta ti panagbiruk ti napno a teksto",
        "logentry-delete-delete": "{{GENDER:$2|Inikkat}} ni $1 ti panid ti $3",
        "logentry-delete-restore": "Ni $1 ket {{GENDER:$2|insublina}} ti panid ti $3",
        "logentry-delete-event": "Ni $1 ket {{GENDER:$2|binaliwanna}} ti panagkita {{PLURAL:$5|iti listaan ti pasamak |dagiti $5 a listaan ti pasamak }} iti $3: $4",
index a752071..7d86d86 100644 (file)
                        "Matteocng",
                        "Einreiher",
                        "Anto",
-                       "Saracrovetto"
+                       "Saracrovetto",
+                       "Tosky"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "createacct-yourpasswordagain-ph": "Inserisci nuovamente la password",
        "userlogin-remembermypassword": "Mantienimi collegato",
        "userlogin-signwithsecure": "Usa una connessione sicura",
-       "cannotlogin-text": "Accesso non è possibile.",
+       "cannotlogin-text": "L'accesso non è possibile.",
        "cannotloginnow-title": "Impossibile accedere ora",
        "cannotloginnow-text": "L'accesso non è possibile quando si sta usando $1.",
        "cannotcreateaccount-title": "Impossibile creare l'utenza",
        "passwordreset": "Reimposta password",
        "passwordreset-text-one": "Compila questo modulo per reimpostare la tua password.",
        "passwordreset-text-many": "{{PLURAL:$1|Compila uno dei campi per ricevere una password temporanea tramite email.}}",
-       "passwordreset-disabled": "La reimpostazione delle password è stata disabilitata su questa wiki",
+       "passwordreset-disabled": "La reimpostazione delle password è stata disabilitata per questo wiki",
        "passwordreset-emaildisabled": "Le funzionalità di posta elettronica sono state disabilitate su questa wiki.",
        "passwordreset-username": "Nome utente:",
        "passwordreset-domain": "Dominio:",
        "htmlform-title-not-exists": "$1 non esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> non esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> non è un nome utente valido.",
-       "sqlite-has-fts": "$1 con la possibilità di ricerca completa nel testo",
-       "sqlite-no-fts": "$1 senza la possibilità di ricerca completa nel testo",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha cancellato}} la pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ha ripristinato}} la pagina \"$3\"",
        "logentry-delete-event": "$1 {{GENDER:$2|ha modificato}} la visibilità di {{PLURAL:$5|un'azione del registro|$5 azioni del registro}} di \"$3\": $4",
index 0fa8f88..50acfa0 100644 (file)
        "htmlform-no": "Ora",
        "htmlform-yes": "Iya",
        "htmlform-chosen-placeholder": "Pilih pilihan",
-       "sqlite-has-fts": "$1 mawa sengkuyungan golèkan tèks jangkep",
-       "sqlite-no-fts": "$1 tanpa sengkuyungan golèkan tèks jangkep",
        "logentry-delete-delete": "$1 {{GENDER:$2|mbusak}} kaca $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|mbalèkaké}} kaca $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ngganti}} parupané {{PLURAL:$5|sak prastawa log|$5 prastawa log}} ana ing $3: $4",
index e9e2f92..a105962 100644 (file)
        "password-change-forbidden": "თქვენ არ შეგიძლიათ ამ ვიკიში პაროლის შეცვლა.",
        "externaldberror": "საგარეო მონაცემთა ბაზაში აუტენტიფიკაციის შეცდომაა, ან თქვენ არ გაქვთ საკმარისი უფლებები საგარეო ანგარიშში ცვლილებების შესატანად.",
        "login": "შესვლა",
-       "login-security": "á\83\93á\83\90á\83\90á\83\93á\83\90á\83¡á\83¢á\83£á\83 á\83\94á\83\97 á\83\97á\83¥á\83\95á\83\94á\83\9cá\83\98 á\83\90á\83\95á\83\97á\83\94á\83\9cá\83¢á\83£á\83 á\83\9dá\83\91ა",
+       "login-security": "á\83\93á\83\90á\83\90á\83\93á\83\90á\83¡á\83¢á\83£á\83 á\83\94á\83\97 á\83\98á\83\93á\83\94á\83\9cá\83¢á\83\98á\83¤á\83\98á\83\99á\83\90á\83ªá\83\98ა",
        "nav-login-createaccount": "შესვლა / რეგისტრაცია",
        "userlogin": "შესვლა/ანგარიშის შექმნა",
        "userloginnocreate": "შესვლა",
        "userlogin-resetpassword-link": "დაგავიწყდათ პაროლი?",
        "userlogin-helplink2": "დახმარება:შესვლა",
        "userlogin-loggedin": "თქვენ უკვე შეხვედით როგორც {{GENDER:$1|$1}}.\nგამოიყენეთ ფორმა ქვემოთ, რათა შეხვიდეთ სხვა ანგარიშიდან.",
-       "userlogin-reauth": "á\83\97á\83¥á\83\95á\83\94á\83\9c á\83\99á\83\95á\83\9aá\83\90á\83\95 á\83£á\83\9cá\83\93á\83\90 á\83¨á\83\94á\83®á\83\95á\83\98á\83\93á\83\94á\83\97 á\83¡á\83\98á\83¡á\83¢á\83\94á\83\9bá\83\90á\83¨á\83\98 á\83 á\83\90á\83\97á\83\90 á\83¨á\83\94á\83\9bá\83\9dá\83¬á\83\9bá\83\93á\83\94á\83¡ á\83 á\83\9dá\83\9b á\83®á\83\90á\83 á\83\97 $1",
+       "userlogin-reauth": "á\83\97á\83¥á\83\95á\83\94á\83\9c á\83£á\83\9cá\83\93á\83\90 á\83\92á\83\90á\83\98á\83\90á\83 á\83\9dá\83\97 á\83\90á\83\95á\83¢á\83\9dá\83 á\83\98á\83\96á\83\90á\83ªá\83\98á\83\90, á\83 á\83\90á\83\97á\83\90 á\83\99á\83\98á\83\93á\83\94á\83\95 á\83\94á\83 á\83\97á\83®á\83\94á\83\9a á\83\9bá\83\9dá\83®á\83\93á\83\94á\83¡ á\83\97á\83¥á\83\95á\83\94á\83\9cá\83\98 á\83\98á\83\93á\83\94á\83\9cá\83¢á\83\98á\83¤á\83\98á\83ªá\83\98á\83 á\83\94á\83\91á\83\90 á\83\90á\83\9cá\83\92á\83\90á\83 á\83\98á\83¨á\83\97á\83\90á\83\9c â\80\9e{{GENDER:$1|$1}}â\80\9c.",
        "userlogin-createanother": "სხვა ანგარიშის შექმნა",
        "createacct-emailrequired": "ელ. ფოსტის მისამართი",
        "createacct-emailoptional": "ელ. ფოსტის მისამართი (არასავალდებულო)",
        "htmlform-title-not-exists": "$1 არ არსებობს.",
        "htmlform-user-not-exists": "<strong>$1</strong> არ არსებობს.",
        "htmlform-user-not-valid": "<strong>$1</strong> არ არის სწორი მომხმარებლის სახელი.",
-       "sqlite-has-fts": "$1 სრული ტექსტის ძიების მხარდაჭერით",
-       "sqlite-no-fts": "$1 სრული ტექსტის ძიების მხარდაჭერის გარეშე",
        "logentry-delete-delete": "მომხმარებელმა $1 {{GENDER:$2|წაშალა}} გვერდი: „$3“",
        "logentry-delete-restore": "მომხმარებელმა $1 {{GENDER:$2|აღადგინა}} გვერდი $3",
        "logentry-delete-event": "მომხმარებელმა $1 {{GENDER:$2|შეცვალა}} {{PLURAL:$5|ჟურნალის ჩანაწერის|$5 ჟურნალის ჩანაწერების}} ხილვადობა $3-ზე: $4",
index 43dc0de..1580a47 100644 (file)
        "hidden-categories": "{{PLURAL:$1|Kategoriya wedariyaiye|Kategoriyê wedariyaey}}",
        "hidden-category-category": "Kategoriyê wedariyaey",
        "category-subcat-count": "{{PLURAL:$2|Na kategoriye de ana kategoriya bınêne esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê.}}, be $2 ra pia.}}",
-       "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê}}.",
+       "category-subcat-count-limited": "Na kategoriya de {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê}}.",
        "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ana pele esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana pele esta|ani $1 peli estê.}}, be $2 ra pêro pia}}",
        "category-article-count-limited": "{{PLURAL:$1|Ana pele kategoriya peyêne dera|Ani $1 peli kategoriya peyêne derê}}.",
        "category-file-count": "{{PLURAL:$2|Na kategoriye de teyna ana dosya esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana dosya esta|ani $1 dosyey estê.}}}}",
index 08b84e8..6d3296c 100644 (file)
        "revdelete-text-file": "Жойылған файл нұсқалары әлі де бет тарихында көрінетін болады, бірақ олардың мағлұмат бөлшектері жалпыға қатынаулы болмайды.",
        "logdelete-text": "Жойылған журнал оқиғалары әлі де бет тарихында көрінетін болады, бірақ олардың мағлұмат бөлшектері жалпыға қатынаулы болмайды.",
        "revdelete-text-others": "Қосымша тиымдар қойылғанша басқа әкімшілер, жасырын мағлұматқа қатынай және оны қалпына келтіре алады.",
-       "revdelete-confirm": "Сіз осыны істеу ниетіңізде салдары қандай болатынын түсінінің және сіз  [[{{MediaWiki:Policy-url}}|ережеге]] сәйкес бұны істегеніңізді құптаңыз.",
+       "revdelete-confirm": "Сіз осыны істеу ниетіңіздің салдары қандай болатынын түсінініңіз және сіз [[{{MediaWiki:Policy-url}}|ережеге]] сәйкес бұны істегеніңізді құптаңыз.",
        "revdelete-suppress-text": "Жасыру <strong>тек</strong> төмендегідей жағдайларда қолданылады:\n* потенциялды ғайбат ақпарат\n* Орынсыз жеке ақпарат\n*: <em>мекенжай және телефон номерлері, жеке сәйкестендіру нөмерлері, тағы сол сияқтылар.</em>",
        "revdelete-legend": "Көрініс тиымдарын қою:",
        "revdelete-hide-text": "Түзету мәтінін жасыр",
        "htmlform-title-not-exists": "$1 беті жоқ.",
        "htmlform-user-not-exists": "<strong>$1</strong> есімді қатысушы жоқ.",
        "htmlform-user-not-valid": "<strong>$1</strong> жарамды қатысушы есімі емес.",
-       "sqlite-has-fts": "$1 дегенмен барлық мәтінде іздеуді қолдайды",
-       "sqlite-no-fts": "$1дегенсіз барлық мәтінде іздеуді қолдайды",
        "logentry-delete-delete": "$1 $3 деген бетті {{GENDER:$2|жойды}}",
        "logentry-delete-restore": "$1 $3 деген бетті {{GENDER:$2|қалпына келтірді}}",
        "logentry-delete-event": "$1 $3 бетіндегі {{PLURAL:$5|журнал оқиғасы|$5 журнал оқиғасы}} көрінісін {{GENDER:$2|өзгертті}}: $4",
index 50d114f..922b555 100644 (file)
        "htmlform-title-not-exists": "$1 문서는 존재하지 않습니다.",
        "htmlform-user-not-exists": "<strong>$1</strong> 문서는 존재하지 않습니다.",
        "htmlform-user-not-valid": "<strong>$1</strong>은 올바른 사용자 이름이 아닙니다.",
-       "sqlite-has-fts": "$1 (본문 전체 검색 지원)",
-       "sqlite-no-fts": "$1 (본문 전체 검색 지원 제외)",
        "logentry-delete-delete": "$1님이 $3 문서를 {{GENDER:$2|삭제했습니다}}",
        "logentry-delete-restore": "$1님이 $3 문서를 {{GENDER:$2|되살렸습니다}}",
        "logentry-delete-event": "$1님이 $3의 {{PLURAL:$1|기록 $5개}}에 대해 보이기 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
index e04e424..08c5a65 100644 (file)
        "htmlform-title-not-exists": "$1 gëtt et net.",
        "htmlform-user-not-exists": "<strong>$1</strong> gëtt et net.",
        "htmlform-user-not-valid": "<strong>$1</strong> ass kee valabele Benotzernumm.",
-       "sqlite-has-fts": "$1 ënnerstëtzt d'Volltextsich",
-       "sqlite-no-fts": "$1 ënnerstëtzt d'Volltextsich net",
        "logentry-delete-delete": "$1 {{GENDER:$2|huet}} d'Säit $3 geläscht",
        "logentry-delete-restore": "$1 {{GENDER:$2|huet}} d'Säit $3 restauréiert",
        "logentry-delete-event": "$1 huet d'Visibilitéit vun {{PLURAL:$5|engem Evenement|$5 Evenementer}} am Logbuch op $3:$4 {{GENDER:$2|geännert}}",
index 5194c8b..8c5b16c 100644 (file)
        "htmlform-title-not-exists": "$1 a no l'existe.",
        "htmlform-user-not-exists": "'''$1''' o no l'existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> o no l'è un nomme utente vallido.",
-       "sqlite-has-fts": "$1 co-a poscibilitæ de riçerca completa into testo",
-       "sqlite-no-fts": "$1 sença a poscibilitæ de riçerca completa into testo",
        "logentry-delete-delete": "$1 {{GENDER:$2|o l'ha scassou}} a paggina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|o|a}} l'ha ripristinou a paggina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|o|a}} l'ha modificou a vixibilitæ de {{PLURAL:$5|un'açion do registro|$5 açioin do registro}} de \"$3\": $4",
index f3cf09b..8644764 100644 (file)
@@ -63,7 +63,7 @@
        "tog-enotifminoredits": "Siųsti man laišką, kai puslapio keitimas yra smulkus",
        "tog-enotifrevealaddr": "Rodyti mano el. pašto adresą priminimo laiškuose",
        "tog-shownumberswatching": "Rodyti stebinčių naudotojų skaičių",
-       "tog-oldsig": "Galiojantis parašas:",
+       "tog-oldsig": "Jūsų egzistuojantis parašas:",
        "tog-fancysig": "Laikyti parašą vikitekstu (be automatinių nuorodų)",
        "tog-uselivepreview": "Naudoti tiesioginę peržiūrą",
        "tog-forceeditsummary": "Klausti, kai palieku tuščią keitimo komentarą",
@@ -80,7 +80,7 @@
        "tog-showhiddencats": "Rodyti paslėptas kategorijas",
        "tog-norollbackdiff": "Nerodyti skirtumo atlikus atmetimą",
        "tog-useeditwarning": "Perspėti mane, kai palieku redagavimo puslapį, o jame yra neišsaugotų pakeitimų",
-       "tog-prefershttps": "Prisiregistruojant visada naudokite saugų ryšį",
+       "tog-prefershttps": "Visada naudoti saugų ryšį esant prisijungus",
        "underline-always": "Visada",
        "underline-never": "Niekada",
        "underline-default": "Pagal naršyklės nustatymus",
        "newwindow": "(atsidaro naujame lange)",
        "cancel": "Atšaukti",
        "moredotdotdot": "Daugiau...",
-       "morenotlisted": "Šis sąrašas nėra išsamus.",
+       "morenotlisted": "Šis sąrašas gali būti nepilnas.",
        "mypage": "Puslapis",
        "mytalk": "Aptarimas",
        "anontalk": "Aptarimas",
        "invalid-content-data": "Neleistinas turinys.",
        "content-not-allowed-here": "Turinys \"$1\" puslapyje [[$2]] nėra leistinas.",
        "editwarning-warning": "Palikdamas šį puslapį jūs galite prarasti visus padarytus pakeitimus.\nJei esate prisijungęs, galite išjungti šį perspėjimą jūsų nustatymų skyrelyje \"{{int:prefs-editing}}\".",
+       "editpage-invalidcontentmodel-title": "Turinio modelis nepalaikomas",
+       "editpage-invalidcontentmodel-text": "Turinio modulis „$1“ nėra palaikomas.",
        "editpage-notsupportedcontentformat-title": "Turinio formatas nepalaikomas",
        "editpage-notsupportedcontentformat-text": "Turinio formatas $1 nepalaiko turinio modelio $2.",
        "content-model-wikitext": "vikitekstas",
        "tag-filter": "[[Special:Tags|Žymų]] filtras:",
        "tag-filter-submit": "Filtras",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Žyma|Žymos}}]]: $2)",
+       "tag-mw-contentmodelchange": "turinio modulio keitimas",
+       "tag-mw-contentmodelchange-description": "Pakeitimai, kurie [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel keičia puslapio turinio modelį]",
        "tags-title": "Žymos",
        "tags-intro": "Šiame puslapyje yra žymų, kuriomis programinė įranga gali pažymėti keitimus, sąrašas bei jų reikšmės.",
        "tags-tag": "Žymos pavadinimas",
        "tags-actions-header": "Veiksmai",
        "tags-active-yes": "Taip",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Apibrėžta papildinio",
+       "tags-source-extension": "Apibrėžta programinės įrangos",
        "tags-source-manual": "Taikoma vartotojų ar robotų rankiniu būdu",
        "tags-source-none": "Nebevartojamas",
        "tags-edit": "taisyti",
        "htmlform-title-not-exists": "$1 neegzistuoja.",
        "htmlform-user-not-exists": "<strong>$1</strong> neegzistuoja.",
        "htmlform-user-not-valid": "<strong>$1</strong> nėra tinkamas naudotojo vardas.",
-       "sqlite-has-fts": "$1 su visatekstės paieškos palaikymu",
-       "sqlite-no-fts": "$1 be visatekstės paieškos palaikymo",
        "logentry-delete-delete": "$1 {{GENDER:$2|ištrynė}} puslapį $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|atkūrė}} puslapį $3",
        "logentry-delete-event": "$1 {{GENDER:$2|pakeitė}} matomumą {{PLURAL:$5|žurnalo įvykio|$5 žurnalo įvykių}} $3: $4",
index bc9db01..380e2b3 100644 (file)
        "htmlform-chosen-placeholder": "Izvēlieties iespēju",
        "htmlform-cloner-create": "Pievienot vairāk",
        "htmlform-cloner-delete": "Noņemt",
-       "sqlite-has-fts": "$1 ar pilnteksta meklēšanas atbalstu",
-       "sqlite-no-fts": "$1 bez pilnteksta meklēšanas atbalsta",
        "logentry-delete-delete": "$1 {{GENDER:$2|izdzēsa}} lapu $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|atjaunoja}} lapu $3",
        "revdelete-content-hid": "saturs slēpts",
index d9d8ea0..ee22017 100644 (file)
        "htmlform-chosen-placeholder": "एकटा विकल्प चुनु",
        "htmlform-cloner-create": "आर जोडु",
        "htmlform-cloner-delete": "हटाउ",
-       "sqlite-has-fts": "$1 पूर्ण-पाठ खोज सहायता युक्त",
-       "sqlite-no-fts": "$1 बिन पूर्ण-पाठ खोज सहायताक",
        "logentry-delete-delete": "$1 पृष्ठ $3 {{GENDER:$2|मेटौलक}}",
        "logentry-delete-restore": "$1 {{GENDER:$2|restored}} page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|changed}} एकर दृश्य{{PLURAL:$5| एकटा वृत्तलेख|$5 वृत्तलेख}}  $3: $4 केँ",
index 7f522e6..71c2dd4 100644 (file)
        "htmlform-title-not-exists": "$1 не постои.",
        "htmlform-user-not-exists": "<strong>$1</strong> не постои.",
        "htmlform-user-not-valid": "<strong>$1</strong> не претставува важечко корисничко име.",
-       "sqlite-has-fts": "$1 со поддршка за пребарување по цели текстови",
-       "sqlite-no-fts": "$1 без поддршка за пребарување по цели текстови",
        "logentry-delete-delete": "$1 {{GENDER:$2|ја избриша}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ја возобнови}} страницата $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ја измени}} видливоста на {{PLURAL:$5|настан во дневникот|$5 настани во дневникот}} на $3: $4",
index fb7b918..b2f84c0 100644 (file)
        "deleteotherreason": "အခြားသော/နောက်ထပ် အကြောင်းပြချက် -",
        "deletereasonotherlist": "အခြား အကြောင်းပြချက်",
        "delete-edit-reasonlist": "ဖျက်ပစ်ရသော အကြောင်းရင်းများကို တည်းဖြတ်ရန်",
+       "deleting-backlinks-warning": "<strong>သတိပေးချက်။</strong> သင်ဖျက်ပစ်တော့မည့် စာမျက်နှာအား [[Special:WhatLinksHere/{{FULLPAGENAME}}|အခြားစာမျက်နှာများမှ]] ချိတ်ဆက်ထားခြင်း သို့မဟုတ် ထည့်သွင်းထားခြင်း ရှိနေသည်။",
        "rollbacklink": "နောက်ပြန် ပြန်သွားရန်",
        "rollbacklinkcount": "{{PLURAL:$1|တည်းဖြတ်မှု|တည်းဖြတ်မှုများ}} $1 ကို နောက်ပြန်ပြင်ရန်",
        "protectlogpage": "ကာကွယ်မှုများ၏ မှတ်တမ်း",
index 5a1a330..6d21b41 100644 (file)
        "htmlform-title-not-exists": "$1 nun esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun è nu nomme buono.",
-       "sqlite-has-fts": "$1 cu supporto 'e ricerche full-text",
-       "sqlite-no-fts": "$1 senza supporto 'e ricerche full-text",
        "logentry-delete-delete": "$1 {{GENDER:$2|scancellaje}} 'a paggena $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|arrepigliaje}} 'a paggena $3",
        "logentry-delete-event": "$1 {{GENDER:$2|cagnaie}} 'a vesibbiletà 'e {{PLURAL:$5|n'azione d' 'o riggistro|$5 aziune d' 'o riggistro}} ncopp' 'a 'a $3: $4",
index 9fce778..45ba999 100644 (file)
        "htmlform-title-not-exists": "$1 forefinnes ikke.",
        "htmlform-user-not-exists": "<strong>$1</strong> eksisterer ikke.",
        "htmlform-user-not-valid": "<strong>$1</strong> er ikke et gyldig brukernavn.",
-       "sqlite-has-fts": "$1 med støtte for fulltekstsøk",
-       "sqlite-no-fts": "$1 uten støtte for fulltekstsøk",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettet}} siden $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|gjenopprettet}} siden $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endret}} synligheten av {{PLURAL:$5|en logghendelse|$5 logghendelser}} på $3: $4",
index fd3ccea..c30c40c 100644 (file)
@@ -28,6 +28,7 @@
        "tog-hideminor": "सामान्य सम्पादनहरूलाई नयाँ परिवर्तनहरूबाट लुकाउने",
        "tog-hidepatrolled": "गस्ती गरिएका सम्पादनहरूलाई नयाँ परिवर्तनहरूबाट लुकाउने",
        "tog-newpageshidepatrolled": "गस्ती गरिएका पृष्ठहरूलाई नयाँ पृष्ठ सूचीबाट लुकाउने",
+       "tog-hidecategorization": "पृष्ठहरूको श्रेणीकरण हटाउनुहोस्",
        "tog-extendwatchlist": "निगरानी सूचीलाई सबै परिवर्तनहरू देखाउने गरी बढाउने, हालैको परिवर्तनहरू बाहेक",
        "tog-usenewrc": "पृष्ठका भर्खरका परिवर्तन र अवलोकन सूचीको आधारमा सामूहिक परिवर्तनहरू",
        "tog-numberheadings": "शीर्षकहरूलाई स्वत:अङ्कित गर्नुहोस्",
@@ -38,6 +39,7 @@
        "tog-watchdefault": "मैले सम्पादन गरेको पृष्ठ र फाइल निगरानी सूचीमा थप्ने",
        "tog-watchmoves": "मैले सारेका पृष्ठहरू र फाइलहरूलाई निगरानी सूचीमा थप्ने",
        "tog-watchdeletion": "मैले हटाएका पृष्ठहरू र फाइलहरूलाई निगरानी सूचीमा थप्ने",
+       "tog-watchuploads": "मेरा नयाँ फाइलहरूलाई मेरो निगरानी सूचीमा राख्ने ।",
        "tog-watchrollback": "मैले रोलब्याक गरेका पृष्ठहरूलाई मेरो निगरानी सूचीमा थप्ने।",
        "tog-minordefault": "सबै सम्पादनहरूलाई पूर्वनिर्धारित रुपमा सामान्य चिनो लगाउने",
        "tog-previewontop": "सम्पादन सन्दुक अघि पूर्वरुप देखाउने",
        "tog-watchlisthidebots": "बोट सम्पादनहरू निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthideminor": "सामान्य सम्पादनहरू निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthideliu": "प्रवेश गरेका प्रयोगकर्ताहरूको सम्पादन निगरानी सूचीबाट लुकाउने",
+       "tog-watchlistreloadautomatically": "जहिले पनि छननी बदल्न निगरानी सूचीलाई आफै लोड गर्नुहोस् (जावास्क्रिप्ट अनिवार्य)",
        "tog-watchlisthideanons": "अज्ञात प्रयोगकर्ताहरूबाट गरिएको सम्पादन निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthidepatrolled": "गस्ती गरिएका सम्पादनहरू मेरो निगरानी सूचीबाट लुकाउने",
+       "tog-watchlisthidecategorization": "पृष्ठहरूको श्रेणीकरण हटाउनुहोस्",
        "tog-ccmeonemails": "मैले अन्य प्रयोगकर्ताहरूलाई पठाउने इ-मेलको प्रतिलिपि मलाई पठाउने",
        "tog-diffonly": "तलका पृष्ठहरूको भिन्नहरू सामग्री नदेखाउने",
        "tog-showhiddencats": "लुकाइएको श्रेणीहरू देखाउने",
        "october-date": "अक्टोबर $1",
        "november-date": "नोभेम्बर $1",
        "december-date": "डिसेम्बर $1",
+       "period-am": "पूर्वाह्न",
+       "period-pm": "अपराह्न",
        "pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीहरू}}",
        "category_header": "\"$1\" श्रेणीमा भएका लेखहरू",
        "subcategories": "उपश्रेणीहरू",
        "newwindow": "(नयाँ विन्डोमा खुल्छ)",
        "cancel": "रद्द",
        "moredotdotdot": "थप...",
-       "morenotlisted": "यà¥\8b à¤¸à¥\82à¤\9aà¥\80 à¤ªà¥\82रà¥\8dण à¤¹à¥\88न।",
+       "morenotlisted": "यà¥\8b à¤¸à¥\82à¤\9aà¥\80 à¤ªà¥\82रà¥\8dण à¤\9bà¥\88न ।",
        "mypage": "पृष्ठ",
        "mytalk": "वार्ता",
        "anontalk": "वार्ता",
        "tagline": "{{SITENAME}}बाट",
        "help": "सहयोग",
        "search": "खोज्ने",
+       "search-ignored-headings": " #<!-- leave this line exactly as it is --> <pre>\n# Headings that will be ignored by search.\n# Changes to this take effect as soon as the page with the heading is indexed.\n# You can force page reindexing by doing a null edit.\n# The syntax is as follows:\n#   * Everything from a \"#\" character to the end of the line is a comment.\n#   * Every non-blank line is the exact title to ignore, case and everything.\nReferences\nExternal links\nSee also\n #</pre> <!-- leave this line exactly as it is -->",
        "searchbutton": "खोज्नुहोस्",
        "go": "जाने",
        "searcharticle": "खोज्ने",
        "backlinksubtitle": "← $1",
        "retrievedfrom": " \"$1\" बाट निकालिएको",
        "youhavenewmessages": "तपाईंको लागि ($2) मा  $1 छ ।",
-       "youhavenewmessagesfromusers": "तपाईंको लागि  {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्ताहरू}} ($2) बाट $1",
+       "youhavenewmessagesfromusers": "तपाईंको लागि {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्ताहरू}} का $1 छन् । ($2)",
        "youhavenewmessagesmanyusers": "तपाईँलाई धेरै प्रयोगकर्ताहरू($2) बाट $1 छ ।",
        "newmessageslinkplural": "{{PLURAL:$1|एउटा नयाँ सन्देश|999=नयाँ सन्देशहरू}}",
        "newmessagesdifflinkplural": "अन्तिम {{PLURAL:$1|परिवर्तन|999=परिवर्तनहरू}}",
        "databaseerror-query": "क्वेरी: $1",
        "databaseerror-function": "फङ्सन : $1",
        "databaseerror-error": "त्रुटि: $1",
+       "transaction-duration-limit-exceeded": "To avoid creating high replication lag, this transaction was aborted because the write duration ($1) exceeded the $2 second limit.\nIf you are changing many items at once, try doing multiple smaller operations instead.",
        "laggedslavemode": "<strong>चेतावनी:</strong> पृष्ठमा हालका अद्यतनहरू नहुनसक्छन् ।",
        "readonly": "डेटाबेस बन्द गरिएको छ",
        "enterlockreason": "ताल्चा मार्नुको कारण दिनुहोस्, साथै ताल्चा हटाउने समयको अवधि अनुमान लगाउनुहोस्।",
        "missingarticle-rev": "(संशोधन #: $1)",
        "missingarticle-diff": "(परि: $1, $2)",
        "readonly_lag": "डेटाबेस स्वतः बन्द गरिएको छ जबकि अधिनस्थ डेटाबेस सर्वरले मूल पहिल्याउँदैछ।",
+       "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' लाई एचटीटीपी शीर्षक द्वारा पठाईयो तर एपीआईमा लेखन मोडल छ ।",
        "internalerror": "आन्तरिक त्रुटि",
        "internalerror_info": "आन्तरिक त्रुटि: $1",
        "internalerror-fatal-exception": "प्रकारको गम्भीर अपवाद \"$1\"",
        "viewsource": "स्रोत हेर्नुहोस",
        "viewsource-title": " $1 को स्रोत हेर्नुहोस",
        "actionthrottled": "कार्य रोकियो",
-       "actionthrottledtext": "सà¥\8dपाम à¤°à¥\8bà¤\95थामà¤\95à¥\8b à¤²à¤¾à¤\97ि , à¤¤à¤ªà¤¾à¤\88à¤\81लाई यो कार्य थोरै समयमा धेरै पटक गर्नबाट सिमित गरिएको छ, र तपाईंले आफ्नो सिमा पार गरिसक्नु भयो ।\nकृपया केही मिनेट पछि पुन: प्रयास गर्नुहोस्  ।",
+       "actionthrottledtext": "सà¥\8dपाम à¤°à¥\8bà¤\95थामà¤\95à¥\8b à¤²à¤¾à¤\97ि , à¤¤à¤ªà¤¾à¤\88à¤\82लाई यो कार्य थोरै समयमा धेरै पटक गर्नबाट सिमित गरिएको छ, र तपाईंले आफ्नो सिमा पार गरिसक्नु भयो ।\nकृपया केही मिनेट पछि पुन: प्रयास गर्नुहोस्  ।",
        "protectedpagetext": "यो पृष्ठ सम्पादन हुनबाट बचाउन सम्पादनमा तथा अन्यकार्यमा रोक लगाइएको छ।",
-       "viewsourcetext": "तपाà¤\88à¤\81लà¥\87 यस पृष्ठको स्रोत हेर्न र प्रतिलिपी गर्न सक्नुहुन्छ ।",
-       "viewyourtext": "यस à¤ªà¥\83षà¥\8dठमा à¤°à¤¹à¥\87à¤\95ा '''तपाà¤\88à¤\81का सम्पादनहरू''' हेर्न या प्रतिलिपी गर्न सक्नुहुन्छ :",
+       "viewsourcetext": "तपाà¤\88à¤\82 यस पृष्ठको स्रोत हेर्न र प्रतिलिपी गर्न सक्नुहुन्छ ।",
+       "viewyourtext": "यस à¤ªà¥\83षà¥\8dठमा à¤°à¤¹à¥\87à¤\95ा '''तपाà¤\88à¤\82का सम्पादनहरू''' हेर्न या प्रतिलिपी गर्न सक्नुहुन्छ :",
        "protectedinterface": "यो पृष्ठले सफ्टवेयरको लागि अन्तरमोहडा पाठ प्रदान गर्दछ , र यसलाई दुरुपयोग हुनबाट बचाउन सुरक्षा प्रादन गरिएको छ।\nसम्पूर्ण विकिहरूका लागि अनुवादमा परिवर्तन गर्नको लागि [https://translatewiki.net/ translatewiki.net], प्रयोग गर्नुहोस् ,  मिडियाविकि स्थानियकरण परियोजना ।",
        "editinginterface": "<strong>चेतावनी:</strong> तपाईं यस पृष्ठलाई सम्पादन गर्नुहुँदैछ, जसले सफ्टवेयरको लागि \nइन्टरफेस सामग्रीहरू प्रदान गर्दछ।\nयस पृष्ठमा गरिएकोपरिवर्तनले यस विकिमा अरु प्रयोगकर्ताको इन्टरफेसको प्रदर्शनमा प्रभाव पार्नेछ ।",
        "translateinterface": "सबै विकिहरूको लागी अनुवाद जोड्न वा परिवर्तन गर्नका लागि मीडियाविकि क्षेत्रीयकरण परियोजना [https://translatewiki.net/ ट्रान्सलेटविकि.नेट]को प्रयोग गर्नुहोस।",
        "userrights-nodatabase": "डेटाबेस $1 उपलब्ध छैन या स्थानीय हैन।",
        "userrights-nologin": "प्रयोगकर्ता अधिकार प्रदान गर्न तपाईंले प्रबन्धक खाताबाट [[Special:UserLogin|प्रवेश]] गर्नुपर्छ।",
        "userrights-notallowed": "प्रयोगकर्तालाई अधिकार प्रदान गर्ने वा हटाउने अनुमति तपाईंलाई छैन।",
-       "userrights-changeable-col": "परिवरà¥\8dतन à¤\97रà¥\8dन à¤¸à¤\95िनà¥\87 à¤¸à¤®à¥\82हहरà¥\81",
+       "userrights-changeable-col": "तपाà¤\88à¤\82लà¥\87 à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97रà¥\8dन à¤¸à¤\95à¥\8dनà¥\87 à¤¸à¤®à¥\82हहरà¥\82",
        "userrights-unchangeable-col": "तपाईंले परिवर्तन गर्न नसक्ने समूहहरू",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "प्रयोगकर्ताको अधिकार परिवर्तनमा मतभेद भयो ! कृपया तपाईंको परिवर्तन पुनरावलोकन तथा पुष्टि गर्नुहोस् ।",
        "htmlform-cloner-create": "अरू जोड्ने",
        "htmlform-cloner-delete": "हटाउने",
        "htmlform-cloner-required": "कम्तिमा एउटामा आवश्यक छ ।",
-       "sqlite-has-fts": "$1 पूरा पाठ खोज समर्थन सहित",
-       "sqlite-no-fts": "$1 पूरा पाठ खोज समर्थन बिना",
        "logentry-delete-delete": "$1 द्वारा पृष्ठ $3 {{GENDER:$2|मेटाइयो}}",
        "logentry-delete-restore": "$3 पृष्ठ $1ले {{GENDER:$2|पुनर्स्थापित}} गरेको हो",
        "logentry-delete-event": "$1 ले $3 पृष्ठको लग {{PLURAL:$5|प्रविष्टि|प्रविष्टिहरू}}को दृश्यता {{GENDER:$2|परिवर्तन गर्यो}}: $4",
index 442c685..e9cfa37 100644 (file)
        "tog-enotifminoredits": "Mij e-mailen bij kleine bewerkingen van pagina’s en bestanden op mijn volglijst",
        "tog-enotifrevealaddr": "Mijn e-mailadres weergeven in e-mailberichten",
        "tog-shownumberswatching": "Het aantal gebruikers weergeven dat deze pagina volgt",
-       "tog-oldsig": "Bestaande ondertekening:",
+       "tog-oldsig": "Uw bestaande ondertekening:",
        "tog-fancysig": "Handtekening als wikitekst behandelen (zonder automatische koppeling)",
        "tog-uselivepreview": "Livevoorvertoning gebruiken",
        "tog-forceeditsummary": "Een melding geven bij een lege bewerkingssamenvatting",
        "newwindow": "(opent in een nieuw venster)",
        "cancel": "Annuleren",
        "moredotdotdot": "Meer…",
-       "morenotlisted": "Deze lijst is niet compleet.",
+       "morenotlisted": "Deze lijst kan onvolledig zijn.",
        "mypage": "Gebruikerspagina",
        "mytalk": "Overleg",
        "anontalk": "Overleg",
        "createacct-yourpasswordagain-ph": "Geef het wachtwoord opnieuw in",
        "userlogin-remembermypassword": "Aangemeld blijven",
        "userlogin-signwithsecure": "Beveiligde verbinding gebruiken",
+       "cannotlogin-title": "Niet mogelijk om aan te melden",
+       "cannotlogin-text": "Aanmelden is niet mogelijk.",
        "cannotloginnow-title": "Niet mogelijk om aan te melden",
        "cannotloginnow-text": "Aanmelden is niet mogelijk bij het gebruik van $1.",
+       "cannotcreateaccount-title": "Kan geen accounts aanmaken",
        "yourdomainname": "Uw domein:",
        "password-change-forbidden": "U kunt uw wachtwoord niet wijzigen in deze wiki.",
        "externaldberror": "Er is een fout opgetreden bij het aanmelden bij de database of u hebt geen toestemming uw externe gebruiker bij te werken.",
        "passwordreset-emailsentemail": "Als dit e-mailadres aan uw account gekoppeld is, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
        "passwordreset-emailsentusername": "Als er een e-mailadres geregistreerd is voor die gebruikersnaam, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
        "passwordreset-invalideamil": "Ongeldig e-mailadres",
+       "passwordreset-nodata": "Er is geen gebruikersnaam of e-mailadres opgegeven",
        "changeemail": "E-mailadres wijzigen of verwijderen",
        "changeemail-header": "Vul dit formulier in om uw e-mailadres te wijzigen. Als u het e-mailadres wilt ontkoppelen van uw account, laat het e-mailadres dan leeg als u het formulier opslaat.",
        "changeemail-no-info": "U moet aangemeld zijn om rechtstreeks toegang te hebben tot deze pagina.",
        "undeletedrevisions": "$1 {{PLURAL:$1|versie|versies}} teruggeplaatst",
        "undeletedrevisions-files": "{{PLURAL:$1|1 versie|$1 versies}} en {{PLURAL:$2|1 bestand|$2 bestanden}} teruggeplaatst",
        "undeletedfiles": "{{PLURAL:$1|1 bestand|$1 bestanden}} teruggeplaatst",
-       "cannotundelete": "Het terugplaatsen is mislukt:\n$1",
+       "cannotundelete": "Het terugplaatsen is (gedeeltelijk) mislukt:\n$1",
        "undeletedpage": "'''$1 is teruggeplaatst'''\n\nIn het [[Special:Log/delete|verwijderingslogboek]] staan recente verwijderingen en herstelhandelingen.",
        "undelete-header": "Zie het [[Special:Log/delete|verwijderingslogboek]] voor recent verwijderde pagina's.",
        "undelete-search-title": "Verwijderde pagina's zoeken",
        "pageinfo-article-id": "Paginanummer",
        "pageinfo-language": "Taal voor de pagina",
        "pageinfo-content-model": "Paginainhoudmodel",
+       "pageinfo-content-model-change": "wijzigen",
        "pageinfo-robot-policy": "Indexering door robots",
        "pageinfo-robot-index": "Toegestaan",
        "pageinfo-robot-noindex": "Niet toegestaan",
        "htmlform-title-not-exists": "$1 bestaat niet.",
        "htmlform-user-not-exists": "<strong>$1</strong> bestaat niet.",
        "htmlform-user-not-valid": "<strong>$1</strong> is geen geldige gebruikersnaam.",
-       "sqlite-has-fts": "Versie $1 met ondersteuning voor \"full-text\" zoeken",
-       "sqlite-no-fts": "Versie $1 zonder ondersteuning voor \"full-text\" zoeken",
        "logentry-delete-delete": "$1 {{GENDER:$2|heeft}} de pagina $3 verwijderd",
        "logentry-delete-restore": "$1 {{GENDER:$2|heeft}} de pagina $3 teruggeplaatst",
        "logentry-delete-event": "$1 {{GENDER:$2|heeft}} de zichtbaarheid van {{PLURAL:$5|een logboekregel|$5 logboekregels}} van $3 gewijzigd: $4",
        "authmanager-email-help": "E-mailadres",
        "authmanager-realname-label": "Echte naam",
        "authmanager-realname-help": "Echte naam van de gebruiker",
+       "authmanager-provider-password": "Op wachtwoord gebaseerde authenticatie",
        "authmanager-provider-temporarypassword": "Tijdelijk wachtwoord",
        "authprovider-resetpass-skip-label": "Overslaan",
-       "specialpage-securitylevel-not-allowed-title": "Niet toegestaan"
+       "specialpage-securitylevel-not-allowed-title": "Niet toegestaan",
+       "cannotauth-not-allowed-title": "Geen toegang",
+       "changecredentials": "Authenticatiegegevens wijzigen",
+       "changecredentials-submit": "Authenticatiegegevens wijzigen",
+       "changecredentials-success": "Uw authenticatiegegevens zijn gewijzigd.",
+       "removecredentials": "Authenticatiegegevens verwijderen",
+       "removecredentials-submit": "Authenticatiegegevens verwijderen",
+       "removecredentials-success": "Uw authenticatiegegevens zijn verwijderd.",
+       "credentialsform-provider": "Soort authenticatiegegevens:",
+       "credentialsform-account": "Gebruikersnaam:",
+       "cannotlink-no-provider-title": "Er zijn geen accounts om te koppelen",
+       "cannotlink-no-provider": "Er zijn geen accounts om te koppelen.",
+       "linkaccounts": "Accounts koppelen",
+       "linkaccounts-success-text": "Het account is gekoppeld.",
+       "linkaccounts-submit": "Accounts koppelen",
+       "unlinkaccounts": "Accounts ontkoppelen",
+       "unlinkaccounts-success": "Het account is ontkoppeld."
 }
index 58b6b3d..f9dc952 100644 (file)
        "htmlform-no": "Nei",
        "htmlform-yes": "Ja",
        "htmlform-chosen-placeholder": "Vel ein",
-       "sqlite-has-fts": "$1 med støtte for fulltekstsøk",
-       "sqlite-no-fts": "$1 utan støtte for fulltekstsøk",
        "logentry-delete-delete": "$1 {{GENDER:$2|sletta}} sida $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|attoppretta}} sida $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endra}} synlegdomen av {{PLURAL:$5|éi loggoppføring|$5 loggoppføringar}} på $3: $4",
index b2fcdde..737143f 100644 (file)
        "htmlform-title-not-exists": "$1 nie istnieje.",
        "htmlform-user-not-exists": "<strong>$1</strong> nie istnieje.",
        "htmlform-user-not-valid": "<strong>$1</strong> nie jest prawidłową nazwą użytkownika.",
-       "sqlite-has-fts": "$1 z obsługą pełnotekstowego wyszukiwania",
-       "sqlite-no-fts": "$1 bez obsługi pełnotekstowego wyszukiwania",
        "logentry-delete-delete": "$1 {{GENDER:$2|usunął|usunęła}} stronę $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|odtworzył|odtworzyła}} stronę $3",
        "logentry-delete-event": "$1 {{GENDER:$2|zmienił|zmieniła}} widoczność {{PLURAL:$5|zdarzenia|$5 zdarzeń}} w rejestrze $3, wykonano następujące operacje: $4",
index 5dac084..3d38947 100644 (file)
@@ -99,7 +99,8 @@
                        "Anderson Costa",
                        "LucyDiniz",
                        "Tusca",
-                       "Cristofer Alves"
+                       "Cristofer Alves",
+                       "Tark"
                ]
        },
        "tog-underline": "Sublinhar links:",
        "rightslogtext": "Este é um registro de mudanças nos privilégios de usuários.",
        "action-read": "ler esta página",
        "action-edit": "editar esta página",
-       "action-createpage": "criar esta páginas",
-       "action-createtalk": "criar esta páginas de discussão",
+       "action-createpage": "criar esta página",
+       "action-createtalk": "criar esta página de discussão",
        "action-createaccount": "criar esta conta de usuário",
        "action-autocreateaccount": "Criar uma conta de usuário externa automaticamente",
        "action-history": "Ver o histórico desta página",
        "htmlform-title-not-exists": "$1 não existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> não existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> não é um nome de usuário válido.",
-       "sqlite-has-fts": "$1 com suporte de pesquisa de texto completo",
-       "sqlite-no-fts": "$1 sem suporte de pesquisa de texto completo",
        "logentry-delete-delete": "$1 apagou a página $3",
        "logentry-delete-restore": "$1 restaurou a página $3",
        "logentry-delete-event": "$1 alterou a visibilidade {{PLURAL:$5|de uma entrada|de $5 entradas}} do registro $3: $4",
index 16d9f85..00896dc 100644 (file)
        "invalid-content-data": "Dados de conteúdo inválidos",
        "content-not-allowed-here": "Conteúdo do tipo \"$1\" não é permitido na página [[$2]]",
        "editwarning-warning": "Sair desta página fará com que perca quaisquer alterações feitas por si.\nSe iniciou sessão, pode desativar este aviso na secção \"{{int:prefs-editing}}\" das suas preferências.",
+       "editpage-invalidcontentmodel-title": "Modelo de conteúdo não suportado",
+       "editpage-invalidcontentmodel-text": "O modelo de conteúdo \"$1\" não é suportado.",
        "editpage-notsupportedcontentformat-title": "Formato de conteúdo não suportado",
        "editpage-notsupportedcontentformat-text": "O formato de conteúdo $1 não é suportado pelo modelo de conteúdo $2.",
        "content-model-wikitext": "wikitexto",
        "htmlform-title-not-exists": "$1 não existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> não existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> não é um nome de utilizador válido.",
-       "sqlite-has-fts": "$1 com suporte de pesquisa de texto completo",
-       "sqlite-no-fts": "$1 sem suporte de pesquisa de texto completo",
        "logentry-delete-delete": "$1 apagou a página $3",
        "logentry-delete-restore": "$1 restaurou a página $3",
        "logentry-delete-event": "$1 alterou a visibilidade de {{PLURAL:$5|uma entrada|$5 entradas}} em $3: $4",
index fbf95cc..163b613 100644 (file)
        "htmlform-user-not-exists": "Error message shown if a user with the name provided by the user does not exist. $1 is the username.",
        "htmlform-user-not-valid": "Error message shown if the name provided by the user isn't a valid username. $1 is the username.",
        "rawmessage": "{{notranslate}} Used to pass arbitrary text as a message specifier array",
-       "sqlite-has-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version",
-       "sqlite-no-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version",
        "logentry-delete-delete": "{{Logentry|[[Special:Log/delete]]}}",
        "logentry-delete-restore": "{{Logentry|[[Special:Log/delete]]}}",
        "logentry-delete-event": "{{Logentry|[[Special:Log/delete]]}}\n{{Logentryparam}}\n* $5 - count of affected log events",
index 2a2e31c..52111d3 100644 (file)
@@ -12,7 +12,8 @@
                        "아라",
                        "Macofe",
                        "Matma Rex",
-                       "Translaziuns"
+                       "Translaziuns",
+                       "Terfili"
                ]
        },
        "tog-underline": "Suttastritgar colliaziuns:",
        "otherlanguages": "En autras linguas",
        "redirectedfrom": "(renvià da $1)",
        "redirectpagesub": "questa pagina renviescha tar in'auter artitgel",
+       "redirectto": "Renviescha a:",
        "lastmodifiedat": "Questa pagina è vegnida modifitgada l'ultima giada ils $1 a las $2.",
        "viewcount": "Questa pagina è vegnida contemplada {{PLURAL:$1|ina giada|$1 giadas}}.",
        "protectedpage": "Pagina protegida",
        "nstab-template": "Model",
        "nstab-help": "Agid",
        "nstab-category": "Categoria",
+       "mainpage-nstab": "Pagina principala",
        "nosuchaction": "Talas acziuns n'existan betg",
        "nosuchactiontext": "L'acziun specifitgada per questa URL è faussa.\nTi has endatà fauss la URL, u es suandà in link incorrect.\nI po dentant er esser ina errur en la software da {{SITENAME}}.",
        "nosuchspecialpage": "I n'exista betg ina tala pagina speziala",
        "welcomeuser": "Bainvegni, $1!",
        "welcomecreation-msg": "Tes conto è vegnì creà. \nN'emblida betg da midar tias [[Special:Preferences|{{SITENAME}} preferenzas]].",
        "yourname": "Num d'utilisader",
+       "userlogin-yourname": "Num d'utilisader",
        "userlogin-yourname-ph": "Endatescha tes num d'utilisader",
        "createacct-another-username-ph": "Endatescha in num d'utilisader",
        "yourpassword": "pled-clav",
        "yourpasswordagain": "repeter pled-clav",
        "createacct-yourpasswordagain": "Confermar il pled-clav",
        "createacct-yourpasswordagain-ph": "Endatescha il pled-clav anc ina giada",
-       "remembermypassword": "S'annunziar permanantamain sin quest computer (per maximalmain $1 {{PLURAL:$1|di|dis}})",
        "userlogin-remembermypassword": "Restar annunzià",
        "userlogin-signwithsecure": "Duvrar ina connexiun segira",
        "yourdomainname": "Vossa domain",
        "pt-login": "T'annunziar",
        "pt-login-button": "T'annunziar",
        "pt-createaccount": "Crear in conto d'utilisader",
+       "pt-userlogout": "Sortir",
        "php-mail-error-unknown": "Errur nunenconuschenta en la funcziun mail() da PHP",
        "user-mail-no-addy": "Empruvà da trametter in e-mail senza ina adressa dad e-mail.",
        "changepassword": "Midar pled-clav",
        "passwordreset-emailtext-user": "L'utilisader $1 sin {{SITENAME}} ha dumandà da redefinir il pled-clav per {{SITENAME}} ($4). \n{{PLURAL:$3|Il suandant conto d'utilisader è collià|Ils suandants contos d'utilisader èn colliads}} cun questa adressa dad e-mail:\n\n$2\n\n{{PLURAL:$3|Quest pled-clav temporar|Quests pled-clav temporars}} èn valids {{PLURAL:$5|in di|$5 dis}}.\nTi duessas t'annunziar ussa e tscherner in nov pled-clav. Sche ti na levas betg quests novs pleds-clav u sche ti ta regordas puspè da tes pled-clav original e na vuls betg pli midar il pled-clav pos ti ignorar quest messadi e cuntinuar dad utilisar tes pled-clav original.",
        "passwordreset-emailelement": "Num d'utilisader: \n$1\n\nPled-clav temporar: \n$2",
        "passwordreset-emailsentemail": "In e-mail per redefinir il pled-clav è vegnì tramess.",
-       "passwordreset-emailsent-capture": "In e-mail (sco mussà sutvart) per redefinir il pled-clav è vegnì tramess.",
-       "passwordreset-emailerror-capture": "In e-mail (sco mussà sutvart) per redefinir il pled-clav è vegnì generà ma n'ha betg pudì envià a l'{{GENDER:$2|utilisader|utilisadra}}: $1",
        "changeemail": "Midar l'adressa dad e-mail",
        "changeemail-header": "Midar l'adressa dad e-mail dal conto",
        "changeemail-no-info": "Ti stos t'annunziar per acceder directamain questa pagina.",
        "newarticle": "(Nov)",
        "newarticletext": "Ti has cliccà ina colliaziun ad ina pagina che n'exista anc betg. Per crear ina pagina, entschaiva a tippar en la stgaffa sutvart (guarda [$1 la pagina d'agid] per t'infurmar).",
        "anontalkpagetext": "----''Quai è la pagina da discussiun per in utilisader anomim che n'ha anc betg creà in conto d'utilisader u che n'al utilisescha betg.\nPerquai avain nus d'utilisar l'adressa dad IP per l'identifitgar.\nIna tala adressa dad IP po vegnir utilisada da differents utilisaders.\nSche ti es in utilisaders anonim e pensas che commentaris che na pertutgan betg tai vegnan adressads a tai, lura [[Special:CreateAccount|creescha in conto]] u [[Special:UserLogin|t'annunzia]] per evitar en futur che ti vegns sbaglià cun auters utilisaders.''",
-       "noarticletext": "Quest artitgel na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar il term]] sin in'autra pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols],\nu [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear questa pagina]</span>.",
+       "noarticletext": "Questa pagina na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar il term]] sin in'autra pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols],\nu [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear questa pagina]</span>.",
        "noarticletext-nopermission": "Questa pagina na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar quest titel]] en autras paginas u <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols correspundents]</span>, ma ti n'has betg ils dretgs da crear questa pagina.",
        "missing-revision": "La versiun #$1 da la pagina cun il num \"{{FULLPAGENAME}}\" n'exista betg.\n\nQuai capita savnes sche ti cliccas sin ina colliaziun antiquada en la cronologia per ina pagina ch'è vegnida stizzada.\nDetagls pon vegnri chattads en il [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protocol da stizzar].",
        "userpage-userdoesnotexist": "Il conto d'utilisader \"<nowiki>$1</nowiki>\" n'èxista betg.\nControllescha sch ti vuls propi crear/modiftgar questa pagina.",
        "undo-failure": "La modificaziun na pudeva betg vegnir revocada causa modificaziuns pli novas che stattan en conflict cun questa acziun.",
        "undo-norev": "La modificaziun na pudeva betg vegnir revocada perquai ch'ella n'exista betg u è vegnida stizzada.",
        "undo-summary": "Revocar la versiun $1 da [[Special:Contributions/$2|$2]] ([[User talk:$2|discussiun]])",
-       "cantcreateaccounttitle": "Betg pussaivel da crear il conto",
        "cantcreateaccount-text": "La creaziun da contos du'utilisader è vegnida bloccada da l'utilisader [[User:$3|$3]] per questa adressa IP ('''$1''').\n\nIl motiv inditgà da $3 è ''$2''",
        "viewpagelogs": "Guardar ils protocols da questa pagina",
        "nohistory": "Per questa pagina n'exista nagina cronologia.",
        "action-siteadmin": "bloccar u debloccar la banca da datas",
        "action-sendemail": "trametter e-mails",
        "nchanges": "$1 {{PLURAL:$1|midada|midadas}}",
+       "enhancedrc-history": "Cronologia",
        "recentchanges": "Ultimas midadas",
        "recentchanges-legend": "Opziuns per las ultimas midadas",
        "recentchanges-summary": "Sin questa pagina pos ti suandar las ultimas midadas sin '''{{SITENAME}}'''.",
        "querypage-disabled": "Questa pagina speciala è deactivada ord motivs da prestaziun.",
        "booksources": "Tschertga da ISBN",
        "booksources-search-legend": "Tschertgar pussaivladad da cumpra per cudeschs",
+       "booksources-search": "Tschertgar",
        "booksources-text": "Sutvart è ina glista da las colliaziuns ad autras paginas che vendan cudeschs novs ed utilisads e che pudessan avair dapli infurmaziuns davart ils cudeschs che ti tschertgas:",
        "booksources-invalid-isbn": "Il numer ISBN na para betg dad esser valid; controllescha che ti n'has betg fatg errurs cun la scriver.",
        "specialloguserlabel": "Acziun exequida da:",
        "contributions": "Contribuziuns {{GENDER:$1|da l'utilisader|da l'utilisadra}}",
        "contributions-title": "Contribuziuns d'utilisader da $1",
        "mycontris": "Contribuziuns",
+       "anoncontribs": "Contribuziuns",
        "contribsub2": "Per {{GENDER:$3|$1}} ($2)",
        "nocontribs": "Chattà naginas modificaziuns che correspundan a quests criteris.",
        "uctop": "(actual)",
        "import-logentry-interwiki-detail": "{{PLURAL:$1|Ina versiun|$1 versiuns}} da $2",
        "javascripttest": "Test da JavaScript",
        "javascripttest-qunit-intro": "Legia la [$1 documentaziun da tests] sin mediawiki.org.",
-       "tooltip-pt-userpage": "Mussar tia pagina d'utilisader",
+       "tooltip-pt-userpage": "Mussar {{GENDER:|tia pagina d'utilisader}}",
        "tooltip-pt-anonuserpage": "La pagina d'utilisader per l'adressa IP cun la quala che ti fas modificaziuns",
-       "tooltip-pt-mytalk": "Mussar tia pagina da discussiun",
+       "tooltip-pt-mytalk": "Mussar {{GENDER:|tia}} pagina da discussiun",
        "tooltip-pt-anontalk": "Discussiun davart modificaziuns che derivan da questa adressa dad IP",
        "tooltip-pt-preferences": "mias preferenzas",
        "tooltip-pt-watchlist": "La glista da las paginas da las qualas jau observ las midadas",
        "tooltip-pt-login": "I fiss bun sche ti s'annunziassas, ti na stos dentant betg.",
        "tooltip-pt-logout": "Sortir",
        "tooltip-ca-talk": "Discussiuns davart il cuntegn da l'artitgel",
-       "tooltip-ca-edit": "Ti pos modifitgar questa pagina.\nUtilisescha per plaschair il buttun 'mussar prevista' avant che memorisar.",
+       "tooltip-ca-edit": "Modifitgar questa pagina",
        "tooltip-ca-addsection": "Cumenzar nov paragraf",
        "tooltip-ca-viewsource": "Questa pagina è protegida.\nTi pos vesair il code-fundamental.",
        "tooltip-ca-history": "Versiuns pli veglias da questa pagina",
        "tooltip-t-recentchangeslinked": "Ultimas midadas sin paginas colliadas cun questa pagina",
        "tooltip-feed-rss": "RSS feed per questa pagina",
        "tooltip-feed-atom": "Atom feed per questa pagina",
-       "tooltip-t-contributions": "Mussar las contribuziuns da quest utilisader",
+       "tooltip-t-contributions": "Mussar las contribuziuns da {{GENDER:$1|quest utilisader}}",
        "tooltip-t-emailuser": "Trametter in e-mail a quest utilisader",
        "tooltip-t-upload": "Chargiar si datotecas",
        "tooltip-t-specialpages": "Glista da tut las paginas spezialas",
        "htmlform-submit": "Trametter",
        "htmlform-reset": "Revocar las midadas",
        "htmlform-selectorother-other": "Auters",
-       "sqlite-has-fts": "$1 cun sustegn per la retschertga da text integrala",
-       "sqlite-no-fts": "$1 senza sustegn per la retschertga da text integrala",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha stizzà}} la pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ha restaurà}} la pagina $3",
        "logentry-delete-event": "$1 ha midà la visibilitad da{{PLURAL:$5|d ina occurrenza en il protocol| $5 occurrenzas en il protocol}} da '''$3''': $4",
        "revdelete-uname-unhid": "dà liber il num d'utilisader",
        "revdelete-restricted": "applitgà restricziuns per administraturs",
        "revdelete-unrestricted": "allontanà restricziuns per administraturs",
-       "logentry-move-move": "$1 ha spustà la pagina $3 a $4",
+       "logentry-move-move": "$1 {{GENDER:$2|ha spustà}} la pagina $3 a $4",
        "logentry-move-move-noredirect": "$1 ha spustà la pagina $3 a $4 senza crear in renviament",
        "logentry-move-move_redir": "$1 ha spustà la pagina $3 a $4 e surscrit quatras in renviament",
        "logentry-move-move_redir-noredirect": "$1 ha spustà la pagina $3 a $4 e surscrit quatras in renviament senza crear in renviament",
        "logentry-patrol-patrol": "$1 ha marcà la versiun $4 da la pagina $3 sco controllada",
        "logentry-patrol-patrol-auto": "$1 ha marcà automaticamain la versiun $4 da la pagina $3 sco controllada",
        "logentry-newusers-newusers": "Il conto $1 è vegnì creà",
-       "logentry-newusers-create": "Il conto $1 è vegnì creà",
+       "logentry-newusers-create": "Il conto $1 è vegnì {{GENDER:$2|creà}}",
        "logentry-newusers-create2": "Il conto $3 è vegnì creà da $1",
        "logentry-newusers-autocreate": "Il conto $1 è vegnì creà automaticamain",
        "logentry-rights-rights": "$1 ha midà la commembranza da gruppas per $3 da $4 a $5",
index f591aba..8a7a974 100644 (file)
        "htmlform-title-not-exists": "$1 nu există.",
        "htmlform-user-not-exists": "<strong>$1</strong> nu există.",
        "htmlform-user-not-valid": "<strong>$1</strong> nu este un nume de utilizator valid.",
-       "sqlite-has-fts": "$1 cu suport de căutare în tot textul",
-       "sqlite-no-fts": "$1 fără suport de căutare în tot textul",
        "logentry-delete-delete": "$1 {{GENDER:$2|a șters}} pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restaurat}} pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a schimbat}} vizibilitatea {{PLURAL:$5|unui eveniment din jurnal|a $5 evenimente din jurnal|a $5 de evenimente din jurnal}} pentru $3: $4",
index 7e6289d..be3713b 100644 (file)
        "htmlform-title-not-exists": "$1 не существует.",
        "htmlform-user-not-exists": "<strong>$1</strong> не существует.",
        "htmlform-user-not-valid": "<strong>$1</strong> — недопустимое имя учётной записи.",
-       "sqlite-has-fts": "$1 с поддержкой полнотекстового поиска",
-       "sqlite-no-fts": "$1 без поддержки полнотекстового поиска",
        "logentry-delete-delete": "$1 {{GENDER:$2|удалил|удалила}} страницу $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|восстановил|восстановила}} страницу $3",
        "logentry-delete-event": "$1 {{GENDER:$2|изменил|изменила}} видимость {{PLURAL:$5|$5 записи|$5 записей|1=записи}} журнала для $3: $4",
index 018652b..9591937 100644 (file)
        "htmlform-cloner-create": "अधिकं योज्यताम्",
        "htmlform-cloner-delete": "निष्कास्यताम्",
        "htmlform-cloner-required": "न्यूनातिन्यूनम् एकं मूल्यम् अपेक्ष्यते ।",
-       "sqlite-has-fts": "$1 अन्वेषणसमर्थपूर्णपाठेन सह",
-       "sqlite-no-fts": "$1 अन्वेषणसमर्थपूर्णपाठेन विना",
        "logentry-delete-delete": "$1 {{GENDER:$2|अपाकृतं}} पृष्ठं $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|पुनस्स्थापितं}} पृष्ठं $3",
        "logentry-delete-event": "$3: $4 इत्यत्र {{PLURAL:$5|संरक्षिताऽऽवलेः घटनायाः|$5 संरक्षिताऽऽवलीनां घटनानां}} दर्शनीयता $1 द्वारा {{GENDER:$2|परिवर्तिता}}",
index d17cdac..b080fcf 100644 (file)
        "htmlform-cloner-create": "Pridať ďalšie",
        "htmlform-cloner-delete": "Odstrániť",
        "htmlform-cloner-required": "Je povinná najmenej jedna hodnota.",
-       "sqlite-has-fts": "$1 s podporou vyhľadávania v plnom texte",
-       "sqlite-no-fts": "$1 bez podpory vyhľadávania v plnom texte",
        "logentry-delete-delete": "$1 zmazal stránku $3",
        "logentry-delete-restore": "$1 obnovil stránku $3",
        "logentry-delete-event": "$1 zmenil viditeľnosť {{PLURAL:$5|záznamu udalostí|$5 záznamov udalostí}} k stránke $3: $4",
index 221e53a..eee92a6 100644 (file)
        "htmlform-title-not-exists": "$1 ne obstaja.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne obstaja.",
        "htmlform-user-not-valid": "<strong>$1</strong> ni veljavno uporabniško ime.",
-       "sqlite-has-fts": "$1 s podporo iskanju polnih besedil",
-       "sqlite-no-fts": "$1 brez podpore iskanju polnih besedil",
        "logentry-delete-delete": "$1 je {{GENDER:$2|izbrisal|izbrisala|izbrisal(-a)}} stran $3",
        "logentry-delete-restore": "$1 je {{GENDER:$2|obnovil|obnovila|obnovil(-a)}} stran $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|spremenil|spremenila|spremenil(-a)}} vidljivost $5 {{PLURAL:$5|dnevniškega dogodka|dnevniških dogodkov}} na $3: $4",
index b04c775..176528f 100644 (file)
        "invalid-content-data": "Неисправни подаци садржаја",
        "content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]",
        "editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили. Ако сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.",
+       "editpage-invalidcontentmodel-title": "Модел садржаја није подржан",
+       "editpage-invalidcontentmodel-text": "Модел садржаја „$1“ није подржан.",
        "editpage-notsupportedcontentformat-title": "Формат садржаја није подржан",
        "editpage-notsupportedcontentformat-text": "Формат садржаја $1 није подржан за модел садржаја $2.",
        "content-model-wikitext": "викитекст",
        "tag-filter": "Филтер за [[Special:Tags|ознаке]]:",
        "tag-filter-submit": "Филтрирај",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Ознака|Ознаке}}]]: $2)",
+       "tag-mw-contentmodelchange": "промена модела садржаја",
        "tags-title": "Ознаке",
        "tags-intro": "На овој страници је наведен списак ознака с којима програм може да означи измене и његово значење.",
        "tags-tag": "Назив ознаке",
        "tags-actions-header": "Радње",
        "tags-active-yes": "Да",
        "tags-active-no": "Не",
-       "tags-source-extension": "Ð\94ео ÐµÐºÑ\81Ñ\82ензиÑ\98е",
+       "tags-source-extension": "Ð\94ео Ð\9cедиÑ\98авикиÑ\98а",
        "tags-source-manual": "Ручно је додају корисници и ботови",
        "tags-source-none": "Ван употребе",
        "tags-edit": "уреди",
        "htmlform-title-not-exists": "$1 не постоји.",
        "htmlform-user-not-exists": "<strong>$1</strong> не постоји.",
        "htmlform-user-not-valid": "<strong>$1</strong> није исправно корисничко име.",
-       "sqlite-has-fts": "$1 с подршком претраге целог текста",
-       "sqlite-no-fts": "$1 без подршке претраге целог текста",
        "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} страницу $3",
        "logentry-delete-restore": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3",
        "logentry-delete-event": "$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|1=догађаја|$5 догађаја}} у дневнику $3: $4",
index 155e8c6..252b76a 100644 (file)
        "yourpasswordagain": "Potvrda lozinke:",
        "createacct-yourpasswordagain": "Potvrdite lozinku",
        "createacct-yourpasswordagain-ph": "Unesite lozinku još jednom",
-       "remembermypassword": "Zapamti me na ovom pregledaču (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Ostavi me prijavljenog/u",
        "userlogin-signwithsecure": "Koristite sigurnu konekciju",
        "yourdomainname": "Domen:",
        "tags-actions-header": "Radnje",
        "tags-active-yes": "Da",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Deo ekstenzije",
+       "tags-source-extension": "Deo Medijavikija",
        "tags-source-manual": "Ručno je dodaju korisnici i botovi",
        "tags-source-none": "Van upotrebe",
        "tags-edit": "uredi",
        "htmlform-title-not-exists": "$1 ne postoji.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
-       "sqlite-has-fts": "$1 s podrškom pretrage celog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretrage celog teksta",
        "logentry-delete-delete": "$1 je {{GENDER:$2|obrisao|obrisala}} stranicu $3",
        "logentry-delete-restore": "$1 je {{GENDER:$2|vratio|vratila}} stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|1=događaja|$5 događaja}} u dnevniku $3: $4",
index 3ea3fc4..e392e0d 100644 (file)
        "tog-enotifminoredits": "Skicka mig e-post även för mindre ändringar av sidor och filer",
        "tog-enotifrevealaddr": "Visa min e-postadress i e-postmeddelanden om ändringar som skickas till andra",
        "tog-shownumberswatching": "Visa antalet användare som bevakar",
-       "tog-oldsig": "Nuvarande signatur:",
+       "tog-oldsig": "Din nuvarande signatur:",
        "tog-fancysig": "Behandla signatur som wikitext (utan en automatisk länk)",
        "tog-uselivepreview": "Använd direktuppdaterad förhandsgranskning",
        "tog-forceeditsummary": "Påminn mig om jag inte fyller i en redigeringskommentar",
        "tog-showhiddencats": "Visa dolda kategorier",
        "tog-norollbackdiff": "Visa inte diff efter tillbakarullning",
        "tog-useeditwarning": "Varna mig om jag lämnar en redigeringssida där jag gjort ändringar men inte sparat.",
-       "tog-prefershttps": "Använd alltid en säker anslutning när jag är inloggad",
+       "tog-prefershttps": "Använd alltid en säker anslutning medan jag är inloggad",
        "underline-always": "Alltid",
        "underline-never": "Aldrig",
        "underline-default": "Webbläsarens eller utseendets standardinställning",
        "newwindow": "(öppnas i ett nytt fönster)",
        "cancel": "Avbryt",
        "moredotdotdot": "Mer...",
-       "morenotlisted": "Denna lista är inte fullständig.",
+       "morenotlisted": "Denna lista är kanske inte fullständig.",
        "mypage": "Sida",
        "mytalk": "Diskussion",
        "anontalk": "Diskussion",
        "botpasswords-updated-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" uppdaterades.",
        "botpasswords-deleted-title": "Botlösenord raderades",
        "botpasswords-deleted-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" raderades.",
-       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em>",
+       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em> <br> (För äldre botar som kräver att inloggningsnamnet är detsamma som det eventuella användarnamnet kan du även använda <strong>$3</strong> som användarnamn och <strong>$4</strong> som lösenord.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider är inte tillgänglig.",
        "botpasswords-restriction-failed": "Begränsningar av botlösenord tillåter inte denna inloggning.",
        "botpasswords-invalid-name": "Det angivna användarnamnet innehåller inte separatorn för botlösenord (\"$1\").",
        "invalid-content-data": "Ogiltig innehållsdata",
        "content-not-allowed-here": "innehåll av \"$1\" är inte tillåtet på sidan [[$2]]",
        "editwarning-warning": "Om du lämnar den här sidan kommer du att förlora alla ändringar du har gjort.\nOm du är inloggad kan du slå av den här varningen under \"{{int:prefs-editing}}\" i dina inställningar.",
+       "editpage-invalidcontentmodel-title": "Innehållsmodellen stöds inte",
+       "editpage-invalidcontentmodel-text": "Innehållsmodellen \"$1\" stöds inte.",
        "editpage-notsupportedcontentformat-title": "Innehållsformat stöds inte",
        "editpage-notsupportedcontentformat-text": "Innehållsformatet $1 stöds inte av innehållsmodellen $2.",
        "content-model-wikitext": "wikitext",
        "tag-filter": "Filter för [[Special:Tags|märken]]:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Märke|Märken}}]]: $2)",
+       "tag-mw-contentmodelchange": "ändring av innehållsmodell",
+       "tag-mw-contentmodelchange-description": "Redigeringar som [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel ändrar innehållsmodellen] för en sida",
        "tags-title": "Märken",
        "tags-intro": "Denna sida listar de taggar som mjukvaran kan markera en redigering med, och deras betydelse.",
        "tags-tag": "Märkesnamn",
        "tags-actions-header": "Handlingar",
        "tags-active-yes": "Ja",
        "tags-active-no": "Nej",
-       "tags-source-extension": "Definieras av ett tillägg",
+       "tags-source-extension": "Definieras av programvaran",
        "tags-source-manual": "Används manuellt av användare och robotar",
        "tags-source-none": "Används inte längre",
        "tags-edit": "redigera",
        "htmlform-title-not-exists": "$1 finns inte.",
        "htmlform-user-not-exists": "<strong>$1</strong> finns inte.",
        "htmlform-user-not-valid": "<strong>$1</strong> är inte ett giltigt användarnamn.",
-       "sqlite-has-fts": "$1 med stöd för fulltextsökning",
-       "sqlite-no-fts": "$1 utan stöd för fulltextsökning",
        "logentry-delete-delete": "$1 {{GENDER:$2|raderade}} sidan $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|återställde}} sidan $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ändrade}} synligheten för {{PLURAL:$5|en logghändelse|$5 logghändelser}} på $3: $4",
index 7c33f6a..c065fce 100644 (file)
        "htmlform-title-not-exists": "$1 இ்டம்பெறவில்லை.",
        "htmlform-user-not-exists": "<strong>$1</strong> இ்டம்பெறவில்லை.",
        "htmlform-user-not-valid": "<strong>$1</strong> என்பது செல்லுபடியான பயனர் பெயர் அல்ல.",
-       "sqlite-has-fts": "$1முழு-உரை தேடல் ஆதரவுடன்",
-       "sqlite-no-fts": "$1 முழு-உரை தேடல் ஆதரவு இல்லாமல்",
        "logentry-delete-delete": "$3 பக்கத்தை $1 {{GENDER:$2|நீக்கினார்}}",
        "logentry-delete-restore": "$3 பக்கத்தை $1 {{GENDER:$2|மீட்டமைத்தார்}}",
        "logentry-delete-event": "$3 :$4 இல் {{PLURAL:$5| ஒரு நிகழ்வு குறிப்பேட்டின்| $5  நிகழ்வுகள் குறிப்பேடுகளின்}} காட்சித்தன்மை $1 மாற்றினார்",
index 7e167ac..1cf4767 100644 (file)
        "htmlform-title-not-exists": "$1 mevcut değil.",
        "htmlform-user-not-exists": "<strong>$1</strong> mevcut değil.",
        "htmlform-user-not-valid": "<strong>$1</strong> geçerli bir kullanıcı ismi değildir.",
-       "sqlite-has-fts": "$1 tam-metin arama desteği ile",
-       "sqlite-no-fts": "$1 tam-metin arama desteği olmaksızın",
        "logentry-delete-delete": "$1 $3 sayfasını {{GENDER:$2|sildi}}",
        "logentry-delete-restore": "$1 $3 sayfasını {{GENDER:$2|geri getirdi}}",
        "logentry-delete-event": "$1, $3 sayfasında {{PLURAL:$5|bir günlük girdisinin |$5 günlük girdisinin}} görünürlüğünü {{GENDER:$2|değiştirdi}}: $4",
index 62dc255..f5d2beb 100644 (file)
        "htmlform-title-not-exists": "$1 не існує.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існує.",
        "htmlform-user-not-valid": "<strong>$1</strong> не є дійсним іменем користувача.",
-       "sqlite-has-fts": "$1 з підтримкою повнотекстового пошуку",
-       "sqlite-no-fts": "$1 без підтримки повнотекстового пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|вилучив|вилучила}} сторінку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|відновив|відновила}} сторінку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|змінив|змінила}} видимість {{PLURAL:$5 запису журналу|$5 записів журналу}} на $3: $4",
index 3a16bb5..88791bc 100644 (file)
        "redirectedfrom": "($1 سے پلٹایا گیا)",
        "redirectpagesub": "لوٹایا گیا صفحہ",
        "redirectto": "لوٹایا گیا صفحہ:",
-       "lastmodifiedat": "آخرÛ\8c Ø¨Ø§Ø± ØªØ¯Ù\88Û\8cÙ\86 $2, $1 Ú©Ù\88 کی گئی۔",
+       "lastmodifiedat": "اس ØµÙ\81Ø­Û\81 Ù\85Û\8cÚº Ø¢Ø®Ø±Û\8c Ø¨Ø§Ø± Ù\85Ù\88رخÛ\81 $1Ø¡ Ú©Ù\88 $2 Ø¨Ø¬Û\92 ØªØ±Ù\85Û\8cÙ\85 کی گئی۔",
        "viewcount": "اِس صفحہ تک {{PLURAL:$1|ایک‌بار|$1 مرتبہ}} رسائی کی گئی",
        "protectedpage": "محفوظ شدہ صفحہ",
        "jumpto": ":چھلانگ بطرف",
        "cannotchangeemail": "کھاتے کا برقی پتہ اس ویکی سے پر رہتے ہوئے نہیں تبدیل کیا جا سکتا۔",
        "emaildisabled": "اس سائٹ سے برقی خط نہیں بھیجے جاسکتے",
        "accountcreated": "تخلیقِ کھاتہ",
-       "accountcreatedtext": "[[{{ns:صارف}}:$1|$1]] ([[{{ns:تبادلۂ خیال صارف}}:$1|تبادلۂ خیال]]) کا صارف کھاتہ بن چکا ہے۔",
+       "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|تبادلۂ خیال]]) کا صارف کھاتہ بن چکا ہے۔",
        "createaccount-title": "کھاتہ سازی برائے {{SITENAME}}",
        "createaccount-text": "کسی نے {{SITENAME}} ($4) پر \"$2\" کے نام سے اور \"$3\" پارلفظ کے ساتھ آپ کا برقی پتہ استعمال کرتے ہوئے کھاتہ بنایا ہے.\nآپ کو چاہئے کہ ابھی داخلِ نوشتہ ہوکر اپنا پارلفظ تبدیل کردیں.\n\nاگر یہ کھاتہ غلطی سے بنا تھا تو آپ یہ پیغام نظرانداز کرسکتے ہیں.",
        "login-throttled": "آپ نے حال ہی میں متعدد مرتبہ لاگ ان ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار فرمائیے۔",
        "changepassword-success": "آپ کا پاس ورڈ تبدیل کر دیا گیا!",
        "changepassword-throttled": "آپ نے حال ہی میں متعدد مرتبہ داخل ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار کریں۔",
        "botpasswords": "روبہ پاس ورڈ",
+       "botpasswords-summary": "<em>روبہ کے پاس ورڈ</em> کے ذریعہ اصل کھاتے کی لاگ ان معلومات کے بغیر اے پی آئی کی مدد سے صارف کھاتے میں رسائی حاصل ہوتی ہے۔\n\nاگر آپ اس سے واقف نہیں ہیں تو بہتر ہوگا کہ آپ اسے نہ چھیڑیں۔ کوئی دوسرا صارف کبھی اس پاس ورڈ کے بنانے اور اسے سپرد کرنے کا آپ سے مطالبہ نہیں کرے گا۔",
        "botpasswords-disabled": "روبہ کے پاس ورڈ غیر فعال ہیں۔",
        "botpasswords-no-central-id": "روبہ کے پاس ورڈ کو استعمال کرنے کے لیے آپ کا مرکزی کھاتے میں داخل رہنا ضروری ہے۔",
        "botpasswords-existing": "روبہ کے موجودہ پاس ورڈ",
        "botpasswords-updated-body": "صارف \"$2\" کے روبہ نام \"$1\" کا پاس ورڈ تازہ کر دیا گیا۔",
        "botpasswords-deleted-title": "روبہ کا پاس ورڈ حذف ہو چکا ہے",
        "botpasswords-deleted-body": "صارف \"$2\" کے روبہ نام \"$1\" کا پاس ورڈ حذف کیا جا چکا ہے۔",
+       "botpasswords-newpassword": "<strong>$1</strong> کے کھاتے میں داخل ہونے کے لیے نیا پاس ورڈ <strong>$2</strong> ہے۔ <em>براہ کرم اسے آئندہ کے لیے محفوظ کر لیں۔</em> <br> (وہ قدیم روبہ جات جنہیں یکساں لاگ ان نام اور آخری نام درکار ہوتا ہے، ان کے لیے آپ <strong>$3</strong> کو صارف نام اور <strong>$4</strong> کو پاس ورڈ کے طور پر استعمال کر سکتے ہیں۔)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider دستیاب نہیں۔",
        "botpasswords-restriction-failed": "روبہ کے پاس ورڈ کی پابندیاں اس لاگ ان سے مانع ہیں۔",
        "botpasswords-invalid-name": "درج کردہ صارف نام میں روبہ کے پاس ورڈ کا فاصل لفظ موجود نہیں ہے (\"$1\")۔",
        "passwordreset-emailerror-capture2": "{{GENDER:$2|صارف}} کو برقی خط بھیجنے میں ناکامی: $1\n{{PLURAL:$3|صارف نام اور پاس ورڈ|صارف ناموں کی فہرست اور ان کے پاس ورڈ}} ذیل میں ملاحظہ فرمائیں۔",
        "passwordreset-nocaller": "کالر کا فراہم کیا جانا لازمی ہے",
        "passwordreset-nosuchcaller": "کالر موجود نہیں: $1",
+       "passwordreset-ignored": "پاس ورڈ کی ترتیب نو مکمل نہیں ہو سکی۔ شاید کوئی پرووائڈر فراہم نہیں کیا گیا تھا؟",
        "passwordreset-invalideamil": "نادرست برقی ڈاک پتا",
        "passwordreset-nodata": "کوئی صارف نام اور نہ کوئی برقی ڈاک پتا فراہم کیا گیا",
        "changeemail": "برقی ڈاک پتا تبدیل یا حذف کریں",
        "accmailtext": "[[User talk:$1|$1]] کے لیے خودکار طریقے سے تخلیق کیا گیا پاسورڈ $2 کو بھیج دیا گیا ہے.\n\nلاگ ان ہونے کے بعد <em>[[Special:ChangePassword|اسے تبدیل]]</em> کیا جا سکتا ہے۔",
        "newarticle": "(نیا)",
        "newarticletext": "آپ نے ایک ایسے صفحے کے ربط کی پیروی کی ہے جو کہ ابھی موجود نہیں ہے.\nیہ صفحہ تخلیق کرنے کیلئے درج ذیل خانہ میں متن درج کیجئے (مزید معلومات کیلئے [$1 صفحۂ معاونت] ملاحظہ فرمائیے).\nاگر آپ یہاں غلطی سے پہنچے ہیں تو پچھلے صفحے پر واپس جانے کیلئے اپنے متصفح پر '''back''' کا بٹن ٹک کیجئے.",
-       "anontalkpagetext": "----''یہ صفحہ ایک ایسے صارف کا ہے جنہوں نے یا تو اب تک اپنا کھاتا نہیں بنایا یا پھر وہ اسے استعمال نہیں کر رہے/ رہی ہیں۔ لہٰذا ہمیں انکی شناخت کے لئے ایک عددی آئی پی پتہ استعمال کرنا پڑرہا ہے۔ اس قسم کا آئی پی پتہ ایک سے زائد صارفین کے لئے مشترک بھی ہوسکتا ہے۔ اگر آپکی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپکی جانب منسوب یہ بیان غیرضروری ہے تو براہ کرم [[Special:CreateAccount|کھاتہ بنائیں]] یا [[Special:UserLogin|داخلِ نوشتہ]] ہوجائیے تاکہ مستقبل میں آپکو گمنام صارفین میں شمار کرنے سے پرہیز کیا جاسکے۔\"",
+       "anontalkpagetext": "----\n<em>یہ تبادلۂ خیال صفحہ ایک ایسے صارف کا ہے جس نے اب تک اپنا کھاتہ نہیں بنایا یا یہ صفحہ اس کے زیر استعمال نہیں۔</em> \nلہٰذا ہمیں اس کی شناخت کے لئے ایک آئی پی پتہ استعمال کرنا پڑ رہا ہے۔ \nاس قسم کا آئی پی پتہ ایک سے زائد صارفین کے درمیان میں مشترک بھی ہوسکتا ہے۔ \nاگر آپ کی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپ کے متعلق یہ تبصرے غیر متعلق ہیں تو براہ کرم [[Special:CreateAccount|ایک کھاتہ بنا لیں]] یا [[Special:UserLogin|داخل ہو جائیں]] تاکہ مستقبل میں آپ کو گمنام صارفین میں شمار کرنے سے گریز کیا جائے۔",
        "noarticletext": "اِس صفحہ میں فی الحال کوئی متن موجود نہیں ہے۔\nآپ دیگر صفحات میں [[Special:Search/{{PAGENAME}}|اِس صفحہ کے عنوان کو تلاش کر سکتے ہیں]]، <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>",
        "userpage-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ موجود نہیں ہے۔\nاگر آپ اس صفحہ کو تخلیق یا اس میں ترمیم کرنا چاہتے ہیں تو براہ کرم پہلے جانچ لیں۔",
        "previewnote": "'''یاد رکھیں، یہ صرف نمائش ہے ۔آپ کی ترامیم ابھی محفوظ نہیں کی گئیں۔'''",
        "continue-editing": "خانہ ترمیم میں جائیں",
        "previewconflict": "اس نمائش میں خانہ ترمیم کے اوپر موجود متن جس انداز میں ظاہر ہو رہا ہے، محفوظ کرنے کے بعد اسی طرح نظر آئے گا۔",
-       "session_fail_preview": "Ù\85عاÙ\81 Ú©Û\8cجئÛ\92! Ù\86شست Ú©Û\92 Ù\85Ù\88اد Ù\85Û\8cÚº Ø®Ø§Ù\85Û\8c Ú©Û\8c Ù\88جÛ\81 Ø³Û\92 Ø¢Ù¾Ú©Û\8c  ØªØ±Ù\85Û\8cÙ\85 Ù¾Ø± Ø¹Ù\85Ù\84 Ù\86Û\81Û\8cÚº Ú©Û\8cا Ø¬Ø§Ø³Ú©Ø§.\nبرائÛ\92 Ù\85Û\81رباÙ\86Û\8c Ø¯Ù\88بارÛ\81 Ú©Ù\88شش Ú©Û\8cجئÛ\92.\nاگر Ø¢Ù¾Ú©Ù\88 Ù¾Ú¾Ø± Ø¨Ú¾Û\8c Ù\85Ø´Ú©Ù\84 Ù¾Û\8cØ´ Ø¢Ø±Û\81Û\8c Û\81Û\92 ØªÙ\88 [[Special:UserLogout|خارجÙ\90 Ù\86Ù\88شتÛ\81]] Û\81Ù\88کر Ù\88اپس Ø¯Ø§Ø®Ù\84Ù\90 Ù\86Ù\88شتÛ\81 Û\81Ù\88جاÛ\8cئÛ\92.",
+       "session_fail_preview": "Ù\85عذرت! Ù\86شست Ú©Û\92 Ù\85Ù\88اد Ù\85Û\8cÚº Ø®Ø§Ù\85Û\8c Ú©Û\8c Ù\88جÛ\81 Ø³Û\92 Ø¢Ù¾ Ú©Û\8c  ØªØ±Ù\85Û\8cÙ\85 Ù\85Ú©Ù\85Ù\84 Ù\86Û\81Û\8cÚº Û\81Ù\88 Ø³Ú©Û\8cÛ\94\n\nشاÛ\8cد Ø¢Ù¾ Ø§Ù¾Ù\86Û\92 Ú©Ú¾Ø§ØªÛ\92 Ø³Û\92 Ø®Ø§Ø±Ø¬ Û\81Ù\88 Ú¯Ø¦Û\92 Û\81Û\8cÚºÛ\94 <strong>براÛ\81 Ú©Ø±Ù\85 Ø§Ø³ Ø¨Ø§Øª Ú©Û\8c ØªØµØ¯Û\8cÙ\82 Ú©Ø± Ù\84Û\8cÚº Ú©Û\81 Ø¢Ù¾ Ø¯Ø§Ø®Ù\84 Û\81Û\8cÚº Ø§Ù\88ر Ø¯Ù\88بارÛ\81 Ú©Ù\88شش Ú©Ø±Û\8cÚºÛ\94</strong> Ø§Ú¯Ø± Ø¢Ù¾ Ú©Ù\88 Ù¾Ú¾Ø± Ø¨Ú¾Û\8c Ù\85Ø´Ú©Ù\84 Ù¾Û\8cØ´ Ø¢Ø±Û\81Û\8c Û\81Ù\88 ØªÙ\88 Ø§Û\8cÚ© Ø¨Ø§Ø± [[Special:UserLogout|خارج Û\81Ù\88 Ú©Ø±]] Ù\88اپس Ø¯Ø§Ø®Ù\84 Û\81Ù\88 Ø¬Ø§Ø¦Û\8cÚº Ø§Ù\88ر Ø§Ù¾Ù\86Û\92 Ø¨Ø±Ø§Ø¤Ø²Ø± Ú©Ù\88 Ø¬Ø§Ù\86Ú\86 Ù\84Û\8cÚº Ú©Û\81 Ø¢Û\8cا Ù\88Û\81 Ø§Ø³ Ø³Ø§Ø¦Ù¹ Ú©Û\8c Ú©Ù\88Ú©Û\8cز Ø§Ø®Ø° Ú©Ø± Ø±Û\81ا Û\81Û\92 Û\8cا Ù\86Û\81Û\8cÚºÛ\94",
        "edit_form_incomplete": "<strong>خانہ ترمیم سے کچھ حصے سرور تک نہیں پہنچ سکے ہیں؛ براہ کرم اپنی ترامیم کو دوبارہ جانچ لیں کہ آیا وہ برقرار ہیں یا نہیں اور دوبارہ کوشش کریں۔</strong>",
        "editing": "آپ \"$1\" میں ترمیم کر رہے ہیں۔",
        "creating": "زیر تخلیق $1",
        "editpage-cannot-use-custom-model": "اس صفحہ کے مواد کے ماڈل کو تبدیل نہیں کیا جا سکتا۔",
        "readonlywarning": "<strong>انتباہ: انتظامی نگہداشت کی خاطر ڈیٹابیس کو مقفل کر دیا گیا ہے، لہذا اس وقت آپ اپنی ترامیم کو محفوظ نہیں کر سکتے۔</strong>\nآپ اپنی تحریر کو کسی ٹیکسٹ فائل میں محفوظ کر سکتے ہیں تاکہ وہ ضائع نہ ہو اور آئندہ اسے استعمال کیا جا سکے۔\n\nانتظامیہ کی جانب سے مقفل کرنے کی حسب ذیل وجہ بیان کی گئی ہے:\n\n$1",
        "protectedpagewarning": "<strong>انتباہ: اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ جاتی اندراج فراہم کیا گیا ہے:",
-       "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو یوں مقفل کیا جاچکا ہے کہ اس میں صرف اندراج شدہ صارفین ہی ترمیم کرسکتے ہیں۔\nحوالہ کے لیے ذیل میں تازہ ترین نوشتہ جاتی اندراج دیا گیا ہے:",
+       "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو محفوظ کر دیا گیا ہے، لہذا اب اس میں محض اندراج شدہ صارفین ہی ترمیم کر سکتے ہیں۔\nحوالہ کے لیے ذیل میں نوشتہ کا تازہ ترین اندراج درج ہے:",
        "cascadeprotectedwarning": "<strong>انتباہ:</strong> اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔ اسے مقفل کرنے کی وجہ یہ ہے کہ پیش نظر صفحہ درج ذیل محفوظ {{PLURAL:$1|صفحہ|صفحات}} کی آبشاری حفاظت میں شامل ہے:",
        "titleprotectedwarning": "<strong>انتباہ: اس صفحہ کو محفوظ کر دیا گیا ہے، چنانچہ اسے تخلیق کرنے کے لیے [[Special:ListGroupRights|خصوصی اختیارات]] درکار ہونگے۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ کا تازہ ترین اندراج موجود ہے:",
        "templatesused": "اِس صفحہ پر مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "sectioneditnotsupported-text": "اِس صفحہ میں قطعہ کی تدوین حمایت شدہ نہیں ہے.",
        "permissionserrors": "خطائے اجازت",
        "permissionserrorstext": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو ایسا کرنے کی اجازت نہیں ہے:",
-       "permissionserrorstext-withaction": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو $2 کرنے کی اجازت نہیں ہے:",
+       "permissionserrorstext-withaction": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو $2  کی اجازت نہیں ہے:",
        "contentmodelediterror": "آپ اس نسخے میں ترمیم نہیں کر سکتے کیونکہ اس کے مواد کا ماڈل ‌‌<code>$1</code> ہے جو اس صفحہ کے مواد کے موجودہ ماڈل <code>$2</code> سے مختلف ہے۔",
        "recreate-moveddeleted-warn": "''' انتباہ: آپ ایک گزشتہ حذف شدہ صفحہ دوبارہ تخلیق کررہے ہیں. '''\n\nآپ کو اِس بات پر غور کرنا چاہئے کہ آیا اِس صفحہ کی تدوین جاری رکھنا موزوں ہے یا نہیں.\nصفحہ کا نوشتۂ حذف شدگی و منتقلی یہاں سہولت کی خاطر مہیّا کیا جارہا ہے:",
-       "moveddeleted-notice": "یہ ایک حذف شدہ صفحہ ہے.\nصفحہ کا نوشتۂ حذف شدگی و منتقلی ذیل میں بطورِ حوالہ دیا جارہا ہے.",
+       "moveddeleted-notice": "اس صفحہ کو حذف کر دیا گیا ہے۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف شدگی اور نوشتہ منتقلی درج ہے۔",
        "moveddeleted-notice-recent": "معذرت، اس صفحہ کو حال ہی میں حذف کیا گیا ہے (گزشتہ چوبیس گھنٹوں میں)۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف اور نوشتہ منتقلی موجود ہے۔",
        "log-fulllog": "پورا نوشتہ دیکھئے",
        "edit-gone-missing": "صفحہ تجدید نہیں کیا جاسکتا.\nلگتا ہے یہ حذف ہوچکا ہے.",
        "content-json-empty-array": "خالی ایرے",
        "deprecated-self-close-category": "صفحات مع نادرست ایچ ٹی ایم ایل ٹیگ",
        "deprecated-self-close-category-desc": "اس صفحہ میں ایچ ٹی ایم ایل کے نادرست ٹیگ مثلاً <code>&lt;b/></code> or <code>&lt;span/></code> استعمال کیے گئے ہیں۔ چونکہ ایچ ٹی ایم ایل 5 میں ان ٹیگوں کا رویہ تبدیل ہو جائے گا، لہذا ویکی متن میں ان کا استعمال متروک ہو چکا ہے۔",
+       "duplicate-args-category": "سانچے میں دوہرے آرگومنٹ کے حامل صفحات",
+       "duplicate-args-category-desc": "وہ صفحات جن میں مکرر یا دوہرے آرگومنٹ مستعمل ہیں، مثلاً <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> یا <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>۔",
+       "expensive-parserfunction-category": "سنگین پارسر فنکشنوں کے بے پناہ استعمال والے صفحات",
+       "post-expand-template-inclusion-warning": "<strong>انتباہ:</strong> سانچہ کا حجم بہت زیادہ ہے۔ کچھ سانچے شامل نہیں ہو سکیں گے۔",
+       "post-expand-template-inclusion-category": "حجم سے متجاوز سانچوں والے صفحات",
+       "post-expand-template-argument-warning": "<strong>انتباہ:</strong> اس صفحہ میں موجود سانچہ کے کم از کم کسی ایک پیرامیٹر کا حجم بہت زیادہ ہے۔\nان پیرامیٹروں کو ترک کر دیا گیا ہے۔",
+       "post-expand-template-argument-category": "سانچہ کے ترک کردہ پیرامیٹروں کے حامل صفحات",
+       "parser-template-loop-warning": "سانچہ میں تکرار پایا گیا: [[$1]]",
+       "parser-template-recursion-depth-warning": "سانچہ میں تکرار کی گہرائی اپنی حد سے تجاوز کر گئی ($1)",
+       "language-converter-depth-warning": "لسانی مبدل کی گہرائی اپنی حد سے تجاوز کر گئی ($1)",
+       "node-count-exceeded-category": "گرہوں کی تعداد سے تجاوز کرنے والے صفحات",
+       "node-count-exceeded-category-desc": "اس صفحہ میں گرہیں اپنی مقررہ تعداد سے تجاوز کر گئیں۔",
+       "node-count-exceeded-warning": "صفحہ کی گرہ اپنی تعداد سے تجاوز کر گئی",
+       "expansion-depth-exceeded-category": "توسیع کی گہرائی سے تجاوز کرنے والے صفحات",
+       "expansion-depth-exceeded-category-desc": "اس صفحہ میں توسیع کی گہرائی اپنی حد سے تجاوز کر گئی۔",
+       "expansion-depth-exceeded-warning": "صفحہ میں توسیع کی گہرائی اپنی حد سے تجاوز کر گئی",
+       "parser-unstrip-loop-warning": "unstrip فنکشن میں تکرار پایا گیا",
+       "parser-unstrip-recursion-limit": "unstrip فنکشن میں تکرار اپنی حد سے تجاوز کر گیا ($1)",
+       "converter-manual-rule-error": "زبان کی دستی تبدیلی کے ضوابط میں نقص دریافت ہوا",
+       "undo-success": "اس ترمیم کو واپس پھیرا جا سکتا ہے۔\nبراہ کرم ذیل میں موجود موازنہ ملاحظہ فرمائیں اور یقین کر لیں کہ اس موازنے میں موجود فرق ہی آپ کا مقصود ہے۔ اس کے بعد تبدیلیوں کو محفوظ کر دیں، ترمیم واپس پھیر دی جائے گی۔",
+       "undo-failure": "درمیان میں متنازع ترامیم کی موجودگی کی بنا پر اس ترمیم کو واپس نہیں پھیرا جا سکا۔",
+       "undo-norev": "اس ترمیم کو واپس نہیں پھیرا جا سکا کیونکہ یہ موجود ہی نہیں یا حذف کر دی گئی ہے۔",
        "undo-nochange": "معلوم ہوتا ہے کہ اس ترمیم کو پہلے ہی واپس پھیر دیا گیا ہے۔",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|تبادلہ خیال]]) کی جانب سے کی گئی ترمیم $1 رد کردی گئی ہے۔",
        "undo-summary-username-hidden": "پوشیدہ صارف کے نسخہ $1 کو واپس پھیریں",
        "revdelete-nooldid-title": "ناقص مقصود نظرثانی",
        "revdelete-nooldid-text": "اس فنکشن کو جس نسخے پر انجام دینا ہے اسے آپ نے منتخب نہیں کیا، یا منتخب کردہ نسخہ موجود نہیں، یا آپ موجودہ نسخہ کو پوشیدہ کرنے کی کوشش کر رہے ہیں۔",
        "revdelete-no-file": "درج کردہ فائل موجود نہیں ہے۔",
+       "revdelete-show-file-confirm": "کیا آپ واقعی فائل «<nowiki>$1</nowiki>» کے مورخہ $2 بوقت $3 بجے حذف ہونے والے نسخے کو دیکھنا چاہتے ہیں؟",
        "revdelete-show-file-submit": "ہاں",
        "revdelete-selected-text": "[[:$2]] {{PLURAL:$1|کا منتخب نسخہ|کے منتخب نسخے}}:",
        "revdelete-selected-file": "[[:$2]] {{PLURAL:$1|کا منتخب فائل نسخہ|کے منتخب فائل نسخے}}:",
        "revdelete-suppress": "منتظمین اور دیگر صارفین سے معلومات کو پوشیدہ کریں",
        "revdelete-unsuppress": "بحال شدہ نظرثانیوں پر پابندیاں ہٹاؤ",
        "revdelete-log": "وجہ",
-       "revdelete-success": "'''رؤیتِ نظرثانی کی تجدید کامیابی سے ہوئی.'''",
-       "logdelete-success": "'''نوشتۂ رویت کامیابی سے مرتب.'''",
+       "revdelete-submit": "منتخب {{PLURAL:$1|نسخے|نسخوں}} پر منطبق کریں",
+       "revdelete-success": "نسخہ کی مرئیت کی تجدید مکمل۔",
+       "revdelete-failure": "نسخہ کی مرئیت کی تجدید نہیں ہو سکی:\n$1",
+       "logdelete-success": "نوشتہ مرئیت میں تبدیلی مکمل۔",
        "logdelete-failure": "'''نوشتۂ رویت مرتب نہیں کیا جاسکتا:'''\n\n$1",
        "revdel-restore": "ظاہریت تبدیل کرو",
        "pagehist": "تاریخچۂ صفحہ",
        "deletedhist": "حذف شدہ تاریخچہ",
+       "revdelete-hide-current": "مورخہ $2، بوقت $1 بجے والے آئٹم کو پوشیدہ کرنے کے دوران میں نقص: یہ موجودہ نسخہ ہے۔ اسے پوشیدہ نہیں کیا جا سکتا۔",
+       "revdelete-show-no-access": "مورخہ $2، بوقت $1 بجے والا آئٹم دکھانے کے دوران میں نقص: اس نسخہ کو  بطور «محدود» نشان زد کر دیا گیا ہے۔ چنانچہ اب یہ آپ کی دسترس سے باہر ہے۔",
+       "revdelete-modify-no-access": "مورخہ $2، بوقت $1 بجے والے آئٹم میں تبدیلی کے دوران میں نقص: اس نسخہ کو  بطور «محدود» نشان زد کر دیا گیا ہے۔ چنانچہ اب یہ آپ کی دسترس سے باہر ہے۔",
+       "revdelete-modify-missing": "آئٹم آئی ڈی $1 میں تبدیلی کے دوران میں نقص: یہ نسخہ ڈیٹابیس میں موجود نہیں ہے!",
+       "revdelete-no-change": "<strong>انتباہ:</strong> مورخہ $2، بوقت $1 بجے والے آئٹم میں پہلے ہی سے مرئیت کی مطلوبہ ترتیبات موجود ہیں۔",
+       "revdelete-concurrent-change": "مورخہ $2، بوقت $1 بجے والے آئٹم میں تبدیلی کے دوران میں نقص: ایسا معلوم ہوتا ہے کہ آپ کی جانب سے تبدیلی کی کوشش کے دوران میں کسی اور نے اس میں تبدیلی کر دی ہے۔\nبراہ کرم نوشتے دیکھ لیں۔",
+       "revdelete-only-restricted": "مورخہ $2، بوقت $1 بجے والے آئٹم کو پوشیدہ کرنے کے دوران میں نقص: مرئیت کے دیگر اختیارات میں سے مزید کسی ایک اختیار کو منتخب کیے بغیر آپ ان آئٹموں کو منتظمین کی نگاہوں سے مخفی نہیں کر سکتے۔",
        "revdelete-reason-dropdown": "* عمومی وجوہات حذف شدگی\n** کاپی رائٹ کی خلاف ورزی\n** نامناسب تبصرہ یا ذاتی معلومات\n** نامناسب صارف نام\n** ممکنہ طور پر افترا آمیر معلومات",
        "revdelete-otherreason": "دوسری/اضافی وجہ:",
        "revdelete-reasonotherlist": "کوئی اَور وجہ",
        "revdelete-edit-reasonlist": "تحذیفی وجوہات کی تدوین",
        "revdelete-offender": "نظرثانی مصنف:",
        "suppressionlog": "نوشتہ پوشیدگی",
+       "suppressionlogtext": "ذیل میں ان حذف شدگیوں اور پابندیوں کی فہرست ہے جن میں منتظمین سے پوشیدہ رکھا گیا مواد موجود ہے۔\nموجودہ جاری پابندیوں اور معطل صارفین کی فہرست دیکھنے کے لیے [[Special:BlockList|فہرست پابندی]] ملاحظہ فرمائیں۔",
        "mergehistory": "تواریخِ صفحہ کا انضمام",
+       "mergehistory-header": "اس صفحہ کے ذریعہ آپ ماخذ صفحہ کے تاریخچہ کے نسخوں کو نئے صفحہ میں ضم کر سکتے ہیں۔\nالبتہ اس بات کا یقین کر لیں کہ اس تبدیلی کے بعد بھی تاریخچہ کا تسلسل حسب سابق برقرار رہے گا۔",
        "mergehistory-box": "دو صفحات کی نظرثانیوں کا انضمام:",
        "mergehistory-from": "مآخذ صفحہ:",
        "mergehistory-into": "صفحۂ مقصود:",
+       "mergehistory-list": "قابل ضم تاریخچہ",
        "mergehistory-go": "ضم پذیر ترامیم دِکھاؤ",
        "mergehistory-submit": "نظرثانیاں ضم کرو",
        "mergehistory-empty": "نظرثانیاں ضم نہیں کی جاسکتیں.",
+       "mergehistory-fail-bad-timestamp": "وقت کی مہر نادرست ہے۔",
+       "mergehistory-fail-invalid-source": "ماخذ درست نہیں۔",
+       "mergehistory-fail-invalid-dest": "مقصود صفحہ درست نہیں۔",
+       "mergehistory-fail-permission": "ناکافی اختیارات برائے ضم تاریخچہ۔",
+       "mergehistory-fail-self-merge": "ماخذ و مقصود صفحات یکساں ہیں۔",
        "mergehistory-no-source": "مآخذ صفحہ $1 موجود نہیں.",
        "mergehistory-no-destination": "مقصود صفحہ $1 موجود نہیں.",
        "mergehistory-invalid-source": "مآخذ صفحہ کا عنوان صحیح ہونا چاہئے.",
        "mergehistory-reason": "وجہ:",
        "mergelog": "نوشتہ کا انضمام",
        "revertmerge": "غیر ضم",
+       "mergelogpagetext": "ذیل میں ان صفحات کی فہرست ہے جن کے تاریخچے حال ہی میں دوسرے صفحوں میں ضم کیے گئے ہیں۔",
        "history-title": "\"$1\" کا نظرثانی تاریخچہ",
        "difference-title": "\"$1\" کے نسخوں کے درمیان فرق",
+       "difference-title-multipage": "«$1» اور «$2» صفحوں کے درمیان فرق",
        "difference-multipage": "(فرق مابین صفحات)",
        "lineno": "لکیر $1:",
        "compareselectedversions": "منتخب متـن کا موازنہ",
+       "showhideselectedversions": "منتخب نسخوں کی مرئیت تبدیل کریں",
        "editundo": "رد ترمیم",
        "diff-empty": "(کوئی فرق نہیں)",
-       "diff-multi-sameuser": "({{PLURAL: $1 | ایک متوسط نظرثانی | $1 کئی متوسط نظرثانیاں}}ایک ہی صارف کی جانب سے نہیں دکھائی گئی)",
+       "diff-multi-sameuser": "(ایک ہی صارف کا {{PLURAL: $1 |ایک درمیانی نسخہ نہیں دکھایا گیا| $1 درمیانی نسخے نہیں دکھائے گئے}})",
        "searchresults": "تلاش کا نتیجہ",
        "searchresults-title": "نتائجِ تلاش برائے \"$1\"",
+       "titlematches": "عنوان صفحہ سے ملتا ہے",
+       "textmatches": "متن صفحہ سے ملتا ہے",
        "notextmatches": "کوئی بھی مماثل متن موجود نہیں",
        "prevn": "پچھلے {{PLURAL:$1|$1}}",
        "nextn": "اگلے {{PLURAL:$1|$1}}",
        "nextn-title": "آگے $1 {{PLURAL:$1|نتیجہ|نتائج}}",
        "shown-title": "فی صفحہ $1 {{PLURAL:$1|نتیجہ|نتائج}} دِکھاؤ",
        "viewprevnext": "دیکھیں($1 {{int:pipe-separator}} $2) ($3)۔",
-       "searchmenu-exists": "'''اِس ویکی پر \"[[:$1]]\" نامی ایک صفحہ موجود ہے'''",
+       "searchmenu-exists": "<strong>اِس ویکی پر «[[:$1]]» نامی ایک صفحہ موجود ہے۔</strong> {{PLURAL:$2|0=|تلاش کے دیگر نتائج بھی ملاحظہ فرمائیں۔}}",
        "searchmenu-new": "<strong>صفحہ \"[[:$1]]\" کو اس ویکی پر تخلیق کریں</strong> {{PLURAL:$2|0=|وہ صفحہ بھی دیکھے جو ٓپ کے تلاش میں پایا گیا|ان نتائج کو بھی دیکھے جو پائے گئے}}",
        "searchprofile-articles": "مشمولاتی صفحات",
        "searchprofile-images": "کثیرالوسیط",
        "search-redirect": "(رجوع مکرر $1)",
        "search-section": "(حصہ $1)",
        "search-category": "(زمرہ $1)",
+       "search-file-match": "فائل مواد سے ملتا ہے",
        "search-suggest": "کیا آپ کا مطلب تھا: $1",
+       "search-rewritten": "$1 کے نتائج کی نمائش، اس کی بجائے آپ $2 کو تلاش کر سکتے ہیں۔",
        "search-interwiki-caption": "ساتھی منصوبے",
        "search-interwiki-default": "$1 نتائج:",
        "search-interwiki-more": "(مزید)",
        "search-relatedarticle": "متعلقہ",
        "searchrelated": "متعلقہ",
        "searchall": "تمام",
-       "search-nonefound": "استفسار کے مطابق نتائج نہیں ملے.",
+       "search-nonefound": "استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
+       "search-nonefound-thiswiki": "اس سائٹ پر استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
        "powersearch-legend": "پیشرفتہ تلاش",
        "powersearch-ns": "جائے نام میں تلاش:",
        "powersearch-togglelabel": "جانچ",
        "powersearch-toggleall": "تمام",
        "powersearch-togglenone": "کوئی نہیں",
+       "powersearch-remember": "اس انتخاب کو مستقبل کی تلاشوں کے لیے یاد رکھیں",
        "search-external": "بیرونی تلاش",
        "searchdisabled": "{{SITENAME}} تلاش غیرفعال.\nآپ فی الحال گوگل کے ذریعے تلاش کرسکتے ہیں.\nیاد رکھئے کہ اُن کے {{SITENAME}} اشاریے ممکناً پرانے ہوسکتے ہیں.",
+       "search-error": "تلاش کے دوران میں کوئی نقص واقع ہوا: $1",
        "preferences": "ترجیحات",
        "mypreferences": "ترجیحات",
        "prefs-edits": "تعداد ترامیم:",
+       "prefsnologintext2": "اپنی ترجیحات میں تبدیلی کے لیے براہ کرم لاگ ان کریں",
        "prefs-skin": "جِلد",
        "skin-preview": "پیش منظر",
        "datedefault": "کوئی ترجیح نہیں",
+       "prefs-labs": "تجرباتی خصوصیتیں",
        "prefs-user-pages": "صارف صفحات",
        "prefs-personal": "پروفائل",
        "prefs-rc": "حالیہ تبدیلیاں",
        "prefs-tokenwatchlist": "ٹوکن",
        "prefs-diffs": "فرق",
        "prefs-help-prefershttps": "یہ ترجیح آپ کے اگلے لاگ ان پر اثر انداز ہوگی۔",
+       "prefswarning-warning": "ترجیحات میں آپ کی جانب سے کی جانے والی تبدیلیاں ابھی محفوظ نہیں ہوئی ہیں۔\nاگر آپ «$1» پر کلک کیے بغیر اس صفحہ کو چھوڑ دیں تو آپ کی تبدیلیاں محفوظ نہیں ہوگی۔",
+       "prefs-tabs-navigation-hint": "نکتہ: مختلف خانوں میں جانے کے لیے آپ دائیں اور بائیں کی جہت نما کلیدیں استعمال کر سکتے ہیں۔",
        "userrights": "حقوقِ صارف کی نظامت",
        "userrights-lookup-user": "گروہائے صارف کا انتظام",
        "userrights-user-editname": "کوئی اسم‌صارف داخل کیجئے:",
-       "editusergroup": "ترمیم گروہائے صارف",
-       "editinguser": "تبدیلی اختیارات صارف برائے {{GENDER:$1|صارف}} <strong>[[صارف:$1|$1]]</strong> $2",
+       "editusergroup": "{{GENDER:$1|صارف}} کے گروہوں میں ترمیم کریں",
+       "editinguser": "{{GENDER:$1|صارف}} <strong>[[صارف:$1|$1]]</strong> $2 کے اختیارات میں تبدیلی",
        "userrights-editusergroup": "ترمیم گروہائے صارف",
-       "saveusergroups": "گروہائے صارف محفوظ",
+       "saveusergroups": "{{GENDER:$1|صارف}} کے گروہوں کو محفوظ کریں",
        "userrights-groupsmember": "رکنِ:",
        "userrights-groupsmember-auto": "اعتباری صارف در",
        "userrights-groups-help": "آپ ان گروہان میں تبدیلی کرسکتے ہیں جن سے صارف متعلق ہے: \n* نشان زد خانہ کا مطلب یہ ہے کہ صارف کا تعلق اس گروہ سے ہے۔ \n* غیر نشان زد خانہ کا مطلب یہ ہے کہ صارف کا تعلق اس گروہ سے نہیں ہے۔ \n* یہ * علامت اس بات کا اشارہ ہے کہ آپ اس گروہ کو نہیں ہٹا سکتے جسے ایک مرتبہ آپ نے شامل کردیا ہو، یا اس کے بر عکس۔",
        "userrights-reason": "وجہ:",
        "userrights-no-interwiki": "دوسرے ویکیوں پر حقوقِ صارف میں ترمیم کی آپ کو اجازت نہیں ہے.",
+       "userrights-nodatabase": "ڈیٹابیس $1 موجود نہیں یا مقامی نہیں۔",
+       "userrights-nologin": "اختیارات تفویض کرنے کے لیے آپ کا کسی منتظم کھاتے سے [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
+       "userrights-notallowed": "آپ کو  اختیارات تفویض کرنے یا انہیں واپس لینے کی اجازت نہیں ہے۔",
        "userrights-changeable-col": "مجموعات جو آپ تبدیل کرسکتے ہیں",
        "userrights-unchangeable-col": "مجموعات جو آپ تبدیل نہیں کرسکتے",
+       "userrights-conflict": "اختیارات کی تبدیلی میں تنازعہ! براہ کرم نظر ثانی کریں اور اپنی تبدیلیوں کی تصدیق کریں۔",
+       "userrights-removed-self": "آپ نے اپنے اختیارات ختم کر لیے ہیں، چنانچہ اب یہ صفحہ آپ کی دسترس سے باہر ہو گیا ہے۔",
        "group": "گروہ:",
        "group-user": "صارفین",
        "group-autoconfirmed": "خود توثیق شدہ صارفین",
        "grouppage-bot": "{{ns:project}}:روبہ جات",
        "grouppage-sysop": "{{ns:project}}:منتظمین",
        "grouppage-bureaucrat": "{{ns:project}}:مامورین اداری",
+       "grouppage-suppress": "{{ns:project}}:پوشیدگی",
        "right-read": "مطالعہ صفحات",
        "right-edit": "ترمیم صفحات",
        "right-createpage": "تخلیق صفحات (تبادلہ خیال صفحات نہیں)",
        "right-minoredit": "ترامیم کی بطور معمولی ترمیم نشان زدگی",
        "right-move": "منتقلی صفحات",
        "right-move-subpages": "منتقلی صفحات مع ذیلی صفحات",
-       "right-upload": "ملفات زبراثقال (اپ لوڈ) کریں",
-       "right-writeapi": "اے پی آئی لکھائی کا استعمال",
+       "right-move-rootuserpages": "منتقلی صارف صفحات",
+       "right-move-categorypages": "منتقلی زمرہ صفحات",
+       "right-movefile": "منتقلی فائل",
+       "right-suppressredirect": "پرانے عنوان سے رجوع مکرر کے بغیر منتقلی صفحہ",
+       "right-upload": "فائلوں کو اپلوڈ کرنا",
+       "right-reupload": "موجود فائلوں کا دوبارہ اپلوڈ",
+       "right-reupload-own": "ذاتی اپلوڈ کردہ فائلوں کا دوبارہ اپلوڈ",
+       "right-reupload-shared": "مقامی طور پر مشترکہ میڈیا کے ذخیرے میں فائلوں کی منسوخی",
+       "right-upload_by_url": "بذریعہ یوآرایل فائل اپلوڈ",
+       "right-purge": "بدون تصدیق صفحہ کے کیشے کی صفائی",
+       "right-autoconfirmed": "آئی پی پر مبنی پابندیوں سے غیر متاثر",
+       "right-bot": "خودکار عمل کے طور پر تعامل",
+       "right-nominornewtalk": "تبادلۂ خیال صفحات میں معمولی ترامیم کرنے پر نئے پیغام کے اعلان کی عدم نمائش",
+       "right-apihighlimits": "API کا بڑے پیمانے پر استعمال",
+       "right-writeapi": "اے پی آئی تحریر کا استعمال",
        "right-delete": "صفحات حذف کریں",
+       "right-bigdelete": "بڑے تاریخچوں پر مشتمل صفحات کی حذف شدگی",
+       "right-deletelogentry": "نوشتہ کے مخصوص اندراجات کی حذف شدگی و بحالی",
+       "right-deleterevision": "صفحات کے مخصوص نسخوں کی حذف شدگی و بحالی",
+       "right-deletedhistory": "ملحقہ متن کے بغیر تاریخچہ کے حذف شدہ اندراجات کا معائنہ",
+       "right-deletedtext": "حذف شدہ متن اور حذف شدہ نسخوں کے درمیان میں تبدیلیوں کا معائنہ",
+       "right-browsearchive": "حذف شدہ صفحات میں تلاش",
+       "right-undelete": "بحالی صفحہ",
+       "right-suppressrevision": "صفحات کے مخصوص نسخوں کا معائنہ و پوشیدگی",
+       "right-viewsuppressed": "پوشیدہ نسخوں کا معائنہ",
+       "right-suppressionlog": "نجی نوشتوں کا معائنہ",
+       "right-block": "صارفین کی ترمیم کاری پر پابندی کا نفاذ",
+       "right-blockemail": "برقی خط بھیجنے پر پابندی کا نفاذ",
+       "right-hideuser": "عمومی نگاہ سے مخفی رکھتے ہوئے صارف نام پر پابندی کا نفاذ",
+       "right-ipblock-exempt": "آئی پی، خودکار اور رینج پر پابندیوں سے خلاصی",
+       "right-unblockself": "رفع پابندی",
+       "right-protect": "آبشاری حفاظت کے حامل صفحات میں ترمیم اور درجات حفاظت میں تبدیلی",
+       "right-editprotected": "\"{{int:protect-level-sysop}}\" کے طور پر محفوظ صفحات میں ترمیم",
+       "right-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" کے طور پر محفوظ صفحات میں ترمیم",
+       "right-editcontentmodel": "صفحہ کے مواد کے ماڈل میں ترمیم",
+       "right-editinterface": "صارف انٹرفیس میں ترمیم",
+       "right-editusercssjs": "دیگر صارفین کی سی ایس ایس اور جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-editusercss": "دیگر صارفین کی سی ایس ایس فائلوں میں ترمیم",
+       "right-edituserjs": "دیگر صارفین کی جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-editmyusercss": "اپنی ذاتی سی ایس ایس فائلوں میں ترمیم",
+       "right-editmyuserjs": "اپنی ذاتی جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-viewmywatchlist": "اپنی ذاتی زیرنظر فہرست کا معائنہ",
+       "right-editmywatchlist": "اپنی ذاتی زیرنظر فہرست میں ترمیم۔ خیال رکھیں کہ اس اختیار کے بغیر بھی بعض اقدامات کے ذریعہ صفحات شامل کیے جا سکتے ہیں۔",
+       "right-viewmyprivateinfo": "اپنی ذاتی نجی معلومات کا معائنہ (مثلاً برقی ڈاک پتہ، حقیقی نام وغیرہ)",
+       "right-editmyprivateinfo": "اپنی ذاتی نجی معلومات میں ترمیم (مثلاً برقی ڈاک پتہ، حقیقی نام وغیرہ)",
+       "right-editmyoptions": "اپنی ذاتی ترجیحات میں ترمیم",
+       "right-rollback": "کسی مخصوص صفحہ پر ترمیم کرنے والے آخری صارف کی ترامیم کا فوری استرجع",
+       "right-markbotedits": "استرجع شدہ ترامیم کی روبہ ترامیم کے طور پر نشان زدگی",
+       "right-noratelimit": "وقت کی پابندیوں سے آزادی",
+       "right-import": "دوسری ویکیوں سے صفحات کی درآمد",
+       "right-importupload": "بذریعہ اپلوڈ صفحات کی درآمد",
+       "right-patrol": "دیگر صارفین کی ترامیم کی مراجعت",
+       "right-autopatrol": "ذاتی ترامیم کی خودکار مراجعت",
+       "right-patrolmarks": "حالیہ تبدیلیوں میں علامات مراجعت کا معائنہ",
+       "right-unwatchedpages": "نادیدہ صفحات کی فہرست کا معائنہ",
+       "right-mergehistory": "صفحات کے تاریخچے کا انضمام",
+       "right-userrights": "تمام اختیارات میں ترمیم",
+       "right-userrights-interwiki": "دوسری ویکیوں پر صارف کے اختیارات میں ترمیم",
+       "right-siteadmin": "ڈیٹابیس کو مقفل یا غیر مقفل کرنا",
+       "right-override-export-depth": "پانچویں سطح کی گہرائی تک مربوط صفحات پر مشتمل صفحات کی برآمد",
        "right-sendemail": "دیگر صارفین کو برقی ڈاک بھیجیں",
+       "right-passwordreset": "پاس ورڈ کی ترتیب نو کے حامل برقی خطوط کا معائنہ",
+       "right-managechangetags": "[[Special:Tags|ٹیگوں]] کی تخلیق اور (غیر)فعالی",
+       "right-applychangetags": "کسی کی تبدیلیوں کے ساتھ [[Special:Tags|ٹیگوں]] کا اطلاق",
+       "right-changetags": "انفرادی نسخوں اور نوشتہ کے اندراج پر [[Special:Tags|ٹیگوں]] کا حذف و اضافہ",
+       "right-deletechangetags": "ڈیٹابیس سے [[Special:Tags|ٹیگوں]] کی حذف شدگی",
+       "grant-generic": "\"$1\" مجموعہ اختیارات",
+       "grant-group-page-interaction": "صفحات سے تعامل",
+       "grant-group-file-interaction": "میڈیا سے تعامل",
+       "grant-group-watchlist-interaction": "اپنی زیرنظر فہرست سے تعامل",
+       "grant-group-email": "برقی خط کی ترسیل",
+       "grant-group-high-volume": "بڑے پیمانے کی سرگرمی کی انجام دہی",
+       "grant-group-customization": "شخصی سازی اور ترجیحات",
+       "grant-group-administration": "انتظامی امور کی انجام دہی",
+       "grant-group-private-information": "اپنے متعلق نجی معلومات تک رسائی",
+       "grant-group-other": "متفرق سرگرمیاں",
+       "grant-blockusers": "پابندی و رفع پابندی",
+       "grant-createaccount": "کھاتہ سازی",
+       "grant-createeditmovepage": "تخلیق، ترمیم و منتقلی صفحات",
+       "grant-delete": "صفحات، اندراجات نوشتہ اور نسخوں کی حذف شدگی",
+       "grant-editinterface": "صارف کی سی ایس ایس/جاوا اسکرپٹ اور میڈیاویکی نام فضا میں ترمیم",
+       "grant-editmycssjs": "اپنی سی ایس ایس/جاوا اسکرپٹ میں ترمیم",
+       "grant-editmyoptions": "اپنی ترجیحات میں ترمیم",
+       "grant-editmywatchlist": "اپنی زیرنظر فہرست میں ترمیم",
+       "grant-editpage": "موجودہ صفحات میں ترمیم",
+       "grant-editprotected": "محفوظ صفحات میں ترمیم",
+       "grant-highvolume": "اعلی حجم کی تدوین",
+       "grant-oversight": "صارفین چھپائیے اور نظرثانی دبائیے",
+       "grant-privateinfo": "ذاتی معلومات تک رسائی",
+       "grant-protect": "صفحات کو محفوظ اور غیر محفوظ کریں",
+       "grant-sendemail": "دیگر صارفین کو برقی خط کی ترسیل",
+       "grant-uploadeditmovefile": "فائلوں کی تبدیلی، اپلوڈ اور منتقلی",
+       "grant-uploadfile": "نئی فائلوں کی اپلوڈ کاری",
+       "grant-basic": "بنیادی اختیارات",
+       "grant-viewdeleted": "حذف شدہ فائلوں اور صفحات کا معائنہ",
+       "grant-viewmywatchlist": "اپنی زیرنظر فہرست کا معائنہ",
        "newuserlogpage": "نوشتۂ آمد صارف",
        "newuserlogpagetext": "یہ نۓ صارفوں کی آمد کا نوشتہ ہے",
        "rightslog": "نوشتہ صارفی اختیارات",
        "rightslogtext": "یہ صارفی اختیارات میں تبدیلیوں کا نوشتہ ہے۔",
+       "action-read": "اس صفحہ کو پڑھنے",
        "action-edit": "اس صفحہ میں ترمیم کریں",
+       "action-createpage": "اس صفحہ کو تخلیق کرنے",
+       "action-createtalk": "اس تبادلۂ خیال صفحہ کو تخلیق کرنے",
+       "action-createaccount": "اس کھاتے کو بنانے",
+       "action-autocreateaccount": "اس بیرونی کھاتے کو خودکار طور پر بنانے",
+       "action-history": "اس صفحہ کا تاریخچہ دیکھنے",
+       "action-minoredit": "اس ترمیم کو معمولی نشان زد کرنے",
+       "action-move": "اس صفحہ کو منتقل کرنے",
+       "action-move-subpages": "اس صفحہ اور اس کے ذیلی صفحات کو منتقل کرنے",
+       "action-move-rootuserpages": "اصل صارف صفحات کو منتقل کرنے",
+       "action-move-categorypages": "زمرے کے صفحات کو منتقل کرنے",
+       "action-movefile": "اس فائل کو منتقل کرنے",
+       "action-upload": "اس فائل کو اپلوڈ کرنے",
+       "action-reupload": "اس موجودہ فائل کو دوبارہ اپلوڈ کرنے",
+       "action-reupload-shared": "مشترکہ ذخیرے میں فائل کو منسوخ کرنے",
+       "action-upload_by_url": "بذریعہ یوآرایل اس فائل کو اپلوڈ کرنے",
+       "action-writeapi": "اے پی آئی تحریر کے استعمال کرنے",
+       "action-delete": "یہ صفحہ حذف کرنے",
+       "action-deleterevision": "یہ نسخہ حذف کرنے",
+       "action-deletedhistory": "اس صفحہ کا حذف شدہ تاریخچہ دیکھنے",
+       "action-browsearchive": "حذف شدہ صفحات میں تلاش کرنے",
+       "action-undelete": "اس صفحہ کو بحال کرنے",
+       "action-suppressrevision": "اس پوشیدہ ترمیم کی نظرثانی اور بحال کرنے",
+       "action-suppressionlog": "نجی نوشتہ کے دیکھنے",
+       "action-block": "اس صارف پر پابندی لگانے",
+       "action-protect": "اس صفحہ کے درجات حفاظت میں تبدیلی کرنے",
+       "action-rollback": "آخری صارف جس نے ایک متعین صفحہ میں ترمیم کی ہے، اس کی ترامیم کا فوری استرجع کرنے",
+       "action-import": "دوسری ویکی سے صفحات درآمد کرنے",
+       "action-importupload": "بذریعہ اپلوڈ صفحات درآمد کرنے",
+       "action-patrol": "دیگر صارفین کی ترامیم کو بطور مراجعت شدہ نشان زد کرنے",
+       "action-autopatrol": "اپنی ترمیم کو بطور مراجعت شدہ نشان زد کرنے",
+       "action-unwatchedpages": "نادیدہ صفحات کی فہرست دیکھنے",
+       "action-mergehistory": "اس صفحہ کے تاریخچہ کو ضم کرنے",
+       "action-userrights": "تمام اختیارات میں تبدیلی کرنے",
+       "action-userrights-interwiki": "دوسری ویکیوں پر صارف کے اختیارات میں ترمیم کرنے",
+       "action-siteadmin": "ڈیٹابیس کو مقفل کرنے یا کھولنے",
+       "action-sendemail": "برقی خطوط روانہ کرنے",
+       "action-editmywatchlist": "اپنی زیرنظر فہرست میں ترمیم کرنے",
+       "action-viewmywatchlist": "اپنی زیر نظر فہرست دیکھنے",
+       "action-viewmyprivateinfo": "اپنی نجی معلومات دیکھنے",
+       "action-editmyprivateinfo": "اپنی نجی معلومات میں ترمیم کرنے",
+       "action-editcontentmodel": "صفحہ کے مواد کے ماڈل میں ترمیم کرنے",
+       "action-managechangetags": "ٹیگوں کو بنانے اور انہیں غیر فعال کرنے",
+       "action-applychangetags": "اپنی تبدیلیوں پر ٹیگ گاری کرنے",
+       "action-changetags": "انفرادی نسخوں اور نوشتہ کے اندراج پر ٹیگوں کو لگانے اور ہٹانے",
+       "action-deletechangetags": "ڈیٹابیس سے ٹیگوں کو حذف کرنے",
+       "action-purge": "اس صفحہ کا کیشے خالی کرنے",
        "nchanges": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|آخری آمد کے بعد سے}}",
        "enhancedrc-history": "تاریخچہ",
        "recentchanges": "حالیہ تبدیلیاں",
        "recentchanges-legend": "اِختیاراتِ حالیہ تبدیلیاں",
-       "recentchanges-summary": "اس صفحے پر ویکی میں ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کیجیۓ۔",
-       "recentchanges-feed-description": "اس خورد میں ویکی پر ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کیجیۓ۔",
+       "recentchanges-summary": "اس صفحے پر ویکی میں ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کریں۔",
+       "recentchanges-noresult": "مقررہ مدت کے دوران میں اس معیار سے مشابہت رکھنے والی کوئی تبدیلی نہیں ہوئی۔",
+       "recentchanges-feed-description": "اس فیڈ میں ویکی پر ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کریں۔",
        "recentchanges-label-newpage": "یہ ترمیم ایک نئے صفحے کی تخلیق ہے",
        "recentchanges-label-minor": "یہ ایک معمولی ترمیم ہے",
        "recentchanges-label-bot": "اس ترمیم کو ایک روبہ نے انجام دیا ہے",
        "minoreditletter": "م",
        "newpageletter": "نیا ..",
        "boteditletter": " خودکار",
+       "number_of_watching_users_pageview": "[$1 مشاہد {{PLURAL:$1|صارف|صارفین}}]",
+       "rc_categories": "ان زمروں تک محدود رکھیں («|» سے علاحدہ کریں):",
        "rc_categories_any": "کوئی بھی منتخب",
-       "rc-change-size-new": "$1 {{PLURAL:$1|بائٹ|بائٹ}} تبدیلی کے بعد",
+       "rc-change-size-new": "تبدیلی کے بعد $1 {{PLURAL:$1|بائٹ}}",
+       "newsectionsummary": "/* $1 */ نیا قطعہ",
        "rc-enhanced-expand": "تفصیلات دکھائیں",
        "rc-enhanced-hide": "تفصیلات چھپائیے",
+       "rc-old-title": "اصلاً «$1» کے عنوان سے تخلیق شدہ",
        "recentchangeslinked": "متعلقہ تبدیلیاں",
        "recentchangeslinked-feed": "متعلقہ تبدیلیاں",
        "recentchangeslinked-toolbox": "متعلقہ تبدیلیاں",
        "recentchangeslinked-title": "\"$1\" سے متعلقہ تبدیلیاں",
        "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات متجل (bold) نظر آئیں گےـ",
        "recentchangeslinked-page": "صفحۂ منصوبہ دیکھئے",
+       "recentchangeslinked-to": "اس کی بجائے درج کردہ صفحہ سے مربوط صفحات کی تبدیلیاں دکھائیں",
        "recentchanges-page-added-to-category": "[[:$1]] کو زمرہ میں شامل کیا گیا",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] اور {{PLURAL:$2|ایک صفحہ|$2 صفحات}} زمرہ میں شامل {{PLURAL:$2|کیا گیا|$2 کیے گئے}}",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] کو زمرہ میں شامل کر دیا گیا، [[Special:WhatLinksHere/$1|یہ صفحہ دیگر صفحات میں بھی موجود ہے]]",
        "recentchanges-page-removed-from-category": "[[:$1]] کو زمرہ سے ہٹایا",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] زمرے سے ہٹا دیا گیا ہے، [[Special:WhatLinksHere/$1|یہ صفحہ دیگر صفحات میں بھی موجود ہے]]",
        "autochange-username": "میڈیاویکی خودکار تبدیلیاں",
        "upload": "اپلوڈ",
        "uploadbtn": "زبراثقال ملف (اپ لوڈ فائل)",
        "reuploaddesc": "زبراثقال ورقہ (فارم) کیجانب واپس۔",
+       "upload-tryagain": "فائل کی تبدیل شدہ وضاحت روانہ کریں",
        "uploadnologin": "آپ داخل شدہ حالت میں نہیں",
        "uploadnologintext": "فائلیں اپلوڈ کرنے کے لیے براہ کرم $1 ہوں",
-       "uploadtext": "\n'''اطلاع''': اگر آپ اپنی فائل اپلوڈ کرتے وقت خلاصہ کے خانے میں درج ذیل دو باتوں کی وضاحت نہیں کریں گے تو اس فائل کو حذف کیا جاسکتا ہے:\n# فائل کا '''مـاخـذ''' ، یعنی:\n#*اگر یہ آپ نے خود تخلیق کی ہے تو اسے بیان کریں۔\n#*اگر یہ آن لائن دستیاب ہے تو اس سائٹ کا  '''ربط''' درج کریں۔\n#*اگر آپ نے اسے کسی دوسری زبان کے {{SITENAME}} سے لیا ہے تو اسکا نام تحریر کریں۔\n#صاحب حق طبع و نشر اور فائل کے اجازت نامہ کے بارے میں:\n#* فائل کے اجازت نامہ کے متعلق یہ درج کریں کہ اس کی موجودہ حیثیت کیا ہے۔\n#*اگر آپ خود اسکا حق طبع و نشر رکھتے ہیں تو آپ پر لازم ہے کہ آپ اسے [[دائرۂ عام]] (پبلک ڈومین) میں بھی شائع کریں۔\n\nجب کوئی صارف مستقل ایسی فائل اپلوڈ کرتا رہے جس کے اجازت نامہ کے بارے میں غلط بیانی کی گئی ہو یا وہ مستقل ایسی تصاویر اپلوڈ کرے جن کے بارے میں کوئی وضاحت موجود نہ ہو تو ایسی صورت میں اس صارف پر پابندی لگائے جانے کا قوی امکان موجود ہے۔\n\nفائل اپلوڈ کرنے کے لیے ذیل میں موجود فارم استعمال کریں، اگر آپ جملہ اپلوڈ کردہ تصاویر کو دیکھنا یا تلاش کرنا چاہتے ہیں تو [[Special:FileList|اس فہرست]] کو ملاحظہ فرمائیں۔ <br /> تمام اپلوڈ کردہ و حذف شدہ تصاویر کو [[Special:Log/upload|نوشتۂ منتقلی]] میں درج کر لیا جاتا ہے۔\n\nتصویر کی منتقلی کے بعد، اسکو کسی صفحہ پر رکھنے کیلیے مندرجہ ذیل طریقہ سے استعمال کریں۔\n\n'''<nowiki>[[تصویر:فائل کا نام|متبادل متن]]</nowiki>'''\n\n* مندرجہ بالا رموز آپ انگریزی میں بھی درج کرسکتے ہیں، یعنی\n<nowiki>[[Image:File name|Alt.text]]</nowiki>\n* فائل کا ربط درج کرنے کے لیے۔ '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>'''\n* ملف کا نام؛ حرف ابجد کے لیے حساس ہے لہذا اگر اپلوڈ کرتے وقت فائل کا نام -- name:JPG  ہے اور آپ name:jpg یــا Name:jpg کا ربط درج کرتے ہیں تو ربط کام نہیں کرے گا۔",
+       "upload_directory_missing": "اپلوڈ فولڈر ($1) موجود نہیں اور ویب سرور کے ذریعہ اسے تخلیق نہیں کیا جا سکا۔",
+       "upload_directory_read_only": "اپلوڈ فولڈر ($1) میں ویب سرور لکھ نہیں پا رہا ہے۔",
+       "uploaderror": "اپلوڈ کے دوران میں نقص",
+       "upload-recreate-warning": "<strong>انتباہ: اس نام کی فائل حذف یا منتقل کر دی گئی ہے۔</strong>\n\nآسانی کے لیے ذیل میں اس صفحہ کا نوشتہ منتقلی و حذف شدگی درج ہے:",
+       "uploadtext": "فائلیں اپلوڈ کرنے کے لیے درج ذیل فارم پُر کریں۔\n\n'''اطلاع''': اگر آپ اپنی فائل اپلوڈ کرتے وقت خلاصہ کے خانے میں درج ذیل دو باتوں کی وضاحت نہیں کریں گے تو اس فائل کو حذف کیا جاسکتا ہے:\n# فائل کا '''مـاخـذ''' ، یعنی:\n#*اگر یہ آپ نے خود تخلیق کی ہے تو اسے بیان کریں۔\n#*اگر یہ آن لائن دستیاب ہے تو اس سائٹ کا  '''ربط''' درج کریں۔\n#*اگر آپ نے اسے کسی دوسری زبان کے {{SITENAME}} سے لیا ہے تو اس کا نام تحریر کریں۔\n#صاحب حق طبع و نشر اور فائل کے اجازت نامہ کے بارے میں:\n#* فائل کے اجازت نامہ کے متعلق یہ درج کریں کہ اس کی موجودہ حیثیت کیا ہے۔\n#*اگر آپ خود اسکا حق طبع و نشر رکھتے ہیں تو آپ پر لازم ہے کہ آپ اسے [[دائرۂ عام]] (پبلک ڈومین) میں بھی شائع کریں۔\n\nجب کوئی صارف مستقل ایسی فائل اپلوڈ کرتا رہے جس کے اجازت نامہ کے بارے میں غلط بیانی کی گئی ہو یا وہ مستقل ایسی تصاویر اپلوڈ کرے جن کے بارے میں کوئی وضاحت موجود نہ ہو تو ایسی صورت میں اس صارف پر پابندی لگائے جانے کا قوی امکان موجود ہے۔\n\nفائل اپلوڈ کرنے کے لیے ذیل میں موجود فارم استعمال کریں، اگر آپ جملہ اپلوڈ کردہ تصاویر کو دیکھنا یا تلاش کرنا چاہتے ہیں تو [[Special:FileList|اس فہرست]] کو ملاحظہ فرمائیں۔ <br /> تمام اپلوڈ کردہ و حذف شدہ تصاویر کو [[Special:Log/upload|نوشتۂ منتقلی]] اور [[Special:Log/delete|نوشتہ حذف شدگی]] میں درج کر لیا جاتا ہے۔\n\nتصویر کی منتقلی کے بعد، اس کو کسی صفحہ پر رکھنے کیلیے مندرجہ ذیل طریقہ سے استعمال کریں۔\n\n'''<nowiki>[[تصویر:فائل کا نام|متبادل متن]]</nowiki>'''\n\n* فائل کا ربط درج کرنے کے لیے۔ '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>'''\n* فائل کا نام چھوٹے بڑے حروف کے معاملہ میں حساس ہے لہذا اگر اپلوڈ کرتے وقت فائل کا نام -- name:JPG  ہے اور آپ name:jpg یــا Name:jpg کا ربط درج کرتے ہیں تو ربط کام نہیں کرے گا۔",
+       "upload-permitted": "اجازت یافتہ فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
+       "upload-preferred": "ترجیحی فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
+       "upload-prohibited": "ممنوع فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
        "uploadlogpage": "نوشتۂ زبراثقال (اپ لوڈ لاگ)",
        "uploadlogpagetext": "درج ذیل میں حالیہ زبراثقال (اپ لوڈ) کی گئی املاف (فائلوں) کی فہرست دی گئی ہے۔",
+       "filename": "فائل کا نام",
        "filedesc": "خلاصہ",
        "fileuploadsummary": "خلاصہ :",
+       "filereuploadsummary": "فائل کی تبدیلیاں:",
+       "filestatus": "کاپی رائٹ کی صورت حال:",
        "filesource": "ذرائع",
        "ignorewarning": "انتباہ نظرانداز کرتے ہوۓ بہرصورت ملف (فائل) کو محفوظ کرلیا جاۓ۔",
        "ignorewarnings": "ہر انتباہ نظرانداز کردیا جاۓ۔",
+       "minlength1": "فائل کے ناموں میں کم از کم ایک حرف ہونا ضروری ہے۔",
+       "illegalfilename": "اس فائل کے نام \"$1\" میں ایسے حروف موجود ہیں جو صفحہ کے عنوانات میں ممنوع ہیں۔\nبراہ کرم فائل کا نام تبدیل کرکے دوبارہ اپلوڈ کرنے کی کوشش کریں۔",
+       "filename-toolong": "فائل کے نام 240 بائٹ سے زیادہ طویل نہ ہوں۔",
        "badfilename": "ملف (فائل) کا نام \"$1\" ، تبدیل کردیا گیا۔",
+       "filetype-mime-mismatch": "فائل کی توسیع «$1.‎» فائل کی MIME قسم ($2) کے مطابق نہیں۔",
+       "filetype-badmime": "MIME قسم \"$1\" کی فائلوں کو اپلوڈ کرنے کی اجازت نہیں ہے۔",
+       "filetype-missing": "اس فائل کی کوئی توسیع نہیں ہے (مثلاً  \".jpg\")۔",
+       "empty-file": "آپ کی ارسال کردہ فائل خالی تھی۔",
+       "file-too-large": "آپ کی ارسال کردہ فائل بہت بڑی تھی",
+       "filename-tooshort": "فائل کا نام انتہائی مختصر ہے۔",
+       "filetype-banned": "فائل کی اس قسم پر پابندی عائد ہے۔",
+       "verification-error": "یہ فائل، فائل کی تصدیق میں کامیاب نہیں ہو سکی۔",
+       "hookaborted": "آپ نے جو تبدیلی کرنے کی کوشش کی اسے کسی توسیع نے منسوخ کر دیا۔",
+       "illegal-filename": "اس نام کی فائل ممنوع ہے۔",
+       "overwrite": "موجودہ فائل کو دوبارہ اپلوڈ کرنے کی اجازت نہیں۔",
+       "unknown-error": "نامعلوم نقص واقع ہوا۔",
+       "tmp-create-error": "عارضی فائل نہیں بن سکی۔",
+       "tmp-write-error": "عارضی فائل کی تحریر کے دوران میں نقص۔",
+       "large-file": "اس بات کی سفارش کی جاتی ہے کہ فائلوں کا حجم $1 سے زیادہ نہ ہو؛\nاس فائل کا حجم $2 ہے۔",
+       "largefileserver": "یہ فائل سرور پر تعین کردہ تشکیل سے بڑی ہے۔",
+       "emptyfile": "لگتا ہے آپ کی اپلوڈ کردہ فائل خالی ہے۔\nایسا ٹائپنگ میں کوئی غلطی کی وجہ سے ہو سکتا ہے۔\nبرائے مہربانی جانچ کر لیں کہ آیا آپ واقعی اس فائل کو اپلوڈ کرنا چاہتے ہیں۔",
+       "windows-nonascii-filename": "یہ ویکی خاص حروف کے ساتھ فائل کا نام تسلیم نہیں کرتا۔",
        "fileexists": "اس نام سے ایک فائل پہلے سے موجود ہے، اگر آپ کو یقین نہ ہو کہ اسے حذف کردیا جانا چاہیے تو براہ کرم  <strong>[[:$1]]</strong> کو ایک نظر دیکھ لیجیے۔ [[$1|thumb]]",
        "uploadwarning": "انتباہ بہ سلسلۂ زبراثقال",
+       "uploadwarning-text": "ذیل میں موجود فائل کی وضاحت میں تبدیلی کریں اور دوبارہ کوشش کریں۔",
        "savefile": "فائل محفوظ کریں",
+       "uploaddisabled": "اپلوڈ غیر فعال ہے۔",
+       "copyuploaddisabled": "بذریعہ یوآرایل اپلوڈ غیر فعال ہے۔",
+       "uploaddisabledtext": "فائل اپلوڈ غیر فعال ہے۔",
+       "uploaded-hostile-svg": "اپلوڈ کردہ ایس وی جی فائل کے اسٹائل عنصر میں غیر محفوظ سی ایس ایس دریافت ہوئی ہے۔",
+       "uploadscriptednamespace": "اس ایس وی جی فائل میں غیر قانونی نام فضا \"$1\" موجود ہے۔",
+       "uploadinvalidxml": "اپلوڈ کردہ فائل میں موجود ایکس ایم ایل کا تجزیہ نہیں کیا جا سکا۔",
+       "uploadvirus": "اس فائل میں وائرس موجود ہے!\nتفصیلات: $1",
+       "upload-source": "اصل فائل",
        "sourcefilename": "اسم ملف (فائل) کا منبع:",
+       "sourceurl": "اصل یوآرایل",
        "destfilename": "تعین شدہ اسم ملف:",
+       "upload-maxfilesize": "فائل کا زیادہ سے زیادہ حجم: $1",
+       "upload-description": "فائل کی وضاحت",
+       "upload-options": "اپلوڈ کے اختیارات",
        "watchthisupload": "یہ صفحہ زیر نظر کریں",
+       "upload-proto-error": "غلط پروٹوکول",
+       "upload-file-error": "داخلی نقص",
+       "upload-misc-error": "اپلوڈ کے دوران میں نامعلوم نقص",
+       "upload-too-many-redirects": "اس یوآرایل میں بہت سارے رجوع مکررات ہیں",
+       "upload-http-error": "ایچ ٹی ٹی پی نقص واقع ہوا: $1",
+       "upload-copy-upload-invalid-domain": "اس ڈومین سے کاپی اپلوڈ دستیاب نہیں ہیں۔",
        "upload-dialog-disabled": "اس ویکی پر اس ڈائیلاگ سے فائل اپ لوڈز غیر فعال ہیںَ",
+       "upload-dialog-title": "فائل اپلوڈ کریں",
        "upload-dialog-button-cancel": "منسوخ",
        "upload-dialog-button-done": "مکمل",
        "upload-dialog-button-save": "محفوظ",
        "upload-form-label-own-work": "یہ میرا ذاتی کام ہے",
        "upload-form-label-infoform-categories": "زمرہ جات",
        "upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work-message-generic-local": "میں اس بات کی تصدیق کرتا ہوں کہ {{SITENAME}} میں موجود اجازت ناموں کی حکمت عملیوں اور استعمال کے جملہ شرائط کی پیروی کرتے ہوئے اس فائل کو اپلوڈ کر رہا ہوں۔",
+       "upload-form-label-not-own-work-message-generic-local": "اگر آپ {{SITENAME}} کی حکمت عملیوں کے تحت اس فائل کو اپلوڈ نہیں کر سکتے تو براہ کرم اسے بند کرکے دوسرا طریقہ استعمال کرنے کی کوشش کریں۔",
+       "upload-form-label-not-own-work-local-generic-local": "نیز آپ [[Special:Upload|ڈیفالٹ اپلوڈ صفحہ]] بھی استعمال کر سکتے ہیں۔",
+       "upload-form-label-own-work-message-generic-foreign": "میں یہ سمجھتا ہوں کہ اس فائل کو ایک مشترکہ ذخیرے میں اپلوڈ کیا جا رہا ہے اور اس امر کی تصدیق کرتا ہوں کہ اس کام کی انجام دہی کے دوران میں یہاں موجود استعمال کے جملہ شرائط اور اجازت ناموں کی تمام حکمت عملیوں کی پیروی کر رہا ہوں۔",
+       "upload-form-label-not-own-work-message-generic-foreign": "اگر آپ مشترکہ ذخیرے کی حکمت عملیوں کے تحت اس فائل کو اپلوڈ نہیں کر سکتے تو براہ کرم اسے بند کرکے دوسرا طریقہ استعمال کرنے کی کوشش کریں۔",
+       "upload-form-label-not-own-work-local-generic-foreign": "اگر اس فائل کو {{SITENAME}} کی مقررہ پالیسیوں کے تحت اپلوڈ کرنا ممکن ہو تو آپ [[Special:Upload|{{SITENAME}} کا اپلوڈ صفحہ]] استعمال کر سکتے ہیں۔",
+       "backend-fail-stream": "فائل $1 کی نمائش ممکن نہیں۔",
+       "backend-fail-backup": "فائل $1 کا احتیاطی نسخہ بنانا ممکن نہیں۔",
+       "backend-fail-notexists": "فائل $1 موجود نہیں ہے۔",
+       "backend-fail-hashes": "موازنہ کے لیے فائل کے ہیش کو حاصل نہیں کیا جا سکا۔",
+       "backend-fail-notsame": "$1 میں ایک غیر یکساں فائل پہلے سے موجود ہے۔",
+       "backend-fail-invalidpath": "$1 ذخیرہ اندوزی کا درست راستہ نہیں ہے۔",
+       "backend-fail-delete": "فائل $1 کو حذف نہیں کیا جا سکا۔",
+       "backend-fail-describe": "فائل $1 کا میٹاڈیٹا تبدیل نہیں کیا جا سکا۔",
+       "backend-fail-alreadyexists": "فائل \"$1\" پہلے سے موجود ہے۔",
+       "backend-fail-store": "فائل $1 کو $2 میں محفوظ نہیں کیا جا سکا۔",
+       "backend-fail-copy": "فائل $1 کو $2 میں نقل نہیں کیا جا سکا۔",
+       "backend-fail-move": "فائل $1 کو $2 میں منتقل نہیں کیا جا سکا۔",
+       "backend-fail-opentemp": "عارضی فائل کھل نہیں سکی۔",
+       "backend-fail-writetemp": "عارضی فائل میں لکھا نہیں جا سکا۔",
+       "backend-fail-closetemp": "عارضی فائل بند نہیں ہو سکی۔",
+       "backend-fail-read": "فائل \"$1\" کو پڑھا نہ جا سکا۔",
+       "backend-fail-create": "فائل \"$1\" کو لکھا نہ جا سکا۔",
+       "backend-fail-maxsize": "فائل $1 کی معلومات نہیں لکھی جا سکی کیونکہ اس کا حجم {{PLURAL:$2|ایک بائٹ|$2 بائٹ}} سے زیادہ ہے۔",
+       "backend-fail-readonly": "فی الحال ذخیرہ کا پس منظر $1 فقط خواندگی حالت میں ہے۔ اس کی وجہ حسب ذیل ہے:\n\n\n<em>«$2»</em>",
+       "backend-fail-synced": "اس وقت فائل $1 داخلی ذخیرہ کے پس منظر کے اندر ناپائیدار حالت میں ہے۔",
+       "zip-wrong-format": "یہ زپ فائل نہیں تھی۔",
+       "uploadstash-errclear": "فائل کی صفائی ناکام۔",
+       "uploadstash-refresh": "فائلوں کی فہرست کو تازہ کریں",
+       "uploadstash-thumbnail": "تھمب نیل دیکھیں",
+       "invalid-chunk-offset": "آفسیٹ کا قطعہ نادرست ہے",
+       "img-auth-accessdenied": "رسائی معطل",
+       "http-invalid-url": "نادرست یوآرایل: $1",
+       "http-read-error": "HTTP خواندگی میں نقص۔",
+       "http-timed-out": "HTTP درخواست کی مہلت ختم ہو گئی۔",
+       "http-curl-error": "یوآرایل $1 کو اخذ کرنے کے دوران میں نقص",
+       "http-bad-status": "HTTP درخواست کے دوران میں ایک مشکل پیش آگئی: $1 $2",
+       "upload-curl-error6": "یوآرایل تک پہنچنا ممکن نہیں",
+       "upload-curl-error6-text": "فراہم کردہ یوآرایل قابل رسائی نہیں ہے۔\nبراہ کرم اس یوآرایل کو دوبارہ جانچ لیں کہ آیا وہ درست ہے اور متعلقہ سائٹ فعال ہے یا نہیں۔",
+       "upload-curl-error28": "اپلوڈ کی مہلت ختم",
+       "upload-curl-error28-text": "یہ سائٹ جواب دینے میں بہت زیادہ وقت لے رہی ہے۔\nبراہ کرم اس سائٹ کو جانچ لیں کہ آیا وہ فعال ہے یا نہیں، اور کچھ دیر انتظار کرنے کے بعد دوبارہ کوشش کریں۔\nشاید آپ اسے کم مصروف وقت میں آزمانا چاہیں۔",
        "license": "اجازہ:",
        "license-header": "اجازہ کاری",
+       "nolicense": "غیر منتخب",
+       "licenses-edit": "اجازت نامہ کے اختیارات میں ترمیم کریں",
+       "license-nopreview": "(نمائش دستیاب نہیں)",
+       "upload_source_url": "(آپ نے ایک درست اور عوامی طور پر قابل رسائی یوآرایل سے اس فائل کا انتخاب کیا ہے)",
+       "upload_source_file": "(آپ نے اپنے کمپیوٹر سے اس فائل کو منتخب کیا ہے)",
        "listfiles-delete": "حذف",
+       "listfiles-summary": "اس خصوصی صفحہ میں تمام اپلوڈ کردہ فائلیں نظر آئیں گی۔",
+       "listfiles_search_for": "میڈیا کے نام کو تلاش کریں:",
+       "listfiles-userdoesnotexist": "«$1» کے نام سے کھاتہ موجود نہیں۔",
        "imgfile": "ملف",
        "listfiles": "فہرست فائل",
+       "listfiles_thumb": "تھمب نیل",
        "listfiles_date": "تاریخ",
        "listfiles_name": "نام",
        "listfiles_user": "صارف",
        "listfiles_size": "حجم",
        "listfiles_description": "تفصیل",
        "listfiles_count": "ورژن",
+       "listfiles-show-all": "تصویروں کے پرانے نسخے شامل کریں",
        "listfiles-latestversion": "موجودہ ورژن",
        "listfiles-latestversion-yes": "ہاں",
        "listfiles-latestversion-no": "نہیں",
        "filehist-datetime": "تاریخ/وقت",
        "filehist-thumb": "اظفورہ",
        "filehist-thumbtext": "$1 کا تھمب نیل (thumbnail) ورژن",
+       "filehist-nothumb": "تھمب نیل نہیں ہے",
        "filehist-user": "صارف",
        "filehist-dimensions": "ابعاد",
        "filehist-filesize": "تصویر کا حجم",
        "imagelinks": "ملف کا استعمال",
        "linkstoimage": "اِس ملف کے ساتھ درج ذیل {{PLURAL:$1|صفحہ مربوط ہے|$1 صفحات مربوط ہیں}}",
        "nolinkstoimage": "ایسے کوئی صفحات نہیں جو اس ملف (فائل) سے رابطہ رکھتے ہوں۔",
+       "morelinkstoimage": "اس فائل کے [[Special:WhatLinksHere/$1|مزید روابط]] ملاحظہ فرمائیں۔",
+       "linkstoimage-redirect": "$1 (فائل رجوع مکرر) $2",
+       "duplicatesoffile": "ذیل میں موجود {{PLURAL:$1|فائل|فائلیں}} اس فائل کی نقل {{PLURAL:$1|ہے|ہیں}}\n([[Special:FileDuplicateSearch/$2|مزید تفصیلات]]):",
+       "sharedupload": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔",
+       "sharedupload-desc-there": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nمزید معلومات کے لیے براہ کرم [$2 فائل کا صفحۂ وضاحت] ملاحظہ فرمائیں۔",
        "sharedupload-desc-here": "یہ ملف $1 سے ہے اور دوسرے منصوبوں میں استعمال ہوسکتا ہے۔\nاِس کے [$2 ملفاتی صفحۂ وضاحت] سے تفصیل درج ذیل ہے۔",
+       "sharedupload-desc-edit": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nاگر آپ [$2 فائل کے صفحۂ وضاحت] میں موجود معلومات میں ترمیم کرنا چاہیں تو وہاں کر سکتے ہیں۔",
+       "sharedupload-desc-create": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nاگر آپ [$2 فائل کے صفحۂ وضاحت] میں موجود معلومات میں ترمیم کرنا چاہیں تو وہاں کر سکتے ہیں۔",
+       "filepage-nofile": "اس نام سے کوئی فائل موجود نہیں ہے۔",
+       "filepage-nofile-link": "اس نام سے کوئی فائل موجود نہیں ہے، لیکن آپ [$1 اسے اپلوڈ کر سکتے ہیں]۔",
+       "uploadnewversion-linktext": "اس فائل کا نیا نسخہ اپلوڈ کریں",
+       "shared-repo-from": "از $1",
+       "shared-repo": "مشترکہ ذخیرہ",
        "upload-disallowed-here": "آپ اوپر چھڑا کر اس ملف کو نہیں لکھ سکتے۔",
+       "filerevert": "$1 کا استرجع کریں",
+       "filerevert-legend": "فائل کا استرجع کریں",
+       "filerevert-comment": "وجہ:",
+       "filerevert-submit": "استرجع کریں",
+       "filedelete": "$1 کو حذف کریں",
+       "filedelete-legend": "فائل حذف کریں",
        "filedelete-comment": "وجہ:",
        "filedelete-submit": "حذف کریں",
        "filedelete-success": " (\"اقدام مکمل ہوا\")۔",
        "filedelete-success-old": " (\"اقدام مکمل ہوا\")",
+       "filedelete-nofile": "<strong>$1</strong> موجود نہیں ہے۔",
+       "filedelete-otherreason": "دوسری/اضافی وجہ:",
+       "filedelete-reason-otherlist": "دوسری وجہ",
+       "filedelete-reason-dropdown": "* عمومی وجوہات حذف\n** کاپی رائٹ کی خلاف ورزی\n** دوہری فائل",
+       "filedelete-edit-reasonlist": "حذف کی وجوہات میں ترمیم کریں",
+       "filedelete-maintenance-title": "فائل حذف نہیں کی جا سکتی",
+       "mimesearch": "MIME تلاش",
+       "mimetype": "MIME قسم:",
        "download": "زیراثقال (ڈاؤن لوڈ)",
+       "unwatchedpages": "نادیدہ صفحات",
        "listredirects": "فہرست متبادل ربط",
+       "listduplicatedfiles": "مکررات کے ساتھ فائلوں کی فہرست",
        "unusedtemplates": "غیر استعمال شدہ سانچے",
        "unusedtemplateswlh": "دیگر روابط",
        "randompage": "بےترتیب صفحہ",
+       "randomincategory": "زمرہ میں بے ترتیب صفحہ",
+       "randomincategory-invalidcategory": "عنوان «$1» زمرے کا درست نام نہیں ہے۔",
+       "randomincategory-nopages": "[[:Category:$1|$1]] زمرہ میں کوئی صفحہ نہیں ہے۔",
        "randomincategory-category": "زمرہ:",
+       "randomincategory-legend": "زمرہ میں بے ترتیب صفحہ",
        "randomincategory-submit": "جانا",
+       "randomredirect": "بے ترتيب رجوع مکرر",
+       "randomredirect-nopages": "«$1» نام فضا میں کوئی رجوع مکرر نہیں ہے۔",
        "statistics": "اعداد و شمار",
        "statistics-header-pages": "صفحات کے اعداد و شمار",
        "statistics-header-edits": "ترمیمی اعداد و شمار",
        "statistics-edits-average": "فی صفحہ اوسط ترامیم",
        "statistics-users": "مندرج [[خاص:فہرست صارفین، صارف فہرست|صارفین]]",
        "statistics-users-active": "متحرک صارفین",
+       "pageswithprop": "صفحات مع خاصیت صفحہ",
+       "pageswithprop-legend": "صفحات مع خاصیت صفحہ",
+       "pageswithprop-text": "اس صفحہ میں ان تمام صفحات کی فہرست موجود ہے جس کسی مخصوص خاصیت صفحہ کو استعمال کر رہے ہیں۔",
+       "pageswithprop-prop": "نام خاصیت:",
        "pageswithprop-submit": "ٹھیک",
        "doubleredirects": "دوہرے متبادل ربط",
+       "double-redirect-fixed-move": "[[$1]] کو منتقل کر دیا گیا۔\nیہ از خود تازہ ہو گیا اور اب [[$2]] سے رجوع مکرر ہے۔",
        "brokenredirects": "نامکمل متبادل ربط",
        "brokenredirects-edit": "ترمیم کریں",
        "brokenredirects-delete": "حذف",
+       "withoutinterwiki": "صفحات بدون بین الویکی روابط",
        "withoutinterwiki-legend": "سابقہ",
        "withoutinterwiki-submit": "دکھائیں",
        "fewestrevisions": "کم نظرِ ثانی شدہ مضامین",
        "nbytes": "$1 {{PLURAL:$1|لکمہ|لکمہ جات}}",
        "ncategories": "{{PLURAL:$1|زمرہ|زمرہ جات}} $1",
-       "ninterwikis": "$1 {{PLURAL:$1|بین الویکی|بین الویکی}}",
-       "nlinks": "$1 {{PLURAL:$1|بÛ\8cÙ\86 Ø§Ù\84Ù\88Û\8cÚ©Û\8c|بÛ\8cÙ\86 Ø§Ù\84Ù\88Û\8cÚ©Û\8c}}",
+       "ninterwikis": "$1 {{PLURAL:$1|بین الویکی ربط|بین الویکی روابط}}",
+       "nlinks": "$1 {{PLURAL:$1|ربط|رÙ\88ابط}}",
        "nmembers": "{{PLURAL:$1|رکن|اراکین}}",
+       "nmemberschanged": "$1 ← $2 {{PLURAL:$2|رکن|اراکین}}",
        "nrevisions": "$1 {{PLURAL:$1|نظر ثانی|نظر ثانیاں}}",
        "nimagelinks": "$1 پر مستعمل {{PLURAL:$1|صفحہ|صفحات}}",
        "ntransclusions": "$1 پر مستعمل {{PLURAL:$1|صفحہ|صفحات}}",
        "unusedimages": "غیر استعمال شدہ فائلیں",
        "wantedcategories": "طلب شدہ زمرہ جات",
        "wantedpages": "درخواست شدہ مضامین",
+       "wantedpages-summary": "ذیل میں ان غیر موجود صفحات کی فہرست ہے جن سے بہت سارے روابط مربوط ہیں، البتہ ان میں وہ صفحات شامل نہیں جن میں محض ان سے مربوط رجوع مکررات موجود ہیں۔ ان صفحوں کو دیکھنے کے لیے [[{{#special:BrokenRedirects}}|شکستہ روابط کی فہرست]] ملاحظہ فرمائیں۔",
+       "wantedpages-badtitle": "نتائج میں نادرست عنوان: $1",
        "wantedfiles": "مطلوب تصاویر",
+       "wantedfiletext-cat": "ذیل میں موجود فائلیں مستعمل ہیں لیکن موجود نہیں۔ البتہ اس بات کا امکان ہے کہ بیرونی ذخیروں کی موجود فائلیں یہاں اس فہرست میں درج ہو گئی ہوں۔ ایسے غلط امکانات کو <del>مٹا دیا جائے گا</del>۔ علاوہ ازیں، غیر موجود فائلوں پر مشتمل صفحات کی فہرست [[:$1]] میں ملاحظہ فرمائیں۔",
+       "wantedfiletext-cat-noforeign": "ذیل میں موجود فائلیں زیر استعمال ہیں لیکن موجود نہیں۔ علاوہ ازیں، جن صفحات میں یہ غیر موجود فائلیں زیر استعمال ہیں ان کی فہرست [[:$1]] میں ملاحظہ فرمائیں۔",
+       "wantedfiletext-nocat": "ذیل میں موجود فائلیں مستعمل ہیں لیکن موجود نہیں۔ البتہ اس بات کا امکان ہے کہ بیرونی ذخیروں کی موجود فائلیں یہاں اس فہرست میں درج ہو گئی ہوں۔ ایسے غلط امکانات کو <del>مٹا دیا جائے گا</del>۔",
+       "wantedfiletext-nocat-noforeign": "ذیل میں موجود فائلیں زیر استعمال ہیں لیکن موجود نہیں۔",
        "wantedtemplates": "مطلوب سانچے",
        "mostlinked": "سب سے زیادہ ربط والے مضامین",
        "mostlinkedcategories": "سب سے زیادہ ربط والے زمرہ جات",
+       "mostlinkedtemplates": "کثیر مستعمل صفحات",
        "mostcategories": "سب سے زیادہ زمرہ جات والے مضامین",
        "mostimages": "سب سے زیادہ استعمال کردہ تصاویر",
        "mostinterwikis": "کثیر اندرونی ربط والے صفحات",
        "shortpages": "چھوٹے صفحات",
        "longpages": "طویل ترین صفحات",
        "deadendpages": "مردہ صفحات",
+       "deadendpagestext": "درج ذیل صفحات {{SITENAME}} کے دیگر صفحوں سے مربوط نہیں ہیں۔",
        "protectedpages": "محفوظ صفحات",
+       "protectedpages-indef": "فقط غیر متعین محفوظ شدگیاں",
        "protectedpages-summary": "ذیل میں ان صفحات کی فہرست موجود ہے جو ابھی محفوظ ہیں۔ محفوظ شدہ عنوانات جنہیں تخلیق نہیں کیا جا سکتا، ان کی فہرست کے لیے [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]] ملاحظہ فرمائیں۔",
+       "protectedpages-cascade": "فقط آبشاری محفوظ شدگیاں",
        "protectedpages-noredirect": "رجوع مکررات چھپائیں",
+       "protectedpagesempty": "ان پیرامیٹروں کے ساتھ فی الحال کوئی صفحہ محفوظ نہیں ہے۔",
        "protectedpages-timestamp": "وقت کی مہر",
        "protectedpages-page": "صفحہ",
        "protectedpages-expiry": "مدت محفوظ شدگی",
        "protectedpages-unknown-performer": "نامعلوم صارف",
        "protectedtitles": "محفوظ عنوانات",
        "protectedtitles-summary": "ذیل میں ان عنوانات کی فہرست ہے جنہیں تخلیق نہیں کیا جا سکتا، یہ عنوانات محفوظ شدہ ہیں۔ ان صفحات کی فہرست کے لیے جو ابھی محفوظ ہیں [[{{#special:ProtectedPages}}|{{int:protectedpages}}]] ملاحظہ فرمائیں۔",
+       "protectedtitlesempty": "ان پیرامیٹروں کے ساتھ فی الحال کوئی عنوان محفوظ نہیں ہے۔",
        "protectedtitles-submit": "دکھائیں",
        "listusers": "فہرست ارکان",
+       "listusers-editsonly": "محض ترمیم کرنے والے صارفین دکھائیں",
+       "listusers-creationsort": "تاریخ تخلیق کے مطابق مرتب کریں",
+       "listusers-desc": "نزولی ترتیب",
        "usereditcount": "$1 {{PLURAL:$1|ترمیم|ترامیم}}",
        "usercreated": "{{GENDER:$3|تخلیق شدہ}}  بتاریخ $1 بوقت $2",
        "newpages": "جدید صفحات",
        "ancientpages": "قدیم ترین صفحات",
        "move": "منتقـل",
        "movethispage": "یہ صفحہ منتقل کیجئے",
+       "unusedimagestext": "درج ذیل فائلیں موجود ہیں لیکن کسی صفحہ میں زیر استعمال نہیں۔\nممکن ہے کہ دیگر ویب سائٹیں براہ راست ربط کے ذریعہ کسی فائل سے مربوط ہوں، اور اس کے باوجود وہ فائل یہاں درج ہو گئی ہوں۔",
+       "unusedcategoriestext": "درج ذیل زمرہ جات موجود ہیں لیکن کسی مضمون یا دوسرے کسی زمرے میں مستعمل نہیں۔",
+       "notargettitle": "کوئی ہدف نہیں",
+       "notargettext": "اس اقدام کی تکمیل کے لیے آپ نے کسی صفحہ یا صارف کا تعین نہیں کیا ہے۔",
+       "nopagetitle": "ایسا کوئی صفحہ موجود نہیں",
+       "nopagetext": "آپ کا درج کردہ ہدف صفحہ موجود نہیں ہے۔",
        "pager-newer-n": "{{PLURAL:$1|جدید 1|جدید $1}}",
        "pager-older-n": "{{PLURAL:$1|پُرانا 1|پُرانے $1}}",
+       "suppress": "دبائیں",
+       "querypage-disabled": "اس خصوصی صفحہ کو بوجوہ غیر فعال کر دیا گیا ہے۔",
        "apihelp": "معاونت اے پی آئی",
        "apihelp-no-such-module": "ماڈیول \"$1\" نہیں ملا",
+       "apisandbox": "اے پی آئی کا تختۂ مشق",
+       "apisandbox-jsonly": "اے پی آئی کے تختۂ مشق کو استعمال کرنے کے لیے جاوا اسکرپٹ درکار ہے۔",
+       "apisandbox-api-disabled": "اس سائٹ پر اے پی آئی غیر فعال ہے۔",
+       "apisandbox-fullscreen": "پینل کو وسیع کریں",
+       "apisandbox-unfullscreen": "صفحہ دکھائیں",
+       "apisandbox-unfullscreen-tooltip": "تختہ مشق کا پینل چھوٹا کریں تاکہ میڈیاویکی کے روابطِ رہنمائی دسترس میں ہوں۔",
        "apisandbox-submit": "بنانے کی درخواست",
        "apisandbox-reset": "واضح",
-       "apisandbox-examples": "مثال کے طور پر",
-       "apisandbox-results": "نتیجہ",
+       "apisandbox-retry": "دوبارہ کوشش کریں",
+       "apisandbox-loading": "اے پی آئی ماڈیول \"$1\" کی معلومات لوڈ ہو رہی ہے۔۔۔",
+       "apisandbox-load-error": "اے پی آئی ماڈیول \"$1\" کی معلومات لوڈ ہونے کے دوران میں نقص واقع ہوا: $2",
+       "apisandbox-no-parameters": "اس اے پی آئی ماڈیول میں کوئی پیرامیٹر نہیں ہے۔",
+       "apisandbox-helpurls": "روابط رہنمائی",
+       "apisandbox-examples": "مثالیں",
+       "apisandbox-dynamic-parameters": "اضافی پیرامیٹر",
+       "apisandbox-dynamic-parameters-add-label": "پیرامیٹر شامل کریں:",
+       "apisandbox-dynamic-parameters-add-placeholder": "پیرامیٹر کا نام",
+       "apisandbox-dynamic-error-exists": "\"$1\" کے نام سے ایک پیرامیٹر پہلے سے موجود ہے۔",
+       "apisandbox-deprecated-parameters": "متروک پیرامیٹر",
+       "apisandbox-fetch-token": "ٹوکن کو خودکار طور پر پُر کریں",
+       "apisandbox-submit-invalid-fields-title": "بعض خانے نادرست ہیں",
+       "apisandbox-submit-invalid-fields-message": "براہ کرم نشان زد خانوں کو درست کرکے دوبارہ کوشش کریں۔",
+       "apisandbox-results": "نتائج",
+       "apisandbox-sending-request": "اے پی آئی درخواست بھیجی جا رہی ہے۔۔۔",
+       "apisandbox-loading-results": "اے پی آئی کے نتائج موصول ہو رہے ہیں۔۔۔",
+       "apisandbox-results-error": "اے پی آئی کوئری کا جواب لوڈ ہونے کے دوران میں نقص واقع ہوا: $1",
+       "apisandbox-request-url-label": "درخواست کا ربط:",
+       "apisandbox-request-time": "درخواست کا وقت: {{PLURAL:$1|$1 ملی سیکنڈ}}",
+       "apisandbox-results-fixtoken": "ٹوکن کو درست کرکے دوبارہ بھیجیں",
+       "apisandbox-results-fixtoken-fail": "\"$1\" ٹوکن اخذ کرنے میں ناکامی۔",
+       "apisandbox-alert-page": "اس صفحہ میں موجود خانے نادرست ہیں۔",
+       "apisandbox-alert-field": "اس خانے کی قدر نادرست ہے۔",
        "booksources": "کتابی وسائل",
        "booksources-search-legend": "تلاش برائے مآخذاتِ کتاب",
        "booksources-search": "تلاش",
+       "booksources-invalid-isbn": "درج کردہ آئی ایس بی این درست نہیں معلوم ہوتا؛ اصل ماخذ سے نقل کے دوران میں ہوئی غلطیوں کو جانچ لیں۔",
        "specialloguserlabel": "صارف:",
        "speciallogtitlelabel": "ہدف (عنوان یا {{ns:user}}:صارف نام برائے صارف):",
        "log": "نوشتہ جات",
        "logeventslist-submit": "دکھائیں",
+       "all-logs-page": "تمام عوامی نوشتہ جات",
+       "logempty": "نوشتہ میں اس سے مشابہ کوئی اندراج موجود نہیں ہے۔",
+       "log-title-wildcard": "اس عبارت سے شروع ہونے والے عناوین میں تلاش کریں",
+       "showhideselectedlogentries": "نوشتہ کے منتخب اندراج کی مرئیت تبدیل کریں",
+       "log-edit-tags": "نوشتہ کے منتخب اندراج کے ٹیگوں میں ترمیم کریں",
        "checkbox-select": "$1 کو منتخب کریں",
        "checkbox-all": "سب",
        "checkbox-none": "کچھ نہیں",
        "allpages": "تمام صفحات",
        "nextpage": "اگلا صفحہ ($1)",
        "prevpage": "پچھلا صفحہ ($1)",
-       "allpagesfrom": "مطلوبہ حرف شروع ہونے والے صفحات کی نمائش:",
+       "allpagesfrom": "اس حرف سے شروع ہونے والے صفحات دکھائیں:",
+       "allpagesto": "اس حرف پر ختم ہونے والے صفحات دکھائیں:",
        "allarticles": "تمام مقالات",
+       "allinnamespace": "تمام صفحات ($1 نام فضا)",
        "allpagessubmit": "چلو",
        "allpagesprefix": "مطلوبہ سابقہ سے شروع ہونے والے صفحات کی نمائش:",
+       "allpages-bad-ns": "{{SITENAME}} میں «$1» نام فضا موجود نہیں۔",
+       "allpages-hide-redirects": "رجوع مکررات چھپائیں",
+       "cachedspecial-viewing-cached-ttl": "آپ اس وقت اس صفحہ کا کیشے شدہ نسخہ دیکھ رہے ہیں جو ممکن ہے $1 پرانا ہو۔",
+       "cachedspecial-viewing-cached-ts": "آپ اس وقت اس صفحہ کا کیشے شدہ نسخہ دیکھ رہے ہیں جو شاید مکمل طور پر اصلی نہ ہو۔",
+       "cachedspecial-refresh-now": "تازہ ترین دیکھیں۔",
        "categories": "زمرہ",
        "categories-submit": "دکھائیں",
        "categoriespagetext": "ذیل میں موجود {{PLURAL:$1|زمرہ|زمرہ جات}} میں صفحات یا میڈیا موجود ہے۔\n[[Special:UnusedCategories|غیر مستعمل زمرہ جات]] یہاں نہیں دکھائے گئے ہیں۔\nنیز [[Special:WantedCategories|مطلوبہ زمرہ جات کی فہرست]] بھی ملاحظہ فرمائیں۔",
+       "categoriesfrom": "اس حرف سے شروع ہونے والے زمرے دکھائیں:",
+       "deletedcontributions": "حذف شدہ صارف کی شراکتیں",
+       "deletedcontributions-title": "صارف کی حذف شدہ شراکتیں",
        "sp-deletedcontributions-contribs": "شراکتیں",
        "linksearch": "بیرونی روابط کی تلاش",
        "linksearch-pat": "تلاش کا انداز",
        "linksearch-ns": "فضائے نام:",
        "linksearch-ok": "تلاش",
        "linksearch-line": "$1 مربوط ہے $2 سے",
+       "listusersfrom": "اس حرف سے شروع ہونے والے صارفین کے نام دکھائیں:",
        "listusers-submit": "دکھاؤ",
        "listusers-noresult": "یہ صارف نہیں ملا",
        "listusers-blocked": "(مسدود)",
        "activeusers": "متحرک صارفین کی فہرست",
+       "activeusers-intro": "ذیل میں ان صارفین کی فہرست ہے جو گزشتہ $1 {{PLURAL:$1|دن|دنوں}} میں کسی وقت فعال رہے ہوں۔",
+       "activeusers-count": "گزشتہ {{PLURAL:$3|دن|$3 دنوں}} میں $1 {{PLURAL:$1|اقدام|اقدامات}}",
+       "activeusers-from": "اس حرف سے شروع ہونے والے صارفین کے نام دکھائیں:",
        "activeusers-hidebots": "پوشیدہ خود کار صارف",
        "activeusers-hidesysops": "پوشیدہ منتظمین",
        "activeusers-noresult": "یہ صارف نہیں مل سکا",
+       "activeusers-submit": "فعال صارفین دکھائیں",
+       "listgrouprights": "صارف گروہوں کے اختیارات",
+       "listgrouprights-summary": "ذیل میں اس ویکی پر موجود صارف گروہوں کی فہرست درج ہے۔ اس میں دائیں جانب گروہ کا نام اور بائیں جانب متعلقہ گروہ کو حاصل شدہ اختیارات کی تفصیل بیان کی گئی ہے۔\nانفرادی اختیارات کے متعلق [[{{MediaWiki:Listgrouprights-helppage}}|اضافی معلومات یہاں]] دیکھی جا سکتی ہیں۔",
+       "listgrouprights-key": "عنوان:\n* <span class=\"listgrouprights-granted\">تفویض کردہ اختیارات</span>\n* <span class=\"listgrouprights-revoked\">منسوخ کردہ اختیارات</span>",
        "listgrouprights-group": "گروہ",
        "listgrouprights-rights": "اختیارات",
+       "listgrouprights-helppage": "Help:اختیاراتِ گروہ",
        "listgrouprights-members": "(اراکین کی فہرست)",
+       "listgrouprights-addgroup": "{{PLURAL:$2|اس گروہ|ان گروہوں}} میں شامل کرنے کا اختیار ہے: \n\n$1",
+       "listgrouprights-removegroup": "{{PLURAL:$2|اس گروہ|ان گروہوں}} سے ہٹانے کا اختیار ہے: \n\n$1",
+       "listgrouprights-addgroup-all": "تمام گروہوں کا ا ضافہ کریں",
+       "listgrouprights-removegroup-all": "تمام گروہوں کو ہٹانے کا اختیار ہے",
+       "listgrouprights-addgroup-self": "{{PLURAL:$2|اس گروہ|ان گروہوں}} میں از خود شامل ہونے کا اختیار ہے: \n\n$1",
+       "listgrouprights-removegroup-self": "{{PLURAL:$2|اس گروہ|ان گروہوں}} سے از خود نکلنے کا اختیار ہے: \n\n$1",
+       "listgrouprights-addgroup-self-all": "تمام گروہوں میں از خود شامل ہونے کا اختیار ہے",
+       "listgrouprights-removegroup-self-all": "تمام گروہوں سے از خود نکلنے کا اختیار ہے",
+       "listgrouprights-namespaceprotection-header": "نام فضا پابندیاں",
        "listgrouprights-namespaceprotection-namespace": "فضائے نام",
+       "listgrouprights-namespaceprotection-restrictedto": "ترمیم کی اجازت دینے والے اختیار(ات)",
+       "listgrants": "عطا",
+       "listgrants-grant": "عطیہ",
+       "listgrants-rights": "حقوق",
+       "trackingcategories": "متلاشی زمرہ جات",
+       "trackingcategories-summary": "اس صفحہ میں ان متلاشی زمروں کی فہرست موجود جنہیں خودکار طور پر میڈیاویکی سافٹ ویئر تخلیق کرتا ہے۔ نیز {{ns:8}} نام فضا میں موجود متعلقہ نظامی پیغامات کے ذریعہ ان کے ناموں میں تبدیلی کی جا سکتی ہے۔",
        "trackingcategories-msg": "کھوجی زمرہ",
        "trackingcategories-name": "پیغام کا عنوان",
        "trackingcategories-desc": "زمرہ کی شمولیت کا معیار",
        "restricted-displaytitle-ignored": "صفحات مع نظرانداز کردہ عناوین",
+       "trackingcategories-nodesc": "کوئی وضاحت دستیاب نہیں۔",
        "trackingcategories-disabled": "زمرہ غیر فعال ہے",
+       "mailnologin": "بھیجنے کے لیے کوئی پتہ نہیں",
        "mailnologintext": "دیگر ارکان کو برقی خط ارسال کرنے کیلیۓ لازم ہے کہ آپ [[Special:UserLogin|داخل شدہ]] حالت میں ہوں اور آپ کی [[Special:Preferences|ترجیحات]] ایک درست برقی خط کا پتا درج ہو۔",
        "emailuser": "صارف کو برقی خط لکھیں",
+       "emailuser-title-target": "اس {{GENDER:$1|صارف}} کو برقی خط لکھیں",
        "emailuser-title-notarget": "ای میل صارف",
+       "emailpagetext": "درج ذیل فارم کے ذریعہ آپ اس {{GENDER:$1|صارف}} کو برقی پیغام بھیج سکتے ہیں۔ جو برقی ڈاک پتا آپ نے [[Special:Preferences|اپنی ترجیحات]] میں دیا ہے وہ یہاں \"از\" کے طور پر نظر آئے گا، تاکہ وصول کنندہ براہ راست آپ کو جواب دے سکے۔",
        "defemailsubject": "{{SITENAME}} سے برقی خط",
        "usermaildisabled": "صارف برقی پتہ غیر فعال ہے",
        "usermaildisabledtext": "آپ اس ویکی پر رہتے ہوئے دوسرے صارف کو برقی خط ارسال نہيں کر سکتے",
        "noemailtitle": "کوئی برقی پتہ نہیں ہے",
-       "noemailtext": "اس صارف نے برقی خط کے لیے پتہ فراہم نہیں کیا، یا یہ چاہتا ہے کا اس سے کوئی صارف رابطہ نہ کرے۔",
+       "noemailtext": "اس صارف نے کوئی درست برقی ڈاک پتا نہیں دیا ہے۔",
+       "nowikiemailtext": "اس صارف نے دیگر صارفین سے برقی خط وصول نہ کرنے کا فیصلہ کیا ہے۔",
+       "emailnotarget": "وصول کنندہ موجود نہیں یا صارف نام نادرست ہے۔",
+       "emailtarget": "وصول کنندہ کا صارف نام داخل کریں",
        "emailusername": "صارف نام:",
+       "emailusernamesubmit": "روانہ کریں",
+       "email-legend": "{{SITENAME}} کے دوسرے صارف کو برقی خط بھیجیں",
        "emailfrom": "از:",
        "emailto": "بہ:",
        "emailsubject": "موضوع:",
        "emailmessage": "پیغام:",
        "emailsend": "بھیجیں",
        "emailccme": "میرے پیغام کی ایک نقل مجھے بھی میل کی جائے۔",
+       "emailccsubject": "$1 کو بھیجے جانے والے پیغام کا نسخہ: $2",
+       "emailsent": "ای میل بھیج دی گئی",
        "emailsenttext": "آپ کا پیغام بھیج دیا گیا۔",
+       "emailuserfooter": "اس برقی خط کو $1 نے {{SITENAME}} پر موجود «{{int:emailuser}}» کی سہولت کو استعمال کرتے ہوئے {{GENDER:$2|$2}} کو {{GENDER:$1|بھیجا}} ہے۔",
+       "usermessage-summary": "نظامی پیغام کی ترسیل۔",
+       "usermessage-editor": "نظامی پیغام رساں",
        "watchlist": "میری زیرنظرفہرست",
        "mywatchlist": "زیرنظرفہرست",
        "watchlistfor2": "براۓ $1 ($2)",
-       "addedwatchtext": "یہ صفحہ \"<nowiki>$1</nowiki>\" آپکی [[Special:Watchlist|زیرنظر]] فہرست میں شامل کردیا گیا ہے۔ اب مستقل میں اس صفحے اور اس سے ملحقہ تبادلہ خیال کا صفحے میں کی جانے والی تبدیلوں کا اندراج کیا جاتا رہے گا، اور ان صفحات کی شناخت کو سہل بنانے کے لیۓ [[Special:حالیہ تبدیلیاں|حالیہ تبدیلیوں کی فہرست]] میں انکو '''مُتَجَل''' (bold) تحریر کیا جاۓ گا۔ <p> اگر آپ کسی وقت اس صفحہ کو زیرنظرفہرست سے خارج کرنا چاہیں تو اوپر دیۓ گۓ \"زیرنظرمنسوخ\" پر ٹک کیجیۓ۔",
-       "removedwatchtext": "صفحہ \"[[:$1]]\" آپ کی زیر نظر فہرست سے خارج کر دیا گیا۔",
-       "watch": "زیرنظر",
-       "watchthispage": "یہ صفحہ زیر نظر کیجیۓ",
+       "nowatchlist": "آپ کی زیرنظر فہرست میں کوئی مواد موجود نہیں ہے۔",
+       "watchlistanontext": "اپنی زیرنظر فہرست میں موجود مواد کو دیکھنے اور ان میں ترمیم کرنے کے لیے براہ کرم لاگ ان کریں۔",
+       "watchnologin": "داخل نوشتہ نہیں",
+       "addwatch": "زیر نظر فہرست میں شامل کریں",
+       "addedwatchtext": "صفحہ «[[:$1]]» اور اس کا تبادلۂ خیال صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] میں شامل کردیا گیا ہے۔",
+       "addedwatchtext-talk": "صفحہ «[[:$1]]» اور اس سے ملحقہ صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] میں شامل کردیا گیا ہے۔",
+       "addedwatchtext-short": "صفحہ «$1» کو آپ کی زیرنظر فہرست میں شامل کر دیا گیا ہے۔",
+       "removewatch": "زیرنظر فہرست سے ہٹائیں",
+       "removedwatchtext": "صفحہ «[[:$1]]» اور اس کا تبادلۂ خیال صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] سے خارج کر دیا گیا ہے۔",
+       "removedwatchtext-talk": "صفحہ «[[:$1]]» اور اس سے ملحقہ صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] سے خارج کر دیا گیا ہے۔",
+       "removedwatchtext-short": "صفحہ «$1» کو آپ کی زیرنظر فہرست سے خارج کر دیا گیا ہے۔",
+       "watch": "زیر نظر کریں",
+       "watchthispage": "اس صفحہ کو زیر نظر کریں",
        "unwatch": "زیرنظرمنسوخ",
-       "watchlist-details": "آپ کی زیرِنظرفہرست پر {{PLURAL:$1|$1 صفحہ ہے|$1 صفحات ہیں}}، اِس میں تبادلۂ خیال صفحات کی تعداد شامل نہیں.",
-       "wlnote": "نیچےآخری $1 تبدیلیاں ہیں جو کے پیچھلے <b>$2</b> گھنٹوں میں کی گئیں۔",
+       "unwatchthispage": "زیرنظر فہرست سے خارج کریں",
+       "notanarticle": "ویکی کے موضوع سے متعلق صفحہ نہیں ہے",
+       "notvisiblerev": "دوسرے صارف کی آخری ترمیم حذف کر دی گئی",
+       "watchlist-details": "آپ کی زیرنظر فہرست میں {{PLURAL:$1|$1 صفحہ ہے|$1 صفحات ہیں}}، اس میں تبادلۂ خیال صفحات کی تعداد شامل نہیں ہے۔",
+       "wlheader-enotif": "ای میل کی اطلاع فعال ہے ۔",
+       "wlheader-showupdated": "آپ کی آخری آمد کے بعد جن صفحات میں تبدیلی ہوئی ہے وہ <strong>جلی حروف</strong> میں نظر آئیں گے۔",
+       "wlnote": "ذیل میں گزشتہ {{PLURAL:$2|گھنٹے|<strong>$2</strong> گھنٹوں}} میں ہونے والی {{PLURAL:$1|تبدیلی|<strong>$1</strong> تبدیلیوں}} کی فہرست درج ہے، تاریخ تجدید $3، $4",
        "wlshowlast": "دکھائیں آخری $1 گھنٹے $2 دن",
        "watchlist-hide": "چھپائیں",
        "watchlist-submit": "دکھائیں",
        "wlshowhidemine": "میری ترامیم",
        "wlshowhidecategorization": "صفحاتی زمرہ بندی",
        "watchlist-options": "اختیارات برائے زیرِنظرفہرست",
+       "watching": "زیرنظر فہرست میں شامل کیا جا رہا ہے۔۔۔",
+       "unwatching": "زیرنظر فہرست سے خارج کیا جا رہا ہے۔۔۔",
+       "watcherrortext": "«$1» کے لیے آپ کی زیرنظر فہرست کی ترتیبات میں تبدیلی کے دوران میں کوئی نقص ہوا۔",
        "enotif_reset": "جملہ صفحات کو بطور زیارت شدہ نشان زد کریں",
+       "enotif_impersonal_salutation": "{{SITENAME}} کا صارف",
        "enotif_subject_deleted": "{{SITENAME}} میں صفحہ $1 صارف $2 نے {{GENDER:$2|حذف کیا}}",
        "enotif_subject_created": "{{SITENAME}} میں صفحہ $1 کو $2 نے {{GENDER:$2|تخلیق کیا}}",
        "enotif_subject_moved": "{{SITENAME}} میں صفحہ $1 کو $2 نے {{GENDER:$2|منتقل کیا}}",
        "enotif_body_intro_changed": "{{SITENAME}} میں صفحہ $1 میں بتاریخ $PAGEEDITDATEء صارف $2 نے {{GENDER:$2|تبدیلی کی}}، موجودہ نسخہ دیکھنے کے لیے $3 ملاحظہ فرمائیں۔",
        "enotif_lastvisited": "آپ کی آخری آمد کے بعد سے ہونے والی تمام تبدیلیوں کو دیکھنے کے لیے $1 کو ملاحظہ فرمائیں۔",
        "enotif_lastdiff": "اس تبدیلی کو دیکھنے کے لیے $1 کو ملاحظہ فرمائیں۔",
+       "enotif_anon_editor": "گمنام صارف $1",
        "enotif_body": "جناب $WATCHINGUSERNAME!\n\n$PAGEINTRO $NEWPAGE\n\nخلاصہ ترمیم: $PAGESUMMARY $PAGEMINOREDIT\n\nصارف سے رابطہ کریں:\nبذریعہ برقی خط: $PAGEEDITOR_EMAIL\nبذریعہ ویکی: $PAGEEDITOR_WIKI\n\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": "تبدیل کردیاگیا",
        "deletepage": "صفحہ ضائع کریں",
        "confirm": "یقین",
        "excontent": "'$1':مواد تھا",
-       "excontentauthor": "حذف شدہ مواد: '$1' (اور صرف '[[Special:Contributions/$2|$2]]' نے حصہ ڈالا)",
+       "excontentauthor": "حذف شدہ مواد: «$1» اور صرف «[[Special:Contributions/$2|$2]]» ([[User talk:$2|تبادلۂ خیال]]) نے اس میں ترمیم کی",
+       "exbeforeblank": "خالی کرنے سے قبل موجود مواد: «$1»",
        "delete-confirm": "حذف ''$1''",
        "delete-legend": "حذف",
        "historywarning": "<strong>انتباہ</strong>: آپ اس صفحہ کو $1 {{PLURAL:$1|نظر ثانی|نظر ثانیوں}} کے تاریخچہ کے ساتھ حذف کر رہے ہیں:",
        "dellogpage": "نوشتۂ حذف شدگی",
        "dellogpagetext": "حالیہ حذف شدگی کی فہرست درج ذیل ہے۔",
        "deletionlog": "نوشتۂ حذف شدگی",
+       "reverted": "ابتدائی نسخہ کی جانب واپس پھیر دیا گیا",
        "deletecomment": "وجہ:",
        "deleteotherreason": "دوسری/اِضافی وجہ:",
        "deletereasonotherlist": "دوسری وجہ",
+       "deletereason-dropdown": "* عمومی وجوہات حذف\n** فاضل کاری\n** تخریب کاری\n** کاپی رائٹ کی خلاف ورزی\n** مصنف کی درخواست\n** شکستہ روابط",
+       "delete-edit-reasonlist": "وجوہات حذف میں ترمیم کریں",
+       "delete-toobig": "$1 {{PLURAL:$1|نسخے|نسخوں}} پر مشتمل اس صفحہ کا تاریخچہ بہت طویل ہے۔\n{{SITENAME}} پر کسی حادثاتی انتشار سے بچنے کے لیے اس طرح کے صفحات کو حذف کرنے کی اجازت نہیں ہے۔",
+       "delete-warning-toobig": "$1 {{PLURAL:$1|نسخے|نسخوں}} پر مشتمل اس صفحہ کا تاریخچہ بہت طویل ہے۔\nعین ممکن ہے کہ اسے حذف کرنے سے {{SITENAME}} کے ڈیٹابیس کی کارروائیاں انتشار کا شکار ہو جائیں؛ لہذا احتیاط سے آگے بڑھیں۔",
+       "deleteprotected": "آپ اس صفحہ کو حذف نہیں کر سکتے کیونکہ اسے محفوظ کر دیا گیا ہے۔",
+       "deleting-backlinks-warning": "<strong>انتباہ:</strong> جس صفحہ کو آپ حذف کر رہے ہیں اس سے مربوط یا اس میں شامل [[Special:WhatLinksHere/{{FULLPAGENAME}}|دیگر صفحات]]۔",
        "rollback": "ترمیمات سابقہ حالت پرواپس",
        "rollbacklink": "استرجع کریں",
        "rollbacklinkcount": "استرجع $1 {{PLURAL:$1|ترمیم|ترامیم}}",
        "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ترمیم|ترامیم}} سے زیادہ کا استرجع",
        "rollbackfailed": "سابقہ حالت پر واپسی ناکام",
+       "rollback-missingparam": "درخواست میں ضروری پیرامیٹر موجود نہیں۔",
        "cantrollback": "تدوین ثانی کا اعادہ نہیں کیا جاسکتا؛ کیونکہ اس میں آخری بار حصہ لینے والا ہی اس صفحہ کا واحد کاتب ہے۔",
        "changecontentmodel-title-label": "صفحہ کا عنوان",
+       "changecontentmodel-model-label": "نیا مواد ماڈل",
        "changecontentmodel-reason-label": "وجہ:",
+       "changecontentmodel-submit": "تبدیلی",
        "log-name-contentmodel": "نوشتہ تبدیلی نمونہ مواد",
        "logentry-contentmodel-change": "$1 نے صفحہ $3 کے مواد کی ساخت کو \"$4\" سے \"$5\" میں {{GENDER:$2|تبدیل کیا}}",
+       "logentry-contentmodel-change-revertlink": "استرجع",
+       "logentry-contentmodel-change-revert": "استرجع",
        "protectlogpage": "نوشتۂ محفوظ شدگی",
        "protectedarticle": "\"[[$1]]\" کومحفوظ کردیا",
        "unprotectedarticle": "\"[[$1]]\" کوغیر محفوظ کیا",
        "movedarticleprotection": "نے \"[[$2]]\" کا درجہ حفاظت \"[[$1]]\" کی جانب منتقل کیا",
        "prot_1movedto2": "[[$1]] بجانب [[$2]] منتقل",
+       "protect-legend": "تحفظ کی تصدیق کریں",
        "protectcomment": "وجہ:",
+       "protectexpiry": "زاید میعاد:",
        "protect-default": "تمام صارفین کو اہل بناؤ",
        "protect-level-sysop": "صرف منتظمین کو اجازت ہے",
        "protect-summary-cascade": "آبشاری",
        "protect-otherreason-op": "دیگر وجہ",
        "protect-expiry-options": "1 hour:1 hour,1 day:1 day,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,infinite:infinite",
        "restriction-type": "اجازت:",
+       "minimum-size": "کم از کم سائز",
+       "maximum-size": "زیادہ سے زیادہ سائز:",
        "pagesize": "(بائیٹ)",
        "restriction-edit": "تحریر و ترمیم",
        "restriction-move": "منتقل",
        "namespace_association": "متعلقہ نام فضا",
        "blanknamespace": "(مرکز)",
        "contributions": "{{GENDER:$1|صارف}} شراکتیں",
-       "contributions-title": "مساہماتِ صارف برائے $1",
+       "contributions-title": "صارف $1 کی شراکتیں",
        "mycontris": "شراکت",
        "anoncontribs": "شراکتیں",
        "contribsub2": "برائے {{GENDER:$3|$1}} ($2)",
        "whatlinkshere-hideimages": "روابطِ تصاویر $1",
        "whatlinkshere-filters": "فلٹرذ",
        "whatlinkshere-submit": "ٹھیک",
+       "block": "صارف مسدود کریں",
        "blockip": "داخلہ ممنوع برائے صارف",
        "blockip-legend": "ممنوع کردہ صارفین",
+       "ipaddressorusername": "آئی پی پتہ یا صارف نام:",
        "ipbreason": "وجہ:",
        "ipbsubmit": "اس صارف کا داخلہ ممنوع کریں",
+       "ipbother": "دیگر وقت:",
        "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",
+       "blocklist": "ممنوع صارفین",
        "ipblocklist": "ممنوع صارفین",
+       "blocklist-target": "ہدف",
        "blocklist-reason": "وجہ",
        "ipblocklist-submit": "تلاش",
        "infiniteblock": "مستقل",
        "unblocklink": "پابندی ختم",
        "change-blocklink": "پابندی میں تبدیلی",
        "contribslink": "شراکتیں",
+       "emaillink": "ای میل بھیجیں",
        "blocklogpage": "نوشتۂ پابندی",
        "block-log-flags-nocreate": "کھاتے کی تخلیق غیرفعال",
        "move-page": "منتقلی $1",
        "movepage-moved-redirect": "رجوع مکرر تخلیق کر دیا گیا۔",
        "movepage-moved-noredirect": "رجوع مکرر کو بننے سے روک دیا گیا ہے۔",
        "articleexists": "اس عنوان سے کوئی صفحہ پہلے ہی موجود ہے، یا آپکا منتخب کردہ نام مستعمل نہیں۔ براۓ مہربانی دوسرا نام منتخب کیجیۓ۔",
+       "movepage-page-moved": "صفحہ $1 کو $2 کی جانب منتقل کر دیا گیا۔",
        "movelogpage": "نوشتۂ منتقلی",
        "movereason": "وجہ:",
        "revertmove": "رجوع",
        "common.css": "body,\ntextarea {\n    font-family: Amiri;\n}",
        "anonymous": "{{SITENAME}} گمنام صارف",
        "others": "دیگر",
-       "pageinfo-visiting-watchers": "تعداد Ù\86اظرÛ\8cÙ\86 Ø¬Ù\86Û\81Ù\88Úº Ù\86Û\92 Ø­Ø§Ù\84Û\8cÛ\81 ØªØ±Ø§Ù\85Û\8cÙ\85 Ú©Ø§ Ù\85شاÛ\81دÛ\81 Ú©Û\8cا",
+       "pageinfo-visiting-watchers": "حاÙ\84Û\8cÛ\81 ØªØ¨Ø¯Û\8cÙ\84Û\8cÙ\88Úº Ù¾Ø± Ø¢Ù\86Û\92 Ù\88اÙ\84Û\92 Ù\86اظرÛ\8cÙ\86 Ú©Û\8c ØªØ¹Ø¯Ø§Ø¯",
        "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
        "pageinfo-toolboxlink": "معلومات صفحہ",
        "pageinfo-category-info": "زمرے کی معلومات",
        "monthsall": "تمام",
        "deletedwhileediting": "انتباہ: آپ کے ترمیم شروع کرنے کے بعد یہ صفحہ حذف کیا جا چکا ہے!",
        "confirm_purge_button": "جی!",
+       "confirm-watch-button": "ٹھیک",
+       "confirm-watch-top": "اس صفحہ کو آپ کی زیر نظر فہرست میں شامل کریں؟",
        "confirm-rollback-button": "ٹھیک ہے",
        "semicolon-separator": "؛&#32;",
        "comma-separator": "،&#32;",
        "htmlform-yes": "ہاں",
        "logentry-delete-delete": "$1 {{GENDER:$2|حذف کیا گیا}} صفحہ $3",
        "logentry-move-move": "$1 نے صفحہ $3 کو $4 کی جانب منتقل کیا",
+       "logentry-move-move_redir-noredirect": "$1 نے صفحہ $3 کو رجوع مکرر چھوڑے بغیر $4 کی جانب جو رجوع مکر تھا {{GENDER:$2|منتقل کیا}}",
        "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
        "logentry-protect-move_prot": "$1 نے ترتیب درجہ حفاظت $4 سے $3 کی طرف {{GENDER:$2|منتقل کی}}",
        "logentry-protect-protect": "$1 نے $3 کو {{GENDER:$2|محفوظ کیا}}  $4",
        "revdelete-summary": "خلاصۂ تدوین",
        "feedback-thanks-title": "شکریہ!",
        "searchsuggest-search": "تلاش",
+       "searchsuggest-containing": "نتائج...",
        "expandtemplates": "سانچے کو وسیع کریں",
        "expand_templates_input": "ان پٹ متن:",
        "expand_templates_output": "نتیجہ",
index 45f0721..ea738ac 100644 (file)
        "htmlform-title-not-exists": "$1不存在",
        "htmlform-user-not-exists": "<strong>$1</strong>不存在。",
        "htmlform-user-not-valid": "<strong>$1</strong>不是一个有效的用户名。",
-       "sqlite-has-fts": "带全文搜索的版本$1",
-       "sqlite-no-fts": "不带全文搜索的版本$1",
        "logentry-delete-delete": "$1{{GENDER:$2|删除}}页面$3",
        "logentry-delete-restore": "$1{{GENDER:$2|还原}}页面$3",
        "logentry-delete-event": "$1{{GENDER:$2|更改}}$3的{{PLURAL:$5|$5个日志事件}}的可见性:$4",
index 37d164b..0f38d8e 100644 (file)
        "htmlform-title-not-exists": "$1 並不存在。",
        "htmlform-user-not-exists": "<strong>$1</strong> 並不存在。",
        "htmlform-user-not-valid": "<strong>$1</strong> 不是有效的使用者名稱。",
-       "sqlite-has-fts": "$1 且支援全文搜索",
-       "sqlite-no-fts": "$1 且不支援全文搜索",
        "logentry-delete-delete": "$1 刪除頁面 $3",
        "logentry-delete-restore": "$1 還原頁面 $3",
        "logentry-delete-event": "$1 {{GENDER:$2|已更改}} $3 中 {{PLURAL:$5|1 筆日誌|$5 筆日誌}}的可見性:$4",
index 6465bb3..458dacf 100644 (file)
@@ -304,6 +304,8 @@ class RebuildRecentchanges extends Maintenance {
                        ]
                );
 
+               $field = $dbw->fieldInfo( 'recentchanges', 'rc_cur_id' );
+
                $inserted = 0;
                foreach ( $res as $row ) {
                        $dbw->insert(
@@ -323,7 +325,7 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_last_oldid' => 0,
                                        'rc_type' => RC_LOG,
                                        'rc_source' => $dbw->addQuotes( RecentChange::SRC_LOG ),
-                                       'rc_cur_id' => $dbw->cascadingDeletes()
+                                       'rc_cur_id' => $field->isNullable()
                                                ? $row->page_id
                                                : (int)$row->page_id, // NULL => 0,
                                        'rc_log_type' => $row->log_type,
index e6a30a3..a9fe45a 100644 (file)
@@ -44,6 +44,8 @@ class MwSql extends Maintenance {
        }
 
        public function execute() {
+               global $IP;
+
                // We wan't to allow "" for the wikidb, meaning don't call select_db()
                $wiki = $this->hasOption( 'wikidb' ) ? $this->getOption( 'wikidb' ) : false;
                // Get the appropriate load balancer (for this wiki)
@@ -66,12 +68,13 @@ class MwSql extends Maintenance {
                                }
                        }
                        if ( $index === null ) {
-                               $this->error( "No replica DB server configured with the name '$server'.", 1 );
+                               $this->error( "No replica DB server configured with the name '$replicaDB'.", 1 );
                        }
                } else {
                        $index = DB_MASTER;
                }
-               // Get a DB handle (with this wiki's DB selected) from the appropriate load balancer
+
+               /** @var Database $db DB handle for the appropriate cluster/wiki */
                $db = $lb->getConnection( $index, [], $wiki );
                if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
                        $this->error( "The server selected ({$db->getServer()}) is not a replica DB.", 1 );
@@ -98,14 +101,15 @@ class MwSql extends Maintenance {
                        return;
                }
 
-               $useReadline = function_exists( 'readline_add_history' )
-                       && Maintenance::posix_isatty( 0 /*STDIN*/ );
-
-               if ( $useReadline ) {
-                       global $IP;
+               if (
+                       function_exists( 'readline_add_history' ) &&
+                       Maintenance::posix_isatty( 0 /*STDIN*/ )
+               ) {
                        $historyFile = isset( $_ENV['HOME'] ) ?
                                "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history";
                        readline_read_history( $historyFile );
+               } else {
+                       $historyFile = null;
                }
 
                $wholeLine = '';
@@ -126,10 +130,10 @@ class MwSql extends Maintenance {
                                $prompt = '    -> ';
                                continue;
                        }
-                       if ( $useReadline ) {
+                       if ( $historyFile ) {
                                # Delimiter is eated by streamStatementEnd, we add it
                                # up in the history (bug 37020)
-                               readline_add_history( $wholeLine . $db->getDelimiter() );
+                               readline_add_history( $wholeLine . ';' );
                                readline_write_history( $historyFile );
                        }
                        $this->sqlDoQuery( $db, $wholeLine, $doDie );
@@ -139,7 +143,7 @@ class MwSql extends Maintenance {
                wfWaitForSlaves();
        }
 
-       protected function sqlDoQuery( $db, $line, $dieOnError ) {
+       protected function sqlDoQuery( IDatabase $db, $line, $dieOnError ) {
                try {
                        $res = $db->query( $line );
                        $this->sqlPrintResult( $res, $db );
@@ -151,7 +155,7 @@ class MwSql extends Maintenance {
        /**
         * Print the results, callback for $db->sourceStream()
         * @param ResultWrapper $res The results object
-        * @param DatabaseBase $db
+        * @param IDatabase $db
         */
        public function sqlPrintResult( $res, $db ) {
                if ( !$res ) {
index a4479f7..6437ca8 100644 (file)
@@ -401,7 +401,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 }
 .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        color: inherit;
-       display: table;
+       display: inline-table;
        box-sizing: border-box;
        max-width: 100%;
        padding: 0;
@@ -421,7 +421,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 .oo-ui-fieldsetLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.oo-ui-fieldsetLayout > .oo-ui-labelElement-label {
+.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        font-size: 1.1em;
        margin-bottom: 0.5em;
        padding: 0.25em 0;
index 09e6cfc..08d91b4 100644 (file)
@@ -524,7 +524,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 }
 .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        color: inherit;
-       display: table;
+       display: inline-table;
        box-sizing: border-box;
        max-width: 100%;
        padding: 0;
@@ -544,7 +544,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
 .oo-ui-fieldsetLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.oo-ui-fieldsetLayout > .oo-ui-labelElement-label {
+.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        margin-bottom: 0.5em;
        font-size: 1.1em;
        font-weight: bold;
index f29897c..3cc94b8 100644 (file)
@@ -23,7 +23,7 @@
                height: auto;
                margin: 0 0.1em 0 0;
                padding: 0;
-               border: 1px solid @colorFieldBorder;
+               border: 1px solid @colorGray7;
                cursor: pointer;
        }
 }
@@ -61,7 +61,7 @@
 
 .button-colors( @bgColor, @highlightColor, @activeColor ) when ( lightness( @bgColor ) >= 70% ) {
        color: @colorButtonText;
-       border: 1px solid @colorGray12;
+       border: 1px solid @colorFieldBorder;
 
        &:hover,
        &:active,
 .button-colors-quiet( @textColor, @highlightColor, @activeColor ) {
        // Quiet buttons all start gray, and reveal
        // constructive/progressive/destructive color on hover and active.
-       color: @colorButtonText;
+       color: @textColor;
 
-       &:hover,
-       &:focus {
+       &:hover {
                background-color: transparent;
-               color: @textColor;
+               color: @highlightColor;
        }
 
        &:active,
                color: @activeColor;
        }
 
+       &:focus {
+               background-color: transparent;
+               color: @textColor;
+       }
+
        &:disabled {
                color: @colorDisabledText;
        }
index b6f6568..77e80b0 100644 (file)
@@ -2,13 +2,13 @@
 
 // Although this defines many shades, be parsimonious in your own use of grays. Prefer
 // colors already in use in MediaWiki. Prefer semantic color names such as "@colorText".
-@colorGray1: #111; // darkest
+@colorGray1: #000; // darkest
 @colorGray2: #222;
 @colorGray3: #333;
 @colorGray4: #444;
 @colorGray5: #555;
 @colorGray6: #666;
-@colorGray7: #777;
+@colorGray7: #72777d;
 @colorGray8: #888;
 @colorGray9: #999;
 @colorGray10: #aaa;
 @colorGray12: #ccc;
 @colorGray13: #ddd;
 @colorGray14: #eee;
-@colorGray15: #f9f9f9; // lightest
+@colorGray15: #f8f9fa; // lightest
 
 // Semantic background colors
 // Blue; for contextual use of a continuing action
-@colorProgressive: #347bff;
-@colorProgressiveHighlight: #2962cc;
-@colorProgressiveActive: #2962cc;
-// Green; for contextual use of a positive finalizing action
-@colorConstructive: #00af89;
-@colorConstructiveHighlight: #008c6d;
-@colorConstructiveActive: #008c6d;
+@colorProgressive: #36c;
+@colorProgressiveHighlight: #447ff5;
+@colorProgressiveActive: #2a4b8d;
 // Orange; for contextual use of returning to a past action
 @colorRegressive: #ff5d00;
 // Red; for contextual use of a negative action of high severity
-@colorDestructive: #d11d13;
-@colorDestructiveHighlight: #a7170f;
-@colorDestructiveActive: #a7170f;
+@colorDestructive: #c33;
+@colorDestructiveHighlight: #e53939;
+@colorDestructiveActive: #873636;
 // Orange; for contextual use of a potentially negative action of medium severity
 @colorMediumSevere: #ff5d00;
 // Yellow; for contextual use of a potentially negative action of low severity
-@colorLowSevere: #ffb50d;
+@colorLowSevere: #fc3;
 
 // Used in mixins to darken contextual colors by the same amount (eg. focus)
 @colorDarkenPercentage: 13.5%;
 // Text colors
 @colorText: @colorGray2;
 @colorTextLight: @colorGray6;
-@colorButtonText: @colorGray5;
-@colorButtonTextHighlight: @colorGray7;
-@colorButtonTextActive: @colorGray7;
+@colorButtonText: @colorGray2;
+@colorButtonTextHighlight: @colorGray4;
+@colorButtonTextActive: @colorGray1;
 @colorDisabledText: @colorGray12;
 @colorErrorText: #c00;
 @colorWarningText: #705000;
 
 // UI colors
-@colorFieldBorder: @colorGray12;
+@colorFieldBorder: #9aa0a7;
 @colorShadow: @colorGray14;
 @colorPlaceholder: @colorGray10;
 @colorNeutral: @colorGray7;
 
-// The following rules are deprecated
-@colorWhite: #fff;
-@colorOffWhite: #fafafa;
-@colorGrayDark: #898989;
-@colorGrayLight: #ccc;
-@colorGrayLighter: #ddd;
-@colorGrayLightest: #eee;
-
 // Global border radius to be used to buttons and inputs
 @borderRadius: 2px;
 
 // Form input sizes
 @checkboxSize: 2em;
 @radioSize: 2em;
+
+// The following rules are deprecated
+@colorWhite: #fff;
+@colorOffWhite: #fafafa;
+@colorGrayDark: #898989;
+@colorGrayLight: #ccc;
+@colorGrayLighter: #ddd;
+@colorGrayLightest: #eee;
+// Green; for contextual use of a positive finalizing action
+@colorConstructive: #00af89;
+@colorConstructiveHighlight: #1c6665;
+@colorConstructiveActive: #134645;
+
index 753f774..cf77a96 100644 (file)
 
 /* Login Button, following `ButtonWidget (progressive)‎` from OOjs UI */
 #mw-createaccount-join {
-       color: #347bff;
+       background-color: #f8f9fa;
+       color: #36c;
 }
 #mw-createaccount-join:hover {
-       background-color: #ebf2ff; /* rgba( 52, 123, 255, 0.1 ); */
+       background-color: #fff;
        border-color: #859ecc;
        box-shadow: none;
 }
 #mw-createaccount-join:active {
-       background-color: #ebf2ff;
-       color: #1f4999;
-       border-color: #1f4999;
+       background-color: #eff3fa;
+       color: #2a4b8d;
+       border-color: #2a4b8d;
 }
 #mw-createaccount-join:focus {
-       background-color: #fff;
-       color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: inset 0 0 0 1px #1f4999;
-}
-#mw-createaccount-join:active:focus {
-       background-color: #ebf2ff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
index 9d30eb8..4d90496 100644 (file)
 
 .mw-widget-calendarWidget:focus {
        outline: none;
-       box-shadow: inset 0 0 0 2px #347bff;
+       box-shadow: inset 0 0 0 2px #36c;
 }
 
 .mw-widget-calendarWidget-day {
index 86018a4..46e6b62 100644 (file)
 
        &.oo-ui-widget-enabled {
                .mw-widget-dateInputWidget-handle:hover {
-                       border-color: #347bff;
+                       border-color: #36c;
                }
        }
 
index 68062d0..03df086 100644 (file)
@@ -30,6 +30,6 @@
 }
 
 .mw-upload-bookletLayout-filePreview .oo-ui-progressBarWidget-bar {
-       background-color: #347bff;
+       background-color: #36c;
        height: 0.5em;
 }
\ No newline at end of file
index 85c95e4..bc50966 100644 (file)
@@ -514,10 +514,6 @@ class HtmlTest extends MediaWikiTestCase {
                        'canvas', [ 'width' => 300 ]
                ];
 
-               $cases[] = [ '<command/>',
-                       'command', [ 'type' => 'command' ]
-               ];
-
                $cases[] = [ '<form></form>',
                        'form', [ 'action' => 'GET' ]
                ];
index 134caf4..846509c 100644 (file)
@@ -175,47 +175,6 @@ class DatabaseTest extends MediaWikiTestCase {
                );
        }
 
-       public function testFillPreparedEmpty() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT * FROM interwiki', [] );
-               $this->assertEquals(
-                       "SELECT * FROM interwiki",
-                       $sql );
-       }
-
-       public function testFillPreparedQuestion() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',
-                       [ 4, "Snicker's_paradox" ] );
-
-               $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker''s_paradox'";
-               if ( $this->db->getType() === 'mysql' ) {
-                       $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'";
-               }
-               $this->assertEquals( $check, $sql );
-       }
-
-       public function testFillPreparedBang() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT user_id FROM ! WHERE user_name=?',
-                       [ '"user"', "Slash's Dot" ] );
-
-               $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash''s Dot'";
-               if ( $this->db->getType() === 'mysql' ) {
-                       $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'";
-               }
-               $this->assertEquals( $check, $sql );
-       }
-
-       public function testFillPreparedRaw() {
-               $sql = $this->db->fillPrepared(
-                       "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",
-                       [ '"user"', "Slash's Dot" ] );
-               $this->assertEquals(
-                       "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'",
-                       $sql );
-       }
-
        public function testStoredFunctions() {
                if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) {
                        $this->markTestSkipped( 'MySQL or Postgres required' );
index 63322cc..caa29bd 100644 (file)
@@ -34,6 +34,8 @@ class DatabaseTestHelper extends DatabaseBase {
                $this->profiler = new ProfilerStub( [] );
                $this->trxProfiler = new TransactionProfiler();
                $this->cliMode = isset( $opts['cliMode'] ) ? $opts['cliMode'] : true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
        }
 
        /**