Merge "Remove old remapping hacks from Database::indexName()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 22 Sep 2016 21:05:56 +0000 (21:05 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 22 Sep 2016 21:05:56 +0000 (21:05 +0000)
123 files changed:
RELEASE-NOTES-1.28
autoload.php
includes/Block.php
includes/GlobalFunctions.php
includes/MWTimestamp.php
includes/MediaWikiServices.php
includes/MimeMagic.php
includes/ProxyLookup.php [new file with mode: 0644]
includes/ServiceWiring.php
includes/WebRequest.php
includes/WebResponse.php
includes/db/CloneDatabase.php
includes/filebackend/FSFile.php [deleted file]
includes/filebackend/FSFileBackend.php
includes/filebackend/FileBackendGroup.php
includes/filebackend/MemoryFileBackend.php
includes/filebackend/SwiftFileBackend.php
includes/filebackend/TempFSFile.php [deleted file]
includes/filerepo/FSRepo.php
includes/filerepo/FileRepo.php
includes/filerepo/RepoGroup.php
includes/filerepo/file/LocalFile.php
includes/installer/i18n/bg.json
includes/libs/IP.php [new file with mode: 0644]
includes/libs/SamplingStatsdClient.php [deleted file]
includes/libs/filebackend/FSFile.php [new file with mode: 0644]
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/TempFSFile.php [new file with mode: 0644]
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/exception/DBAccessError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBConnectionError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBError.php
includes/libs/rdbms/exception/DBExpectedError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBQueryError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBReadOnlyError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBReplicationWaitError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBTransactionError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBTransactionSizeError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBUnexpectedError.php [new file with mode: 0644]
includes/libs/stats/SamplingStatsdClient.php [new file with mode: 0644]
includes/libs/time/ConvertableTimestamp.php [deleted file]
includes/libs/time/ConvertibleTimestamp.php [new file with mode: 0644]
includes/libs/xmp/XMPValidate.php
includes/media/BMP.php
includes/media/DjVu.php
includes/media/ExifBitmap.php
includes/media/MediaHandler.php
includes/media/PNG.php
includes/media/SVG.php
includes/media/Tiff.php
includes/media/WebP.php
includes/media/XCF.php
includes/objectcache/ObjectCache.php
includes/page/Article.php
includes/specials/SpecialVersion.php
includes/upload/UploadBase.php
includes/upload/UploadStash.php
includes/utils/IP.php [deleted file]
includes/utils/MWFileProps.php [new file with mode: 0644]
languages/i18n/ar.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ca.json
languages/i18n/cdo.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/id.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/lt.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/or.json
languages/i18n/pl.json
languages/i18n/qqq.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/tcy.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/zh-hans.json
maintenance/importImages.php
maintenance/refreshLinks.php
resources/src/mediawiki.less/mediawiki.ui/mixins.less
tests/common/TestsAutoLoader.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/WebRequestTest.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
tests/phpunit/includes/db/DatabaseSQLTest.php
tests/phpunit/includes/db/DatabaseTestHelper.php
tests/phpunit/includes/installer/DatabaseUpdaterTest.php
tests/phpunit/includes/libs/IPTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/time/ConvertableTimestampTest.php [deleted file]
tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php [new file with mode: 0644]
tests/phpunit/includes/media/MediaWikiMediaTestCase.php
tests/phpunit/includes/utils/IPTest.php [deleted file]
tests/phpunit/mocks/filerepo/MockLocalRepo.php [new file with mode: 0644]

index fd6c613..a24f97a 100644 (file)
@@ -202,6 +202,9 @@ changes to languages because of Phabricator reports.
   Instead of --keep-uploads, use the same option to parserTests.php, but you
   must specify a directory with --upload-dir.
 * The 'jquery.arrowSteps' ResourceLoader module is now deprecated.
+* IP::isConfiguredProxy() and IP::isTrustedProxy() were removed. Callers should
+  migrate to using the same functions on a ProxyLookup instance, obtainable from
+  MediaWikiServices.
 
 == Compatibility ==
 
index 0c16e76..018e85e 100644 (file)
@@ -281,8 +281,8 @@ $wgAutoloadLocalClasses = [
        'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
-       'ConvertableTimestamp' => __DIR__ . '/includes/libs/time/ConvertableTimestamp.php',
        'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+       'ConvertibleTimestamp' => __DIR__ . '/includes/libs/time/ConvertibleTimestamp.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
@@ -299,22 +299,22 @@ $wgAutoloadLocalClasses = [
        'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'CurlHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
-       'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBAccessError.php',
        'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
        'DBConnRef' => __DIR__ . '/includes/libs/rdbms/database/DBConnRef.php',
-       'DBConnectionError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBConnectionError' => __DIR__ . '/includes/libs/rdbms/exception/DBConnectionError.php',
        'DBError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
-       'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBExpectedError.php',
        'DBFileJournal' => __DIR__ . '/includes/filebackend/filejournal/DBFileJournal.php',
        'DBLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
        'DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
-       'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
-       'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
-       'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryError.php',
+       'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyError.php',
+       'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBReplicationWaitError.php',
        'DBSiteStore' => __DIR__ . '/includes/site/DBSiteStore.php',
-       'DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
-       'DBTransactionSizeError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
-       'DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionError.php',
+       'DBTransactionSizeError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionSizeError.php',
+       'DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBUnexpectedError.php',
        'DataUpdate' => __DIR__ . '/includes/deferred/DataUpdate.php',
        'Database' => __DIR__ . '/includes/libs/rdbms/database/Database.php',
        'DatabaseBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseBase.php',
@@ -432,7 +432,7 @@ $wgAutoloadLocalClasses = [
        'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
        'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
        'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
-       'FSFile' => __DIR__ . '/includes/filebackend/FSFile.php',
+       'FSFile' => __DIR__ . '/includes/libs/filebackend/FSFile.php',
        'FSFileBackend' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendDirList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendFileList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
@@ -586,7 +586,7 @@ $wgAutoloadLocalClasses = [
        'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
        'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
        'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
-       'IP' => __DIR__ . '/includes/utils/IP.php',
+       'IP' => __DIR__ . '/includes/libs/IP.php',
        'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
        'IRCColourfulRCFeedFormatter' => __DIR__ . '/includes/rcfeed/IRCColourfulRCFeedFormatter.php',
@@ -773,6 +773,7 @@ $wgAutoloadLocalClasses = [
        'MWException' => __DIR__ . '/includes/exception/MWException.php',
        'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
        'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
+       'MWFileProps' => __DIR__ . '/includes/utils/MWFileProps.php',
        'MWGrants' => __DIR__ . '/includes/utils/MWGrants.php',
        'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
@@ -1099,6 +1100,7 @@ $wgAutoloadLocalClasses = [
        'ProtectedPagesPager' => __DIR__ . '/includes/specials/SpecialProtectedpages.php',
        'ProtectedTitlesPager' => __DIR__ . '/includes/specials/pagers/ProtectedTitlesPager.php',
        'ProtectionForm' => __DIR__ . '/includes/ProtectionForm.php',
+       'ProxyLookup' => __DIR__ . '/includes/ProxyLookup.php',
        'PruneFileCache' => __DIR__ . '/maintenance/pruneFileCache.php',
        'PublishStashedFileJob' => __DIR__ . '/includes/jobqueue/jobs/PublishStashedFileJob.php',
        'PurgeAction' => __DIR__ . '/includes/actions/PurgeAction.php',
@@ -1224,7 +1226,7 @@ $wgAutoloadLocalClasses = [
        'SQLiteField' => __DIR__ . '/includes/libs/rdbms/field/SQLiteField.php',
        'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
        'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
-       'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
+       'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php',
        'Sanitizer' => __DIR__ . '/includes/Sanitizer.php',
        'SavepointPostgres' => __DIR__ . '/includes/libs/rdbms/database/utils/SavepointPostgres.php',
        'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php',
@@ -1402,7 +1404,7 @@ $wgAutoloadLocalClasses = [
        'TableDiffFormatter' => __DIR__ . '/includes/diff/TableDiffFormatter.php',
        'TablePager' => __DIR__ . '/includes/pager/TablePager.php',
        'TagLogFormatter' => __DIR__ . '/includes/logging/TagLogFormatter.php',
-       'TempFSFile' => __DIR__ . '/includes/filebackend/TempFSFile.php',
+       'TempFSFile' => __DIR__ . '/includes/libs/filebackend/TempFSFile.php',
        'TempFileRepo' => __DIR__ . '/includes/filerepo/FileRepo.php',
        'TemplateParser' => __DIR__ . '/includes/TemplateParser.php',
        'TemplatesOnThisPageFormatter' => __DIR__ . '/includes/TemplatesOnThisPageFormatter.php',
index 19ba0a2..098d51c 100644 (file)
@@ -19,6 +19,9 @@
  *
  * @file
  */
+
+use MediaWiki\MediaWikiServices;
+
 class Block {
        /** @var string */
        public $mReason;
@@ -1120,6 +1123,7 @@ class Block {
                }
 
                $conds = [];
+               $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
                foreach ( array_unique( $ipChain ) as $ipaddr ) {
                        # Discard invalid IP addresses. Since XFF can be spoofed and we do not
                        # necessarily trust the header given to us, make sure that we are only
@@ -1130,7 +1134,7 @@ class Block {
                                continue;
                        }
                        # Don't check trusted IPs (includes local squids which will be in every request)
-                       if ( IP::isTrustedProxy( $ipaddr ) ) {
+                       if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
                                continue;
                        }
                        # Check both the original IP (to check against single blocks), as well as build
index e5f518f..6e8ce8f 100644 (file)
@@ -2003,13 +2003,11 @@ require_once __DIR__ . '/libs/time/defines.php';
  * @return string|bool String / false The same date in the format specified in $outputtype or false
  */
 function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
-       try {
-               $timestamp = new MWTimestamp( $ts );
-               return $timestamp->getTimestamp( $outputtype );
-       } catch ( TimestampException $e ) {
+       $ret = MWTimestamp::convert( $outputtype, $ts );
+       if ( $ret === false ) {
                wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
-               return false;
        }
+       return $ret;
 }
 
 /**
@@ -2035,7 +2033,7 @@ function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
  */
 function wfTimestampNow() {
        # return NOW
-       return wfTimestamp( TS_MW, time() );
+       return MWTimestamp::now( TS_MW );
 }
 
 /**
index 201e9b6..c1e5cc4 100644 (file)
@@ -28,7 +28,7 @@
  *
  * @since 1.20
  */
-class MWTimestamp extends ConvertableTimestamp {
+class MWTimestamp extends ConvertibleTimestamp {
        /**
         * Get a timestamp instance in GMT
         *
index f621867..b16044e 100644 (file)
@@ -19,6 +19,7 @@ use MediaWiki\Services\ServiceContainer;
 use MediaWiki\Services\NoSuchServiceException;
 use MWException;
 use ObjectCache;
+use ProxyLookup;
 use SearchEngine;
 use SearchEngineConfig;
 use SearchEngineFactory;
@@ -529,6 +530,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'MediaHandlerFactory' );
        }
 
+       /**
+        * @since 1.28
+        * @return ProxyLookup
+        */
+       public function getProxyLookup() {
+               return $this->getService( 'ProxyLookup' );
+       }
+
        /**
         * @since 1.28
         * @return GenderCache
index a432d44..54d58d2 100644 (file)
@@ -863,10 +863,8 @@ class MimeMagic {
                        $mime = "application/x-opc+zip";
                        # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
                        if ( $ext !== true && $ext !== false ) {
-                               /** This is the mode used by getPropsFromPath
-                                * These MIME's are stored in the database, where we don't really want
-                                * x-opc+zip, because we use it only for internal purposes
-                                */
+                               // These MIME's are stored in the database, where we don't really want
+                               // x-opc+zip, because we use it only for internal purposes
                                if ( $this->isMatchingExtension( $ext, $mime ) ) {
                                        /* A known file extension for an OPC file,
                                         * find the proper mime type for that file extension
@@ -1046,6 +1044,7 @@ class MimeMagic {
                        }
                }
 
+               $type = null;
                // Check for entry for full MIME type
                if ( $mime ) {
                        $type = $this->findMediaType( $mime );
diff --git a/includes/ProxyLookup.php b/includes/ProxyLookup.php
new file mode 100644 (file)
index 0000000..3a3243a
--- /dev/null
@@ -0,0 +1,85 @@
+<?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
+ */
+
+use IPSet\IPSet;
+
+/**
+ * @since 1.28
+ */
+class ProxyLookup {
+
+       /**
+        * @var string[]
+        */
+       private $proxyServers;
+
+       /**
+        * @var string[]
+        */
+       private $proxyServersComplex;
+
+       /**
+        * @var IPSet|null
+        */
+       private $proxyIPSet;
+
+       /**
+        * @param string[] $proxyServers Simple list of IPs
+        * @param string[] $proxyServersComplex Complex list of IPs/ranges
+        */
+       public function __construct( $proxyServers, $proxyServersComplex ) {
+               $this->proxyServers = $proxyServers;
+               $this->proxyServersComplex = $proxyServersComplex;
+       }
+
+       /**
+        * Checks if an IP matches a proxy we've configured
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public function isConfiguredProxy( $ip ) {
+               // Quick check of known singular proxy servers
+               if ( in_array( $ip, $this->proxyServers ) ) {
+                       return true;
+               }
+
+               // Check against addresses and CIDR nets in the complex list
+               if ( !$this->proxyIPSet ) {
+                       $this->proxyIPSet = new IPSet( $this->proxyServersComplex );
+               }
+               return $this->proxyIPSet->match( $ip );
+       }
+
+       /**
+        * Checks if an IP is a trusted proxy provider.
+        * Useful to tell if X-Forwarded-For data is possibly bogus.
+        * CDN cache servers for the site are whitelisted.
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public function isTrustedProxy( $ip ) {
+               $trusted = $this->isConfiguredProxy( $ip );
+               Hooks::run( 'IsTrustedProxy', [ &$ip, &$trusted ] );
+               return $trusted;
+       }
+}
index 8c7d802..6044911 100644 (file)
@@ -164,6 +164,14 @@ return [
                );
        },
 
+       'ProxyLookup' => function( MediaWikiServices $services ) {
+               $mainConfig = $services->getMainConfig();
+               return new ProxyLookup(
+                       $mainConfig->get( 'SquidServers' ),
+                       $mainConfig->get( 'SquidServersNoPurge' )
+               );
+       },
+
        'LinkCache' => function( MediaWikiServices $services ) {
                return new LinkCache(
                        $services->getTitleFormatter(),
index a5ae461..0065135 100644 (file)
@@ -23,6 +23,7 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\Session;
 use MediaWiki\Session\SessionId;
 use MediaWiki\Session\SessionManager;
@@ -1222,7 +1223,8 @@ HTML;
                # Append XFF
                $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
                if ( $forwardedFor !== false ) {
-                       $isConfigured = IP::isConfiguredProxy( $ip );
+                       $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
+                       $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
                        $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
                        $ipchain = array_reverse( $ipchain );
                        array_unshift( $ipchain, $ip );
@@ -1235,14 +1237,14 @@ HTML;
                        foreach ( $ipchain as $i => $curIP ) {
                                $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
                                if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
-                                       || !IP::isTrustedProxy( $curIP )
+                                       || !$proxyLookup->isTrustedProxy( $curIP )
                                ) {
                                        break; // IP is not valid/trusted or does not point to anything
                                }
                                if (
                                        IP::isPublic( $ipchain[$i + 1] ) ||
                                        $wgUsePrivateIPs ||
-                                       IP::isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane
+                                       $proxyLookup->isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane
                                ) {
                                        // Follow the next IP according to the proxy
                                        $nextIP = IP::canonicalize( $ipchain[$i + 1] );
index 90b76e3..339b2e3 100644 (file)
@@ -39,7 +39,11 @@ class WebResponse {
         * @param null|int $http_response_code Forces the HTTP response code to the specified value.
         */
        public function header( $string, $replace = true, $http_response_code = null ) {
-               header( $string, $replace, $http_response_code );
+               if ( $http_response_code ) {
+                       header( $string, $replace, $http_response_code );
+               } else {
+                       header( $string, $replace );
+               }
        }
 
        /**
index 2af742e..f1ccd2a 100644 (file)
@@ -23,6 +23,7 @@
  * @file
  * @ingroup Database
  */
+use MediaWiki\MediaWikiServices;
 
 class CloneDatabase {
        /** @var string Table prefix for cloning */
@@ -40,16 +41,19 @@ class CloneDatabase {
        /** @var bool Whether to use temporary tables or not */
        private $useTemporaryTables = true;
 
+       /** @var Database */
+       private $db;
+
        /**
         * Constructor
         *
-        * @param IDatabase $db A database subclass
+        * @param Database $db A database subclass
         * @param array $tablesToClone An array of tables to clone, unprefixed
         * @param string $newTablePrefix Prefix to assign to the tables
         * @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
         * @param bool $dropCurrentTables
         */
-       public function __construct( IDatabase $db, array $tablesToClone,
+       public function __construct( Database $db, array $tablesToClone,
                $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
        ) {
                $this->db = $db;
@@ -130,7 +134,7 @@ class CloneDatabase {
        public static function changePrefix( $prefix ) {
                global $wgDBprefix;
 
-               $lbFactory = wfGetLBFactory();
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lbFactory->setDomainPrefix( $prefix );
                $wgDBprefix = $prefix;
        }
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
deleted file mode 100644 (file)
index 221fe9f..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-<?php
-/**
- * Non-directory file on the file system.
- *
- * 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 FileBackend
- */
-
-/**
- * Class representing a non-directory file on the file system
- *
- * @ingroup FileBackend
- */
-class FSFile {
-       /** @var string Path to file */
-       protected $path;
-
-       /** @var string File SHA-1 in base 36 */
-       protected $sha1Base36;
-
-       /**
-        * Sets up the file object
-        *
-        * @param string $path Path to temporary file on local disk
-        */
-       public function __construct( $path ) {
-               $this->path = $path;
-       }
-
-       /**
-        * Returns the file system path
-        *
-        * @return string
-        */
-       public function getPath() {
-               return $this->path;
-       }
-
-       /**
-        * Checks if the file exists
-        *
-        * @return bool
-        */
-       public function exists() {
-               return is_file( $this->path );
-       }
-
-       /**
-        * Get the file size in bytes
-        *
-        * @return int|bool
-        */
-       public function getSize() {
-               return filesize( $this->path );
-       }
-
-       /**
-        * Get the file's last-modified timestamp
-        *
-        * @return string|bool TS_MW timestamp or false on failure
-        */
-       public function getTimestamp() {
-               MediaWiki\suppressWarnings();
-               $timestamp = filemtime( $this->path );
-               MediaWiki\restoreWarnings();
-               if ( $timestamp !== false ) {
-                       $timestamp = wfTimestamp( TS_MW, $timestamp );
-               }
-
-               return $timestamp;
-       }
-
-       /**
-        * Get an associative array containing information about
-        * a file with the given storage path.
-        *
-        * Resulting array fields include:
-        *   - fileExists
-        *   - size (filesize in bytes)
-        *   - mime (as major/minor)
-        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
-        *   - metadata (handler specific)
-        *   - sha1 (in base 36)
-        *   - width
-        *   - height
-        *   - bits (bitrate)
-        *   - file-mime
-        *   - major_mime
-        *   - minor_mime
-        *
-        * @param string|bool $ext The file extension, or true to extract it from the filename.
-        *             Set it to false to ignore the extension.
-        * @return array
-        */
-       public function getProps( $ext = true ) {
-               $info = self::placeholderProps();
-               $info['fileExists'] = $this->exists();
-
-               if ( $info['fileExists'] ) {
-                       $magic = MimeMagic::singleton();
-
-                       # get the file extension
-                       if ( $ext === true ) {
-                               $ext = self::extensionFromPath( $this->path );
-                       }
-
-                       # MIME type according to file contents
-                       $info['file-mime'] = $magic->guessMimeType( $this->path, false );
-                       # logical MIME type
-                       $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
-
-                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
-                       $info['media_type'] = $magic->getMediaType( $this->path, $info['mime'] );
-
-                       # Get size in bytes
-                       $info['size'] = $this->getSize();
-
-                       # Height, width and metadata
-                       $handler = MediaHandler::getHandler( $info['mime'] );
-                       if ( $handler ) {
-                               $tempImage = (object)[]; // XXX (hack for File object)
-                               /** @noinspection PhpParamsInspection */
-                               $info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
-                               /** @noinspection PhpParamsInspection */
-                               $gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
-                               if ( is_array( $gis ) ) {
-                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
-                               }
-                       }
-                       $info['sha1'] = $this->getSha1Base36();
-               }
-
-               return $info;
-       }
-
-       /**
-        * Placeholder file properties to use for files that don't exist
-        *
-        * Resulting array fields include:
-        *   - fileExists
-        *   - mime (as major/minor)
-        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
-        *   - metadata (handler specific)
-        *   - sha1 (in base 36)
-        *   - width
-        *   - height
-        *   - bits (bitrate)
-        *
-        * @return array
-        */
-       public static function placeholderProps() {
-               $info = [];
-               $info['fileExists'] = false;
-               $info['mime'] = null;
-               $info['media_type'] = MEDIATYPE_UNKNOWN;
-               $info['metadata'] = '';
-               $info['sha1'] = '';
-               $info['width'] = 0;
-               $info['height'] = 0;
-               $info['bits'] = 0;
-
-               return $info;
-       }
-
-       /**
-        * Exract image size information
-        *
-        * @param array $gis
-        * @return array
-        */
-       protected function extractImageSizeInfo( array $gis ) {
-               $info = [];
-               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
-               $info['width'] = $gis[0];
-               $info['height'] = $gis[1];
-               if ( isset( $gis['bits'] ) ) {
-                       $info['bits'] = $gis['bits'];
-               } else {
-                       $info['bits'] = 0;
-               }
-
-               return $info;
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @param bool $recache
-        * @return bool|string False on failure
-        */
-       public function getSha1Base36( $recache = false ) {
-               if ( $this->sha1Base36 !== null && !$recache ) {
-                       return $this->sha1Base36;
-               }
-
-               MediaWiki\suppressWarnings();
-               $this->sha1Base36 = sha1_file( $this->path );
-               MediaWiki\restoreWarnings();
-
-               if ( $this->sha1Base36 !== false ) {
-                       $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
-               }
-
-               return $this->sha1Base36;
-       }
-
-       /**
-        * Get the final file extension from a file system path
-        *
-        * @param string $path
-        * @return string
-        */
-       public static function extensionFromPath( $path ) {
-               $i = strrpos( $path, '.' );
-
-               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
-       }
-
-       /**
-        * Get an associative array containing information about a file in the local filesystem.
-        *
-        * @param string $path Absolute local filesystem path
-        * @param string|bool $ext The file extension, or true to extract it from the filename.
-        *   Set it to false to ignore the extension.
-        * @return array
-        */
-       public static function getPropsFromPath( $path, $ext = true ) {
-               $fsFile = new self( $path );
-
-               return $fsFile->getProps( $ext );
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @param string $path
-        * @return bool|string False on failure
-        */
-       public static function getSha1Base36FromPath( $path ) {
-               $fsFile = new self( $path );
-
-               return $fsFile->getSha1Base36();
-       }
-}
index b0e3eee..45951ec 100644 (file)
@@ -195,7 +195,7 @@ class FSFileBackend extends FileBackendStore {
                }
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = TempFSFile::factory( 'create_', 'tmp' );
+                       $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
                        if ( !$tempFile ) {
                                $status->fatal( 'backend-fail-create', $params['dst'] );
 
@@ -653,7 +653,7 @@ class FSFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+                               $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                                if ( !$tmpFile ) {
                                        $tmpFiles[$src] = null;
                                } else {
index 0bae5ff..b560e94 100644 (file)
@@ -168,6 +168,7 @@ class FileBackendGroup {
                        $config['wanCache'] = ObjectCache::getMainWANInstance();
                        $config['mimeCallback'] = [ $this, 'guessMimeInternal' ];
                        $config['statusWrapper'] = [ 'Status', 'wrap' ];
+                       $config['tmpDirectory'] = wfTempDir();
 
                        $this->backends[$name]['instance'] = new $class( $config );
                }
@@ -222,7 +223,7 @@ class FileBackendGroup {
                if ( !$type && $fsPath ) {
                        $type = $magic->guessMimeType( $fsPath, false );
                } elseif ( !$type && strlen( $content ) ) {
-                       $tmpFile = TempFSFile::factory( 'mime_' );
+                       $tmpFile = TempFSFile::factory( 'mime_', '', wfTempDir() );
                        file_put_contents( $tmpFile->getPath(), $content );
                        $type = $magic->guessMimeType( $tmpFile->getPath(), false );
                }
index 74a0068..44fe2cb 100644 (file)
@@ -169,7 +169,7 @@ class MemoryFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $fsFile = TempFSFile::factory( 'localcopy_', $ext );
+                               $fsFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                                if ( $fsFile ) {
                                        $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
                                        if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
index a0027e4..0a0e9f5 100644 (file)
@@ -1127,7 +1127,7 @@ class SwiftFileBackend extends FileBackendStore {
                        // Get source file extension
                        $ext = FileBackend::extensionFromPath( $path );
                        // Create a new temporary file...
-                       $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+                       $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                        if ( $tmpFile ) {
                                $handle = fopen( $tmpFile->getPath(), 'wb' );
                                if ( $handle ) {
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
deleted file mode 100644 (file)
index fed6812..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-<?php
-/**
- * Location holder of files stored temporarily
- *
- * 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 FileBackend
- */
-
-/**
- * This class is used to hold the location and do limited manipulation
- * of files stored temporarily (this will be whatever wfTempDir() returns)
- *
- * @ingroup FileBackend
- */
-class TempFSFile extends FSFile {
-       /** @var bool Garbage collect the temp file */
-       protected $canDelete = false;
-
-       /** @var array Map of (path => 1) for paths to delete on shutdown */
-       protected static $pathsCollect = null;
-
-       public function __construct( $path ) {
-               parent::__construct( $path );
-
-               if ( self::$pathsCollect === null ) {
-                       self::$pathsCollect = [];
-                       register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
-               }
-       }
-
-       /**
-        * Make a new temporary file on the file system.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * @param string $prefix
-        * @param string $extension Optional file extension
-        * @param string|null $tmpDirectory Optional parent directory
-        * @return TempFSFile|null
-        */
-       public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
-               $ext = ( $extension != '' ) ? ".{$extension}" : '';
-
-               $attempts = 5;
-               while ( $attempts-- ) {
-                       $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
-                       if ( !is_string( $tmpDirectory ) ) {
-                               $tmpDirectory = self::getUsableTempDirectory();
-                       }
-                       $path = wfTempDir() . '/' . $prefix . $hex . $ext;
-                       MediaWiki\suppressWarnings();
-                       $newFileHandle = fopen( $path, 'x' );
-                       MediaWiki\restoreWarnings();
-                       if ( $newFileHandle ) {
-                               fclose( $newFileHandle );
-                               $tmpFile = new self( $path );
-                               $tmpFile->autocollect();
-                               // Safely instantiated, end loop.
-                               return $tmpFile;
-                       }
-               }
-
-               // Give up
-               return null;
-       }
-
-       /**
-        * @return string Filesystem path to a temporary directory
-        * @throws RuntimeException
-        */
-       public static function getUsableTempDirectory() {
-               $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
-               $tmpDir[] = sys_get_temp_dir();
-               $tmpDir[] = ini_get( 'upload_tmp_dir' );
-               foreach ( $tmpDir as $tmp ) {
-                       if ( $tmp != '' && is_dir( $tmp ) && is_writable( $tmp ) ) {
-                               return $tmp;
-                       }
-               }
-
-               // PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to
-               // it so create a directory within that called 'mwtmp' with a suffix of the user running
-               // the current process.
-               // The user is included as if various scripts are run by different users they will likely
-               // not be able to access each others temporary files.
-               if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
-                       $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp-' . get_current_user();
-                       if ( !file_exists( $tmp ) ) {
-                               mkdir( $tmp );
-                       }
-                       if ( is_dir( $tmp ) && is_writable( $tmp ) ) {
-                               return $tmp;
-                       }
-               }
-
-               throw new RuntimeException(
-                       'No writable temporary directory could be found. ' .
-                       'Please explicitly specify a writable directory in configuration.' );
-       }
-
-       /**
-        * Purge this file off the file system
-        *
-        * @return bool Success
-        */
-       public function purge() {
-               $this->canDelete = false; // done
-               MediaWiki\suppressWarnings();
-               $ok = unlink( $this->path );
-               MediaWiki\restoreWarnings();
-
-               unset( self::$pathsCollect[$this->path] );
-
-               return $ok;
-       }
-
-       /**
-        * Clean up the temporary file only after an object goes out of scope
-        *
-        * @param object $object
-        * @return TempFSFile This object
-        */
-       public function bind( $object ) {
-               if ( is_object( $object ) ) {
-                       if ( !isset( $object->tempFSFileReferences ) ) {
-                               // Init first since $object might use __get() and return only a copy variable
-                               $object->tempFSFileReferences = [];
-                       }
-                       $object->tempFSFileReferences[] = $this;
-               }
-
-               return $this;
-       }
-
-       /**
-        * Set flag to not clean up after the temporary file
-        *
-        * @return TempFSFile This object
-        */
-       public function preserve() {
-               $this->canDelete = false;
-
-               unset( self::$pathsCollect[$this->path] );
-
-               return $this;
-       }
-
-       /**
-        * Set flag clean up after the temporary file
-        *
-        * @return TempFSFile This object
-        */
-       public function autocollect() {
-               $this->canDelete = true;
-
-               self::$pathsCollect[$this->path] = 1;
-
-               return $this;
-       }
-
-       /**
-        * Try to make sure that all files are purged on error
-        *
-        * This method should only be called internally
-        */
-       public static function purgeAllOnShutdown() {
-               foreach ( self::$pathsCollect as $path ) {
-                       MediaWiki\suppressWarnings();
-                       unlink( $path );
-                       MediaWiki\restoreWarnings();
-               }
-       }
-
-       /**
-        * Cleans up after the temporary file by deleting it
-        */
-       function __destruct() {
-               if ( $this->canDelete ) {
-                       $this->purge();
-               }
-       }
-}
index b24354d..d06acf2 100644 (file)
@@ -66,6 +66,7 @@ class FSRepo extends FileRepo {
                                        "{$repoName}-deleted" => $deletedDir
                                ],
                                'fileMode' => $fileMode,
+                               'tmpDirectory' => wfTempDir()
                        ] );
                        // Update repo config to use this backend
                        $info['backend'] = $backend;
index 8fee3bf..1a6c818 100644 (file)
@@ -1539,9 +1539,15 @@ class FileRepo {
         * @return array
         */
        public function getFileProps( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $fsFile = $this->getLocalReference( $virtualUrl );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               if ( $fsFile ) {
+                       $props = $mwProps->getPropsFromPath( $fsFile->getPath(), true );
+               } else {
+                       $props = $mwProps->newPlaceholderProps();
+               }
 
-               return $this->backend->getFileProps( [ 'src' => $path ] );
+               return $props;
        }
 
        /**
index bd32de0..d47624f 100644 (file)
@@ -452,7 +452,9 @@ class RepoGroup {
 
                        return $repo->getFileProps( $fileName );
                } else {
-                       return FSFile::getPropsFromPath( $fileName );
+                       $mwProps = new MWFileProps( MimeMagic::singleton() );
+
+                       return $mwProps->getPropsFromPath( $fileName, true );
                }
        }
 
index 396b47c..60cfdac 100644 (file)
@@ -1179,7 +1179,8 @@ class LocalFile extends File {
                        ) {
                                $props = $this->repo->getFileProps( $srcPath );
                        } else {
-                               $props = FSFile::getPropsFromPath( $srcPath );
+                               $mwProps = new MWFileProps( MimeMagic::singleton() );
+                               $props = $mwProps->getPropsFromPath( $srcPath, true );
                        }
                }
 
index d3973a1..69891ea 100644 (file)
@@ -43,7 +43,7 @@
        "config-page-existingwiki": "Съществуващо уики",
        "config-help-restart": "Необходимо е потвърждение за изтриване на всички въведени и съхранени данни и започване отначало на процеса по инсталация.",
        "config-restart": "Да, започване отначало",
-       "config-welcome": "=== Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ñ\81Ñ\80едаÑ\82а ===\nЩе Ð±Ñ\8aдаÑ\82 Ð¸Ð·Ð²Ñ\8aÑ\80Ñ\88ени Ð¾Ñ\81новни Ð¿Ñ\80овеÑ\80ки, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\83Ñ\81Ñ\82ановÑ\8fÑ\82 Ð´Ð°Ð»Ð¸ Ñ\81Ñ\80едаÑ\82а Ðµ Ð¿Ð¾Ð´Ñ\85одÑ\8fÑ\89а за инсталиране на МедияУики.\nАко е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.",
+       "config-welcome": "=== Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ñ\83Ñ\81ловиÑ\8fÑ\82а ===\nЩе Ð±Ñ\8aдаÑ\82 Ð¸Ð·Ð²Ñ\8aÑ\80Ñ\88ени Ð¾Ñ\81новни Ð¿Ñ\80овеÑ\80ки, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\83Ñ\81Ñ\82ановÑ\8fÑ\82 Ð´Ð°Ð»Ð¸ Ñ\83Ñ\81ловиÑ\8fÑ\82а Ñ\81а Ð¿Ð¾Ð´Ñ\85одÑ\8fÑ\89и за инсталиране на МедияУики.\nАко е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.",
        "config-copyright": "=== Авторски права и условия ===\n\n$1\n\nТази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.\n\nТази програма се разпространява с надеждата, че ще е полезна, но <strong>без каквито и да е гаранции</strong>; без дори косвена гаранция за <strong>продаваемост</strong>  или <strong>прогодност за конкретна употреба</strong> .\nЗа повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.\n\nКъм програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
        "config-sidebar": "* [https://www.mediawiki.org Сайт на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Наръчник на потребителя]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Наръчник на администратора]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ]\n----\n* <doclink href=Readme>Документация</doclink>\n* <doclink href=ReleaseNotes>Бележки за версията</doclink>\n* <doclink href=Copying>Авторски права</doclink>\n* <doclink href=UpgradeDoc>Обновяване</doclink>",
        "config-env-good": "Средата беше проверена.\nИнсталирането на МедияУики е възможно.",
        "config-help": "помощ",
        "config-nofile": "Файлът „$1“ не може да бъде открит. Да не е бил изтрит?",
        "config-extension-link": "Знаете ли, че това уики поддържа [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions разширения]?\n\nМожете да разгледате [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category разширенията по категория] или [https://www.mediawiki.org/wiki/Extension_Matrix Матрицата на разширенията] за пълен списък на разширенията.",
-       "mainpagetext": "<strong>УикиÑ\82о беше успешно инсталирано.</strong>",
+       "mainpagetext": "<strong>Ð\9cедиÑ\8fУики беше успешно инсталирано.</strong>",
        "mainpagedocfooter": "Разгледайте [https://meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.\n\n== Първи стъпки ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Настройки за конфигуриране]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ за МедияУики]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локализиране на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Научете как да се справяте със спама във вашето уики]"
 }
diff --git a/includes/libs/IP.php b/includes/libs/IP.php
new file mode 100644 (file)
index 0000000..21203a4
--- /dev/null
@@ -0,0 +1,744 @@
+<?php
+/**
+ * Functions and constants to play with IP addresses and ranges
+ *
+ * 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
+ * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz
+ */
+
+use IPSet\IPSet;
+
+// Some regex definition to "play" with IP address and IP address blocks
+
+// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
+define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
+define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
+// An IPv4 block is an IP address and a prefix (d1 to d32)
+define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
+define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
+
+// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
+// However, the "::" abbreviation can be used on consecutive x0000 words.
+define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
+define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
+define( 'RE_IPV6_ADD',
+       '(?:' . // starts with "::" (including "::")
+               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
+       '|' . // ends with "::" (except "::")
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
+       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
+               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
+       '|' . // contains no "::"
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
+       ')'
+);
+// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
+// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
+define( 'IP_ADDRESS_STRING',
+       '(?:' .
+               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
+       '|' .
+               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
+       ')'
+);
+
+/**
+ * A collection of public static functions to play with IP address
+ * and IP blocks.
+ */
+class IP {
+       /** @var IPSet */
+       private static $proxyIpSet = null;
+
+       /**
+        * Determine if a string is as valid IP address or network (CIDR prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPAddress( $ip ) {
+               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv6 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv6( $ip ) {
+               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv4 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv4( $ip ) {
+               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Validate an IP address. Ranges are NOT considered valid.
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip
+        * @return bool True if it is valid
+        */
+       public static function isValid( $ip ) {
+               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+       }
+
+       /**
+        * Validate an IP Block (valid address WITH a valid prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ipblock
+        * @return bool True if it is valid
+        */
+       public static function isValidBlock( $ipblock ) {
+               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
+       }
+
+       /**
+        * Convert an IP into a verbose, uppercase, normalized form.
+        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
+        * IPv6 addresses in octet notation are expanded to 8 words;
+        * IPv4 addresses have leading zeros, in each octet, removed.
+        *
+        * @param string $ip IP address in quad or octet form (CIDR or not).
+        * @return string
+        */
+       public static function sanitizeIP( $ip ) {
+               $ip = trim( $ip );
+               if ( $ip === '' ) {
+                       return null;
+               }
+               /* If not an IP, just return trimmed value, since sanitizeIP() is called
+                * in a number of contexts where usernames are supplied as input.
+                */
+               if ( !self::isIPAddress( $ip ) ) {
+                       return $ip;
+               }
+               if ( self::isIPv4( $ip ) ) {
+                       // Remove leading 0's from octet representation of IPv4 address
+                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
+                       return $ip;
+               }
+               // Remove any whitespaces, convert to upper case
+               $ip = strtoupper( $ip );
+               // Expand zero abbreviations
+               $abbrevPos = strpos( $ip, '::' );
+               if ( $abbrevPos !== false ) {
+                       // We know this is valid IPv6. Find the last index of the
+                       // address before any CIDR number (e.g. "a:b:c::/24").
+                       $CIDRStart = strpos( $ip, "/" );
+                       $addressEnd = ( $CIDRStart !== false )
+                               ? $CIDRStart - 1
+                               : strlen( $ip ) - 1;
+                       // If the '::' is at the beginning...
+                       if ( $abbrevPos == 0 ) {
+                               $repeat = '0:';
+                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is at the end...
+                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
+                               $repeat = ':0';
+                               $extra = '';
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is in the middle...
+                       } else {
+                               $repeat = ':0';
+                               $extra = ':';
+                               $pad = 8; // 6+2 (due to '::')
+                       }
+                       $ip = str_replace( '::',
+                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
+                               $ip
+                       );
+               }
+               // Remove leading zeros from each bloc as needed
+               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
+
+               return $ip;
+       }
+
+       /**
+        * Prettify an IP for display to end users.
+        * This will make it more compact and lower-case.
+        *
+        * @param string $ip
+        * @return string
+        */
+       public static function prettifyIP( $ip ) {
+               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
+               if ( self::isIPv6( $ip ) ) {
+                       // Split IP into an address and a CIDR
+                       if ( strpos( $ip, '/' ) !== false ) {
+                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
+                       } else {
+                               list( $ip, $cidr ) = [ $ip, '' ];
+                       }
+                       // Get the largest slice of words with multiple zeros
+                       $offset = 0;
+                       $longest = $longestPos = false;
+                       while ( preg_match(
+                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
+                       ) ) {
+                               list( $match, $pos ) = $m[0]; // full match
+                               if ( strlen( $match ) > strlen( $longest ) ) {
+                                       $longest = $match;
+                                       $longestPos = $pos;
+                               }
+                               $offset = ( $pos + strlen( $match ) ); // advance
+                       }
+                       if ( $longest !== false ) {
+                               // Replace this portion of the string with the '::' abbreviation
+                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
+                       }
+                       // Add any CIDR back on
+                       if ( $cidr !== '' ) {
+                               $ip = "{$ip}/{$cidr}";
+                       }
+                       // Convert to lower case to make it more readable
+                       $ip = strtolower( $ip );
+               }
+
+               return $ip;
+       }
+
+       /**
+        * Given a host/port string, like one might find in the host part of a URL
+        * per RFC 2732, split the hostname part and the port part and return an
+        * array with an element for each. If there is no port part, the array will
+        * have false in place of the port. If the string was invalid in some way,
+        * false is returned.
+        *
+        * This was easy with IPv4 and was generally done in an ad-hoc way, but
+        * with IPv6 it's somewhat more complicated due to the need to parse the
+        * square brackets and colons.
+        *
+        * A bare IPv6 address is accepted despite the lack of square brackets.
+        *
+        * @param string $both The string with the host and port
+        * @return array|false Array normally, false on certain failures
+        */
+       public static function splitHostAndPort( $both ) {
+               if ( substr( $both, 0, 1 ) === '[' ) {
+                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
+                               if ( isset( $m['port'] ) ) {
+                                       return [ $m[1], intval( $m['port'] ) ];
+                               } else {
+                                       return [ $m[1], false ];
+                               }
+                       } else {
+                               // Square bracket found but no IPv6
+                               return false;
+                       }
+               }
+               $numColons = substr_count( $both, ':' );
+               if ( $numColons >= 2 ) {
+                       // Is it a bare IPv6 address?
+                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
+                               return [ $both, false ];
+                       } else {
+                               // Not valid IPv6, but too many colons for anything else
+                               return false;
+                       }
+               }
+               if ( $numColons >= 1 ) {
+                       // Host:port?
+                       $bits = explode( ':', $both );
+                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
+                               return [ $bits[0], intval( $bits[1] ) ];
+                       } else {
+                               // Not a valid port
+                               return false;
+                       }
+               }
+
+               // Plain hostname
+               return [ $both, false ];
+       }
+
+       /**
+        * Given a host name and a port, combine them into host/port string like
+        * you might find in a URL. If the host contains a colon, wrap it in square
+        * brackets like in RFC 2732. If the port matches the default port, omit
+        * the port specification
+        *
+        * @param string $host
+        * @param int $port
+        * @param bool|int $defaultPort
+        * @return string
+        */
+       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
+               if ( strpos( $host, ':' ) !== false ) {
+                       $host = "[$host]";
+               }
+               if ( $defaultPort !== false && $port == $defaultPort ) {
+                       return $host;
+               } else {
+                       return "$host:$port";
+               }
+       }
+
+       /**
+        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
+        *
+        * @param string $hex Number, with "v6-" prefix if it is IPv6
+        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
+        */
+       public static function formatHex( $hex ) {
+               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
+                       return self::hexToOctet( substr( $hex, 3 ) );
+               } else { // IPv4
+                       return self::hexToQuad( $hex );
+               }
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv6 address in octet notation
+        *
+        * @param string $ip_hex Pure hex (no v6- prefix)
+        * @return string (of format a:b:c:d:e:f:g:h)
+        */
+       public static function hexToOctet( $ip_hex ) {
+               // Pad hex to 32 chars (128 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
+               // Separate into 8 words
+               $ip_oct = substr( $ip_hex, 0, 4 );
+               for ( $n = 1; $n < 8; $n++ ) {
+                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
+               }
+               // NO leading zeroes
+               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
+
+               return $ip_oct;
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
+        *
+        * @param string $ip_hex Pure hex
+        * @return string (of format a.b.c.d)
+        */
+       public static function hexToQuad( $ip_hex ) {
+               // Pad hex to 8 chars (32 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+               // Separate into four quads
+               $s = '';
+               for ( $i = 0; $i < 4; $i++ ) {
+                       if ( $s !== '' ) {
+                               $s .= '.';
+                       }
+                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
+               }
+
+               return $s;
+       }
+
+       /**
+        * Determine if an IP address really is an IP address, and if it is public,
+        * i.e. not RFC 1918 or similar
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public static function isPublic( $ip ) {
+               static $privateSet = null;
+               if ( !$privateSet ) {
+                       $privateSet = new IPSet( [
+                               '10.0.0.0/8', # RFC 1918 (private)
+                               '172.16.0.0/12', # RFC 1918 (private)
+                               '192.168.0.0/16', # RFC 1918 (private)
+                               '0.0.0.0/8', # this network
+                               '127.0.0.0/8', # loopback
+                               'fc00::/7', # RFC 4193 (local)
+                               '0:0:0:0:0:0:0:1', # loopback
+                               '169.254.0.0/16', # link-local
+                               'fe80::/10', # link-local
+                       ] );
+               }
+               return !$privateSet->match( $ip );
+       }
+
+       /**
+        * Return a zero-padded upper case hexadecimal representation of an IP address.
+        *
+        * Hexadecimal addresses are used because they can easily be extended to
+        * IPv6 support. To separate the ranges, the return value from this
+        * function for an IPv6 address will be prefixed with "v6-", a non-
+        * hexadecimal string which sorts after the IPv4 addresses.
+        *
+        * @param string $ip Quad dotted/octet IP address.
+        * @return string|bool False on failure
+        */
+       public static function toHex( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
+               } elseif ( self::isIPv4( $ip ) ) {
+                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
+                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
+                       $ip = self::sanitizeIP( $ip );
+                       $n = ip2long( $ip );
+                       if ( $n < 0 ) {
+                               $n += pow( 2, 32 );
+                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+                               # so $n becomes a float. We convert it to string instead.
+                               if ( is_float( $n ) ) {
+                                       $n = (string)$n;
+                               }
+                       }
+                       if ( $n !== false ) {
+                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
+                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
+                       }
+               } else {
+                       $n = false;
+               }
+
+               return $n;
+       }
+
+       /**
+        * Given an IPv6 address in octet notation, returns a pure hex string.
+        *
+        * @param string $ip Octet ipv6 IP address.
+        * @return string|bool Pure hex (uppercase); false on failure
+        */
+       private static function IPv6ToRawHex( $ip ) {
+               $ip = self::sanitizeIP( $ip );
+               if ( !$ip ) {
+                       return false;
+               }
+               $r_ip = '';
+               foreach ( explode( ':', $ip ) as $v ) {
+                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+               }
+
+               return $r_ip;
+       }
+
+       /**
+        * Convert a network specification in CIDR notation
+        * to an integer network and a number of bits
+        *
+        * @param string $range IP with CIDR prefix
+        * @return array(int or string, int)
+        */
+       public static function parseCIDR( $range ) {
+               if ( self::isIPv6( $range ) ) {
+                       return self::parseCIDR6( $range );
+               }
+               $parts = explode( '/', $range, 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = ip2long( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
+                       if ( $bits == 0 ) {
+                               $network = 0;
+                       } else {
+                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
+                       }
+                       # Convert to unsigned
+                       if ( $network < 0 ) {
+                               $network += pow( 2, 32 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, $bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats,
+        * return the start and end of the range in hexadecimal.
+        *
+        * Formats are:
+        *     1.2.3.4/24          CIDR
+        *     1.2.3.4 - 1.2.3.5   Explicit range
+        *     1.2.3.4             Single IP
+        *
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344                          Single IP
+        * @param string $range IP range
+        * @return array(string, string)
+        */
+       public static function parseRange( $range ) {
+               // CIDR notation
+               if ( strpos( $range, '/' ) !== false ) {
+                       if ( self::isIPv6( $range ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       list( $network, $bits ) = self::parseCIDR( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = sprintf( '%08X', $network );
+                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
+                       }
+               // Explicit range
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
+                               $start = self::toHex( $start );
+                               $end = self::toHex( $end );
+                               if ( $start > $end ) {
+                                       $start = $end = false;
+                               }
+                       } else {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Convert a network specification in IPv6 CIDR notation to an
+        * integer network and a number of bits
+        *
+        * @param string $range
+        *
+        * @return array(string, int)
+        */
+       private static function parseCIDR6( $range ) {
+               # Explode into <expanded IP,range>
+               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = self::IPv6ToRawHex( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+                       if ( $bits == 0 ) {
+                               $network = "0";
+                       } else {
+                               # Native 32 bit functions WONT work here!!!
+                               # Convert to a padded binary number
+                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with zeros
+                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+                               # Convert back to an integer
+                               $network = Wikimedia\base_convert( $network, 2, 10 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, (int)$bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats, return the
+        * start and end of the range in hexadecimal. For IPv6.
+        *
+        * Formats are:
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344/96                       Single IP
+        *
+        * @param string $range
+        *
+        * @return array(string, string)
+        */
+       private static function parseRange6( $range ) {
+               # Expand any IPv6 IP
+               $range = IP::sanitizeIP( $range );
+               // CIDR notation...
+               if ( strpos( $range, '/' ) !== false ) {
+                       list( $network, $bits ) = self::parseCIDR6( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
+                               # Turn network to binary (again)
+                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with ones
+                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+                               # Convert to hex
+                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
+                               # see toHex() comment
+                               $start = "v6-$start";
+                               $end = "v6-$end";
+                       }
+               // Explicit range notation...
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       $start = self::toHex( $start );
+                       $end = self::toHex( $end );
+                       if ( $start > $end ) {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Determine if a given IPv4/IPv6 address is in a given CIDR network
+        *
+        * @param string $addr The address to check against the given range.
+        * @param string $range The range to check the given address against.
+        * @return bool Whether or not the given address is in the given range.
+        *
+        * @note This can return unexpected results for invalid arguments!
+        *       Make sure you pass a valid IP address and IP range.
+        */
+       public static function isInRange( $addr, $range ) {
+               $hexIP = self::toHex( $addr );
+               list( $start, $end ) = self::parseRange( $range );
+
+               return ( strcmp( $hexIP, $start ) >= 0 &&
+                       strcmp( $hexIP, $end ) <= 0 );
+       }
+
+       /**
+        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
+        *
+        * @since 1.25
+        *
+        * @param string $ip the IP to check
+        * @param array $ranges the IP ranges, each element a range
+        *
+        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
+        */
+       public static function isInRanges( $ip, $ranges ) {
+               foreach ( $ranges as $range ) {
+                       if ( self::isInRange( $ip, $range ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Convert some unusual representations of IPv4 addresses to their
+        * canonical dotted quad representation.
+        *
+        * This currently only checks a few IPV4-to-IPv6 related cases.  More
+        * unusual representations may be added later.
+        *
+        * @param string $addr Something that might be an IP address
+        * @return string|null Valid dotted quad IPv4 address or null
+        */
+       public static function canonicalize( $addr ) {
+               // remove zone info (bug 35738)
+               $addr = preg_replace( '/\%.*/', '', $addr );
+
+               if ( self::isValid( $addr ) ) {
+                       return $addr;
+               }
+               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
+               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
+                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
+                       if ( self::isIPv4( $addr ) ) {
+                               return $addr;
+                       }
+               }
+               // IPv6 loopback address
+               $m = [];
+               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
+                       return '127.0.0.1';
+               }
+               // IPv4-mapped and IPv4-compatible IPv6 addresses
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
+                       return $m[1];
+               }
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
+                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
+               ) {
+                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+               }
+
+               return null; // give up
+       }
+
+       /**
+        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
+        * For example, 127.111.113.151/24 -> 127.111.113.0/24
+        * @param string $range IP address to normalize
+        * @return string
+        */
+       public static function sanitizeRange( $range ) {
+               list( /*...*/, $bits ) = self::parseCIDR( $range );
+               list( $start, /*...*/ ) = self::parseRange( $range );
+               $start = self::formatHex( $start );
+               if ( $bits === false ) {
+                       return $start; // wasn't actually a range
+               }
+
+               return "$start/$bits";
+       }
+
+       /**
+        * Returns the subnet of a given IP
+        *
+        * @param string $ip
+        * @return string|false
+        */
+       public static function getSubnet( $ip ) {
+               $matches = [];
+               $subnet = false;
+               if ( IP::isIPv6( $ip ) ) {
+                       $parts = IP::parseRange( "$ip/64" );
+                       $subnet = $parts[0];
+               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
+                       // IPv4
+                       $subnet = $matches[1];
+               }
+               return $subnet;
+       }
+}
diff --git a/includes/libs/SamplingStatsdClient.php b/includes/libs/SamplingStatsdClient.php
deleted file mode 100644 (file)
index dd1976c..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-/**
- * Copyright 2015
- *
- * 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
- */
-
-use Liuggio\StatsdClient\StatsdClient;
-use Liuggio\StatsdClient\Entity\StatsdData;
-use Liuggio\StatsdClient\Entity\StatsdDataInterface;
-
-/**
- * A statsd client that applies the sampling rate to the data items before sending them.
- *
- * @since 1.26
- */
-class SamplingStatsdClient extends StatsdClient {
-       protected $samplingRates = [];
-
-       /**
-        * Sampling rates as an associative array of patterns and rates.
-        * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
-        * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
-        * @param array $samplingRates
-        * @since 1.28
-        */
-       public function setSamplingRates( array $samplingRates ) {
-               $this->samplingRates = $samplingRates;
-       }
-
-       /**
-        * Sets sampling rate for all items in $data.
-        * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
-        *
-        * {@inheritDoc}
-        */
-       public function appendSampleRate( $data, $sampleRate = 1 ) {
-               $samplingRates = $this->samplingRates;
-               if ( !$samplingRates && $sampleRate !== 1 ) {
-                       $samplingRates = [ '*' => $sampleRate ];
-               }
-               if ( $samplingRates ) {
-                       array_walk( $data, function( $item ) use ( $samplingRates ) {
-                               /** @var $item StatsdData */
-                               foreach ( $samplingRates as $pattern => $rate ) {
-                                       if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
-                                               $item->setSampleRate( $item->getSampleRate() * $rate );
-                                               break;
-                                       }
-                               }
-                       } );
-               }
-
-               return $data;
-       }
-
-       /*
-        * Send the metrics over UDP
-        * Sample the metrics according to their sample rate and send the remaining ones.
-        *
-        * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent
-        *        strings are not allowed here as sampleData requires a StatsdDataInterface
-        * @param int $sampleRate
-        *
-        * @return integer the data sent in bytes
-        */
-       public function send( $data, $sampleRate = 1 ) {
-               if ( !is_array( $data ) ) {
-                       $data = [ $data ];
-               }
-               if ( !$data ) {
-                       return;
-               }
-               foreach ( $data as $item ) {
-                       if ( !( $item instanceof StatsdDataInterface ) ) {
-                               throw new InvalidArgumentException(
-                                       'SamplingStatsdClient does not accept stringified messages' );
-                       }
-               }
-
-               // add sampling
-               $data = $this->appendSampleRate( $data, $sampleRate );
-               $data = $this->sampleData( $data );
-
-               $data = array_map( 'strval', $data );
-
-               // reduce number of packets
-               if ( $this->getReducePacket() ) {
-                       $data = $this->reduceCount( $data );
-               }
-
-               // failures in any of this should be silently ignored if ..
-               $written = 0;
-               try {
-                       $fp = $this->getSender()->open();
-                       if ( !$fp ) {
-                               return;
-                       }
-                       foreach ( $data as $message ) {
-                               $written += $this->getSender()->write( $fp, $message );
-                       }
-                       $this->getSender()->close( $fp );
-               } catch ( Exception $e ) {
-                       $this->throwException( $e );
-               }
-
-               return $written;
-       }
-
-       /**
-        * Throw away some of the data according to the sample rate.
-        * @param StatsdDataInterface[] $data
-        * @return StatsdDataInterface[]
-        * @throws LogicException
-        */
-       protected function sampleData( $data ) {
-               $newData = [];
-               $mt_rand_max = mt_getrandmax();
-               foreach ( $data as $item ) {
-                       $samplingRate = $item->getSampleRate();
-                       if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
-                               throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
-                       }
-                       if (
-                               $samplingRate === 1 ||
-                               ( mt_rand() / $mt_rand_max <= $samplingRate )
-                       ) {
-                               $newData[] = $item;
-                       }
-               }
-               return $newData;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       protected function throwException( Exception $exception ) {
-               if ( !$this->getFailSilently() ) {
-                       throw $exception;
-               }
-       }
-}
diff --git a/includes/libs/filebackend/FSFile.php b/includes/libs/filebackend/FSFile.php
new file mode 100644 (file)
index 0000000..d0e93da
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Non-directory file on the file system.
+ *
+ * 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 FileBackend
+ */
+
+/**
+ * Class representing a non-directory file on the file system
+ *
+ * @ingroup FileBackend
+ */
+class FSFile {
+       /** @var string Path to file */
+       protected $path;
+
+       /** @var string File SHA-1 in base 36 */
+       protected $sha1Base36;
+
+       /**
+        * Sets up the file object
+        *
+        * @param string $path Path to temporary file on local disk
+        */
+       public function __construct( $path ) {
+               $this->path = $path;
+       }
+
+       /**
+        * Returns the file system path
+        *
+        * @return string
+        */
+       public function getPath() {
+               return $this->path;
+       }
+
+       /**
+        * Checks if the file exists
+        *
+        * @return bool
+        */
+       public function exists() {
+               return is_file( $this->path );
+       }
+
+       /**
+        * Get the file size in bytes
+        *
+        * @return int|bool
+        */
+       public function getSize() {
+               return filesize( $this->path );
+       }
+
+       /**
+        * Get the file's last-modified timestamp
+        *
+        * @return string|bool TS_MW timestamp or false on failure
+        */
+       public function getTimestamp() {
+               MediaWiki\suppressWarnings();
+               $timestamp = filemtime( $this->path );
+               MediaWiki\restoreWarnings();
+               if ( $timestamp !== false ) {
+                       $timestamp = wfTimestamp( TS_MW, $timestamp );
+               }
+
+               return $timestamp;
+       }
+
+       /**
+        * Get an associative array containing information about
+        * a file with the given storage path.
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - file-mime (as major/minor)
+        *   - sha1 (in base 36)
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension. Currently unused.
+        * @return array
+        */
+       public function getProps( $ext = true ) {
+               $info = self::placeholderProps();
+               $info['fileExists'] = $this->exists();
+
+               if ( $info['fileExists'] ) {
+                       $info['size'] = $this->getSize(); // bytes
+                       $info['sha1'] = $this->getSha1Base36();
+
+                       $mime = mime_content_type( $this->path );
+                       # MIME type according to file contents
+                       $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
+                       # logical MIME type
+                       $info['mime'] = $mime;
+
+                       if ( strpos( $mime, '/' ) !== false ) {
+                               list( $info['major_mime'], $info['minor_mime'] ) = explode( '/', $mime, 2 );
+                       } else {
+                               list( $info['major_mime'], $info['minor_mime'] ) = [ $mime, 'unknown' ];
+                       }
+               }
+
+               return $info;
+       }
+
+       /**
+        * Placeholder file properties to use for files that don't exist
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - file-mime (as major/minor)
+        *   - sha1 (in base 36)
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @return array
+        */
+       public static function placeholderProps() {
+               $info = [];
+               $info['fileExists'] = false;
+               $info['size'] = 0;
+               $info['file-mime'] = null;
+               $info['major_mime'] = null;
+               $info['minor_mime'] = null;
+               $info['mime'] = null;
+               $info['sha1'] = '';
+
+               return $info;
+       }
+
+       /**
+        * Exract image size information
+        *
+        * @param array $gis
+        * @return array
+        */
+       protected function extractImageSizeInfo( array $gis ) {
+               $info = [];
+               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+               $info['width'] = $gis[0];
+               $info['height'] = $gis[1];
+               if ( isset( $gis['bits'] ) ) {
+                       $info['bits'] = $gis['bits'];
+               } else {
+                       $info['bits'] = 0;
+               }
+
+               return $info;
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @param bool $recache
+        * @return bool|string False on failure
+        */
+       public function getSha1Base36( $recache = false ) {
+               if ( $this->sha1Base36 !== null && !$recache ) {
+                       return $this->sha1Base36;
+               }
+
+               MediaWiki\suppressWarnings();
+               $this->sha1Base36 = sha1_file( $this->path );
+               MediaWiki\restoreWarnings();
+
+               if ( $this->sha1Base36 !== false ) {
+                       $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
+               }
+
+               return $this->sha1Base36;
+       }
+
+       /**
+        * Get the final file extension from a file system path
+        *
+        * @param string $path
+        * @return string
+        */
+       public static function extensionFromPath( $path ) {
+               $i = strrpos( $path, '.' );
+
+               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+       }
+
+       /**
+        * Get an associative array containing information about a file in the local filesystem.
+        *
+        * @param string $path Absolute local filesystem path
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *   Set it to false to ignore the extension.
+        * @return array
+        */
+       public static function getPropsFromPath( $path, $ext = true ) {
+               $fsFile = new self( $path );
+
+               return $fsFile->getProps( $ext );
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @param string $path
+        * @return bool|string False on failure
+        */
+       public static function getSha1Base36FromPath( $path ) {
+               $fsFile = new self( $path );
+
+               return $fsFile->getSha1Base36();
+       }
+}
index 4ff342f..1317b65 100644 (file)
@@ -98,6 +98,9 @@ abstract class FileBackend {
        /** @var int How many operations can be done in parallel */
        protected $concurrency;
 
+       /** @var string Temporary file directory */
+       protected $tmpDirectory;
+
        /** @var LockManager */
        protected $lockManager;
 
@@ -134,6 +137,8 @@ abstract class FileBackend {
         *   - parallelize : When to do file operations in parallel (when possible).
         *                   Allowed values are "implicit", "explicit" and "off".
         *   - concurrency : How many file operations can be done in parallel.
+        *   - tmpDirectory : Directory to use for temporary files. If this is not set or null,
+        *                    then the backend will try to discover a usable temporary directory.
         * @throws FileBackendException
         */
        public function __construct( array $config ) {
@@ -160,6 +165,7 @@ abstract class FileBackend {
                        ? (int)$config['concurrency']
                        : 50;
                $this->statusWrapper = isset( $config['statusWrapper'] ) ? $config['statusWrapper'] : null;
+               $this->tmpDirectory = isset( $config['tmpDirectory'] ) ? $config['tmpDirectory'] : null;
        }
 
        /**
diff --git a/includes/libs/filebackend/TempFSFile.php b/includes/libs/filebackend/TempFSFile.php
new file mode 100644 (file)
index 0000000..fed6812
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Location holder of files stored temporarily
+ *
+ * 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 FileBackend
+ */
+
+/**
+ * This class is used to hold the location and do limited manipulation
+ * of files stored temporarily (this will be whatever wfTempDir() returns)
+ *
+ * @ingroup FileBackend
+ */
+class TempFSFile extends FSFile {
+       /** @var bool Garbage collect the temp file */
+       protected $canDelete = false;
+
+       /** @var array Map of (path => 1) for paths to delete on shutdown */
+       protected static $pathsCollect = null;
+
+       public function __construct( $path ) {
+               parent::__construct( $path );
+
+               if ( self::$pathsCollect === null ) {
+                       self::$pathsCollect = [];
+                       register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
+               }
+       }
+
+       /**
+        * Make a new temporary file on the file system.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * @param string $prefix
+        * @param string $extension Optional file extension
+        * @param string|null $tmpDirectory Optional parent directory
+        * @return TempFSFile|null
+        */
+       public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
+               $ext = ( $extension != '' ) ? ".{$extension}" : '';
+
+               $attempts = 5;
+               while ( $attempts-- ) {
+                       $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
+                       if ( !is_string( $tmpDirectory ) ) {
+                               $tmpDirectory = self::getUsableTempDirectory();
+                       }
+                       $path = wfTempDir() . '/' . $prefix . $hex . $ext;
+                       MediaWiki\suppressWarnings();
+                       $newFileHandle = fopen( $path, 'x' );
+                       MediaWiki\restoreWarnings();
+                       if ( $newFileHandle ) {
+                               fclose( $newFileHandle );
+                               $tmpFile = new self( $path );
+                               $tmpFile->autocollect();
+                               // Safely instantiated, end loop.
+                               return $tmpFile;
+                       }
+               }
+
+               // Give up
+               return null;
+       }
+
+       /**
+        * @return string Filesystem path to a temporary directory
+        * @throws RuntimeException
+        */
+       public static function getUsableTempDirectory() {
+               $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
+               $tmpDir[] = sys_get_temp_dir();
+               $tmpDir[] = ini_get( 'upload_tmp_dir' );
+               foreach ( $tmpDir as $tmp ) {
+                       if ( $tmp != '' && is_dir( $tmp ) && is_writable( $tmp ) ) {
+                               return $tmp;
+                       }
+               }
+
+               // PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to
+               // it so create a directory within that called 'mwtmp' with a suffix of the user running
+               // the current process.
+               // The user is included as if various scripts are run by different users they will likely
+               // not be able to access each others temporary files.
+               if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
+                       $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp-' . get_current_user();
+                       if ( !file_exists( $tmp ) ) {
+                               mkdir( $tmp );
+                       }
+                       if ( is_dir( $tmp ) && is_writable( $tmp ) ) {
+                               return $tmp;
+                       }
+               }
+
+               throw new RuntimeException(
+                       'No writable temporary directory could be found. ' .
+                       'Please explicitly specify a writable directory in configuration.' );
+       }
+
+       /**
+        * Purge this file off the file system
+        *
+        * @return bool Success
+        */
+       public function purge() {
+               $this->canDelete = false; // done
+               MediaWiki\suppressWarnings();
+               $ok = unlink( $this->path );
+               MediaWiki\restoreWarnings();
+
+               unset( self::$pathsCollect[$this->path] );
+
+               return $ok;
+       }
+
+       /**
+        * Clean up the temporary file only after an object goes out of scope
+        *
+        * @param object $object
+        * @return TempFSFile This object
+        */
+       public function bind( $object ) {
+               if ( is_object( $object ) ) {
+                       if ( !isset( $object->tempFSFileReferences ) ) {
+                               // Init first since $object might use __get() and return only a copy variable
+                               $object->tempFSFileReferences = [];
+                       }
+                       $object->tempFSFileReferences[] = $this;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Set flag to not clean up after the temporary file
+        *
+        * @return TempFSFile This object
+        */
+       public function preserve() {
+               $this->canDelete = false;
+
+               unset( self::$pathsCollect[$this->path] );
+
+               return $this;
+       }
+
+       /**
+        * Set flag clean up after the temporary file
+        *
+        * @return TempFSFile This object
+        */
+       public function autocollect() {
+               $this->canDelete = true;
+
+               self::$pathsCollect[$this->path] = 1;
+
+               return $this;
+       }
+
+       /**
+        * Try to make sure that all files are purged on error
+        *
+        * This method should only be called internally
+        */
+       public static function purgeAllOnShutdown() {
+               foreach ( self::$pathsCollect as $path ) {
+                       MediaWiki\suppressWarnings();
+                       unlink( $path );
+                       MediaWiki\restoreWarnings();
+               }
+       }
+
+       /**
+        * Cleans up after the temporary file by deleting it
+        */
+       function __destruct() {
+               if ( $this->canDelete ) {
+                       $this->purge();
+               }
+       }
+}
index 2375678..1cb8906 100644 (file)
@@ -316,6 +316,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function aggregateValue( $valuedata, $valuename = 'value' ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function bitNot( $field ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
@@ -338,6 +342,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function buildStringCast( $field ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function selectDB( $db ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
index 4f663b3..bf3a5f0 100644 (file)
@@ -204,17 +204,11 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        /** @var array Map of (name => 1) for locks obtained via lock() */
        private $mNamedLocksHeld = [];
        /** @var array Map of (table name => 1) for TEMPORARY tables */
-       private $mSessionTempTables = [];
+       protected $mSessionTempTables = [];
 
        /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
        private $lazyMasterHandle;
 
-       /**
-        * @since 1.21
-        * @var resource File handle for upgrade
-        */
-       protected $fileHandle = null;
-
        /**
         * @since 1.22
         * @var string[] Process cache of VIEWs names in the database
@@ -272,6 +266,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $this->trxProfiler = $params['trxProfiler'];
                $this->connLogger = $params['connLogger'];
                $this->queryLogger = $params['queryLogger'];
+               $this->errorLogger = $params['errorLogger'];
 
                // Set initial dummy domain until open() sets the final DB/prefix
                $this->currentDomain = DatabaseDomain::newUnspecified();
@@ -470,15 +465,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return $old;
        }
 
-       /**
-        * Set the filehandle to copy write statements to.
-        *
-        * @param resource $fh File handle
-        */
-       public function setFileHandle( $fh ) {
-               $this->fileHandle = $fh;
-       }
-
        public function getLBInfo( $name = null ) {
                if ( is_null( $name ) ) {
                        return $this->mLBInfo;
@@ -654,7 +640,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        protected function installErrorHandler() {
                $this->mPHPError = false;
                $this->htmlErrors = ini_set( 'html_errors', '0' );
-               set_error_handler( [ $this, 'connectionerrorLogger' ] );
+               set_error_handler( [ $this, 'connectionErrorLogger' ] );
        }
 
        /**
@@ -676,10 +662,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
+        * This method should not be used outside of Database classes
+        *
         * @param int $errno
         * @param string $errstr
         */
-       public function connectionerrorLogger( $errno, $errstr ) {
+       public function connectionErrorLogger( $errno, $errstr ) {
                $this->mPHPError = $errstr;
        }
 
@@ -736,7 +724,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         */
        abstract protected function closeConnection();
 
-       function reportConnectionError( $error = 'Unknown error' ) {
+       public function reportConnectionError( $error = 'Unknown error' ) {
                $myError = $this->lastError();
                if ( $myError ) {
                        $error = $myError;
@@ -795,16 +783,19 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         */
        protected function registerTempTableOperation( $sql ) {
                if ( preg_match(
-                       '/^(CREATE|DROP)\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
                        $sql,
                        $matches
                ) ) {
-                       list( , $verb, $table ) = $matches;
-                       if ( $verb === 'CREATE' ) {
-                               $this->mSessionTempTables[$table] = 1;
-                       } else {
-                               unset( $this->mSessionTempTables[$table] );
-                       }
+                       $this->mSessionTempTables[$matches[1]] = 1;
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       unset( $this->mSessionTempTables[$matches[1]] );
 
                        return true;
                } elseif ( preg_match(
@@ -1112,7 +1103,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array
         * @see DatabaseBase::select()
         */
-       public function makeSelectOptions( $options ) {
+       protected function makeSelectOptions( $options ) {
                $preLimitTail = $postLimitTail = '';
                $startOpts = '';
 
@@ -1201,7 +1192,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @see DatabaseBase::select()
         * @since 1.21
         */
-       public function makeGroupByWithHaving( $options ) {
+       protected function makeGroupByWithHaving( $options ) {
                $sql = '';
                if ( isset( $options['GROUP BY'] ) ) {
                        $gb = is_array( $options['GROUP BY'] )
@@ -1227,7 +1218,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @see DatabaseBase::select()
         * @since 1.21
         */
-       public function makeOrderBy( $options ) {
+       protected function makeOrderBy( $options ) {
                if ( isset( $options['ORDER BY'] ) ) {
                        $ob = is_array( $options['ORDER BY'] )
                                ? implode( ',', $options['ORDER BY'] )
@@ -1400,6 +1391,11 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        public function tableExists( $table, $fname = __METHOD__ ) {
+               $tableRaw = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+                       return true; // already known to exist
+               }
+
                $table = $this->tableName( $table );
                $old = $this->ignoreErrors( true );
                $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
@@ -1512,7 +1508,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return implode( ' ', $opts );
        }
 
-       function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
                $table = $this->tableName( $table );
                $opts = $this->makeUpdateOptions( $options );
                $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
@@ -1623,14 +1619,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                }
        }
 
-       /**
-        * Return aggregated value alias
-        *
-        * @param array $valuedata
-        * @param string $valuename
-        *
-        * @return string
-        */
        public function aggregateValue( $valuedata, $valuename = 'value' ) {
                return $valuename;
        }
@@ -1659,11 +1647,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                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;
        }
@@ -1836,7 +1819,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param string|bool $alias Alias (optional)
         * @return string SQL name for aliased table. Will not alias a table to its own name
         */
-       public function tableNameWithAlias( $name, $alias = false ) {
+       protected function tableNameWithAlias( $name, $alias = false ) {
                if ( !$alias || $alias == $name ) {
                        return $this->tableName( $name );
                } else {
@@ -1850,7 +1833,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param array $tables [ [alias] => table ]
         * @return string[] See tableNameWithAlias()
         */
-       public function tableNamesWithAlias( $tables ) {
+       protected function tableNamesWithAlias( $tables ) {
                $retval = [];
                foreach ( $tables as $alias => $table ) {
                        if ( is_numeric( $alias ) ) {
@@ -1870,7 +1853,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param string|bool $alias Alias (optional)
         * @return string SQL name for aliased field. Will not alias a field to its own name
         */
-       public function fieldNameWithAlias( $name, $alias = false ) {
+       protected function fieldNameWithAlias( $name, $alias = false ) {
                if ( !$alias || (string)$alias === (string)$name ) {
                        return $name;
                } else {
@@ -1884,7 +1867,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param array $fields [ [alias] => field ]
         * @return string[] See fieldNameWithAlias()
         */
-       public function fieldNamesWithAlias( $fields ) {
+       protected function fieldNamesWithAlias( $fields ) {
                $retval = [];
                foreach ( $fields as $alias => $field ) {
                        if ( is_numeric( $alias ) ) {
@@ -2330,7 +2313,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return $this->insert( $destTable, $rows, $fname, $insertOptions );
        }
 
-       public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
+       protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
                $fname = __METHOD__,
                $insertOptions = [], $selectOptions = []
        ) {
@@ -2392,7 +2375,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         */
        public function limitResult( $sql, $limit, $offset = false ) {
                if ( !is_numeric( $limit ) ) {
-                       throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+                       throw new DBUnexpectedError( $this,
+                               "Invalid non-numeric limit passed to limitResult()\n" );
                }
 
                return "$sql LIMIT "
@@ -2443,11 +2427,10 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
-        * Determines if the given query error was a connection drop
-        * STUB
+        * Do no use the class outside of Database/DBError classes
         *
         * @param integer|string $errno
-        * @return bool
+        * @return bool Whether the given query error was a connection drop
         */
        public function wasConnectionError( $errno ) {
                return false;
@@ -2938,7 +2921,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
        }
 
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
+       public function listTables( $prefix = null, $fname = __METHOD__ ) {
                throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
        }
 
@@ -2979,7 +2962,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        public function timestamp( $ts = 0 ) {
-               $t = new ConvertableTimestamp( $ts );
+               $t = new ConvertibleTimestamp( $ts );
                // Let errors bubble up to avoid putting garbage in the DB
                return $t->getTimestamp( TS_MW );
        }
@@ -3072,7 +3055,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
         * @since 1.27
         */
-       public function getTransactionLagStatus() {
+       protected function getTransactionLagStatus() {
                return $this->mTrxLevel
                        ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
                        : null;
@@ -3084,7 +3067,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
         * @since 1.27
         */
-       public function getApproximateLagStatus() {
+       protected function getApproximateLagStatus() {
                return [
                        'lag'   => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
                        'since' => microtime( true )
@@ -3130,7 +3113,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return 0;
        }
 
-       function maxListLen() {
+       public function maxListLen() {
                return 0;
        }
 
@@ -3465,13 +3448,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        return 'infinity';
                }
 
-               try {
-                       $t = new ConvertableTimestamp( $expiry );
-
-                       return $t->getTimestamp( $format );
-               } catch ( TimestampException $e ) {
-                       return false;
-               }
+               return ConvertibleTimestamp::convert( $format, $expiry );
        }
 
        public function setBigSelects( $value = true ) {
@@ -3525,7 +3502,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        // Open a new connection resource without messing with the old one
                        $this->mOpened = false;
                        $this->mConn = false;
-                       $this->mTrxLevel = 0; // no trx anymore
+                       $this->mTrxEndCallbacks = []; // don't copy
+                       $this->handleSessionLoss(); // no trx or locks anymore
                        $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
                        $this->lastPing = microtime( true );
                }
index 39cf5bc..5134a4a 100644 (file)
@@ -520,6 +520,10 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
 
        function tableExists( $table, $fname = __METHOD__ ) {
                $table = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$table] ) ) {
+                       return true; // already known to exist and won't show in SHOW TABLES anyway
+               }
+
                $encLike = $this->buildLike( $table );
 
                return $this->query( "SHOW TABLES $encLike", $fname )->numRows() > 0;
@@ -969,8 +973,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @since 1.20
         */
        public function lockIsFree( $lockName, $method ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
+               $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
+               $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
                $row = $this->fetchObject( $result );
 
                return ( $row->lockstatus == 1 );
@@ -983,8 +987,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @return bool
         */
        public function lock( $lockName, $method, $timeout = 5 ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
+               $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
+               $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
                $row = $this->fetchObject( $result );
 
                if ( $row->lockstatus == 1 ) {
@@ -1005,8 +1009,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @return bool
         */
        public function unlock( $lockName, $method ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
+               $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
+               $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
                $row = $this->fetchObject( $result );
 
                if ( $row->lockstatus == 1 ) {
index e468601..fb983bd 100644 (file)
@@ -54,8 +54,6 @@ class DatabaseMysqli extends DatabaseMysqlBase {
         * @throws DBConnectionError
         */
        protected function mysqlConnect( $realServer ) {
-               global $wgDBmysql5;
-
                # Avoid suppressed fatal error, which is very hard to track down
                if ( !function_exists( 'mysqli_init' ) ) {
                        throw new DBConnectionError( $this, "MySQLi functions missing,"
@@ -101,7 +99,7 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                        $realServer = 'p:' . $realServer;
                }
 
-               if ( $wgDBmysql5 ) {
+               if ( $this->utf8Mode ) {
                        // Tell the server we're communicating with it in UTF-8.
                        // This may engage various charset conversions.
                        $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
index 69488af..8c73a15 100644 (file)
@@ -860,7 +860,7 @@ __INDEXATTR__;
        }
 
        function timestamp( $ts = 0 ) {
-               $ct = new ConvertableTimestamp( $ts );
+               $ct = new ConvertibleTimestamp( $ts );
 
                return $ct->getTimestamp( TS_POSTGRES );
        }
index 29a32f5..21ffa8d 100644 (file)
@@ -944,6 +944,16 @@ interface IDatabase {
         */
        public function makeWhereFrom2d( $data, $baseKey, $subKey );
 
+       /**
+        * Return aggregated value alias
+        *
+        * @param array $valuedata
+        * @param string $valuename
+        *
+        * @return string
+        */
+       public function aggregateValue( $valuedata, $valuename = 'value' );
+
        /**
         * @param string $field
         * @return string
@@ -992,6 +1002,13 @@ interface IDatabase {
                $delim, $table, $field, $conds = '', $join_conds = []
        );
 
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field );
+
        /**
         * Change the current database
         *
diff --git a/includes/libs/rdbms/exception/DBAccessError.php b/includes/libs/rdbms/exception/DBAccessError.php
new file mode 100644 (file)
index 0000000..7971dba
--- /dev/null
@@ -0,0 +1,32 @@
+<?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
+ */
+
+/**
+ * Exception class for attempted DB access
+ * @ingroup Database
+ */
+class DBAccessError extends DBUnexpectedError {
+       public function __construct() {
+               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
+                       "This is not allowed, because database access has been disabled." );
+       }
+}
+
diff --git a/includes/libs/rdbms/exception/DBConnectionError.php b/includes/libs/rdbms/exception/DBConnectionError.php
new file mode 100644 (file)
index 0000000..47f8c96
--- /dev/null
@@ -0,0 +1,38 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBConnectionError extends DBExpectedError {
+       /**
+        * @param IDatabase $db Object throwing the error
+        * @param string $error Error text
+        */
+       function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+               $msg = 'Cannot access the database';
+               if ( trim( $error ) != '' ) {
+                       $msg .= ": $error";
+               }
+
+               parent::__construct( $db, $msg );
+       }
+}
index 38887cf..526596d 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * This file contains database error classes.
- *
  * 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
@@ -39,135 +37,3 @@ class DBError extends Exception {
                parent::__construct( $error );
        }
 }
-
-/**
- * Base class for the more common types of database errors. These are known to occur
- * frequently, so we try to give friendly error messages for them.
- *
- * @ingroup Database
- * @since 1.23
- */
-class DBExpectedError extends DBError implements MessageSpecifier {
-       /** @var string[] Message parameters */
-       protected $params;
-
-       function __construct( IDatabase $db = null, $error, array $params = [] ) {
-               parent::__construct( $db, $error );
-               $this->params = $params;
-       }
-
-       public function getKey() {
-               return 'databaseerror-text';
-       }
-
-       public function getParams() {
-               return $this->params;
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBConnectionError extends DBExpectedError {
-       /**
-        * @param IDatabase $db Object throwing the error
-        * @param string $error Error text
-        */
-       function __construct( IDatabase $db = null, $error = 'unknown error' ) {
-               $msg = 'Cannot access the database';
-               if ( trim( $error ) != '' ) {
-                       $msg .= ": $error";
-               }
-
-               parent::__construct( $db, $msg );
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBQueryError extends DBExpectedError {
-       /** @var string */
-       public $error;
-       /** @var integer */
-       public $errno;
-       /** @var string */
-       public $sql;
-       /** @var string */
-       public $fname;
-
-       /**
-        * @param IDatabase $db
-        * @param string $error
-        * @param int|string $errno
-        * @param string $sql
-        * @param string $fname
-        */
-       function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
-               if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) {
-                       $message = "A connection error occured. \n" .
-                               "Query: $sql\n" .
-                               "Function: $fname\n" .
-                               "Error: $errno $error\n";
-               } else {
-                       $message = "A database error has occurred. Did you forget to run " .
-                               "maintenance/update.php after upgrading?  See: " .
-                               "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
-                               "Query: $sql\n" .
-                               "Function: $fname\n" .
-                               "Error: $errno $error\n";
-               }
-               parent::__construct( $db, $message );
-
-               $this->error = $error;
-               $this->errno = $errno;
-               $this->sql = $sql;
-               $this->fname = $fname;
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBReadOnlyError extends DBExpectedError {
-}
-
-/**
- * @ingroup Database
- */
-class DBTransactionError extends DBExpectedError {
-}
-
-/**
- * @ingroup Database
- */
-class DBTransactionSizeError extends DBTransactionError {
-       function getKey() {
-               return 'transaction-duration-limit-exceeded';
-       }
-}
-
-/**
- * Exception class for replica DB wait timeouts
- * @ingroup Database
- */
-class DBReplicationWaitError extends DBExpectedError {
-}
-
-/**
- * @ingroup Database
- */
-class DBUnexpectedError extends DBError {
-}
-
-/**
- * Exception class for attempted DB access
- * @ingroup Database
- */
-class DBAccessError extends DBUnexpectedError {
-       public function __construct() {
-               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
-                       "This is not allowed, because database access has been disabled." );
-       }
-}
-
diff --git a/includes/libs/rdbms/exception/DBExpectedError.php b/includes/libs/rdbms/exception/DBExpectedError.php
new file mode 100644 (file)
index 0000000..9e10884
--- /dev/null
@@ -0,0 +1,45 @@
+<?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
+ */
+
+/**
+ * Base class for the more common types of database errors. These are known to occur
+ * frequently, so we try to give friendly error messages for them.
+ *
+ * @ingroup Database
+ * @since 1.23
+ */
+class DBExpectedError extends DBError implements MessageSpecifier {
+       /** @var string[] Message parameters */
+       protected $params;
+
+       function __construct( IDatabase $db = null, $error, array $params = [] ) {
+               parent::__construct( $db, $error );
+               $this->params = $params;
+       }
+
+       public function getKey() {
+               return 'databaseerror-text';
+       }
+
+       public function getParams() {
+               return $this->params;
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBQueryError.php b/includes/libs/rdbms/exception/DBQueryError.php
new file mode 100644 (file)
index 0000000..ac9217d
--- /dev/null
@@ -0,0 +1,63 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBQueryError extends DBExpectedError {
+       /** @var string */
+       public $error;
+       /** @var integer */
+       public $errno;
+       /** @var string */
+       public $sql;
+       /** @var string */
+       public $fname;
+
+       /**
+        * @param IDatabase $db
+        * @param string $error
+        * @param int|string $errno
+        * @param string $sql
+        * @param string $fname
+        */
+       function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+               if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) {
+                       $message = "A connection error occured. \n" .
+                               "Query: $sql\n" .
+                               "Function: $fname\n" .
+                               "Error: $errno $error\n";
+               } else {
+                       $message = "A database query error has occurred. Did you forget to run " .
+                               "your application's database schema updater after upgrading? \n" .
+                               "Query: $sql\n" .
+                               "Function: $fname\n" .
+                               "Error: $errno $error\n";
+               }
+
+               parent::__construct( $db, $message );
+
+               $this->error = $error;
+               $this->errno = $errno;
+               $this->sql = $sql;
+               $this->fname = $fname;
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBReadOnlyError.php b/includes/libs/rdbms/exception/DBReadOnlyError.php
new file mode 100644 (file)
index 0000000..d4dce1e
--- /dev/null
@@ -0,0 +1,26 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBReadOnlyError extends DBExpectedError {
+}
diff --git a/includes/libs/rdbms/exception/DBReplicationWaitError.php b/includes/libs/rdbms/exception/DBReplicationWaitError.php
new file mode 100644 (file)
index 0000000..f1dabd5
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
+ */
+
+/**
+ * Exception class for replica DB wait timeouts
+ * @ingroup Database
+ */
+class DBReplicationWaitError extends DBExpectedError {
+}
+
diff --git a/includes/libs/rdbms/exception/DBTransactionError.php b/includes/libs/rdbms/exception/DBTransactionError.php
new file mode 100644 (file)
index 0000000..a488667
--- /dev/null
@@ -0,0 +1,26 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionError extends DBExpectedError {
+}
diff --git a/includes/libs/rdbms/exception/DBTransactionSizeError.php b/includes/libs/rdbms/exception/DBTransactionSizeError.php
new file mode 100644 (file)
index 0000000..4e467b2
--- /dev/null
@@ -0,0 +1,29 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionSizeError extends DBTransactionError {
+       function getKey() {
+               return 'transaction-duration-limit-exceeded';
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBUnexpectedError.php b/includes/libs/rdbms/exception/DBUnexpectedError.php
new file mode 100644 (file)
index 0000000..5a12671
--- /dev/null
@@ -0,0 +1,26 @@
+<?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
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBUnexpectedError extends DBError {
+}
diff --git a/includes/libs/stats/SamplingStatsdClient.php b/includes/libs/stats/SamplingStatsdClient.php
new file mode 100644 (file)
index 0000000..dd1976c
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Copyright 2015
+ *
+ * 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
+ */
+
+use Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Entity\StatsdData;
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+/**
+ * A statsd client that applies the sampling rate to the data items before sending them.
+ *
+ * @since 1.26
+ */
+class SamplingStatsdClient extends StatsdClient {
+       protected $samplingRates = [];
+
+       /**
+        * Sampling rates as an associative array of patterns and rates.
+        * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
+        * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
+        * @param array $samplingRates
+        * @since 1.28
+        */
+       public function setSamplingRates( array $samplingRates ) {
+               $this->samplingRates = $samplingRates;
+       }
+
+       /**
+        * Sets sampling rate for all items in $data.
+        * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
+        *
+        * {@inheritDoc}
+        */
+       public function appendSampleRate( $data, $sampleRate = 1 ) {
+               $samplingRates = $this->samplingRates;
+               if ( !$samplingRates && $sampleRate !== 1 ) {
+                       $samplingRates = [ '*' => $sampleRate ];
+               }
+               if ( $samplingRates ) {
+                       array_walk( $data, function( $item ) use ( $samplingRates ) {
+                               /** @var $item StatsdData */
+                               foreach ( $samplingRates as $pattern => $rate ) {
+                                       if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
+                                               $item->setSampleRate( $item->getSampleRate() * $rate );
+                                               break;
+                                       }
+                               }
+                       } );
+               }
+
+               return $data;
+       }
+
+       /*
+        * Send the metrics over UDP
+        * Sample the metrics according to their sample rate and send the remaining ones.
+        *
+        * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent
+        *        strings are not allowed here as sampleData requires a StatsdDataInterface
+        * @param int $sampleRate
+        *
+        * @return integer the data sent in bytes
+        */
+       public function send( $data, $sampleRate = 1 ) {
+               if ( !is_array( $data ) ) {
+                       $data = [ $data ];
+               }
+               if ( !$data ) {
+                       return;
+               }
+               foreach ( $data as $item ) {
+                       if ( !( $item instanceof StatsdDataInterface ) ) {
+                               throw new InvalidArgumentException(
+                                       'SamplingStatsdClient does not accept stringified messages' );
+                       }
+               }
+
+               // add sampling
+               $data = $this->appendSampleRate( $data, $sampleRate );
+               $data = $this->sampleData( $data );
+
+               $data = array_map( 'strval', $data );
+
+               // reduce number of packets
+               if ( $this->getReducePacket() ) {
+                       $data = $this->reduceCount( $data );
+               }
+
+               // failures in any of this should be silently ignored if ..
+               $written = 0;
+               try {
+                       $fp = $this->getSender()->open();
+                       if ( !$fp ) {
+                               return;
+                       }
+                       foreach ( $data as $message ) {
+                               $written += $this->getSender()->write( $fp, $message );
+                       }
+                       $this->getSender()->close( $fp );
+               } catch ( Exception $e ) {
+                       $this->throwException( $e );
+               }
+
+               return $written;
+       }
+
+       /**
+        * Throw away some of the data according to the sample rate.
+        * @param StatsdDataInterface[] $data
+        * @return StatsdDataInterface[]
+        * @throws LogicException
+        */
+       protected function sampleData( $data ) {
+               $newData = [];
+               $mt_rand_max = mt_getrandmax();
+               foreach ( $data as $item ) {
+                       $samplingRate = $item->getSampleRate();
+                       if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
+                               throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
+                       }
+                       if (
+                               $samplingRate === 1 ||
+                               ( mt_rand() / $mt_rand_max <= $samplingRate )
+                       ) {
+                               $newData[] = $item;
+                       }
+               }
+               return $newData;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       protected function throwException( Exception $exception ) {
+               if ( !$this->getFailSilently() ) {
+                       throw $exception;
+               }
+       }
+}
diff --git a/includes/libs/time/ConvertableTimestamp.php b/includes/libs/time/ConvertableTimestamp.php
deleted file mode 100644 (file)
index af7eca6..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-<?php
-/**
- * Creation, parsing, and conversion of timestamps.
- *
- * 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
- * @since 1.20
- * @author Tyler Romeo, 2012
- */
-
-/**
- * Library for creating, parsing, and converting timestamps. Based on the JS
- * library that does the same thing.
- *
- * @since 1.28
- */
-class ConvertableTimestamp {
-       /**
-        * Standard gmdate() formats for the different timestamp types.
-        */
-       private static $formats = [
-               TS_UNIX => 'U',
-               TS_MW => 'YmdHis',
-               TS_DB => 'Y-m-d H:i:s',
-               TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
-               TS_ISO_8601_BASIC => 'Ymd\THis\Z',
-               TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
-               TS_RFC2822 => 'D, d M Y H:i:s',
-               TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
-               TS_POSTGRES => 'Y-m-d H:i:s',
-       ];
-
-       /**
-        * The actual timestamp being wrapped (DateTime object).
-        * @var DateTime
-        */
-       public $timestamp;
-
-       /**
-        * Make a new timestamp and set it to the specified time,
-        * or the current time if unspecified.
-        *
-        * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
-        */
-       public function __construct( $timestamp = false ) {
-               if ( $timestamp instanceof DateTime ) {
-                       $this->timestamp = $timestamp;
-               } else {
-                       $this->setTimestamp( $timestamp );
-               }
-       }
-
-       /**
-        * Set the timestamp to the specified time, or the current time if unspecified.
-        *
-        * Parse the given timestamp into either a DateTime object or a Unix timestamp,
-        * and then store it.
-        *
-        * @param string|bool $ts Timestamp to store, or false for now
-        * @throws TimestampException
-        */
-       public function setTimestamp( $ts = false ) {
-               $m = [];
-               $da = [];
-               $strtime = '';
-
-               // We want to catch 0, '', null... but not date strings starting with a letter.
-               if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
-                       $uts = time();
-                       $strtime = "@$uts";
-               } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
-                       # TS_DB
-               } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
-                       # TS_EXIF
-               } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
-                       # TS_MW
-               } elseif ( preg_match( '/^(-?\d{1,13})(\.\d+)?$/D', $ts, $m ) ) {
-                       # TS_UNIX
-                       $strtime = "@{$m[1]}"; // http://php.net/manual/en/datetime.formats.compound.php
-               } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
-                       # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
-                       $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
-                               str_replace( '+00:00', 'UTC', $ts ) );
-               } elseif ( preg_match(
-                       '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_ISO_8601
-               } elseif ( preg_match(
-                       '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_ISO_8601_BASIC
-               } elseif ( preg_match(
-                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_POSTGRES
-               } elseif ( preg_match(
-                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_POSTGRES
-               } elseif ( preg_match(
-               # Day of week
-                       '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
-                       # dd Mon yyyy
-                       '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
-                       # hh:mm:ss
-                       '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
-                       $ts
-               ) ) {
-                       # TS_RFC2822, accepting a trailing comment.
-                       # See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
-                       # The regex is a superset of rfc2822 for readability
-                       $strtime = strtok( $ts, ';' );
-               } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
-                       # TS_RFC850
-                       $strtime = $ts;
-               } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
-                       # asctime
-                       $strtime = $ts;
-               } else {
-                       throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
-               }
-
-               if ( !$strtime ) {
-                       $da = array_map( 'intval', $da );
-                       $da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
-                       $strtime = call_user_func_array( "sprintf", $da );
-               }
-
-               try {
-                       $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
-               } catch ( Exception $e ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
-               }
-
-               if ( $final === false ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
-               }
-
-               $this->timestamp = $final;
-       }
-
-       /**
-        * Get the timestamp represented by this object in a certain form.
-        *
-        * Convert the internal timestamp to the specified format and then
-        * return it.
-        *
-        * @param int $style Constant Output format for timestamp
-        * @throws TimestampException
-        * @return string The formatted timestamp
-        */
-       public function getTimestamp( $style = TS_UNIX ) {
-               if ( !isset( self::$formats[$style] ) ) {
-                       throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
-               }
-
-               $output = $this->timestamp->format( self::$formats[$style] );
-
-               if ( ( $style == TS_RFC2822 ) || ( $style == TS_POSTGRES ) ) {
-                       $output .= ' GMT';
-               }
-
-               if ( $style == TS_MW && strlen( $output ) !== 14 ) {
-                       throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
-                               'the specified format' );
-               }
-
-               return $output;
-       }
-
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return $this->getTimestamp();
-       }
-
-       /**
-        * Calculate the difference between two ConvertableTimestamp objects.
-        *
-        * @param ConvertableTimestamp $relativeTo Base time to calculate difference from
-        * @return DateInterval|bool The DateInterval object representing the
-        *   difference between the two dates or false on failure
-        */
-       public function diff( ConvertableTimestamp $relativeTo ) {
-               return $this->timestamp->diff( $relativeTo->timestamp );
-       }
-
-       /**
-        * Set the timezone of this timestamp to the specified timezone.
-        *
-        * @param string $timezone Timezone to set
-        * @throws TimestampException
-        */
-       public function setTimezone( $timezone ) {
-               try {
-                       $this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
-               } catch ( Exception $e ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
-               }
-       }
-
-       /**
-        * Get the timezone of this timestamp.
-        *
-        * @return DateTimeZone The timezone
-        */
-       public function getTimezone() {
-               return $this->timestamp->getTimezone();
-       }
-
-       /**
-        * Format the timestamp in a given format.
-        *
-        * @param string $format Pattern to format in
-        * @return string The formatted timestamp
-        */
-       public function format( $format ) {
-               return $this->timestamp->format( $format );
-       }
-}
diff --git a/includes/libs/time/ConvertibleTimestamp.php b/includes/libs/time/ConvertibleTimestamp.php
new file mode 100644 (file)
index 0000000..7cada84
--- /dev/null
@@ -0,0 +1,269 @@
+<?php
+/**
+ * Creation, parsing, and conversion of timestamps.
+ *
+ * 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
+ * @since 1.20
+ * @author Tyler Romeo, 2012
+ */
+
+/**
+ * Library for creating, parsing, and converting timestamps. Based on the JS
+ * library that does the same thing.
+ *
+ * @since 1.28
+ */
+class ConvertibleTimestamp {
+       /**
+        * Standard gmdate() formats for the different timestamp types.
+        */
+       private static $formats = [
+               TS_UNIX => 'U',
+               TS_MW => 'YmdHis',
+               TS_DB => 'Y-m-d H:i:s',
+               TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
+               TS_ISO_8601_BASIC => 'Ymd\THis\Z',
+               TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
+               TS_RFC2822 => 'D, d M Y H:i:s',
+               TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
+               TS_POSTGRES => 'Y-m-d H:i:s',
+       ];
+
+       /**
+        * The actual timestamp being wrapped (DateTime object).
+        * @var DateTime
+        */
+       public $timestamp;
+
+       /**
+        * Make a new timestamp and set it to the specified time,
+        * or the current time if unspecified.
+        *
+        * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
+        */
+       public function __construct( $timestamp = false ) {
+               if ( $timestamp instanceof DateTime ) {
+                       $this->timestamp = $timestamp;
+               } else {
+                       $this->setTimestamp( $timestamp );
+               }
+       }
+
+       /**
+        * Set the timestamp to the specified time, or the current time if unspecified.
+        *
+        * Parse the given timestamp into either a DateTime object or a Unix timestamp,
+        * and then store it.
+        *
+        * @param string|bool $ts Timestamp to store, or false for now
+        * @throws TimestampException
+        */
+       public function setTimestamp( $ts = false ) {
+               $m = [];
+               $da = [];
+               $strtime = '';
+
+               // We want to catch 0, '', null... but not date strings starting with a letter.
+               if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
+                       $uts = time();
+                       $strtime = "@$uts";
+               } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
+                       # TS_DB
+               } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
+                       # TS_EXIF
+               } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
+                       # TS_MW
+               } elseif ( preg_match( '/^(-?\d{1,13})(\.\d+)?$/D', $ts, $m ) ) {
+                       # TS_UNIX
+                       $strtime = "@{$m[1]}"; // http://php.net/manual/en/datetime.formats.compound.php
+               } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
+                       # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
+                       $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
+                               str_replace( '+00:00', 'UTC', $ts ) );
+               } elseif ( preg_match(
+                       '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_ISO_8601
+               } elseif ( preg_match(
+                       '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_ISO_8601_BASIC
+               } elseif ( preg_match(
+                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_POSTGRES
+               } elseif ( preg_match(
+                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_POSTGRES
+               } elseif ( preg_match(
+               # Day of week
+                       '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
+                       # dd Mon yyyy
+                       '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
+                       # hh:mm:ss
+                       '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
+                       $ts
+               ) ) {
+                       # TS_RFC2822, accepting a trailing comment.
+                       # See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
+                       # The regex is a superset of rfc2822 for readability
+                       $strtime = strtok( $ts, ';' );
+               } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
+                       # TS_RFC850
+                       $strtime = $ts;
+               } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
+                       # asctime
+                       $strtime = $ts;
+               } else {
+                       throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
+               }
+
+               if ( !$strtime ) {
+                       $da = array_map( 'intval', $da );
+                       $da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
+                       $strtime = call_user_func_array( "sprintf", $da );
+               }
+
+               try {
+                       $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
+               } catch ( Exception $e ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
+               }
+
+               if ( $final === false ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
+               }
+
+               $this->timestamp = $final;
+       }
+
+       /**
+        * Convert a timestamp string to a given format.
+        *
+        * @param int $style Constant Output format for timestamp
+        * @param string $ts Timestamp
+        * @return string|bool Formatted timestamp or false on failure
+        */
+       public static function convert( $style = TS_UNIX, $ts ) {
+               try {
+                       $ct = new static( $ts );
+                       return $ct->getTimestamp( $style );
+               } catch ( TimestampException $e ) {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the current time in the given format
+        *
+        * @param int $style Constant Output format for timestamp
+        * @return string
+        */
+       public static function now( $style = TS_MW ) {
+               return static::convert( $style, time() );
+       }
+
+       /**
+        * Get the timestamp represented by this object in a certain form.
+        *
+        * Convert the internal timestamp to the specified format and then
+        * return it.
+        *
+        * @param int $style Constant Output format for timestamp
+        * @throws TimestampException
+        * @return string The formatted timestamp
+        */
+       public function getTimestamp( $style = TS_UNIX ) {
+               if ( !isset( self::$formats[$style] ) ) {
+                       throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
+               }
+
+               $output = $this->timestamp->format( self::$formats[$style] );
+
+               if ( ( $style == TS_RFC2822 ) || ( $style == TS_POSTGRES ) ) {
+                       $output .= ' GMT';
+               }
+
+               if ( $style == TS_MW && strlen( $output ) !== 14 ) {
+                       throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
+                               'the specified format' );
+               }
+
+               return $output;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return $this->getTimestamp();
+       }
+
+       /**
+        * Calculate the difference between two ConvertableTimestamp objects.
+        *
+        * @param ConvertibleTimestamp $relativeTo Base time to calculate difference from
+        * @return DateInterval|bool The DateInterval object representing the
+        *   difference between the two dates or false on failure
+        */
+       public function diff( ConvertibleTimestamp $relativeTo ) {
+               return $this->timestamp->diff( $relativeTo->timestamp );
+       }
+
+       /**
+        * Set the timezone of this timestamp to the specified timezone.
+        *
+        * @param string $timezone Timezone to set
+        * @throws TimestampException
+        */
+       public function setTimezone( $timezone ) {
+               try {
+                       $this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
+               } catch ( Exception $e ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
+               }
+       }
+
+       /**
+        * Get the timezone of this timestamp.
+        *
+        * @return DateTimeZone The timezone
+        */
+       public function getTimezone() {
+               return $this->timestamp->getTimezone();
+       }
+
+       /**
+        * Format the timestamp in a given format.
+        *
+        * @param string $format Pattern to format in
+        * @return string The formatted timestamp
+        */
+       public function format( $format ) {
+               return $this->timestamp->format( $format );
+       }
+}
index 32a3340..31eaa3b 100644 (file)
@@ -329,15 +329,15 @@ class XMPValidate implements LoggerAwareInterface {
                        // We know that if we got to this step, year, month day hour and min must be set
                        // by virtue of regex not failing.
 
-                       $unix = ( new ConvertableTimestamp(
+                       $unix = ConvertibleTimestamp::convert( TS_UNIX,
                                $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6]
-                       ) )->getTimestamp( TS_UNIX );
+                       );
                        $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
                        $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
                        if ( substr( $res[7], 0, 1 ) === '-' ) {
                                $offset = -$offset;
                        }
-                       $val = ( new ConvertableTimestamp( $unix + $offset ) )->getTimestamp( TS_EXIF );
+                       $val = ConvertibleTimestamp::convert( TS_EXIF, $unix + $offset );
 
                        if ( $stripSeconds ) {
                                // If seconds weren't specified, remove the trailing ':00'.
index 4b9b268..0229ac1 100644 (file)
@@ -51,7 +51,7 @@ class BmpHandler extends BitmapHandler {
        /**
         * Get width and height from the bmp header.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return array
         */
index 9add138..18f75ec 100644 (file)
@@ -235,7 +235,7 @@ class DjVuHandler extends ImageHandler {
        /**
         * Cache an instance of DjVuImage in an Image object, return that instance
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $path
         * @return DjVuImage
         */
@@ -335,11 +335,6 @@ class DjVuHandler extends ImageHandler {
                }
        }
 
-       /**
-        * @param File $image
-        * @param string $path
-        * @return bool|array False on failure
-        */
        function getImageSize( $image, $path ) {
                return $this->getDjVuImage( $image, $path )->getImageSize();
        }
index 732be3d..7aeefa0 100644 (file)
@@ -165,7 +165,7 @@ class ExifBitmapHandler extends BitmapHandler {
         * Wrapper for base classes ImageHandler::getImageSize() that checks for
         * rotation reported from metadata and swaps the sizes to match.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $path
         * @return array
         */
index 70a43f2..4ca2663 100644 (file)
@@ -100,21 +100,22 @@ abstract class MediaHandler {
         * @note If this is a multipage file, return the width and height of the
         *  first page.
         *
-        * @param File $image The image object, or false if there isn't one
+        * @param File|FSFile $image The image object, or false if there isn't one.
+        *   Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!)
         * @param string $path The filename
-        * @return array Follow the format of PHP getimagesize() internal function.
+        * @return array|bool Follow the format of PHP getimagesize() internal function.
         *   See http://www.php.net/getimagesize. MediaWiki will only ever use the
         *   first two array keys (the width and height), and the 'bits' associative
         *   key. All other array keys are ignored. Returning a 'bits' key is optional
-        *   as not all formats have a notion of "bitdepth".
+        *   as not all formats have a notion of "bitdepth". Returns false on failure.
         */
        abstract function getImageSize( $image, $path );
 
        /**
         * Get handler-specific metadata which will be saved in the img_metadata field.
         *
-        * @param File $image The image object, or false if there isn't one.
-        *   Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
+        * @param File|FSFile $image The image object, or false if there isn't one.
+        *   Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!)
         * @param string $path The filename
         * @return string A string of metadata in php serialized form (Run through serialize())
         */
index 8a3e001..294abb3 100644 (file)
@@ -30,7 +30,7 @@ class PNGHandler extends BitmapHandler {
        const BROKEN_FILE = '0';
 
        /**
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return string
         */
index 2bb6d13..8360920 100644 (file)
@@ -301,13 +301,13 @@ class SvgHandler extends ImageHandler {
        }
 
        /**
-        * @param File $file
+        * @param File|FSFile $file
         * @param string $path Unused
         * @param bool|array $metadata
         * @return array
         */
        function getImageSize( $file, $path, $metadata = false ) {
-               if ( $metadata === false ) {
+               if ( $metadata === false && $file instanceof File ) {
                        $metadata = $file->getMetadata();
                }
                $metadata = $this->unpackMetadata( $metadata );
@@ -355,7 +355,7 @@ class SvgHandler extends ImageHandler {
        }
 
        /**
-        * @param File $file
+        * @param File|FSFile $file
         * @param string $filename
         * @return string Serialised metadata
         */
index 2e73249..f0f4cda 100644 (file)
@@ -71,13 +71,14 @@ class TiffHandler extends ExifBitmapHandler {
        }
 
        /**
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @throws MWException
         * @return string
         */
        function getMetadata( $image, $filename ) {
                global $wgShowEXIF;
+
                if ( $wgShowEXIF ) {
                        try {
                                $meta = BitmapMetadataHandler::Tiff( $filename );
index 35e885f..e2c2d2d 100644 (file)
@@ -230,7 +230,7 @@ class WebPHandler extends BitmapHandler {
                if ( $file === null ) {
                        $metadata = self::getMetadata( $file, $path );
                }
-               if ( $metadata === false ) {
+               if ( $metadata === false && $file instanceof File ) {
                        $metadata = $file->getMetadata();
                }
 
index 6ac675e..108d6fb 100644 (file)
@@ -56,7 +56,7 @@ class XCFHandler extends BitmapHandler {
        /**
         * Get width and height from the XCF header.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return array
         */
@@ -149,7 +149,7 @@ class XCFHandler extends BitmapHandler {
         *
         * Greyscale files need different command line options.
         *
-        * @param File $file The image object, or false if there isn't one.
+        * @param File|FSFile $file The image object, or false if there isn't one.
         *   Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
         * @param string $filename The filename
         * @return string
index ea237aa..14809a8 100644 (file)
@@ -23,7 +23,6 @@
 
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Services\ServiceDisabledException;
 
 /**
  * Functions to get cache objects
@@ -118,13 +117,20 @@ class ObjectCache {
         *
         * @param string $id A key in $wgObjectCaches.
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         */
        public static function newFromId( $id ) {
                global $wgObjectCaches;
 
                if ( !isset( $wgObjectCaches[$id] ) ) {
-                       throw new MWException( "Invalid object cache type \"$id\" requested. " .
+                       // Always recognize these ones
+                       if ( $id === CACHE_NONE ) {
+                               return new EmptyBagOStuff();
+                       } elseif ( $id === 'hash' ) {
+                               return new HashBagOStuff();
+                       }
+
+                       throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
                                "It is not present in \$wgObjectCaches." );
                }
 
@@ -160,7 +166,7 @@ class ObjectCache {
         *  - loggroup: Alias to set 'logger' key with LoggerFactory group.
         *  - .. Other parameters passed to factory or class.
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         */
        public static function newFromParams( $params ) {
                if ( isset( $params['loggroup'] ) ) {
@@ -187,7 +193,7 @@ class ObjectCache {
                        if ( is_subclass_of( $class, SqlBagOStuff::class ) ) {
                                if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
                                        $params['servers'] = [ $params['server'] ];
-                                       unset( $param['server'] );
+                                       unset( $params['server'] );
                                }
                                // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
                                if ( isset( $params['servers'] ) ) {
@@ -217,7 +223,7 @@ class ObjectCache {
                        }
                        return new $class( $params );
                } else {
-                       throw new MWException( "The definition of cache type \""
+                       throw new InvalidArgumentException( "The definition of cache type \""
                                . print_r( $params, true ) . "\" lacks both "
                                . "factory and class parameters." );
                }
@@ -270,7 +276,7 @@ class ObjectCache {
         *
         * @param int|string|array $fallback Fallback cache or parameter map with 'fallback'
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         * @since 1.27
         */
        public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
@@ -315,13 +321,13 @@ class ObjectCache {
         * @since 1.26
         * @param string $id A key in $wgWANObjectCaches.
         * @return WANObjectCache
-        * @throws MWException
+        * @throws InvalidArgumentException
         */
        public static function newWANCacheFromId( $id ) {
                global $wgWANObjectCaches;
 
                if ( !isset( $wgWANObjectCaches[$id] ) ) {
-                       throw new MWException( "Invalid object cache type \"$id\" requested. " .
+                       throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
                                "It is not present in \$wgWANObjectCaches." );
                }
 
index ba0a484..6d370b0 100644 (file)
@@ -2109,6 +2109,8 @@ class Article implements Page {
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::doEdit
+        *
+        * @deprecated since 1.21: use doEditContent() instead.
         */
        public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
                ContentHandler::deprecated( __METHOD__, '1.21' );
index c8b85ae..2cd492e 100644 (file)
@@ -633,7 +633,7 @@ class SpecialVersion extends SpecialPage {
                        usort( $wgExtensionCredits[$type], [ $this, 'compare' ] );
 
                        foreach ( $wgExtensionCredits[$type] as $extension ) {
-                               $out .= $this->getCreditsForExtension( $extension );
+                               $out .= $this->getCreditsForExtension( $type, $extension );
                        }
                }
 
@@ -669,11 +669,12 @@ class SpecialVersion extends SpecialPage {
         *  - Description of extension (descriptionmsg or description)
         *  - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
         *
+        * @param string $type Category name of the extension
         * @param array $extension
         *
         * @return string Raw HTML
         */
-       public function getCreditsForExtension( array $extension ) {
+       public function getCreditsForExtension( $type, array $extension ) {
                $out = $this->getOutput();
 
                // We must obtain the information for all the bits and pieces!
@@ -830,7 +831,7 @@ class SpecialVersion extends SpecialPage {
                // Finally! Create the table
                $html = Html::openElement( 'tr', [
                                'class' => 'mw-version-ext',
-                               'id' => Sanitizer::escapeId( 'mw-version-ext-' . $extension['name'] )
+                               'id' => Sanitizer::escapeId( 'mw-version-ext-' . $type . '-' . $extension['name'] )
                        ]
                );
 
index 1be5c24..91b4133 100644 (file)
@@ -446,7 +446,8 @@ abstract class UploadBase {
                        return $status;
                }
 
-               $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
                $mime = $this->mFileProps['mime'];
 
                if ( $wgVerifyMimeType ) {
@@ -504,7 +505,8 @@ abstract class UploadBase {
                # getTitle() sets some internal parameters like $this->mFinalExtension
                $this->getTitle();
 
-               $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
 
                # check MIME type, if desired
                $mime = $this->mFileProps['file-mime'];
index 000c6a4..b7160b3 100644 (file)
@@ -207,7 +207,9 @@ class UploadStash {
                        wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
                        throw new UploadStashBadPathException( "path doesn't exist" );
                }
-               $fileProps = FSFile::getPropsFromPath( $path );
+
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $fileProps = $mwProps->getPropsFromPath( $path, true );
                wfDebug( __METHOD__ . " stashing file at '$path'\n" );
 
                // we will be initializing from some tmpnam files that don't have extensions.
diff --git a/includes/utils/IP.php b/includes/utils/IP.php
deleted file mode 100644 (file)
index 8676baf..0000000
+++ /dev/null
@@ -1,791 +0,0 @@
-<?php
-/**
- * Functions and constants to play with IP addresses and ranges
- *
- * 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
- * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz
- */
-
-use IPSet\IPSet;
-
-// Some regex definition to "play" with IP address and IP address blocks
-
-// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
-define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
-define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
-// An IPv4 block is an IP address and a prefix (d1 to d32)
-define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
-define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
-
-// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
-// However, the "::" abbreviation can be used on consecutive x0000 words.
-define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
-define( 'RE_IPV6_ADD',
-       '(?:' . // starts with "::" (including "::")
-               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
-       '|' . // ends with "::" (except "::")
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
-       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
-               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
-       '|' . // contains no "::"
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
-       ')'
-);
-// An IPv6 block is an IP address and a prefix (d1 to d128)
-define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
-
-// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
-define( 'IP_ADDRESS_STRING',
-       '(?:' .
-               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
-       '|' .
-               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
-       ')'
-);
-
-/**
- * A collection of public static functions to play with IP address
- * and IP blocks.
- */
-class IP {
-       /** @var IPSet */
-       private static $proxyIpSet = null;
-
-       /**
-        * Determine if a string is as valid IP address or network (CIDR prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPAddress( $ip ) {
-               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv6 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv6( $ip ) {
-               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv4 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv4( $ip ) {
-               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Validate an IP address. Ranges are NOT considered valid.
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip
-        * @return bool True if it is valid
-        */
-       public static function isValid( $ip ) {
-               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
-                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
-       }
-
-       /**
-        * Validate an IP Block (valid address WITH a valid prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ipblock
-        * @return bool True if it is valid
-        */
-       public static function isValidBlock( $ipblock ) {
-               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
-                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
-       }
-
-       /**
-        * Convert an IP into a verbose, uppercase, normalized form.
-        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
-        * IPv6 addresses in octet notation are expanded to 8 words;
-        * IPv4 addresses have leading zeros, in each octet, removed.
-        *
-        * @param string $ip IP address in quad or octet form (CIDR or not).
-        * @return string
-        */
-       public static function sanitizeIP( $ip ) {
-               $ip = trim( $ip );
-               if ( $ip === '' ) {
-                       return null;
-               }
-               /* If not an IP, just return trimmed value, since sanitizeIP() is called
-                * in a number of contexts where usernames are supplied as input.
-                */
-               if ( !self::isIPAddress( $ip ) ) {
-                       return $ip;
-               }
-               if ( self::isIPv4( $ip ) ) {
-                       // Remove leading 0's from octet representation of IPv4 address
-                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
-                       return $ip;
-               }
-               // Remove any whitespaces, convert to upper case
-               $ip = strtoupper( $ip );
-               // Expand zero abbreviations
-               $abbrevPos = strpos( $ip, '::' );
-               if ( $abbrevPos !== false ) {
-                       // We know this is valid IPv6. Find the last index of the
-                       // address before any CIDR number (e.g. "a:b:c::/24").
-                       $CIDRStart = strpos( $ip, "/" );
-                       $addressEnd = ( $CIDRStart !== false )
-                               ? $CIDRStart - 1
-                               : strlen( $ip ) - 1;
-                       // If the '::' is at the beginning...
-                       if ( $abbrevPos == 0 ) {
-                               $repeat = '0:';
-                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is at the end...
-                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
-                               $repeat = ':0';
-                               $extra = '';
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is in the middle...
-                       } else {
-                               $repeat = ':0';
-                               $extra = ':';
-                               $pad = 8; // 6+2 (due to '::')
-                       }
-                       $ip = str_replace( '::',
-                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
-                               $ip
-                       );
-               }
-               // Remove leading zeros from each bloc as needed
-               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
-
-               return $ip;
-       }
-
-       /**
-        * Prettify an IP for display to end users.
-        * This will make it more compact and lower-case.
-        *
-        * @param string $ip
-        * @return string
-        */
-       public static function prettifyIP( $ip ) {
-               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
-               if ( self::isIPv6( $ip ) ) {
-                       // Split IP into an address and a CIDR
-                       if ( strpos( $ip, '/' ) !== false ) {
-                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
-                       } else {
-                               list( $ip, $cidr ) = [ $ip, '' ];
-                       }
-                       // Get the largest slice of words with multiple zeros
-                       $offset = 0;
-                       $longest = $longestPos = false;
-                       while ( preg_match(
-                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
-                       ) ) {
-                               list( $match, $pos ) = $m[0]; // full match
-                               if ( strlen( $match ) > strlen( $longest ) ) {
-                                       $longest = $match;
-                                       $longestPos = $pos;
-                               }
-                               $offset = ( $pos + strlen( $match ) ); // advance
-                       }
-                       if ( $longest !== false ) {
-                               // Replace this portion of the string with the '::' abbreviation
-                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
-                       }
-                       // Add any CIDR back on
-                       if ( $cidr !== '' ) {
-                               $ip = "{$ip}/{$cidr}";
-                       }
-                       // Convert to lower case to make it more readable
-                       $ip = strtolower( $ip );
-               }
-
-               return $ip;
-       }
-
-       /**
-        * Given a host/port string, like one might find in the host part of a URL
-        * per RFC 2732, split the hostname part and the port part and return an
-        * array with an element for each. If there is no port part, the array will
-        * have false in place of the port. If the string was invalid in some way,
-        * false is returned.
-        *
-        * This was easy with IPv4 and was generally done in an ad-hoc way, but
-        * with IPv6 it's somewhat more complicated due to the need to parse the
-        * square brackets and colons.
-        *
-        * A bare IPv6 address is accepted despite the lack of square brackets.
-        *
-        * @param string $both The string with the host and port
-        * @return array|false Array normally, false on certain failures
-        */
-       public static function splitHostAndPort( $both ) {
-               if ( substr( $both, 0, 1 ) === '[' ) {
-                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
-                               if ( isset( $m['port'] ) ) {
-                                       return [ $m[1], intval( $m['port'] ) ];
-                               } else {
-                                       return [ $m[1], false ];
-                               }
-                       } else {
-                               // Square bracket found but no IPv6
-                               return false;
-                       }
-               }
-               $numColons = substr_count( $both, ':' );
-               if ( $numColons >= 2 ) {
-                       // Is it a bare IPv6 address?
-                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
-                               return [ $both, false ];
-                       } else {
-                               // Not valid IPv6, but too many colons for anything else
-                               return false;
-                       }
-               }
-               if ( $numColons >= 1 ) {
-                       // Host:port?
-                       $bits = explode( ':', $both );
-                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
-                               return [ $bits[0], intval( $bits[1] ) ];
-                       } else {
-                               // Not a valid port
-                               return false;
-                       }
-               }
-
-               // Plain hostname
-               return [ $both, false ];
-       }
-
-       /**
-        * Given a host name and a port, combine them into host/port string like
-        * you might find in a URL. If the host contains a colon, wrap it in square
-        * brackets like in RFC 2732. If the port matches the default port, omit
-        * the port specification
-        *
-        * @param string $host
-        * @param int $port
-        * @param bool|int $defaultPort
-        * @return string
-        */
-       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
-               if ( strpos( $host, ':' ) !== false ) {
-                       $host = "[$host]";
-               }
-               if ( $defaultPort !== false && $port == $defaultPort ) {
-                       return $host;
-               } else {
-                       return "$host:$port";
-               }
-       }
-
-       /**
-        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
-        *
-        * @param string $hex Number, with "v6-" prefix if it is IPv6
-        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
-        */
-       public static function formatHex( $hex ) {
-               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
-                       return self::hexToOctet( substr( $hex, 3 ) );
-               } else { // IPv4
-                       return self::hexToQuad( $hex );
-               }
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv6 address in octet notation
-        *
-        * @param string $ip_hex Pure hex (no v6- prefix)
-        * @return string (of format a:b:c:d:e:f:g:h)
-        */
-       public static function hexToOctet( $ip_hex ) {
-               // Pad hex to 32 chars (128 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
-               // Separate into 8 words
-               $ip_oct = substr( $ip_hex, 0, 4 );
-               for ( $n = 1; $n < 8; $n++ ) {
-                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
-               }
-               // NO leading zeroes
-               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
-
-               return $ip_oct;
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
-        *
-        * @param string $ip_hex Pure hex
-        * @return string (of format a.b.c.d)
-        */
-       public static function hexToQuad( $ip_hex ) {
-               // Pad hex to 8 chars (32 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
-               // Separate into four quads
-               $s = '';
-               for ( $i = 0; $i < 4; $i++ ) {
-                       if ( $s !== '' ) {
-                               $s .= '.';
-                       }
-                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
-               }
-
-               return $s;
-       }
-
-       /**
-        * Determine if an IP address really is an IP address, and if it is public,
-        * i.e. not RFC 1918 or similar
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isPublic( $ip ) {
-               static $privateSet = null;
-               if ( !$privateSet ) {
-                       $privateSet = new IPSet( [
-                               '10.0.0.0/8', # RFC 1918 (private)
-                               '172.16.0.0/12', # RFC 1918 (private)
-                               '192.168.0.0/16', # RFC 1918 (private)
-                               '0.0.0.0/8', # this network
-                               '127.0.0.0/8', # loopback
-                               'fc00::/7', # RFC 4193 (local)
-                               '0:0:0:0:0:0:0:1', # loopback
-                               '169.254.0.0/16', # link-local
-                               'fe80::/10', # link-local
-                       ] );
-               }
-               return !$privateSet->match( $ip );
-       }
-
-       /**
-        * Return a zero-padded upper case hexadecimal representation of an IP address.
-        *
-        * Hexadecimal addresses are used because they can easily be extended to
-        * IPv6 support. To separate the ranges, the return value from this
-        * function for an IPv6 address will be prefixed with "v6-", a non-
-        * hexadecimal string which sorts after the IPv4 addresses.
-        *
-        * @param string $ip Quad dotted/octet IP address.
-        * @return string|bool False on failure
-        */
-       public static function toHex( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
-               } elseif ( self::isIPv4( $ip ) ) {
-                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
-                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
-                       $ip = self::sanitizeIP( $ip );
-                       $n = ip2long( $ip );
-                       if ( $n < 0 ) {
-                               $n += pow( 2, 32 );
-                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
-                               # so $n becomes a float. We convert it to string instead.
-                               if ( is_float( $n ) ) {
-                                       $n = (string)$n;
-                               }
-                       }
-                       if ( $n !== false ) {
-                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
-                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
-                       }
-               } else {
-                       $n = false;
-               }
-
-               return $n;
-       }
-
-       /**
-        * Given an IPv6 address in octet notation, returns a pure hex string.
-        *
-        * @param string $ip Octet ipv6 IP address.
-        * @return string|bool Pure hex (uppercase); false on failure
-        */
-       private static function IPv6ToRawHex( $ip ) {
-               $ip = self::sanitizeIP( $ip );
-               if ( !$ip ) {
-                       return false;
-               }
-               $r_ip = '';
-               foreach ( explode( ':', $ip ) as $v ) {
-                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
-               }
-
-               return $r_ip;
-       }
-
-       /**
-        * Convert a network specification in CIDR notation
-        * to an integer network and a number of bits
-        *
-        * @param string $range IP with CIDR prefix
-        * @return array(int or string, int)
-        */
-       public static function parseCIDR( $range ) {
-               if ( self::isIPv6( $range ) ) {
-                       return self::parseCIDR6( $range );
-               }
-               $parts = explode( '/', $range, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = ip2long( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
-                       if ( $bits == 0 ) {
-                               $network = 0;
-                       } else {
-                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
-                       }
-                       # Convert to unsigned
-                       if ( $network < 0 ) {
-                               $network += pow( 2, 32 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, $bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats,
-        * return the start and end of the range in hexadecimal.
-        *
-        * Formats are:
-        *     1.2.3.4/24          CIDR
-        *     1.2.3.4 - 1.2.3.5   Explicit range
-        *     1.2.3.4             Single IP
-        *
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344                          Single IP
-        * @param string $range IP range
-        * @return array(string, string)
-        */
-       public static function parseRange( $range ) {
-               // CIDR notation
-               if ( strpos( $range, '/' ) !== false ) {
-                       if ( self::isIPv6( $range ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       list( $network, $bits ) = self::parseCIDR( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = sprintf( '%08X', $network );
-                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
-                       }
-               // Explicit range
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
-                               $start = self::toHex( $start );
-                               $end = self::toHex( $end );
-                               if ( $start > $end ) {
-                                       $start = $end = false;
-                               }
-                       } else {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Convert a network specification in IPv6 CIDR notation to an
-        * integer network and a number of bits
-        *
-        * @param string $range
-        *
-        * @return array(string, int)
-        */
-       private static function parseCIDR6( $range ) {
-               # Explode into <expanded IP,range>
-               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = self::IPv6ToRawHex( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
-                       if ( $bits == 0 ) {
-                               $network = "0";
-                       } else {
-                               # Native 32 bit functions WONT work here!!!
-                               # Convert to a padded binary number
-                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with zeros
-                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
-                               # Convert back to an integer
-                               $network = Wikimedia\base_convert( $network, 2, 10 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, (int)$bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats, return the
-        * start and end of the range in hexadecimal. For IPv6.
-        *
-        * Formats are:
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344/96                       Single IP
-        *
-        * @param string $range
-        *
-        * @return array(string, string)
-        */
-       private static function parseRange6( $range ) {
-               # Expand any IPv6 IP
-               $range = IP::sanitizeIP( $range );
-               // CIDR notation...
-               if ( strpos( $range, '/' ) !== false ) {
-                       list( $network, $bits ) = self::parseCIDR6( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
-                               # Turn network to binary (again)
-                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with ones
-                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
-                               # Convert to hex
-                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
-                               # see toHex() comment
-                               $start = "v6-$start";
-                               $end = "v6-$end";
-                       }
-               // Explicit range notation...
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       $start = self::toHex( $start );
-                       $end = self::toHex( $end );
-                       if ( $start > $end ) {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Determine if a given IPv4/IPv6 address is in a given CIDR network
-        *
-        * @param string $addr The address to check against the given range.
-        * @param string $range The range to check the given address against.
-        * @return bool Whether or not the given address is in the given range.
-        *
-        * @note This can return unexpected results for invalid arguments!
-        *       Make sure you pass a valid IP address and IP range.
-        */
-       public static function isInRange( $addr, $range ) {
-               $hexIP = self::toHex( $addr );
-               list( $start, $end ) = self::parseRange( $range );
-
-               return ( strcmp( $hexIP, $start ) >= 0 &&
-                       strcmp( $hexIP, $end ) <= 0 );
-       }
-
-       /**
-        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
-        *
-        * @since 1.25
-        *
-        * @param string $ip the IP to check
-        * @param array $ranges the IP ranges, each element a range
-        *
-        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
-        */
-       public static function isInRanges( $ip, $ranges ) {
-               foreach ( $ranges as $range ) {
-                       if ( self::isInRange( $ip, $range ) ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Convert some unusual representations of IPv4 addresses to their
-        * canonical dotted quad representation.
-        *
-        * This currently only checks a few IPV4-to-IPv6 related cases.  More
-        * unusual representations may be added later.
-        *
-        * @param string $addr Something that might be an IP address
-        * @return string|null Valid dotted quad IPv4 address or null
-        */
-       public static function canonicalize( $addr ) {
-               // remove zone info (bug 35738)
-               $addr = preg_replace( '/\%.*/', '', $addr );
-
-               if ( self::isValid( $addr ) ) {
-                       return $addr;
-               }
-               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
-               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
-                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
-                       if ( self::isIPv4( $addr ) ) {
-                               return $addr;
-                       }
-               }
-               // IPv6 loopback address
-               $m = [];
-               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
-                       return '127.0.0.1';
-               }
-               // IPv4-mapped and IPv4-compatible IPv6 addresses
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
-                       return $m[1];
-               }
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
-                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
-               ) {
-                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
-               }
-
-               return null; // give up
-       }
-
-       /**
-        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
-        * For example, 127.111.113.151/24 -> 127.111.113.0/24
-        * @param string $range IP address to normalize
-        * @return string
-        */
-       public static function sanitizeRange( $range ) {
-               list( /*...*/, $bits ) = self::parseCIDR( $range );
-               list( $start, /*...*/ ) = self::parseRange( $range );
-               $start = self::formatHex( $start );
-               if ( $bits === false ) {
-                       return $start; // wasn't actually a range
-               }
-
-               return "$start/$bits";
-       }
-
-       /**
-        * Checks if an IP is a trusted proxy provider.
-        * Useful to tell if X-Forwarded-For data is possibly bogus.
-        * CDN cache servers for the site are whitelisted.
-        * @since 1.24
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isTrustedProxy( $ip ) {
-               $trusted = self::isConfiguredProxy( $ip );
-               Hooks::run( 'IsTrustedProxy', [ &$ip, &$trusted ] );
-               return $trusted;
-       }
-
-       /**
-        * Checks if an IP matches a proxy we've configured
-        * @since 1.24
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isConfiguredProxy( $ip ) {
-               global $wgSquidServers, $wgSquidServersNoPurge;
-
-               // Quick check of known singular proxy servers
-               $trusted = in_array( $ip, $wgSquidServers );
-
-               // Check against addresses and CIDR nets in the NoPurge list
-               if ( !$trusted ) {
-                       if ( !self::$proxyIpSet ) {
-                               self::$proxyIpSet = new IPSet( $wgSquidServersNoPurge );
-                       }
-                       $trusted = self::$proxyIpSet->match( $ip );
-               }
-
-               return $trusted;
-       }
-
-       /**
-        * Clears precomputed data used for proxy support.
-        * Use this only for unit tests.
-        */
-       public static function clearCaches() {
-               self::$proxyIpSet = null;
-       }
-
-       /**
-        * Returns the subnet of a given IP
-        *
-        * @param string $ip
-        * @return string|false
-        */
-       public static function getSubnet( $ip ) {
-               $matches = [];
-               $subnet = false;
-               if ( IP::isIPv6( $ip ) ) {
-                       $parts = IP::parseRange( "$ip/64" );
-                       $subnet = $parts[0];
-               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
-                       // IPv4
-                       $subnet = $matches[1];
-               }
-               return $subnet;
-       }
-}
diff --git a/includes/utils/MWFileProps.php b/includes/utils/MWFileProps.php
new file mode 100644 (file)
index 0000000..e60b9ab
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+/**
+ * MimeMagic helper functions for detecting and dealing with MIME types.
+ *
+ * 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
+ */
+
+/**
+ * MimeMagic helper wrapper
+ *
+ * @since 1.28
+ */
+class MWFileProps {
+       /** @var MimeMagic */
+       private $magic;
+
+       /**
+        * @param MimeMagic $magic
+        */
+       public function __construct( MimeMagic $magic ) {
+               $this->magic = $magic;
+       }
+
+       /**
+        * Get an associative array containing information about
+        * a file with the given storage path.
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
+        *   - metadata (handler specific)
+        *   - sha1 (in base 36)
+        *   - width
+        *   - height
+        *   - bits (bitrate)
+        *   - file-mime
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @param string $path Filesystem path to a file
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension.
+        * @return array
+        * @since 1.28
+        */
+       public function getPropsFromPath( $path, $ext ) {
+               $fsFile = new FSFile( $path );
+
+               $info = $this->newPlaceholderProps();
+               $info['fileExists'] = $fsFile->exists();
+               if ( $info['fileExists'] ) {
+                       $info['size'] = $fsFile->getSize(); // bytes
+                       $info['sha1'] = $fsFile->getSha1Base36();
+
+                       # MIME type according to file contents
+                       $info['file-mime'] = $this->magic->guessMimeType( $path, false );
+                       # Logical MIME type
+                       $ext = ( $ext === true ) ? FileBackend::extensionFromPath( $path ) : $ext;
+                       $info['mime'] = $this->magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
+                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
+                       $info['media_type'] = $this->magic->getMediaType( $path, $info['mime'] );
+
+                       # Height, width and metadata
+                       $handler = MediaHandler::getHandler( $info['mime'] );
+                       if ( $handler ) {
+                               $info['metadata'] = $handler->getMetadata( $fsFile, $path );
+                               /** @noinspection PhpMethodParametersCountMismatchInspection */
+                               $gis = $handler->getImageSize( $fsFile, $path, $info['metadata'] );
+                               if ( is_array( $gis ) ) {
+                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
+                               }
+                       }
+               }
+
+               return $info;
+       }
+
+       /**
+        * Exract image size information
+        *
+        * @param array $gis
+        * @return array
+        */
+       private function extractImageSizeInfo( array $gis ) {
+               $info = [];
+               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+               $info['width'] = $gis[0];
+               $info['height'] = $gis[1];
+               if ( isset( $gis['bits'] ) ) {
+                       $info['bits'] = $gis['bits'];
+               } else {
+                       $info['bits'] = 0;
+               }
+
+               return $info;
+       }
+
+       /**
+        * Empty place holder props for non-existing files
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
+        *   - metadata (handler specific)
+        *   - sha1 (in base 36)
+        *   - width
+        *   - height
+        *   - bits (bitrate)
+        *   - file-mime
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @return array
+        * @since 1.28
+        */
+       public function newPlaceholderProps() {
+               return FSFile::placeholderProps() + [
+                       'metadata' => '',
+                       'width' => 0,
+                       'height' => 0,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_UNKNOWN
+               ];
+       }
+}
index db567fc..ef0da8e 100644 (file)
        "talk": "نقاش",
        "views": "معاينة",
        "toolbox": "أدوات",
+       "tool-link-userrights": "تغيير مجموعات {{GENDER:$1|المستخدم|المستخدمة}}",
+       "tool-link-emailuser": "أرسل رسالة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
        "userpage": "طالع صفحة المستخدم",
        "projectpage": "طالع صفحة المشروع",
        "imagepage": "طالع صفحة الملف",
index 6530ebc..2a75b5c 100644 (file)
        "talk": "Абмеркаваньне",
        "views": "Рэжымы",
        "toolbox": "Інструмэнты",
+       "tool-link-userrights": "Зьмяніць групы {{GENDER:$1|ўдзельніка|ўдзельніцы}}",
+       "tool-link-emailuser": "Даслаць {{GENDER:$1|удзельніку|удзельніцы}} ліст электроннай поштай",
        "userpage": "Паказаць старонку ўдзельніка",
        "projectpage": "Паказаць старонку праекту",
        "imagepage": "Паказаць старонку файла",
        "passwordreset-emailelement": "Імя ўдзельніка: \n$1\n\nЧасовы пароль: \n$2",
        "passwordreset-emailsentemail": "Калі гэты адрас электроннай пошты далучаны да вашага рахунку, тады будзе дасланы ліст пра скідваньне паролю.",
        "passwordreset-emailsentusername": "Калі ёсьць адрас электроннай пошты, злучаны з гэтым імем удзельніка, тады будзе дасланы ліст пра скідваньне паролю.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Электронны ліст|Электронныя лісты}} скіданьня паролю {{PLURAL:$1|быў дасланы|былі дасланыя}}. {{PLURAL:$1|Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя ніжэй.",
-       "passwordreset-emailerror-capture2": "Не атрымалася даслаць {{GENDER:$2|удзельніку|удзельніцы}} ліст электроннай поштай: $1 {{PLURAL:$3|Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя ніжэй.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|1=Электронны ліст|Электронныя лісты}} скіданьня паролю {{PLURAL:$1|1=быў дасланы|былі дасланыя}}. {{PLURAL:$1|1=Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя тут.",
+       "passwordreset-emailerror-capture2": "Не атрымалася даслаць {{GENDER:$2|удзельніку|удзельніцы}} ліст электроннай поштай: $1 {{PLURAL:$3|1=Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя тут.",
        "passwordreset-nocaller": "Мусіць быць пададзены той, хто робіць выклік",
        "passwordreset-nosuchcaller": "Аўтар выкліку не існуе: $1",
        "passwordreset-ignored": "Скіданьне паролю не адбылося. Магчыма, ня быў наладжаны пастаўшчык?",
index ac0322a..b7255f3 100644 (file)
        "talk": "Размовы",
        "views": "Віды",
        "toolbox": "Прылады",
+       "tool-link-userrights": "Змяніць групы {{GENDER:$1|ўдзельніка|ўдзельніцы}}",
        "userpage": "Паказаць старонку ўдзельніка",
        "projectpage": "Паказаць старонку праекта",
        "imagepage": "Гл. старонку файла",
        "expand_templates_generate_xml": "Паказаць дрэва сінтаксічнага аналізу XML",
        "expand_templates_generate_rawhtml": "Паказаць зыходны код HTML",
        "expand_templates_preview": "Перадпаказ",
+       "expand_templates_input_missing": "Трэба ўвесці хоць які-небудзь тэкст.",
        "pagelanguage": "Змяніць мову старонкі",
        "pagelang-name": "Старонка",
        "pagelang-language": "Мова",
        "mediastatistics-header-unknown": "Невядомыя",
        "mediastatistics-header-bitmap": "Растравыя выявы",
        "mediastatistics-header-drawing": "Рысункі (вектарныя выявы)",
+       "mediastatistics-header-text": "Тэкст",
+       "mediastatistics-header-archive": "Сціснутыя фарматы",
        "mediastatistics-header-total": "Усе файлы",
        "json-error-state-mismatch": "Недапушчальны або некарэктны JSON",
        "json-error-syntax": "Памылка сінтаксісу",
+       "headline-anchor-title": "Спасылка на гэты раздзел",
        "special-characters-group-latin": "Лацінскія",
        "special-characters-group-latinextended": "Лацінскія дадатковыя",
        "special-characters-group-ipa": "IPA",
index ac68b88..255097d 100644 (file)
        "newwindow": "(отваря се в нов прозорец)",
        "cancel": "Отказ",
        "moredotdotdot": "Още…",
-       "morenotlisted": "Този Ñ\81пиÑ\81Ñ\8aк Ð¼Ð¾Ð¶Ðµ да е непълен.",
+       "morenotlisted": "Ð\92Ñ\8aзможно Ðµ Ñ\82ози Ñ\81пиÑ\81Ñ\8aк да е непълен.",
        "mypage": "Страница",
        "mytalk": "Беседа",
        "anontalk": "Беседа",
        "mergehistory-empty": "Няма редакции, които могат да бъдат слети.",
        "mergehistory-done": "$3 {{PLURAL:$3|версия|версии}} от $1 {{PLURAL:$3|беше успешно слята|бяха успешно слети}} с редакционната история на [[:$2]].",
        "mergehistory-fail": "Невъзможно е да се извърши сливане на редакционните истории; проверете страницата и времевите параметри.",
+       "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": "Изходната страница трябва да притежава коректно име.",
        "grant-editmywatchlist": "редактиране на списъка ви за наблюдение",
        "grant-editpage": "Редактиране на съществуващи страници",
        "grant-editprotected": "Редактиране на защитени страници",
+       "grant-uploadeditmovefile": "Качване, заменяне и прехвърляне на файлове",
        "grant-uploadfile": "Качване на нови файлове",
        "grant-basic": "Основни права",
        "grant-viewdeleted": "Преглед на изтрити файлове и страници",
        "action-viewmywatchlist": "преглед на списъка ви за наблюдение",
        "action-viewmyprivateinfo": "преглеждане на личните данни",
        "action-editmyprivateinfo": "редактиране на личната си информация",
+       "action-managechangetags": "създаване и (де)активиране на етикети",
+       "action-applychangetags": "прилагане на етикетите заедно с промените ви",
        "action-purge": "почисти кеша на тази страница",
        "nchanges": "$1 {{PLURAL:$1|промяна|промени}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|от последното посещение}}",
        "apihelp-no-such-module": "Модул \"$1\" не беше намерен.",
        "apisandbox": "Пясъчник за API",
        "apisandbox-fullscreen": "Разшири полето",
+       "apisandbox-submit": "Направи запитване",
        "apisandbox-reset": "Изчистване",
        "apisandbox-retry": "Повторен опит",
+       "apisandbox-loading": "Зареждане на информация за API-модул \"$1\"...",
+       "apisandbox-load-error": "Възникна грешка при зареждането на информация за API-модул \"$1\": $2",
+       "apisandbox-no-parameters": "Този API-модул няма параметри.",
+       "apisandbox-helpurls": "Връзки за помощ",
        "apisandbox-examples": "Примери",
+       "apisandbox-dynamic-parameters": "Допълнителни параметри",
        "apisandbox-dynamic-parameters-add-label": "Добавяне на параметър:",
        "apisandbox-dynamic-parameters-add-placeholder": "Име на параметъра",
+       "apisandbox-dynamic-error-exists": "Параметър с име \"$1\" вече съществува.",
        "apisandbox-results": "Резултати",
        "apisandbox-request-url-label": "URL-адрес на заявката:",
        "booksources": "Източници на книги",
        "booksources-text": "По-долу е списъкът от връзки към други сайтове, продаващи нови и използвани книги или имащи повече информация за книгите, които търсите:",
        "booksources-invalid-isbn": "Предоставеният ISBN изглежда е невалиден; проверете за грешки и копирайте от оригиналния източник.",
        "specialloguserlabel": "Изпълнител:",
-       "speciallogtitlelabel": "Цел (заглавие или потребител):",
+       "speciallogtitlelabel": "Цел (заглавие или {{ns:user}}:потребителско име за потребител):",
        "log": "Дневници",
        "logeventslist-submit": "Показване",
        "all-logs-page": "Всички публични дневници",
        "movenotallowedfile": "Нямате права да премествате файлове.",
        "cant-move-user-page": "Нямате нужните права на достъп, за да местите потребителски страници (можете да местите само подстраници).",
        "cant-move-to-user-page": "Нямате нужните права на достъп, за да извършвате преместване на страници върху потребителски страници (можете да местите само върху подстраници от потребителското пространство).",
+       "cant-move-category-page": "Нямате необходимите права за преместване на страници на категории.",
+       "cant-move-to-category-page": "Нямате необходимите права за преместване на страница в страница на категория.",
        "newtitle": "Ново заглавие:",
        "move-watch": "Наблюдаване на страницата",
        "movepagebtn": "Преместване",
        "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": "Непозволено",
        "exif-ycbcrpositioning-1": "Центрирани",
        "exif-dc-contributor": "Сътрудници",
        "exif-dc-date": "Дата(и)",
+       "exif-dc-publisher": "Издател",
+       "exif-dc-relation": "Свързани медии",
        "exif-dc-rights": "Права",
+       "exif-dc-source": "Източник медия",
        "exif-dc-type": "Вид медия",
        "exif-isospeedratings-overflow": "По-голяма от 65535",
        "exif-iimcategory-ace": "Изкуствa, култура и забавление",
        "watchlistedit-raw-done": "Списъкът ви за наблюдение беше обновен.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 страница беше добавена|$1 страници бяха добавени}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|Една страница беше премахната|$1 страници бяха премахнати}}:",
+       "watchlistedit-clear-title": "Изчистване на списъка за наблюдение",
        "watchlistedit-clear-legend": "Изчистване на списъка за наблюдение",
        "watchlistedit-clear-explain": "Всички заглавия ще бъдат премахнати от списъка ви за наблюдение",
        "watchlistedit-clear-titles": "Заглавия:",
        "tags-edit-chosen-placeholder": "Избиране на няколко етикета",
        "tags-edit-reason": "Причина:",
        "tags-edit-revision-submit": "Прилагане на промените към {{PLURAL:$1|тази редакция|$1 редакции}}",
+       "tags-edit-success": "Промените са приложени.",
+       "tags-edit-failure": "Промените не могат да бъдат приложени:\n$1",
        "tags-edit-nooldid-title": "Не е зададена версия",
        "comparepages": "Сравняване на страници",
        "compare-page1": "Страница 1",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>изключено</strong>)",
        "mediastatistics": "Медия статистики",
        "mediastatistics-table-mimetype": "MIME тип",
+       "mediastatistics-table-count": "Брой файлове",
+       "mediastatistics-table-totalbytes": "Общ размер",
+       "mediastatistics-header-unknown": "Неизвестно",
        "mediastatistics-header-audio": "Аудио",
        "mediastatistics-header-video": "Видео",
        "mediastatistics-header-total": "Всички файлове",
index 411ddab..d00c9d7 100644 (file)
        "talk": "আলোচনা",
        "views": "দৃষ্টিকোণ",
        "toolbox": "সরঞ্জাম",
+       "tool-link-userrights": "{{GENDER:$1|ব্যবহারকারী}} দল পরিবর্তন করুন",
+       "tool-link-emailuser": "এই {{GENDER:$1|ব্যবহারকারী}}কে ইমেইল পাঠান",
        "userpage": "ব্যাবহারকারীর পাতা দেখুন",
        "projectpage": "মেটা-পাতা দেখুন",
        "imagepage": "ফাইল পাতা দেখুন",
        "botpasswords": "বট পাসওয়ার্ড",
        "botpasswords-disabled": "বট পাসওয়ার্ড নিষ্ক্রিয় করা।",
        "botpasswords-no-central-id": "বট পাসওয়ার্ড ব্যবহার করার জন্য, আপনাকে একটি কেন্দ্রীভূত অ্যাকাউন্টে প্রবেশ করতে হবে।",
+       "botpasswords-existing": "বিদ্যমান বট শব্দচাবি",
        "botpasswords-createnew": "একটি নতুন বট পাসওয়ার্ড তৈরি করুন",
+       "botpasswords-editexisting": "একটি বিদ্যমান বট শব্দচাবি পরিবর্তন করুন",
        "botpasswords-label-appid": "বটের নাম:",
        "botpasswords-label-create": "তৈরি করো",
        "botpasswords-label-update": "হালনাগাদ",
        "grant-editmycssjs": "আপনার সিএসএস/জাভাস্ক্রিপ্ট সম্পাদনা করুন",
        "grant-editmyoptions": "আপনার ব্যবহারকারী পছন্দসমূহ সম্পাদনা করুন",
        "grant-editmywatchlist": "আপনার নজরতালিকা সম্পাদনা করুন",
+       "grant-editpage": "বিদ্যমান পাতা সম্পাদনা করুন",
        "grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
        "grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
        "grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
index 9e9e6b6..d5dbe64 100644 (file)
        "databaseerror-query": "Consulta: $1",
        "databaseerror-function": "Funció: $1",
        "databaseerror-error": "Error:$1",
+       "transaction-duration-limit-exceeded": "Per evitar una alta demora de resposta, s'ha interromput aquesta transacció perquè la durada d'escriptura ($1) ha sobrepassat el límit de $2 segons.\nSi esteu canviant molts elements alhora, intenteu fer-ho amb diverses operacions més petites.",
        "laggedslavemode": "Avís: La pàgina podria mancar de modificacions recents.",
        "readonly": "La base de dades està bloquejada",
        "enterlockreason": "Escriviu una raó pel bloqueig, així com una estimació de quan tindrà lloc el desbloqueig",
        "htmlform-title-not-exists": "$1 no existeix.",
        "htmlform-user-not-exists": "<strong>$1</strong> no existeix.",
        "htmlform-user-not-valid": "<strong>$1</strong> no és nom d'usuari vàlid.",
-       "sqlite-has-fts": "$1, amb suport de cerca de text íntegre",
-       "sqlite-no-fts": "$1, sense supor de cerca de text íntegre",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha esborrat}} la pàgina $3",
        "logentry-delete-restore": "$1 ha restaurat $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ha canviat}} la visibilitat {{PLURAL:$5|d'un esdeveniment al registre|de $5 esdeveniments al registre}} de $3: $4",
index 071a23d..8d380cd 100644 (file)
@@ -53,7 +53,7 @@
        "tog-ccmeonemails": "共我發乞其他用戶其電子郵件其備份發乞我。",
        "tog-diffonly": "伓使敆下底其顯示𣍐蜀様其地方顯示頁面內容",
        "tog-showhiddencats": "㪗藏類別",
-       "tog-norollbackdiff": "敆回滾其時候,無叕𣍐蜀様其地方",
+       "tog-norollbackdiff": "Cék-hèng huòi-gūng ī-hâiu ng-sāi hiēng-sê chă-biék",
        "tog-useeditwarning": "我編輯頁面其時候離開,起動警告我蜀下",
        "tog-prefershttps": "Láuk-diē ī-hâiu sṳ̄-cṳ̆ng sāi ăng-cuòng lièng-giék",
        "underline-always": "直頭",
        "sunday": "Lā̤-bái",
        "monday": "Bái-ék",
        "tuesday": "Bái-nê",
-       "wednesday": "拜三",
+       "wednesday": "Bái-săng",
        "thursday": "Bái-sé",
        "friday": "Bái-ngô",
        "saturday": "Bái-lĕ̤k",
        "sun": "禮拜",
-       "mon": "拜一",
+       "mon": "B1",
        "tue": "Bái-nê",
        "wed": "拜三",
        "thu": "拜四",
        "category-empty": "''茲類別下底現在無文章也無媒體。''",
        "hidden-categories": "{{PLURAL:$1}} bĭk ké̤ṳk káung kī gì lôi-biék",
        "hidden-category-category": "已經囥起其類別",
-       "category-subcat-count": "{{PLURAL:$2|茲萆分類僅包括下底蜀萆子分類|茲分類有 {{PLURAL:$1|子分類|$1 萆子分類}},總計 $2 萆。}}",
+       "category-subcat-count": "{{PLURAL:$2|Ciā lôi-biék nâ bău-guák â-dā̤ siŏh bĭk cṳ̄-lôi-biék.|Ciā lôi-biék bău-guák â-dā̤ $1 bĭk cṳ̄-lôi-biék, gê̤ṳng-cūng $2 bĭk.}}",
        "category-subcat-count-limited": "茲蜀萆類別下底有子類別{{PLURAL:$1}}",
-       "category-article-count": "{{PLURAL:$2|茲蜀萆類別儷有下底蜀頁。|共總有$2頁,下底其茲$1頁敆茲蜀萆類別𡅏。}}",
+       "category-article-count": "{{PLURAL:$2|Ciā lôi-biék nâ bău-guák â-dā̤ siŏh bĭk hiĕk-miêng.|Ciā lôi-biék bău-guák â-dā̤ $1 bĭk hiĕk-miêng, gê̤ṳng-cūng $2 bĭk.}}",
        "category-article-count-limited": "下底$1頁敆茲蜀萆類別𡅏{{PLURAL:$1}}",
        "category-file-count": "茲蜀萆類別共總有$2萆文件,下底茲$1萆文件都敆茲蜀萆類別𡅏。",
        "category-file-count-limited": "下底其茲$1萆文件都敆茲蜀萆類別𡅏。{{PLURAL:$1}}",
        "searchbutton": "Tō̤",
        "go": "去",
        "searcharticle": "Kó̤",
-       "history": "頁面歷史",
+       "history": "Hiĕk-miêng lĭk-sṳ̄",
        "history_short": "Lĭk-sṳ̄",
        "updatedmarker": "趁我最後蜀回訪問開始更新",
        "printableversion": "Â̤ páh-éng gì bēng-buōng",
        "aboutsite": "Guăng-ṳ̀ {{SITENAME}}",
        "aboutpage": "Project:Guăng-ṳ̀",
        "copyright": "內容會使敆$1下底會使獲得遘,若無會給出其它提示。",
-       "copyrightpage": "{{ns:project}}:版權",
+       "copyrightpage": "{{ns:project}}:Bēng-guòng",
        "currentevents": "Duâi Ché̤ṳ Â",
        "currentevents-url": "Project:Duâi Ché̤ṳ Â",
        "disclaimers": "Mò̤-hô-cáik sĭng-mìng",
        "feed-invalid": "無乇使其下標填充類型",
        "feed-unavailable": "𣍐使聚合訂閱",
        "site-rss-feed": "$1 RSS 訂閱",
-       "site-atom-feed": "$1 Nguòng-cṳ̄ déng-iŏk",
+       "site-atom-feed": "$1 Atom déng-iŏk",
        "page-rss-feed": "「$1」RSS訂閱",
-       "page-atom-feed": "「$1」原子訂閱",
+       "page-atom-feed": "$1 Atom déng-iŏk",
        "red-link-title": "$1 (mò̤ hī hiĕh)",
        "sort-descending": "降序排序",
        "sort-ascending": "升序排序",
        "perfcached": "下底其數據乞緩存固加可能伓是最新其。{{PLURAL:$1|$1條結果}}會敆緩存臺中討著。",
        "perfcachedts": "下底其數據已經緩存過了,最後更新遘$1。{{PLURAL:$4|$4條結果}}會敆緩存臺中討著。",
        "querypage-no-updates": "茲蜀頁其更新乞禁止了。\n數據嚽塊現刻時𣍐更新了。",
-       "viewsource": "看源代碼",
+       "viewsource": "Káng nguòng-dâi-mā",
        "viewsource-title": "覷\"$1\"其源代碼",
        "actionthrottled": "行動乞取消咯",
        "protectedpagetext": "茲頁已經乞保護起咯,𣍐使修改或者其它行動。",
        "nocookiesnew": "用戶賬號已經開好了,不過汝固未躒入。\n{{SITENAME}}使cookie來記錄已經躒入其用戶。\n汝其cookie固未開起來。\n起動汝開啟cookie,仱再使汝新其賬號共密碼來躒入。",
        "nocookieslogin": "{{SITENAME}}使cookies來記錄已經登錄其用戶。\n但是汝禁用了cookie。\n起動汝開起cookie,然後再試蜀試。",
        "noname": "汝未指定蜀萆合法其用戶名。",
-       "loginsuccesstitle": "躒入成功",
+       "loginsuccesstitle": "Láuk-diē sìng-gŭng",
        "loginsuccess": "'''汝現在已經「$1」其成功躒入{{SITENAME}}了。'''",
-       "nosuchuser": "無總款其用戶名「$1」。\n用户名是大小写敏感其。\n检查汝其拼写,或者覷蜀覷[[Special:CreateAccount|開新賬戶]]。",
+       "nosuchuser": "Că mò̤ ming-chĭng sê \"$1\" gì ê̤ṳng-hô.\nÊ̤ṳng-hô-miàng ô buŏng duâi-siēu-siā.\nChiāng giēng-că nṳ̄ gì pĭng-siā, hĕ̤k-chiā [[Special:CreateAccount|kŭi sĭng dióng-hô]].",
        "nosuchusershort": "無總款其用戶名「$1」。\n檢查汝其拼寫。",
        "nouserspecified": "汝著指定蜀萆用戶名。",
        "login-userblocked": "茲隻用戶已經乞封鎖去了。登錄是𣍐允許其。",
        "accountcreated": "賬戶創建了",
        "accountcreatedtext": "[[{{ns:User}}:$1|$1]]([[{{ns:User talk}}:$1|talk]])用戶已經創建。",
        "createaccount-title": "{{SITENAME}}其開賬戶",
-       "login-abort-generic": "汝其登錄𣍐成功——放棄去了",
+       "login-abort-generic": "Nṳ̄ láuk-diē sék-bâi - Sák gó̤ lāu",
        "loginlanguagelabel": "語言:$1",
        "pt-login": "Láuk-diē",
        "pt-login-button": "躒入",
        "subject": "主題/標題:",
        "minoredit": "過幼修改",
        "watchthis": "監視茲頁",
-       "savearticle": "保存茲頁",
+       "savearticle": "Bō̤-còng ciā hiĕk",
        "preview": "預覽",
        "showpreview": "顯示預覽",
        "showdiff": "看改變其部分",
        "blockednoreason": "無掏出原因",
        "whitelistedittext": "汝必須$1乍會使修改頁面。",
        "loginreqtitle": "需要登錄",
-       "loginreqlink": "躒入",
+       "loginreqlink": "láuk-diē",
        "loginreqpagetext": "起動汝$1以後再看其它頁面。",
        "accmailtitle": "密碼寄出了",
        "accmailtext": "共[[User talk:$1|$1]]用戶隨機生成其密碼已經發遘$2了。汝登錄以後會使敆[[Special:ChangePassword|修改密碼]]頁面修改茲蜀萆密碼。",
        "templatesusedpreview": "茲萆預覽使其{{PLURAL:$1|模板}}:",
        "templatesusedsection": "茲蜀段使其{{PLURAL:$1|模板}}:",
        "template-protected": "(bō̤-hô)",
-       "template-semiprotected": "(半保護)",
+       "template-semiprotected": "(buáng bō̤-hô)",
        "permissionserrorstext-withaction": "因為下底其{{PLURAL:$1|原因}},汝無能耐 $2 :",
        "recreate-moveddeleted-warn": "'''注意:汝敆𡅏重新創建舊底已經乞刪唻其頁面。'''\n\n汝應該考慮蜀下繼續去編輯茲蜀頁到底是伓是合適其。茲蜀頁其刪除記錄共移動記錄都敆嚽塊:",
        "edit-conflict": "編輯衝突",
        "previousrevision": "← Gá-gô gì bēng-buōng",
        "nextrevision": "加新其版本→",
        "currentrevisionlink": "最新版本",
-       "cur": "",
+       "cur": "dāng",
        "next": "下",
-       "last": "",
+       "last": "sèng",
        "page_first": "頭",
        "page_last": "尾",
        "histlegend": "差別揀選:選擇卜比並其版本,再擪「回車」('''Enter''')或者擪底底其'''比並揀選版本'''。<br />\n說明:(伶)=共第一新其版本比並,(前)=共前蜀版本比並,~=過幼修改。",
        "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萆",
+       "prevn": "sèng $1 bĭk",
+       "nextn": "âu $1 bĭk",
        "shown-title": "Mūi hiĕk hiēng-sê $1{{PLURAL:$1|bĭk giék-guō}}",
-       "viewprevnext": "看($1 {{int:pipe-separator}} $2)($3)。",
+       "viewprevnext": "Káng ($1 {{int:pipe-separator}} $2) ($3)",
        "searchprofile-articles": "Nô̤i-ṳ̀ng hiĕk",
        "searchprofile-images": "Dŏ̤-mùi-tā̤",
        "searchprofile-everything": "Sū-iū-nó̤h",
        "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-redirect": "(dêng-hióng $1)",
        "search-suggest": "汝其意思是伓是:$1",
        "searchrelated": "相關其",
        "searchall": "全部",
        "showingresults": "顯示趁#<b>$2</b>開始其{{PLURAL:$1|'''$1'''萆結果}}。",
-       "search-nonefound": "討毋着",
+       "search-nonefound": "Tō̤ mâ̤ diŏh.",
        "preferences": "設定",
        "mypreferences": "我其設定",
        "prefs-edits": "修改數量:",
        "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機器人",
-       "rcshowhideliu": "$1已註冊其用戶",
-       "rcshowhideanons": "$1無名用戶",
-       "rcshowhidemine": "$1我其修改",
-       "rclinks": "顯示$2日以內產生其$1回改變<br />$3",
+       "rclistfrom": "Hiēng-sê téng $3 $2 gáu dāng gì sĭng gāi-biéng",
+       "rcshowhideminor": "$1 guó-éu siŭ-gāi",
+       "rcshowhidebots": "$1 gĭ-ké-nè̤ng",
+       "rcshowhideliu": "$1 ī dĕng-gé gì ê̤ṳng-hô",
+       "rcshowhideanons": "$1 ù-mìng-sê",
+       "rcshowhidemine": "$1 nguāi gì siŭ-gāi",
+       "rclinks": "Hiēng-sê có̤i-gê̤ṳng $2 gĕ̤ng ī-nô̤i gì $1 huòi gāi-biéng<br />$3",
        "diff": "chă",
        "hist": "sṳ̄",
        "hide": "掩",
index cf16049..cae8039 100644 (file)
        "talk": "Diskuse",
        "views": "Zobrazení",
        "toolbox": "Nástroje",
+       "tool-link-userrights": "Změnit uživatelské skupiny {{GENDER:$1|tohoto uživatele|této uživatelky}}",
+       "tool-link-emailuser": "Poslat e-mail {{GENDER:$1|tomuto uživateli|této uživatelce}}",
        "userpage": "Prohlédnout si uživatelskou stránku",
        "projectpage": "Prohlédnout si stránku projektu",
        "imagepage": "Prohlédnout si stránku o souboru",
        "passwordreset-emailelement": "Uživatelské jméno: \n$1\n\nDočasné heslo: \n$2",
        "passwordreset-emailsentemail": "Pokud je u vašeho účtu nastavena tato e-mailová adresa, bude vám zaslán e-mail pro získání nového hesla.",
        "passwordreset-emailsentusername": "Pokud je u tohoto účtu nastavena e-mailová adresa, bude vám zaslán e-mail pro získání nového hesla.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Byl odeslán e-mail|Byly odeslány e-maily}} pro získání nového hesla. {{PLURAL:$1|Uživatelské jméno a heslo jsou zobrazeny|Seznam uživatelských jmen a hesel je zobrazen}} níže.",
-       "passwordreset-emailerror-capture2": "{{GENDER:$2|Uživateli|Uživatelce}} se nepodařilo odeslat e-mail: $1 {{PLURAL:$3|Uživatelské jméno a heslo jsou zobrazeny|Seznam uživatelských jmen a hesel je zobrazen}} níže.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Byl odeslán e-mail|Byly odeslány e-maily}} pro získání nového hesla. Zde {{PLURAL:$1|jsou zobrazeny uživatelské jméno a heslo|je zobrazen seznam uživatelských jmen a hesel}}.",
+       "passwordreset-emailerror-capture2": "{{GENDER:$2|Uživateli|Uživatelce}} se nepodařilo odeslat e-mail: $1 Zde {{PLURAL:$3|jsou zobrazeny uživatelské jméno a heslo|je zobrazen seznam uživatelských jmen a hesel}}.",
        "passwordreset-nocaller": "Musí být uveden volající",
        "passwordreset-nosuchcaller": "Volající neexistuje: $1",
        "passwordreset-ignored": "Žádost o nové heslo nebyla zpracována. Možná není nakonfigurován žádný poskytovatel?",
index f2c4c01..9d42c88 100644 (file)
        "talk": "Diskussion",
        "views": "Ansichten",
        "toolbox": "Werkzeuge",
+       "tool-link-userrights": "{{GENDER:$1|Benutzergruppen}} ändern",
+       "tool-link-emailuser": "E-Mail an {{GENDER:$1|diesen Benutzer|diese Benutzerin}} senden",
        "userpage": "Benutzerseite anzeigen",
        "projectpage": "Projektseite anzeigen",
        "imagepage": "Dateiseite anzeigen",
        "passwordreset-emailelement": "Benutzername: \n$1\n\nTemporäres Passwort: \n$2",
        "passwordreset-emailsentemail": "Falls diese E-Mail-Adresse mit deinem Benutzerkonto verknüpft ist, wird eine Passwort-Zurücksetzungs-E-Mail versandt.",
        "passwordreset-emailsentusername": "Falls es eine E-Mail-Adresse gibt, die mit diesem Benutzernamen verknüpft ist, wird eine Passwort-Zurücksetzungs-E-Mail versandt.",
-       "passwordreset-emailsent-capture2": "Die Passwort-Zurücksetzungs-{{PLURAL:$1|E-Mail wurde|E-Mails wurden}} versandt. {{PLURAL:$1|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
-       "passwordreset-emailerror-capture2": "Das Senden der E-Mail an {{GENDER:$2|den Benutzer|die Benutzerin}} ist fehlgeschlagen: $1 {{PLURAL:$3|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
+       "passwordreset-emailsent-capture2": "Die Passwort-Zurücksetzungs-{{PLURAL:$1|E-Mail wurde|E-Mails wurden}} versandt. {{PLURAL:$1|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird hier angezeigt.",
+       "passwordreset-emailerror-capture2": "Das Senden der E-Mail an {{GENDER:$2|den Benutzer|die Benutzerin}} ist fehlgeschlagen: $1 {{PLURAL:$3|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird hier angezeigt.",
        "passwordreset-nocaller": "Es muss ein Rufer angegeben werden",
        "passwordreset-nosuchcaller": "Rufer ist nicht vorhanden: $1",
        "passwordreset-ignored": "Die Passwortzurücksetzung konnte nicht verarbeitet werden. Vielleicht wurde kein Dienstanbieter konfiguriert?",
index 81380ce..fe63a3b 100644 (file)
        "about": "Heqa cı de",
        "article": "Pela zerreki",
        "newwindow": "(pençereyê newey de beno a)",
-       "cancel": "Bıterkın",
+       "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": "Perra bağsi",
+       "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pera zerreki bıvin",
-       "talk": "Vaten",
+       "talk": "Werênayış",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "userpage": "Pela karberi bıvêne",
        "summary": "Xulasa:",
        "subject": "Mewzu:",
        "minoredit": "Vurriyayışo werdiyo",
-       "watchthis": "Perrer bıpaw",
+       "watchthis": "Na pele seyr ke",
        "savearticle": "Qeyd ke",
        "savechanges": "Vurnayışan qeyd ke",
        "publishpage": "Perer bıhesırne",
        "prefs-labs": "Xacetê labs",
        "prefs-user-pages": "Pelê karberi",
        "prefs-personal": "Pela karberi",
-       "prefs-rc": "Vırnayışë newey",
+       "prefs-rc": "Vurriyayışê peyêni",
        "prefs-watchlist": "Lista seyrkerdışi",
        "prefs-editwatchlist": "Lista seyrkerdışi bıvurne",
        "prefs-editwatchlist-label": "Listey serkerdışanê cıkewtışi timar kerê",
        "nchanges": "$1 {{PLURAL:$1|vurnayış|vurnayışi}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ziyaretê peyêni ra nata}}",
        "enhancedrc-history": "tarix",
-       "recentchanges": "Vırnayışë newey",
+       "recentchanges": "Vurriyayışê peyêni",
        "recentchanges-legend": "Tercihê vurnayışanê peyênan",
        "recentchanges-summary": "Wiki sero vurriyayışê peyêni asenê.",
        "recentchanges-noresult": "Goreyê kriteranê kıfşkerdeyan ra qet yew vurnayış nêvêniya.",
index b3781c2..338d650 100644 (file)
        "passwordreset-emailelement": "Username:\n$1\n\nTemporary password:\n$2",
        "passwordreset-emailsentemail": "If this email address is associated with your account, then a password reset email will be sent.",
        "passwordreset-emailsentusername": "If there is an email address associated with this username, then a password reset email will be sent.",
-       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|email has|emails have}} been sent. The {{PLURAL:$1|username and password|list of usernames and passwords}} is shown below.",
-       "passwordreset-emailerror-capture2": "Emailing the {{GENDER:$2|user}} failed: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} is shown below.",
+       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|email has|emails have}} been sent. The {{PLURAL:$1|username and password|list of usernames and passwords}} is shown here.",
+       "passwordreset-emailerror-capture2": "Emailing the {{GENDER:$2|user}} failed: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} is shown here.",
        "passwordreset-nocaller": "A caller must be provided",
        "passwordreset-nosuchcaller": "Caller does not exist: $1",
        "passwordreset-ignored": "The password reset was not handled. Maybe no provider was configured?",
index 29707f2..7d9c922 100644 (file)
        "prefs-watchlist": "Atentaro",
        "prefs-editwatchlist": "Redakti atentaron",
        "prefs-editwatchlist-label": "Redakti erojn de via atentaro:",
-       "prefs-editwatchlist-edit": "Montri kaj forigi erojn de vi atentaro",
+       "prefs-editwatchlist-edit": "Rigardi kaj forigi titolojn el via atentaro",
        "prefs-editwatchlist-raw": "Redakti krudan atentaron",
        "prefs-editwatchlist-clear": "Malplenigi vian atentaron",
        "prefs-watchlist-days": "Kiom da tagoj montriĝu en la atentaro:",
index 6034afb..814fcf8 100644 (file)
        "logentry-patrol-patrol": "$1 {{GENDER:$2|marcó}} la revisión $4 de la página $3 como verificada",
        "logentry-patrol-patrol-auto": "$1 {{GENDER:$2|marcó}} automáticamente la revisión $4 de la página $3 como verificada",
        "logentry-newusers-newusers": "La cuenta de usuario $1 ha sido {{GENDER:$2|creada}}",
-       "logentry-newusers-create": "Se ha {{GENDER:$2|creado}} la cuenta de usuario $1",
+       "logentry-newusers-create": "Se ha {{GENDER:$2|creado}} la cuenta de {{GENDER:$4|usuario|usuaria}} $1",
        "logentry-newusers-create2": "La cuenta de usuario $3 ha sido {{GENDER:$2|creada}} por $1",
        "logentry-newusers-byemail": "La cuenta de usuario $3 ha sido {{GENDER:$2|creada}} por $1 y la contraseña ha sido enviada por correo",
-       "logentry-newusers-autocreate": "La cuenta $1 se {{GENDER:$2|creó}} automáticamente",
+       "logentry-newusers-autocreate": "Se ha {{GENDER:$2|creado}} automáticamente la cuenta de {{GENDER:$4|usuario|usuaria}} $1",
        "logentry-protect-move_prot": "$1 {{GENDER:$2|trasladó}} las preferencias de protección de $4 a $3",
        "logentry-protect-unprotect": "$1 {{GENDER:$2|eliminó}} la protección de $3",
-       "logentry-protect-protect": "$1 {{GENDER:$2|protegió}} $3 $4",
+       "logentry-protect-protect": "$1 {{GENDER:$2|protegió}} $3 $4",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protegió}} a $3 $4 [en cascada]",
        "logentry-protect-modify": "$1 {{GENDER:$2|cambió}} el nivel de protección de $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|cambió}} el nivel de protección de $3 $4 [en cascada]",
index d544a4e..f7a01e8 100644 (file)
        "newwindow": "(avaneb uues aknas)",
        "cancel": "Loobu",
        "moredotdotdot": "Veel...",
-       "morenotlisted": "See loend pole täielik.",
+       "morenotlisted": "See loend võib olla ebatäielik.",
        "mypage": "Minu lehekülg",
        "mytalk": "Arutelu",
        "anontalk": "Arutelu",
        "tags-actions-header": "Toimingud",
        "tags-active-yes": "Jah",
        "tags-active-no": "Ei",
-       "tags-source-extension": "Määratletud tarkvaralisas",
+       "tags-source-extension": "Määratletud tarkvaraliselt",
        "tags-source-manual": "Kasutaja või robot rakendab käsitsi",
        "tags-source-none": "Pole enam kasutuses",
        "tags-edit": "muuda",
        "htmlform-title-not-exists": "Lehekülge $1 pole olemas.",
        "htmlform-user-not-exists": "Kasutajat <strong>$1</strong> pole olemas.",
        "htmlform-user-not-valid": "<strong>$1</strong> pole sobiv kasutajanimi.",
-       "sqlite-has-fts": "$1 koos täistekstiotsingu toega",
-       "sqlite-no-fts": "$1 ilma täistekstiotsingu toeta",
        "logentry-delete-delete": "$1 {{GENDER:$2|kustutas}} lehekülje $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|taastas}} lehekülje $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutis}} leheküljel $3 {{PLURAL:$5|ühe|$5}} logisündmuse nähtavust: $4",
        "mw-widgets-dateinput-placeholder-month": "AAAA-KK",
        "mw-widgets-titleinput-description-new-page": "lehekülge pole veel",
        "mw-widgets-titleinput-description-redirect": "ümbersuunamine leheküljele \"$1\"",
-       "randomrootpage": "Juhuslik juurlehekülg"
+       "randomrootpage": "Juhuslik juurlehekülg",
+       "userjsispublic": "Pea silmas, et JavaScripti alamleheküljed ei tohiks sisaldada konfidentsiaalseid andmeid, kuna neid näevad teised kasutajad."
 }
index 55df8ff..f07e45c 100644 (file)
        "talk": "Discussion",
        "views": "Affichages",
        "toolbox": "Outils",
+       "tool-link-userrights": "Modifier les groupes de {{GENDER:$1|l’utilisateur|l’utilisatrice}}",
+       "tool-link-emailuser": "Envoyer un courriel à {{GENDER:$1|l’utilisateur|l’utilisatrice}}",
        "userpage": "Voir la page utilisateur",
        "projectpage": "Voir la page du projet",
        "imagepage": "Voir la page du fichier",
index ab12a38..cf91cbe 100644 (file)
        "talk": "Conversa",
        "views": "Vistas",
        "toolbox": "Ferramentas",
+       "tool-link-userrights": "Modificar os grupos {{GENDER:$1|do usuario|da usuaria}}",
+       "tool-link-emailuser": "Enviar un correo electrónico {{GENDER:$1|ó usuario|á usuaria}}",
        "userpage": "Ver a páxina {{GENDER:{{BASEPAGENAME}}|do usuario|da usuaria}}",
        "projectpage": "Ver a páxina do proxecto",
        "imagepage": "Ver a páxina do ficheiro",
index 28843a4..0eff530 100644 (file)
        "deletethispage": "מחיקת דף זה",
        "undeletethispage": "שחזור דף זה",
        "undelete_short": "שחזור {{PLURAL:$1|עריכה אחת|$1 עריכות}}",
-       "viewdeleted_short": "צפ×\99×\99×\94 ×\91{{PLURAL:$1|ער×\99×\9b×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|Ö¾$1 ×¢×¨×\99×\9b×\95ת מחוקות}}",
+       "viewdeleted_short": "×\94צ×\92ת {{PLURAL:$1|×\92רס×\94 ×¢×¨×\99×\9b×\94 ×\90×\97ת|$1 ×¢×¨×\99×\9b×\94 מחוקות}}",
        "protect": "הגנה",
        "protect_change": "שינוי",
-       "protectthispage": "הגנה על דף זה",
+       "protectthispage": "×\94פע×\9cת ×\94×\92× ×\94 ×¢×\9c ×\93×£ ×\96×\94",
        "unprotect": "שינוי הגנה",
-       "unprotectthispage": "ש×\99× ×\95×\99 ×\94×\94×\92× ×\94 ×©ל דף זה",
+       "unprotectthispage": "ש×\99× ×\95×\99 ×\94×\94×\92× ×\94 ×¢ל דף זה",
        "newpage": "דף חדש",
        "talkpage": "שיחה על דף זה",
        "talkpagelinktext": "שיחה",
        "talk": "שיחה",
        "views": "צפיות",
        "toolbox": "כלים",
+       "tool-link-userrights": "שינוי הרשאות ה{{GENDER:$1|משתמש|משתמשת}}",
+       "tool-link-emailuser": "שליחת דוא\"ל ל{{GENDER:$1|משתמש|משתמשת}}",
        "userpage": "צפייה בדף המשתמש",
        "projectpage": "צפייה בדף המיזם",
        "imagepage": "צפייה בדף הקובץ",
        "jumpto": "קפיצה אל:",
        "jumptonavigation": "ניווט",
        "jumptosearch": "חיפוש",
-       "view-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\93×£ ×\96×\94.\n×\90× ×\90 ×\94×\9eת×\99× ×\95 ×\96×\9e×\9f ×\9e×\94 ×\9cפנ×\99 ×©×ª× ×¡×\95 ×©×\95×\91 ×\9cצפ×\95ת ×\91×\93×£.\n\n$1",
-       "generic-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.\n×\90× ×\90 ×\94×\9eת×\99× ×\95 ×\96×\9e×\9f ×\9e×\94 ×\9cפנ×\99 ×©×ª× ×¡×\95 ×©×\95×\91 ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.",
+       "view-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\93×£ ×\94×\96×\94.\n× ×\90 ×\9c×\94×\9eת×\99×\9f ×\96×\9e×\9f ×\9e×\94 ×\95×\9c×\90×\97ר ×\9e×\9b×\9f ×\9cנס×\95ת ×©×\95×\91.\n\n$1",
+       "generic-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.\n× ×\90 ×\9c×\94×\9eת×\99×\9f ×\96×\9e×\9f ×\9e×\94 ×\95×\9c×\90×\97ר ×\9e×\9b×\9f ×\9cנס×\95ת ×©×\95×\91.",
        "pool-timeout": "זמן ההמתנה לסיום הנעילה עבר",
        "pool-queuefull": "התור מלא",
        "pool-errorunknown": "שגיאה בלתי ידועה",
        "portal-url": "Project:שער הקהילה",
        "privacy": "מדיניות הפרטיות",
        "privacypage": "Project:מדיניות הפרטיות",
-       "badaccess": "ש×\92×\99×\90×\94 ×\91×\94רש×\90×\95ת",
+       "badaccess": "ש×\92×\99×\90ת ×\94רש×\90×\94",
        "badaccess-group0": "אין {{GENDER:|לך|לך|לכם}} הרשאה לבצע את הפעולה ש{{GENDER:|ביקשת|ביקשת|ביקשתם}}.",
        "badaccess-groups": "הפעולה ש{{GENDER:|ביקשת|ביקשת|ביקשתם}} לבצע מוגבלת למשתמשים ב{{PLURAL:$2|קבוצה הבאה|אחת הקבוצות הבאות}}: $1.",
        "versionrequired": "נדרשת גרסה $1 של מדיה־ויקי",
-       "versionrequiredtext": "×\92רס×\94 $1 ×©×\9c ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 × ×\93רשת ×\9cש×\99×\9e×\95ש ×\91×\93×£ ×\96×\94. ×\9c×\9e×\99×\93×¢ × ×\95סף, ×¨×\90×\95 ×\90ת [[Special:Version|×\93×£ הגרסה]].",
+       "versionrequiredtext": "×\92רס×\94 $1 ×©×\9c ×ª×\95×\9bנת ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 × ×\93רשת ×\9cש×\99×\9e×\95ש ×\91×\93×£ ×\94×\96×\94.\n{{GENDER:|ר×\90×\94|ר×\90×\99|ר×\90×\95}} [[Special:Version|×\9e×\99×\93×¢ ×¢×\9c הגרסה]].",
        "ok": "אישור",
        "pagetitle": "$1 – {{SITENAME}}",
        "backlinksubtitle": "→ $1",
-       "retrievedfrom": "×\9eק×\95ר: $1",
+       "retrievedfrom": "×\90×\95×\97×\96ר ×\9eת×\95×\9a \"$1\"",
        "youhavenewmessages": "יש לך $1 ($2).",
        "youhavenewmessagesfromusers": "יש לך $1 {{PLURAL:$3|ממשתמש אחר|מ־$3 משתמשים}} ($2).",
        "youhavenewmessagesmanyusers": "יש לך $1 ממשתמשים רבים ($2).",
        "confirmable-no": "לא",
        "thisisdeleted": "להציג או לשחזר $1?",
        "viewdeleted": "להציג $1?",
-       "restorelink": "{{PLURAL:$1|×\92רס×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|$1 ×\92רס×\90ות מחוקות}}",
+       "restorelink": "{{PLURAL:$1|ער×\99×\9b×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|$1 ×¢×¨×\99×\9bות מחוקות}}",
        "feedlinks": "הזנה:",
        "feed-invalid": "סוג הזנת המנוי שגוי.",
        "feed-unavailable": "הזנות אינן זמינות",
        "nstab-category": "קטגוריה",
        "mainpage-nstab": "עמוד ראשי",
        "nosuchaction": "אין פעולה כזו",
-       "nosuchactiontext": "הפעולה שצוינה בכתובת ה־URL אינה תקינה.\nייתכן שטעית בהקלדת ה־URL, או שהשתמשת בקישור לא נכון.\nייתכן גם שהבעיה נוצרה כתוצאה מבאג בתוכנה המשמשת את {{SITENAME}}.",
+       "nosuchactiontext": "הפעולה שצוינה בכתובת ה־URL אינה תקינה.\nייתכן שטעית בהקלדת הכתובת, או שהשתמשת בקישור לא נכון.\nייתכן גם שהבעיה נוצרה כתוצאה מבאג בתוכנה המשמשת את {{SITENAME}}.",
        "nosuchspecialpage": "אין דף מיוחד בשם זה",
        "nospecialpagetext": "<strong>ביקשת דף מיוחד שאינו קיים.</strong>\n\nרשימה של הדפים המיוחדים הקיימים ניתן למצוא בדף [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "שגיאה",
        "databaseerror-function": "פונקציה: $1",
        "databaseerror-error": "שגיאה: $1",
        "transaction-duration-limit-exceeded": "כדי למנוע עיכובי העתקה גדולים, פעולה זו הופסקה כיוון שמשך הכתיבה ($1) עבר את המגבלה של {{PLURAL:$2|שנייה אחת|$2 שניות}}.\nאם הפעולה דורשת שינוי של פריטים רבים בו־זמנית, ניתן לנסות לבצע מספר פעולות קטנות יותר.",
-       "laggedslavemode": "'''אזהרה:''' הדף עשוי שלא להכיל עדכונים אחרונים.",
+       "laggedslavemode": "<strong>אזהרה:</strong> הדף עשוי שלא להכיל עדכונים אחרונים.",
        "readonly": "בסיס הנתונים נעול",
        "enterlockreason": "יש להקליד סיבה לנעילה, כולל הערכה למועד שחרור הנעילה",
-       "readonlytext": "×\91ס×\99ס × ×ª×\95× ×\99×\9d ×\96×\94 ×©×\9c ×\94×\90תר × ×¢×\95×\9c ×\91ר×\92×¢ ×\96×\94 ×\9cצ×\95ר×\9a הזנת נתונים ושינויים. ככל הנראה מדובר בתחזוקה שוטפת, שלאחריה יחזור האתר לפעולתו הרגילה.\n\nמנהל המערכת שנעל את בסיס הנתונים סיפק את ההסבר הבא: $1",
-       "missing-article": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d ×\9c×\90 ×\9eצ×\90 ×\90ת ×\94×\98קס×\98 ×©×\9c ×\94×\93×£ ×©×\94×\95×\90 ×\94×\99×\94 ×\90×\9e×\95ר ×\9c×\9eצ×\95×\90, ×\91ש×\9d \"$1\" $2.\n\n×\94×\93×\91ר × ×\92ר×\9d ×\91×\93ר×\9a ×\9b×\9c×\9c ×¢×\9cÖ¾×\99×\93×\99 ×§×\99ש×\95ר ×\99ש×\9f ×\9c×\94ש×\95×\95×\90ת ×\92רס×\90×\95ת ×©×\9c ×\93×£ ×©× ×\9e×\97ק ×\90×\95 ×\9c×\92רס×\94 ×©×\9c ×\93×£ ×\9b×\96×\94.\n\n×\90×\9d ×\96×\94 ×\90×\99× ×\95 ×\94×\9eקר×\94, ×\96×\94×\95 ×\9bנר×\90×\94 ×\91×\90×\92 ×\91ת×\95×\9b× ×\94.\n×\90× ×\90 ×\93×\95×\95×\97×\95 על כך ל[[Special:ListUsers/sysop|מפעיל מערכת]], תוך שמירת פרטי כתובת ה־URL.",
+       "readonlytext": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d × ×¢×\95×\9c ×\9bר×\92×¢ ×\9cהזנת נתונים ושינויים. ככל הנראה מדובר בתחזוקה שוטפת, שלאחריה יחזור האתר לפעולתו הרגילה.\n\nמנהל המערכת שנעל את בסיס הנתונים סיפק את ההסבר הבא: $1",
+       "missing-article": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d ×\9c×\90 ×\9eצ×\90 ×\90ת ×\94×\98קס×\98 ×©×\9c ×\94×\93×£ ×©×\94×\95×\90 ×\94×\99×\94 ×\90×\9e×\95ר ×\9c×\9eצ×\95×\90, ×\91ש×\9d \"$1\" $2.\n\n×\96×\94 × ×\92ר×\9d ×\91×\93ר×\9aÖ¾×\9b×\9c×\9c ×¢×§×\91 ×\9c×\97×\99צ×\94 ×¢×\9c ×§×\99ש×\95ר ×\99ש×\9f ×\9c×\92רס×\94 ×©×\9c ×\93×£ ×©× ×\9e×\97ק.\n\n×\90×\9d ×\96×\94 ×\90×\99× ×\95 ×\94×\9eקר×\94, ×\96×\94×\95 ×\9bנר×\90×\94 ×\91×\90×\92 ×\91ת×\95×\9b× ×\94.\n× ×\90 ×\9c×\93×\95×\95×\97 על כך ל[[Special:ListUsers/sysop|מפעיל מערכת]], תוך שמירת פרטי כתובת ה־URL.",
        "missingarticle-rev": "(מספר גרסה: $1)",
        "missingarticle-diff": "(השוואת הגרסאות: $1, $2)",
        "readonly_lag": "בסיס הנתונים ננעל אוטומטית כדי לאפשר לבסיסי הנתונים המשניים להתעדכן מהבסיס הראשי.",
        "internalerror": "שגיאה פנימית",
        "internalerror_info": "שגיאה פנימית: $1",
        "internalerror-fatal-exception": "שגיאה חמורה מסוג \"$1\"",
-       "filecopyerror": "×\94עתקת \"$1\" ×\9cÖ¾\"$2\" × ×\9bש×\9c×\94.",
-       "filerenameerror": "ש×\99× ×\95×\99 ×\94ש×\9d ×©×\9c \"$1\" ×\9cÖ¾\"$2\" × ×\9bש×\9c.",
-       "filedeleteerror": "×\9e×\97×\99קת \"$1\" × ×\9bש×\9c×\94.",
-       "directorycreateerror": "×\99צ×\99רת ×\94ת×\99ק×\99×\99×\94 \"$1\" × ×\9bש×\9c×\94.",
+       "filecopyerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\94עת×\99ק ×\90ת ×\94ק×\95×\91×¥ \"$1\" ×\9cק×\95×\91×¥ \"$2\".",
+       "filerenameerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cשנ×\95ת ×\90ת ×©×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9cש×\9d \"$2\".",
+       "filedeleteerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\9e×\97×\95ק ×\90ת ×\94ק×\95×\91×¥ \"$1\".",
+       "directorycreateerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\99צ×\95ר ×\90ת ×\94ת×\99ק×\99×\99×\94 \"$1\".",
        "directoryreadonlyerror": "התיקייה \"$1\" היא לקריאה בלבד.",
        "directorynotreadableerror": "התיקייה \"$1\" אינה ניתנת לקריאה.",
        "filenotfound": "הקובץ \"$1\" לא נמצא.",
-       "unexpected": "ערך לא צפוי: \"$1\"=\"$2\"",
+       "unexpected": "ערך לא צפוי: \"$1\"=\"$2\".",
        "formerror": "שגיאה: לא ניתן היה לשלוח את הטופס.",
        "badarticleerror": "לא ניתן לבצע את הפעולה הזאת בדף זה.",
        "cannotdelete": "לא ניתן היה למחוק את הדף או הקובץ \"$1\".\nייתכן שהוא כבר נמחק על־ידי משתמש אחר.",
        "delete-hook-aborted": "המחיקה הופסקה על־ידי מבנה Hook.\nלא ניתן הסבר.",
        "no-null-revision": "לא ניתן היה ליצור גרסת־דמה בדף \"$1\"",
        "badtitle": "כותרת שגויה",
-       "badtitletext": "כותרת הדף המבוקש הייתה בלתי־תקינה, ריקה, או קישור שגוי לשפה אחרת או למיזם אחר.\nייתכן שהיא מכילה תו אחד או יותר שאינו יכול לשמש בכותרות.",
+       "badtitletext": "כותרת הדף המבוקש הייתה בלתי תקינה, ריקה, או קישור שגוי לשפה אחרת או למיזם אחר.\nייתכן שהיא מכילה תו אחד או יותר שאינו יכול לשמש בכותרות.",
        "title-invalid-empty": "כותרת הדף המבוקש ריקה או מכילה רק שם של מרחב שם.",
-       "title-invalid-utf8": "כותרת הדף המבוקש מכילה רצף UTF-8 בלתי־תקין.",
+       "title-invalid-utf8": "כותרת הדף המבוקש מכילה רצף UTF-8 בלתי תקין.",
        "title-invalid-interwiki": "כותרת הדף המבוקש מכילה קישור בינוויקי, שלא ניתן להשתמש בו בכותרות.",
        "title-invalid-talk-namespace": "כותרת הדף המבוקש מפנה לדף שיחה שאינו יכול להתקיים.",
-       "title-invalid-characters": "כותרת הדף המבוקש מכילה תווים בלתי־תקינים: \"$1\".",
-       "title-invalid-relative": "×\91×\9b×\95תרת ×\99ש × ×ª×\99×\91 ×\99×\97ס×\99. ×\9b×\95תרת ×\93פ×\99×\9d ×\99×\97ס×\99×\95ת (./, ../) ×\90×\99× ×\9f ×ª×§×\99× ×\95ת, ×\9b×\99×\95×\95×\9f ×©×\9cעת×\99×\9d ×§×¨×\95×\91×\95ת ×\94×\9f ×\9c×\90 ×\99×\94×\99×\95 ×\91× ×\95ת־השגה כשתטופלנה על־ידי הדפדפן של המשתמש.",
-       "title-invalid-magic-tilde": "כותרת הדף המבוקש מכילה רצף טילדות מיוחד (<nowiki>~~~</nowiki>).",
-       "title-invalid-too-long": "×\9b×\95תרת ×\94×\93×£ ×\94×\9e×\91×\95קש ×\90ר×\95×\9b×\94 ×\9e×\93×\99. ×\94×\99×\90 ×¦×¨×\99×\9b×\94 ×\9c×\94×\99×\95ת ×\9c×\9b×\9c ×\94×\99×\95תר ×\91×\90×\95ר×\9a {{PLURAL:$1|×\91×\99ת ×\90×\97×\93|$1 ×\91תים}} בקידוד UTF-8.",
-       "title-invalid-leading-colon": "כותרת הדף המבוקש מכילה תו נקודתיים בלתי־תקין בתחילתה.",
+       "title-invalid-characters": "כותרת הדף המבוקש מכילה תווים בלתי תקינים: \"$1\".",
+       "title-invalid-relative": "×\91×\9b×\95תרת ×\99ש × ×ª×\99×\91 ×\99×\97ס×\99. ×\9b×\95תרת ×\93פ×\99×\9d ×\99×\97ס×\99×\95ת (./, ../) ×\90×\99× ×\9f ×ª×§×\99× ×\95ת, ×\9eש×\95×\9d ×©×\9cעת×\99×\9d ×§×¨×\95×\91×\95ת ×\94×\9f ×\99×\94×\99×\95 ×\91×\9cת×\99Ö¾× ×\99תנ×\95ת ×\9cהשגה כשתטופלנה על־ידי הדפדפן של המשתמש.",
+       "title-invalid-magic-tilde": "כותרת הדף המבוקש מכילה רצף טילדות מיוחד שאינו תקין (<nowiki>~~~</nowiki>).",
+       "title-invalid-too-long": "×\9b×\95תרת ×\94×\93×£ ×\94×\9e×\91×\95קש ×\90ר×\95×\9b×\94 ×\9e×\93×\99. ×\94×\99×\90 ×¦×¨×\99×\9b×\94 ×\9c×\94×\99×\95ת ×\9c×\9b×\9c ×\94×\99×\95תר ×\91×\90×\95ר×\9a {{PLURAL:$1|×\91×\99×\99×\98 ×\90×\97×\93|$1 ×\91×\99×\99×\98ים}} בקידוד UTF-8.",
+       "title-invalid-leading-colon": "כותרת הדף המבוקש מכילה תו נקודתיים בלתי תקין בתחילתה.",
        "perfcached": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, ועשוי שלא להיות מעודכן. לכל היותר {{PLURAL:$1|תוצאה אחת נשמרת|$1 תוצאות נשמרות}} בזיכרון המטמון.",
        "perfcachedts": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, שעודכן לאחרונה ב־$1. לכל היותר {{PLURAL:$4|תוצאה אחת נשמרת|$4 תוצאות נשמרות}} בזיכרון המטמון.",
        "querypage-no-updates": "העדכונים לדף זה כרגע מופסקים, והמידע לא יעודכן באופן שוטף.",
        "passwordreset-emailelement": "שם משתמש:\n$1\n\nסיסמה זמנית:\n$2",
        "passwordreset-emailsentemail": "אם כתובת הדואר האלקטרוני הזאת משויכת לחשבון שלך, אז יישלח דואר אלקטרוני לאיפוס הסיסמה.",
        "passwordreset-emailsentusername": "אם יש כתובת דואר אלקטרוני שמשויכת לשם המשתמש הזה, אז יישלח דואר אלקטרוני לאיפוס הסיסמה.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|×\93×\95×\90\"×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97\94×\95×\93×¢×\95ת ×\93×\95×\90\"×\9c ×©×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97×\95}}. {{PLURAL:$1|ש×\9d ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9c×\94×\9cן.",
-       "passwordreset-emailerror-capture2": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cש×\9c×\95×\97 ×\93×\95×\90\"×\9c ×\9c{{GENDER:$2|×\9eשת×\9eש|×\9eשת×\9eשת}}: $1 {{PLURAL:$3|ש×\9d ×\94×\9eשת×\9eש ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9c×\94×\9cן.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|×\93×\95×\90\"×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97\94×\95×\93×¢×\95ת ×\93×\95×\90\"×\9c ×©×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97×\95}}. {{PLURAL:$1|ש×\9d ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9b×\90ן.",
+       "passwordreset-emailerror-capture2": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cש×\9c×\95×\97 ×\93×\95×\90\"×\9c ×\9c{{GENDER:$2|×\9eשת×\9eש|×\9eשת×\9eשת}}: $1 {{PLURAL:$3|ש×\9d ×\94×\9eשת×\9eש ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9b×\90ן.",
        "passwordreset-nocaller": "לא סופק הקורא הנדרש",
        "passwordreset-nosuchcaller": "הקורא אינו קיים: $1",
        "passwordreset-ignored": "איפוס הסיסמה לא בוצע. ייתכן שלא הוגדר ספק.",
        "tags-update-remove-not-allowed-multi": "לא ניתן להסיר את {{PLURAL:$2|התגית הבאה|התגיות הבאות}} ידנית: $1",
        "tags-edit-title": "עריכת תגיות",
        "tags-edit-manage-link": "ניהול תגיות",
-       "tags-edit-revision-selected": "{{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}} מתוך [[:$2]]:",
+       "tags-edit-revision-selected": "{{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}} מתוך הדף [[:$2]]:",
        "tags-edit-logentry-selected": "{{PLURAL:$1|פעולת היומן שנבחרה|פעולות היומן שנבחרו}}:",
-       "tags-edit-revision-legend": "×\94×\95ספ×\94 ×©×\9c ×ª×\92×\99×\95ת {{PLURAL:$1|×\9c×\92רס×\94 ×\94×\96×\90ת|×\9c×\9b×\9c $1 ×\94×\92רס×\90×\95ת}} ×\90×\95 ×\94סרת×\9f",
+       "tags-edit-revision-legend": "×\94×\95ספ×\94 ×\90×\95 ×\94סר×\94 ×©×\9c ×ª×\92×\99×\95ת {{PLURAL:$1|×\9e×\92רס×\94 ×\96×\95\9eÖ¾$1 ×\92רס×\90×\95ת}}",
        "tags-edit-logentry-legend": "הוספה של תגיות {{PLURAL:$1|לרשומת היומן הזאת|לכל $1 רשומות היומן}} או הסרתן",
-       "tags-edit-existing-tags": "ת×\92×\99×\95ת ×§×\99×\99×\9eות:",
+       "tags-edit-existing-tags": "×\94ת×\92×\99×\95ת ×\94× ×\95×\9b×\97×\99ות:",
        "tags-edit-existing-tags-none": "<em>אין</em>",
        "tags-edit-new-tags": "תגיות חדשות:",
        "tags-edit-add": "הוספת התגיות הבאות:",
        "tags-edit-remove": "הסרת התגיות הבאות:",
        "tags-edit-remove-all-tags": "(הסרת כל התגיות)",
-       "tags-edit-chosen-placeholder": "×\91×\97×\99רת ×ª×\92×\99×\95ת ×\9eס×\95×\99×\9eות",
+       "tags-edit-chosen-placeholder": "× ×\90 ×\9c×\91×\97×\95ר ×ª×\92×\99ות",
        "tags-edit-chosen-no-results": "לא נמצאו תגיות מתאימות",
        "tags-edit-reason": "סיבה:",
-       "tags-edit-revision-submit": "×\94×\97×\9cת ×©×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9c×\92רס×\94 ×\94×\96×\90ת|ל־$1 גרסאות}}",
-       "tags-edit-logentry-submit": "×\94×\97×\9cת ×©×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9cרש×\95×\9eת ×\94×\99×\95×\9e×\9f ×\94×\96×\90ת|×\9cÖ¾$1 ×¨×©×\95×\9eת ×\94יומן}}",
+       "tags-edit-revision-submit": "×\94×\97×\9cת ×\94ש×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9c×\92רס×\94 ×\96×\95|ל־$1 גרסאות}}",
+       "tags-edit-logentry-submit": "×\94×\97×\9cת ×\94ש×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9cרש×\95×\9eת ×\94×\99×\95×\9e×\9f ×\94×\96×\95\9cÖ¾$1 ×¨×©×\95×\9e×\95ת יומן}}",
        "tags-edit-success": "השינויים הוחלו.",
        "tags-edit-failure": "החלת השינויים נכשלה:\n$1",
        "tags-edit-nooldid-title": "גרסת היעד אינה תקינה",
index ef1b90a..850fdba 100644 (file)
        "yourpasswordagain": "Ponovno upišite lozinku",
        "createacct-yourpasswordagain": "Potvrdi zaporku",
        "createacct-yourpasswordagain-ph": "Unesite zaporku ponovno",
-       "remembermypassword": "Zapamti moju lozinku na ovom računalu (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Zapamti me",
        "userlogin-signwithsecure": "Rabi sigurnu vezu",
        "cannotloginnow-title": "Prijava trenutno nije moguća.",
        "tooltip-t-recentchangeslinked": "Nedavne promjene na stranicama na koje vode ovdašnje poveznice",
        "tooltip-feed-rss": "RSS feed za ovu stranicu",
        "tooltip-feed-atom": "Atom feed za ovu stranicu",
-       "tooltip-t-contributions": "Pogledaj popis doprinosa suradnika  {{GENDER:$1|this user}}",
+       "tooltip-t-contributions": "Pogledaj popis doprinosa {{GENDER:$1|ovog suradnika|ove suradnice}}",
        "tooltip-t-emailuser": "Pošalji suradniku e-mail",
        "tooltip-t-info": "Više informacija o ovoj stranici",
        "tooltip-t-upload": "Postavi slike i druge medije",
        "htmlform-cloner-create": "Dodaj još",
        "htmlform-cloner-delete": "Ukloni",
        "htmlform-cloner-required": "Potrebna je barem jedna vrijednost.",
-       "sqlite-has-fts": "$1 s podrškom pretraživanja cijelog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretraživanja cijelog 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|promijenio|promijenila}} vidljivost {{PLURAL:$5|zapisa u evidenciji|$5 zapisa u evidenciji}} na $3: $4",
index ba6c84e..55603f9 100644 (file)
@@ -72,7 +72,7 @@
        "tog-enotifminoredits": "Kapjak értesítést e-mailben a lapok és fájlok apró változtatásairól",
        "tog-enotifrevealaddr": "Jelenjen meg az e-mail címem a figyelmeztető e-mailekben",
        "tog-shownumberswatching": "A lapot figyelő szerkesztők számának megjelenítése",
-       "tog-oldsig": "A jelenlegi aláírás:",
+       "tog-oldsig": "A jelenlegi aláírásod:",
        "tog-fancysig": "Az aláírás wikiszöveg (nem lesz automatikusan hivatkozásba rakva)",
        "tog-uselivepreview": "Élő előnézet használata",
        "tog-forceeditsummary": "Figyelmeztessen, ha nem adok meg szerkesztési összefoglalót",
        "newwindow": "(új ablakban nyílik meg)",
        "cancel": "Mégse",
        "moredotdotdot": "Tovább…",
-       "morenotlisted": "A lista nem teljes.",
+       "morenotlisted": "A lista hiányos lehet.",
        "mypage": "Lapom",
        "mytalk": "Vitalap",
        "anontalk": "Vitalap",
        "talk": "Vitalap",
        "views": "Nézetek",
        "toolbox": "Eszközök",
+       "tool-link-userrights": "{{GENDER:$1|Felhasználócsoportok}} módosítása",
+       "tool-link-emailuser": "E-mail küldése ennek a {{GENDER:$1|felhasználónak}}",
        "userpage": "Felhasználó lapjának megtekintése",
        "projectpage": "Projektlap megtekintése",
        "imagepage": "A fájl leírólapjának megtekintése",
        "missingarticle-rev": "(változat azonosítója: $1)",
        "missingarticle-diff": "(eltérés: $1, $2)",
        "readonly_lag": "Az adatbázis automatikusan le lett zárva, amíg a mellékkiszolgálók utolérik a főkiszolgálót.",
+       "nonwrite-api-promise-error": "A „Promise-Non-Write-API-Action” (ígéret nem író API-műveletre) HTTP-fejléc szerepelt a kérésben, de a kérés egy író API-modulra irányult.",
        "internalerror": "Belső hiba",
        "internalerror_info": "Belső hiba: $1",
        "internalerror-fatal-exception": "Végzetes kivétel: „$1”",
        "createacct-email-ph": "Add meg e-mail címed",
        "createacct-another-email-ph": "Add meg az emailcímet",
        "createaccountmail": "Átmeneti, véletlenszerű jelszó beállítása és kiküldése a megadott e-mail címre",
+       "createaccountmail-help": "A jelszó megismerése nélkül készíthető valaki másnak fiók.",
        "createacct-realname": "Igazi neved (nem kötelező)",
        "createaccountreason": "Indoklás:",
        "createacct-reason": "Indoklás",
        "createacct-reason-ph": "Miért hozol létre egy másik fiókot",
+       "createacct-reason-help": "A fióklétrehozási naplóban megjelenő üzenet",
        "createacct-submit": "Felhasználói fiók létrehozása",
        "createacct-another-submit": "Fiók létrehozása",
        "createacct-continue-submit": "Fiók létrehozásának folytatása",
        "botpasswords-label-grants-column": "Megadva",
        "botpasswords-bad-appid": "A(z) „$1” botnév érvénytelen.",
        "botpasswords-insert-failed": "A(z) „$1” botnév hozzáadása sikertelen. Nem lehet, hogy már hozzá lett adva?",
+       "botpasswords-update-failed": "A(z) „$1” nevű botfiók frissítése sikertelen. Lehet, hogy törölted?",
        "botpasswords-created-title": "Botjelszó létrehozva",
        "botpasswords-created-body": "\"$2\" felhasználó \"$1\" bot jelszava létrehozva.",
        "botpasswords-updated-title": "Botjelszó frissítve",
        "botpasswords-updated-body": "\"$2\" felhasználó \"$1\" bot jelszava módosítva.",
        "botpasswords-deleted-title": "Botjelszó törölve",
        "botpasswords-deleted-body": "\"$2\" felhasználó \"$1\" bot jelszava törölve.",
+       "botpasswords-newpassword": "A bejelentkezéshez használható új felhasználóneved <strong>$1</strong>, jelszavad <strong>$2</strong>. <em>Ezeket jegyezd fel a későbbiekre.</em> <br> (Régebbi botoknál, amik megkövetelhetik, hogy a bejelentkezési név megegyezzen magával a felhasználónévvel, használhatod a(z) <strong>$3</strong> felhasználónevet is <strong>$4</strong> jelszóval.)",
        "botpasswords-no-provider": "A BotPasswordsSessionProvider nem áll rendelkezésre.",
+       "botpasswords-restriction-failed": "A botjelszó-korlátozások megakadályozzák ezt a bejelentkezést.",
+       "botpasswords-invalid-name": "A megadott felhasználónév nem tartalmazza a botjelszó-elválasztót („$1”).",
+       "botpasswords-not-exist": "A(z) „$1” felhasználó nem rendelkezik „$2” nevű botjelszóval.",
        "resetpass_forbidden": "A jelszavak nem változtathatók meg",
        "resetpass_forbidden-reason": "A jelszavakat nem változtathatóak meg: $1",
        "resetpass-no-info": "Be kell jelentkezned, hogy közvetlenül elérd ezt a lapot.",
        "passwordreset-emailelement": "Felhasználónév: \n$1\n\nIdeiglenes jelszó: \n$2",
        "passwordreset-emailsentemail": "Ha ez az e-mail-cím van a fiókodhoz társítva, egy jelszó-visszaállító e-mailt küldünk.",
        "passwordreset-emailsentusername": "Ha ehhez a felhasználónévhez tartozik e-mail cím, akkor egy jelszó-visszaállító levelet küld a rendszer.",
-       "passwordreset-emailsent-capture2": "A jelszóvisszaállító {{PLURAL:$1|e-mailt|e-maileket}} elküldtük. A felhasználói {{PLURAL:$1|név és a jelszó|nevek és jelszavak listája}} lentebb látható.",
+       "passwordreset-emailsent-capture2": "A jelszóvisszaállító {{PLURAL:$1|e-mailt|e-maileket}} elküldtük. A {{PLURAL:$1|felhasználónév és a jelszó|felhasználónevek és jelszavak listája}} itt látható.",
+       "passwordreset-emailerror-capture2": "Az e-mail-küldés {{GENDER:$2|sikertelen}}: $1. A {{PLURAL:$3|felhasználónév és a jelszó|felhasználónevek és jelszavak listája}} itt látható.",
+       "passwordreset-nocaller": "A hívó megadása kötelező",
+       "passwordreset-nosuchcaller": "A hívó nem létezik: $1",
+       "passwordreset-ignored": "A jelszó-visszaállítás nem lett kezelve. Talán nincs konfigurálva szolgáltató?",
        "passwordreset-invalideamil": "Érvénytelen e-mail cím",
+       "passwordreset-nodata": "Se felhasználónevet, sem e-mail-címet nem adtál meg",
        "changeemail": "E-mail cím megváltoztatása vagy eltávolítása",
        "changeemail-header": "Töltsd ki ezt az űrlapot az e-mail-címed megváltoztatásához. Ha nem szeretnél semmilyen e-mail-címet kapcsolni a fiókodhoz, hagyd üresen az új e-mail-cím mezőjét az űrlap elküldésekor.",
        "changeemail-no-info": "A lap közvetlen eléréséhez be kell jelentkezned.",
        "accmailtext": "A(z) [[User talk:$1|$1]] fiókhoz egy véletlenszerűen generált jelszót küldünk a(z) $2 címre.\n\nAz új fiók jelszava a ''[[Special:ChangePassword|jelszó megváltoztatása]]'' lapon módosítható a bejelentkezés után.",
        "newarticle": "(Új)",
        "newarticletext": "Egy olyan lapra mutató hivatkozást követtél, ami még nem létezik.\nA lap létrehozásához csak gépeld be a szövegét a lenti szövegdobozba. Ha kész vagy, az „Előnézet megtekintése” gombbal ellenőrizheted, hogy úgy fog-e kinézni, ahogy szeretnéd, és a „Lap mentése” gombbal tudod elmenteni. (További információkat a [$1 súgólapon] találsz).\nHa tévedésből jutottál ide, kattints a böngésződ '''vissza''' vagy '''back''' gombjára.",
-       "anontalkpagetext": "----''Ez egy olyan anonim szerkesztő vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.\nEzért az IP-címét használjuk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa úgy látod, hogy az üzenetek, amiket ide kapsz, nem neked szólnak, [[Special:CreateAccount|regisztrálj]] vagy ha már regisztráltál, [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.''",
+       "anontalkpagetext": "----\n<em>Ez egy olyan anonim felhasználó vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.</em>\nEzért az IP-címét kell használnunk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa anonim felhasználó vagy, és úgy látod, hogy az üzenetek, amiket kapsz, nem neked szólnak, [[Special:CreateAccount|regisztrálj]] vagy [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.",
        "noarticletext": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz erre a címszóra]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplókat],\nvagy [{{fullurl:{{FULLPAGENAME}}|action=edit}} létrehozhatod a lapot].</span>",
        "noarticletext-nopermission": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz a lap címére]] más lapok tartalmában, vagy <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplófájlokat]</span>.",
        "missing-revision": "A(z) \"{{FULLPAGENAME}}\" nevű oldal #$1 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.",
        "invalid-content-data": "Érvénytelen tartalom adat",
        "content-not-allowed-here": "\"$1\" tartalom nem engedélyezett a [[$2]] oldalon",
        "editwarning-warning": "A lap elhagyásával az összes itt végzett változtatás elveszhet.\nHa be vagy jelentkezve letilthatod ezt a figyelmeztetést a beállításaid „{{int:prefs-editing}}” szakaszában.",
+       "editpage-invalidcontentmodel-title": "A tartalommodell nem támogatott",
+       "editpage-invalidcontentmodel-text": "A(z) „$1” tartalommodell nem támogatott.",
        "editpage-notsupportedcontentformat-title": "Nem támogatott tartalom formátum",
        "editpage-notsupportedcontentformat-text": "$2 tartalommodell nem támogatja $1 tartalomformátumot.",
        "content-model-wikitext": "wikiszöveg",
        "content-json-empty-object": "Üres objektum",
        "content-json-empty-array": "Üres tömb",
        "deprecated-self-close-category": "Érvénytelen önzáró HTML-címkéket használó lapok",
+       "deprecated-self-close-category-desc": "A lap érvénytelen önzáró HTML-címkéket használ (pl. <code>&lt;b/></code> vagy <code>&lt;span/></code>). Ezeknek a működése hamarosan meg fog változni a HTML5 szabvánnyal összhangban lévőre, ezért a wikiszövegben való használatuk elavult.",
        "duplicate-args-warning": "<strong>Figyelmeztetés:</strong> A(z) [[:$1]] lap dupla értékkel hívja meg a(z) [[:$2]] sablont („$3” paraméter). Csak az utolsó érték lesz felhasználva.",
        "duplicate-args-category": "Dupla paramétermegadást tartalmazó lapok",
        "duplicate-args-category-desc": "Az oldal olyan sablon hívásokat tartalmaz, amely ugyanazt a paramétert használja, például <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> or <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "undo-summary": "Visszavontam [[Special:Contributions/$2|$2]] ([[User talk:$2|vita]]) szerkesztését (oldid: $1)",
        "undo-summary-username-hidden": "A rejtett felhasználó által végzett $1 változat visszavonása",
        "cantcreateaccount-text": "Erről az IP-címről ('''$1''') nem lehet regisztrálni, mert [[User:$3|$3]] blokkolta az alábbi indokkal:\n\n:''$2''",
-       "cantcreateaccount-range-text": "A regisztrációt a(z) <strong>$1</strong> IP-címtartományban, amelybe a te IP-címed (<strong>$4</strong>) is tartozik, [[User:$3|$3]] blokkolta.",
+       "cantcreateaccount-range-text": "A regisztrációt a(z) <strong>$1</strong> IP-címtartományban, amelybe a te IP-címed (<strong>$4</strong>) is tartozik, [[User:$3|$3]] blokkolta.\n\n$3 a következő indoklást adta: <em>$2</em>",
        "viewpagelogs": "A lap a rendszernaplókban",
        "nohistory": "A lap nem rendelkezik laptörténettel.",
        "currentrev": "Aktuális változat",
        "revdelete-submit": "Alkalmazás a kiválasztott {{PLURAL:$1|változatra|változatokra}}",
        "revdelete-success": "A változat láthatósága sikeresen frissítve.",
        "revdelete-failure": "'''Nem sikerült frissíteni a változat láthatóságát:'''\n$1",
-       "logdelete-success": "'''Az esemény láthatóságának beállítása sikeresen elvégezve.'''",
+       "logdelete-success": "A naplóbejegyzés láthatósága beállítva.",
        "logdelete-failure": "'''Nem sikerült módosítani a naplóbejegyzés láthatóságát:'''\n$1",
        "revdel-restore": "Láthatóság megváltoztatása",
        "pagehist": "Laptörténet",
        "mergehistory-fail-bad-timestamp": "Érvénytelen időbélyeg.",
        "mergehistory-fail-invalid-source": "Érvénytelen forráslap.",
        "mergehistory-fail-invalid-dest": "Érvénytelen céllap.",
+       "mergehistory-fail-no-change": "A laptörténet-összefésülő nem fésült össze egy változatot sem. Ellenőrizd a lap és idő paramétereket.",
        "mergehistory-fail-permission": "Nincsen jogod a laptörténetek egyesítéséhez.",
        "mergehistory-fail-self-merge": "A forrás- és céllap megegyezik.",
+       "mergehistory-fail-timestamps-overlap": "A forrásváltozatok átfedésben vannak vagy későbbiek a célváltozatoknál.",
        "mergehistory-fail-toobig": "Nem lehetséges a laptörténetek egyesítése, mivel több mint $1 {{PLURAL:$1|változást}} kellene áthelyezni.",
        "mergehistory-no-source": "Nem létezik forráslap $1 néven.",
        "mergehistory-no-destination": "Nem létezik céllap $1 néven.",
        "grant-group-high-volume": "Nagy mennyiségű szerkesztés végrehajtása",
        "grant-group-customization": "Személyre szabás és beállítások",
        "grant-group-administration": "Adminisztratív műveletek végrehajtása",
+       "grant-group-private-information": "Privát adataid elérése",
        "grant-group-other": "egyéb műveletek",
        "grant-blockusers": "felhasználók blokkolása és blokk feloldása",
        "grant-createaccount": "fiókok létrehozása",
        "recentchanges-page-added-to-category-bundled": "[[:$1]] hozzáadva a kategóriához, [[Special:WhatLinksHere/$1|ez a lap be van illesztve más lapokra]]",
        "recentchanges-page-removed-from-category": "[[:$1]] eltávolítva a kategóriából",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] eltávolítva a kategóriából, [[Special:WhatLinksHere/$1|ez a lap be van illesztve más lapokra]]",
+       "autochange-username": "MediaWiki automatikus módosítása",
        "upload": "Fájl feltöltése",
        "uploadbtn": "Fájl feltöltése",
        "reuploaddesc": "Visszatérés a feltöltési űrlaphoz.",
        "file-thumbnail-no": "A fájlnév a(z) <strong>$1</strong> karakterlánccal kezdődik.\nÚgy tűnik, hogy ez egy kisméretű kép ''(bélyegkép)''.\nHa rendelkezel a teljesméretű képpel, akkor töltsd fel azt, egyébként kérjük, hogy változtasd meg a fájlnevet.",
        "fileexists-forbidden": "Már létezik egy ugyanilyen nevű fájl, és nem lehet felülírni.\nHa még mindig fel szeretnéd tölteni a fájlt, menj vissza, és adj meg egy új nevet. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Egy ugyanilyen nevű fájl már létezik a közös fájlmegosztóban; kérlek menj vissza és válassz egy másik nevet a fájlnak, ha még mindig fel akarod tölteni! [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "A feltöltendő fájl pontos másolata a(z) <strong>[[:$1]]</strong> jelenlegi változatának.",
+       "fileexists-duplicate-version": "A feltöltendő fájl pontos másolata a(z) <strong>[[:$1]]</strong> {{PLURAL:$2|egy régebbi változatának|régebbi változatainak}}.",
        "file-exists-duplicate": "Ez a következő {{PLURAL:$1|fájl|fájlok}} duplikátuma:",
        "file-deleted-duplicate": "Egy ehhez hasonló fájlt ([[:$1]]) korábban már töröltek. Ellenőrizd a fájl törlési naplóját, mielőtt újra feltöltenéd.",
        "file-deleted-duplicate-notitle": "Egy ugyanilyen fájlt korábban már töröltek, és címét eltávolították. Kérj meg valakit, aki meg tudja nézni a törölt fájlokat, hogy tekintse át a helyzetet, mielőtt újra feltöltenéd a fájlt.",
        "uploaded-script-svg": "A feltöltött SVG fájlodban szkriptelemet találtunk: \"$1\".",
        "uploaded-hostile-svg": "Nem biztonságos CSS kódot találtunk a feltöltött SVG fájlod stíluselemei között.",
        "uploaded-event-handler-on-svg": "Az alábbi eseménykezelő-attribútum beállítása nem megengedett az SVG fájlokban: <code>$1=$2</code>.",
+       "uploaded-href-attribute-svg": "href attribútumok SVG fájlokban csak http:// vagy https:// protokollal engedélyezettek, <code>&lt;$1 $2=\"$3\"&gt;</code> található.",
        "uploaded-href-unsafe-target-svg": "Nem biztonságos adatra mutató href-et találtunk a feltöltött SVG-fájlban: URI-cél <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "A feltöltött SVG fájlban \"animate\" taget találtam, ami az alábbi \"from\" attribútumával megváltoztathat egy href-et: <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-setting-event-handler-svg": "Eseménykezelő attribútumok beállítása blokkolva van, <code>&lt;$1 $2=\"$3\"&gt;</code> található a feltöltendő SVG fájlban.",
        "uploaded-setting-handler-svg": "Az SVG kódok, amelyek a \"handler\" attribútumot távolra/adatra/szkriptre állítják, le vannak tiltva. A feltöltött SVG fájlban a következőt találtam: <code>$1=\"$2\"</code>.",
        "uploaded-remote-url-svg": "Az SVG kódok, amelyek bármely stílus-attribútumot távoli URL-ra állítják, le vannak tiltva. A feltöltött SVG fájlban a következőt találtam: <code>$1=\"$2\"</code>.",
        "uploaded-image-filter-svg": "A feltöltött SVG fájl URL-t tartalmazó képfiltert tartalmaz: <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "upload-too-many-redirects": "Az URL túl sokszor volt átirányítva",
        "upload-http-error": "HTTP-hiba történt: $1",
        "upload-copy-upload-invalid-domain": "Másolás nem engedélyezett ebből a tartományból.",
+       "upload-foreign-cant-upload": "A wiki nincs konfigurálva fájlok feltöltésére a kért külső fájltárolóba.",
+       "upload-foreign-cant-load-config": "A külső fájltárolóba való fájlfeltöltés konfigurációjának beolvasása sikertelen.",
        "upload-dialog-disabled": "Fájl feltöltés ezzel a párbeszéddel tiltott ezen a wikin.",
        "upload-dialog-title": "Fájl feltöltése",
        "upload-dialog-button-cancel": "Mégse",
        "backend-fail-read": "Nem sikerült olvasni ebből a fájlból: $1.",
        "backend-fail-create": "Nem sikerült írni ebbe a fájlba: $1.",
        "backend-fail-maxsize": "Nem lehet írni ezt a fájlt: $1, mert a mérete nagyobb, mint $2 bájt.",
-       "backend-fail-readonly": "A(z) „$1” tárolórendszer jelenleg csak olvasható. Ennek oka a következő: „$2”",
+       "backend-fail-readonly": "A(z) „$1” tárolórendszer jelenleg csak olvasható. Ennek oka a következő: <em>$2</em>",
        "backend-fail-synced": "A(z) „$1” fájl inkonzisztens állapotban van a tárolórendszerek között",
        "backend-fail-connect": "Nem sikerült csatlakozni a(z) „$1” tárolórendszerhez.",
        "backend-fail-internal": "Ismeretlen hiba keletkezett a(z) „$1” tárolórendszerben.",
        "filerevert-submit": "Visszaállítás",
        "filerevert-success": "<span class=\"plainlinks\">A(z) '''[[Media:$1|$1]]''' fájl visszaállítása a(z) [$4 verzióra, $3, $2] sikerült.</span>",
        "filerevert-badversion": "A megadott időbélyegzésű fájlnak nincs helyi változata.",
+       "filerevert-identical": "A fájl jelenlegi verziója már azonos a kiválasztottal.",
        "filedelete": "$1 törlése",
        "filedelete-legend": "Fájl törlése",
        "filedelete-intro": "Törölni készülsz a(z) '''[[Media:$1|$1]]''' médiafájlt, a teljes fájltörténetével együtt.",
        "apisandbox-api-disabled": "API le van tiltva ezen az oldalon.",
        "apisandbox-intro": "Ezen az oldalon kísérletezhetsz a <strong>MediaWiki web service API</strong>-val.\nA használattal kapcsolatos további részletek az [[mw:API:Main page|API-dokumentációnál]] találhatók. Példa: [https://www.mediawiki.org/wiki/API#A_simple_example olvasd el a főoldal tartalomjegyzékét]. További példákért válassz egy tevékenységet!\n\nFigyelj rá, hogy bár ez csak egy „homokozó”, ettől még az általad végzett műveletek módosíthatják a wikit!",
        "apisandbox-fullscreen": "Panel kinyitása",
+       "apisandbox-fullscreen-tooltip": "A homokozópanel kinyitása a böngészőablak kitöltéséhez.",
        "apisandbox-unfullscreen": "Lap mutatása",
+       "apisandbox-unfullscreen-tooltip": "A homokozópanel méretének csökkentése a MediaWiki navigációs hivatkozásainak megjelenítéséhez.",
        "apisandbox-submit": "Kérés végrehajtása",
        "apisandbox-reset": "Törlés",
        "apisandbox-retry": "Újra",
        "apisandbox-dynamic-parameters-add-placeholder": "Paraméter neve",
        "apisandbox-dynamic-error-exists": "A(z) „$1” nevű paraméter már létezik.",
        "apisandbox-deprecated-parameters": "Elavult paraméterek",
+       "apisandbox-fetch-token": "A token automatikus kitöltése",
        "apisandbox-submit-invalid-fields-title": "Egyes mezők érvénytelenek",
        "apisandbox-submit-invalid-fields-message": "Javítsd ki a jelzett mezőket, és próbáld újra.",
        "apisandbox-results": "Eredmények",
        "apisandbox-sending-request": "API-kérés küldése…",
        "apisandbox-loading-results": "API-válaszok fogadása…",
+       "apisandbox-results-error": "Hiba történt az API-lekérdezés válaszának betöltése közben: $1.",
        "apisandbox-request-url-label": "Kérő URL:",
        "apisandbox-request-time": "Kérés hossza: $1 ms",
+       "apisandbox-results-fixtoken": "Token javítása és újrapróbálkozás",
+       "apisandbox-results-fixtoken-fail": "A(z) „$1” token lekérése sikertelen.",
+       "apisandbox-alert-page": "Hibás mezők vannak ezen a lapon.",
        "apisandbox-alert-field": "Ennek a mezőnek az értéke érvénytelen.",
        "booksources": "Könyvforrások",
        "booksources-search-legend": "Könyvforrások keresése",
        "trackingcategories-msg": "Nyomkövető kategória",
        "trackingcategories-name": "Üzenetnév",
        "trackingcategories-desc": "Kategóriába kerülés feltétele",
+       "restricted-displaytitle-ignored": "Lapok figyelmen kívül hagyott megjelenítendő lapcímmel",
+       "restricted-displaytitle-ignored-desc": "A lapon figyelmen kívül hagyott <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> van, mivel a megadott cím nem egyezik a lap tényleges címével.",
        "noindex-category-desc": "A lapot nem indexelik a keresőrobotok, mert tartalmazza a <code><nowiki>__NOINDEX__</nowiki></code> varázsszót, és egy olyan névtérben található, ahol ez engedélyezett.",
        "index-category-desc": "A lapot akkor is indexelik a keresőrobotok, ha egyébként nem tennék, mert tartalmazza az <code><nowiki>__INDEX__</nowiki></code> varázsszót, és egy olyan névtérben található, ahol ez engedélyezett.",
        "post-expand-template-inclusion-category-desc": "A lap mérete nagyobb a <code>$wgMaxArticleSize</code> változóban tárolt értéknél a sablonok kibontása után, így néhány sablon nem került kibontásra.",
        "watchnologin": "Nem vagy bejelentkezve",
        "addwatch": "Hozzáadás a figyelőlistához",
        "addedwatchtext": "A(z) „[[:$1]]” lapot és vitalapját hozzáadtam a [[Special:Watchlist|figyelőlistádhoz]].",
+       "addedwatchtext-talk": "A(z) „[[:$1]]” lapot és a hozzá tartozó tartalmi lapot hozzáadtam a [[Special:Watchlist|figyelőlistádhoz]].",
        "addedwatchtext-short": "Az oldal: \"$1\" hozzá lett adva a figyelőlistádhoz.",
        "removewatch": "Eltávolítás a figyelőlistáról",
        "removedwatchtext": "A(z) „[[:$1]]” lapot és vitalapját eltávolítottam a [[Special:Watchlist|figyelőlistáról]].",
+       "removedwatchtext-talk": "A(z) „[[:$1]]” lapot és a hozzá tartozó tartalmi lapot eltávolítottam a [[Special:Watchlist|figyelőlistáról]].",
        "removedwatchtext-short": "Az oldal: \"$1\" el lett távolítva a figyelőlistádról.",
        "watch": "Lap figyelése",
        "watchthispage": "Lap figyelése",
        "delete-toobig": "Ennek a lapnak a laptörténete több mint {{PLURAL:$1|egy|$1}} változatot őriz. A szervert kímélendő az ilyen lapok törlése nem engedélyezett.",
        "delete-warning-toobig": "Ennek a lapnak a laptörténete több mint {{PLURAL:$1|egy|$1}} változatot őriz. Törlése fennakadásokat okozhat a wiki adatbázis-műveleteiben; óvatosan járj el.",
        "deleteprotected": "Nem tudod törölni a lapot, mivel le van védve.",
-       "deleting-backlinks-warning": "'''Figyelem:'''  [[Special:WhatLinksHere/{{FULLPAGENAME}}|Más lapok]] hivatkoznak a törlendő oldalra.",
+       "deleting-backlinks-warning": "<strong>Figyelem:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Más lapok]] hivatkoznak a törlendő oldalra (vagy beillesztik azt).",
        "rollback": "Szerkesztések visszaállítása",
        "rollbacklink": "visszaállítás",
        "rollbacklinkcount": "$1 szerkesztés visszaállítása",
        "rollbacklinkcount-morethan": "több mint $1 szerkesztés visszaállítása",
        "rollbackfailed": "A visszaállítás nem sikerült",
+       "rollback-missingparam": "Kötelező paraméterek hiányoznak a kérésből.",
+       "rollback-missingrevision": "A lapváltozat adatainak betöltése sikertelen.",
        "cantrollback": "Nem lehet visszaállítani: az utolsó szerkesztést végző felhasználó az egyetlen, aki a lapot szerkesztette.",
        "alreadyrolled": "[[:$1]] utolsó, [[User:$2|$2]] ([[User talk:$2|vita]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) általi szerkesztését nem lehet visszavonni:\nidőközben valaki már visszavonta vagy szerkesztette a lapot.\n\nAz utolsó szerkesztést [[User:$3|$3]] ([[User talk:$3|vita]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) végezte.",
        "editcomment": "A szerkesztési összefoglaló <em>$1</em> volt.",
        "revertpage": "Visszaállítottam a lap korábbi változatát: [[Special:Contributions/$2|$2]]  ([[User talk:$2|vita]]) szerkesztéséről [[User:$1|$1]] szerkesztésére",
        "revertpage-nouser": "Visszaállítottam a lap korábbi változatát (szerkesztőnév eltávolítva) szerkesztéséről [[User:$1|$1]] szerkesztésére",
        "rollback-success": "$1 szerkesztéseit visszaállítottam $2 utolsó változatára.",
+       "rollback-success-notify": "$1 szerkesztései visszaállítva;\nhelyreállítva $2 utolsó változata. [$3 Változtatások megtekintése]",
        "sessionfailure-title": "Munkamenethiba",
        "sessionfailure": "Úgy látszik, hogy probléma van a bejelentkezési munkameneteddel;\nez a művelet a munkamenet eltérítése miatti óvatosságból megszakadt.\nKérjük, hogy nyomd meg a „vissza” gombot, és töltsd le újra az oldalt, ahonnan jöttél, majd próbáld újra.",
        "changecontentmodel": "A lap tartalommodelljének megváltoztatása",
        "changecontentmodel-success-text": "A(z) [[:$1]] lap tartalommodellje sikeresen megváltoztatva.",
        "changecontentmodel-cannot-convert": "A(z) [[:$1]] lap nem alakítható át $2 típusúvá.",
        "changecontentmodel-nodirectediting": "A(z) $1 tartalommodell nem támogatja a közvetlen szerkesztést",
+       "changecontentmodel-emptymodels-title": "Nincs elérhető tartalommodell",
+       "changecontentmodel-emptymodels-text": "A(z) [[:$1]] lapon lévő tartalom nem alakítható át semmilyen típusúvá.",
        "log-name-contentmodel": "Tartalommodell-változások naplója",
        "log-description-contentmodel": "Egy lap tartalommodelljéhez kapcsolódó események",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|létrehozta}} a(z) $3 lapot nem alapértelmezett „$5” tartalommodellel.",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|megváltoztatta}} a(z) $3 lap tartalommodeljét erről: „$4” erre: „$5”",
        "logentry-contentmodel-change-revertlink": "visszaállítás",
        "logentry-contentmodel-change-revert": "visszaállítás",
        "undeletehistorynoadmin": "Ezt a szócikket törölték. A törlés okát alább az összegzésben\nláthatod, az oldalt a törlés előtt szerkesztő felhasználók részleteivel együtt. Ezeknek\na törölt változatoknak a tényleges szövege csak az adminisztrátorok számára hozzáférhető.",
        "undelete-revision": "$1 $4, $5-kori törölt változata (szerző: $3).",
        "undeleterevision-missing": "Érvénytelen vagy hiányzó változat. Lehet, hogy rossz hivatkozásod van, ill. a\nváltozatot visszaállították vagy eltávolították az archívumból.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Egy|$1}} változat nem állítható vissza, mert a <code>rev_id</code>-{{PLURAL:$1|je|jük}} már használatban van.",
        "undelete-nodiff": "Nem található korábbi változat.",
        "undeletebtn": "Helyreállítás",
        "undeletelink": "megtekintés/helyreállítás",
        "undeletedrevisions": "$1 változat helyreállítva",
        "undeletedrevisions-files": "{{PLURAL:$1|egy|$1}} változat és {{PLURAL:$2|egy|$2}} fájl visszaállítva",
        "undeletedfiles": "{{PLURAL:$1|egy|$1}} fájl visszaállítva",
-       "cannotundelete": "Lap visszaállítása sikertelen: $1",
+       "cannotundelete": "Egy vagy több visszaállítás sikertelen:\n$1",
        "undeletedpage": "'''$1 helyreállítva'''\n\nLásd a [[Special:Log/delete|törlési naplót]] a legutóbbi törlések és helyreállítások listájához.",
        "undelete-header": "A legutoljára törölt lapokat lásd a [[Special:Log/delete|törlési naplóban]].",
        "undelete-search-title": "Törölt lapok keresése",
        "sp-contributions-newbies-sub": "Új szerkesztők lapjai",
        "sp-contributions-newbies-title": "Új szerkesztők közreműködései",
        "sp-contributions-blocklog": "Blokkolási napló",
-       "sp-contributions-suppresslog": "elrejtett szerkesztők közreműködései",
-       "sp-contributions-deleted": "törölt szerkesztések",
+       "sp-contributions-suppresslog": "elrejtett {{GENDER:$1|felhasználók}} közreműködései",
+       "sp-contributions-deleted": "törölt {{GENDER:$1|szerkesztések}}",
        "sp-contributions-uploads": "feltöltések",
        "sp-contributions-logs": "naplók",
        "sp-contributions-talk": "vitalap",
        "tooltip-feed-rss": "A lap tartalma RSS hírcsatorna formájában",
        "tooltip-feed-atom": "A lap tartalma Atom hírcsatorna formájában",
        "tooltip-t-contributions": "A {{GENDER:$1|felhasználó}} közreműködéseinek listája",
-       "tooltip-t-emailuser": "Írj levelet ennek a felhasználónak!",
+       "tooltip-t-emailuser": "Írj e-mailt ennek a {{GENDER:$1|felhasználónak}}",
        "tooltip-t-info": "További információk erről a lapról",
        "tooltip-t-upload": "Képek vagy egyéb fájlok feltöltése",
        "tooltip-t-specialpages": "Az összes speciális lap listája",
        "confirmemail_body_set": "Valaki, valószínűleg te, ezt az email címet adta meg\n„$2” nevű {{SITENAME}}-fiókjához a következő IP-címről: $1.\n\nHa meg szeretnéd erősíteni, hogy a fiók valóban hozzád tartozik, így aktiválva a(z) {{SITENAME}} e-mailes funkcióit, nyisd meg az alábbi linket a böngésződben:\n\n$3\n\nHa a fiók *nem* hozzád tartozik, kövesd az alábbi linket a\nmegerősítés visszavonásához:\n\n$5\n\nEz a megerősítő e-mail $4-ig érvényes.",
        "confirmemail_invalidated": "E-mail-cím megerősíthetősége visszavonva",
        "invalidateemail": "E-mail-cím megerősíthetőségének visszavonása",
+       "notificationemail_subject_changed": "Megváltozott az e-mail-címed a(z) {{SITENAME}} wikin",
        "scarytranscludedisabled": "[Wikiközi beillesztés le van tiltva]",
        "scarytranscludefailed": "[$1 sablon letöltése sikertelen]",
        "scarytranscludefailed-httpstatus": " [Nem sikerült betölteni a(z) $1 sablont: HTTP $2]",
        "scarytranscludetoolong": "[Az URL túl hosszú]",
        "deletedwhileediting": "'''Figyelmeztetés:''' A lapot a szerkesztés megkezdése után törölték!",
-       "confirmrecreate": "Miután elkezdted szerkeszteni, [[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot a következő indokkal:\n: ''$2''\nKérlek erősítsd meg, hogy tényleg újra akarod-e írni a lapot.",
-       "confirmrecreate-noreason": "[[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot, miután elkezdtél szerkeszteni. Erősítsd meg, hogy tényleg ismét létre szeretnéd hozni a lapot.",
+       "confirmrecreate": "Miután elkezdted szerkeszteni, [[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot a következő indokkal:\n: <em>$2</em>\nKérlek erősítsd meg, hogy tényleg újra létre akarod-e hozni a lapot.",
+       "confirmrecreate-noreason": "[[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot, miután elkezdted szerkeszteni. Erősítsd meg, hogy tényleg ismét létre szeretnéd hozni a lapot.",
        "recreate": "Újraírás",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Törlöd az oldal gyorsítótárban (cache) található változatát?",
        "version-libraries-license": "Licenc",
        "version-libraries-description": "Leírás",
        "version-libraries-authors": "Szerzők",
-       "redirect": "Átirányítás fájl, szerkesztő, oldal vagy oldalváltozat alapján",
+       "redirect": "Átirányítás fájl, szerkesztő, olda, oldalváltozat vagy naplóazonosító alapján",
        "redirect-summary": "Ez a speciális lap átirányít egy fájlra (megadott fájlnévvel), lapra (megadott lapváltozat- vagy lapazonosító számmal) vagy felhasználóra (felhasználó azonosítószáma alapján). Használat: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] vagy [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Mehet",
        "redirect-lookup": "Keresés:",
        "redirect-value": "Érték:",
-       "redirect-user": "Felhasználóazonosító",
+       "redirect-user": "Felhasználóazonosító",
        "redirect-page": "Lapazonosító",
-       "redirect-revision": "Oldal felülvizsgálata",
+       "redirect-revision": "Lapváltozat",
        "redirect-file": "Fájlnév",
+       "redirect-logid": "Naplóazonosító",
        "redirect-not-exists": "Érték nem található",
        "fileduplicatesearch": "Duplikátumok keresése",
        "fileduplicatesearch-summary": "Fájlok duplikátumainak keresése hash értékük alapján.",
index 2d8d4ef..f6fb4b1 100644 (file)
        "right-suppressrevision": "Menampilkan, menyembunyikan dan membatalkan penyembunyian revisi tertentu atas suatu halaman dari pengguna",
        "right-viewsuppressed": "Lihat revisi yang disembunyikan dari semua pengguna",
        "right-suppressionlog": "Melihat log privat",
-       "right-block": "Memblokir penyuntingan oleh pengguna lain",
+       "right-block": "Blokir pengguna lain dari penyuntingan",
        "right-blockemail": "Memblokir pengiriman surel oleh pengguna",
        "right-hideuser": "Memblokir nama pengguna dan menyembunyikannya dari publik",
        "right-ipblock-exempt": "Mengabaikan pemblokiran IP, pemblokiran otomatis, dan rentang pemblokiran",
        "action-undelete": "membatalkan penghapusan halaman ini",
        "action-suppressrevision": "meninjau dan mengembalikan revisi yang disembunyikan ini",
        "action-suppressionlog": "melihat log privat ini",
-       "action-block": "memblokir pengguna ini dari penyuntingan",
+       "action-block": "Blokir pengguna ini dari penyuntingan",
        "action-protect": "mengganti tingkat pelindungan halaman ini",
        "action-rollback": "mengembalikan dengan cepat suntingan-suntingan pengguna terakhir yang menyunting halaman tertentu",
        "action-import": "mengimpor halaman ini dari wiki lain",
        "htmlform-title-not-exists": "$1 tidak ada.",
        "htmlform-user-not-exists": "<strong>$1</strong> tidak ada.",
        "htmlform-user-not-valid": "<strong>$1</strong> bukan merupakan nama pengguna sah.",
-       "sqlite-has-fts": "$1 dengan dukungan pencarian teks lengkap",
-       "sqlite-no-fts": "$1 tanpa dukungan pencarian teks lengkap",
        "logentry-delete-delete": "$1 {{GENDER:$2|menghapus}} halaman $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|mengembalikan}} halaman $3",
        "logentry-delete-event": "$1 {{GENDER:$2|mengubah}} tampilan {{PLURAL:$5|$5 log peristiwa}} di $3: $4",
index 7d86d86..c4b08ba 100644 (file)
        "talk": "Discussione",
        "views": "Visite",
        "toolbox": "Strumenti",
+       "tool-link-emailuser": "Invia una email a questo {{GENDER:$1|utente}}",
        "userpage": "Visualizza la pagina utente",
        "projectpage": "Visualizza la pagina di servizio",
        "imagepage": "Visualizza la pagina del file",
index 5f451b9..892ba1b 100644 (file)
        "talk": "議論",
        "views": "表示",
        "toolbox": "ツール",
+       "tool-link-emailuser": "この{{GENDER:$1|利用者}}にメールを送信",
        "userpage": "利用者ページを表示",
        "projectpage": "プロジェクトのページを表示",
        "imagepage": "ファイルのページを表示",
        "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 922b555..db9434b 100644 (file)
        "talk": "토론",
        "views": "보기",
        "toolbox": "도구",
+       "tool-link-emailuser": "이 {{GENDER:$1|사용자}}에게 이메일 보내기",
        "userpage": "사용자 문서 보기",
        "projectpage": "프로젝트 문서 보기",
        "imagepage": "파일 문서 보기",
index 8644764..ce6a28e 100644 (file)
        "talk": "Aptarimas",
        "views": "Peržiūros",
        "toolbox": "Įrankiai",
+       "tool-link-userrights": "Keisti {{GENDER:$1|vartotojo|vartotojos}} grupes",
+       "tool-link-emailuser": "Siusti el. laišką {{GENDER:$1|šiam vartotojui|šiai vartotojai}}",
        "userpage": "Rodyti naudotojo puslapį",
        "projectpage": "Rodyti projekto puslapį",
        "imagepage": "Žiūrėti failo puslapį",
        "passwordreset-emailelement": "Naudotojo vardas: \n$1\n\nLaikinas slaptažodis: \n$2",
        "passwordreset-emailsentemail": "Jeigu šis el. pašto adresas yra susietas su jūsų paskyra, tada slaptažodžio atkūrimo laiškas bus išsiųstas.",
        "passwordreset-emailsentusername": "Jeigu buvo el. paštas susietas su šiuo naudotojo vardu, tai slaptažodžio atkūrimo el. laiškas bus išsiųstas.",
-       "passwordreset-emailsent-capture2": "Slaptažodžio keitimo {{PLURAL:$1|el. laiškas buvo išsiųstas|el. laiškai buvo išsiųsti}}. {{PLURAL:$1|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} žemiau.",
-       "passwordreset-emailerror-capture2": "El. laiško siuntimas {{GENDER:$2|vartotojui}} nepavyko: $1 {{PLURAL:$3|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} žemiau.",
+       "passwordreset-emailsent-capture2": "Slaptažodžio keitimo {{PLURAL:$1|el. laiškas buvo išsiųstas|el. laiškai buvo išsiųsti}}. {{PLURAL:$1|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} čia.",
+       "passwordreset-emailerror-capture2": "El. laiško siuntimas {{GENDER:$2|vartotojui}} nepavyko: $1 {{PLURAL:$3|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} čia.",
        "passwordreset-nocaller": "Skambinantysis turi būti nurodytas",
        "passwordreset-nosuchcaller": "Skambinantysis neegzistuoja: $1",
        "passwordreset-invalideamil": "Neteisingas el. pašto adresas",
        "blanknamespace": "(Pagrindinis)",
        "contributions": "{{GENDER:$1|Naudotojo}} indėlis",
        "contributions-title": "{{GENDER:$1|Naudotojo|Naudotojos}} $1 indėlis",
-       "mycontris": "Įnašai",
-       "anoncontribs": "Įnašai",
+       "mycontris": "Indėlis",
+       "anoncontribs": "Indėlis",
        "contribsub2": "Dėl {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "Naudotojo paskyra „$1“ neužregistruota.",
        "nocontribs": "Jokie keitimai neatitiko šių kriterijų.",
        "blocklink": "blokuoti",
        "unblocklink": "atblokuoti",
        "change-blocklink": "keisti blokavimo nustatymus",
-       "contribslink": "įnašai",
+       "contribslink": "indėlis",
        "emaillink": "siųsti el. laišką",
        "autoblocker": "Jūs buvote automatiškai užblokuotas, nes jūsų IP adresą neseniai naudojo „[[User:$1|$1]]“. Nurodyta naudotojo $1 blokavimo priežastis: „$2“.",
        "blocklogpage": "Blokavimų sąrašas",
        "version-libraries-description": "Aprašymas",
        "version-libraries-authors": "Autoriai",
        "redirect": "Nukreiptas iš failo, naudotojo, versijos arba žurnalo įrašo ID",
-       "redirect-summary": "Šis specialus puslapis peradresuoją į failą (nurodant failo pavadinimą), puslapį (nurodant versijos ID ar puslapio ID), naudotojo puslapį (nurodant skaitinį naudotojo ID), arba žurnalo įrašą (nurodant žurnalo įrašo ID).\nNaudojimas: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], arba[[{{#Special:Redirect}}/logid/186]].",
+       "redirect-summary": "Šis specialus puslapis peradresuoja į failą (nurodant failo pavadinimą), puslapį (nurodant versijos ID ar puslapio ID), naudotojo puslapį (nurodant skaitinį naudotojo ID), arba žurnalo įrašą (nurodant žurnalo įrašo ID).\nNaudojimas: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], arba[[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Eiti",
        "redirect-lookup": "Peržvalgos:",
        "redirect-value": "Vertė:",
index ee22017..8f29932 100644 (file)
        "right-createaccount": "नव प्रयोक्ता खातासभ बनाबी",
        "right-autocreateaccount": "बाहरी खातासँ स्वतः प्रवेश",
        "right-minoredit": "सम्पादन सभकेँ मामूली चिन्हित करी",
-       "right-move": "पनà¥\8dना à¤\98सà¤\95ाबी",
-       "right-move-subpages": "पà¥\83षà¥\8dठ à¤\89पपà¥\83षà¥\8dठसभ à¤¸à¤¹à¤¿à¤¤ à¤\98सà¤\95ाबी",
-       "right-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤\98सà¤\95ाबी",
-       "right-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤\98सà¤\95ाबी",
-       "right-movefile": "सञ्चिका सभ घसकाबी",
+       "right-move": "पनà¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-subpages": "पà¥\83षà¥\8dठ à¤\89पपà¥\83षà¥\8dठसभ à¤¸à¤¹à¤¿à¤¤ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-movefile": "सञ्चिकासभ स्थानान्तरित करी",
        "right-suppressredirect": "पृष्ठ घसकेबाकाल पुनर्निर्देश नै छोडी",
        "right-upload": "सञ्चिकासभ उपारोपित करी",
        "right-reupload": "वर्तमान सञ्चिकासभक पुनर्लेखन करी",
        "right-patrol": "दोसराक सम्पादनकेँ संचालित देखाउ",
        "right-autopatrol": "अपन सम्पादनकेँ स्वचालित रूपेँ संचालित देखाउ",
        "right-patrolmarks": "हालक परिवर्तनमे संचालन चेन्ह देखू",
-       "right-unwatchedpages": "बिना à¤¸à¤\82à¤\9aालित à¤ªà¤¨à¥\8dना à¤¸à¤­à¤\95 à¤¸à¥\82à¤\9aà¥\80à¤\95à¥\87à¤\81 à¤¦à¥\87à¤\96à¥\82",
+       "right-unwatchedpages": "à¤\8fहन à¤ªà¥\83षà¥\8dठसभà¤\95 à¤¸à¥\82à¤\9aà¥\80 à¤¦à¥\87à¤\96à¥\80 à¤\9cà¥\87 à¤\95à¥\87à¤\95रà¥\8b à¤§à¥\8dयानसà¥\82à¤\9aà¥\80मà¥\87 à¤¨à¥\88 à¤\85à¤\9bि",
        "right-mergehistory": "पन्नाक इतिहास सभकेँ मिज्झर करू",
        "right-userrights": "सभटा प्रयोक्ता अधिकारकेँ सम्पादित करू",
        "right-userrights-interwiki": "दोसर विकीपर प्रयोक्ताक प्रयोक्ता अधिकारक सम्पादन करी",
        "action-createaccount": "ई प्रयोक्ता खाता बनाबी",
        "action-history": "पन्नाक इतिहास मिज्झर करी",
        "action-minoredit": "ऐ सम्पादनके मामूली कही",
-       "action-move": "à¤\90 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤\98सà¤\95ाबी",
-       "action-move-subpages": "à¤\90 à¤ªà¤¨à¥\8dना à¤\86 à¤\8fà¤\95र à¤\89पपनà¥\8dनाà¤\95à¥\87 à¤\98सà¤\95ाबी",
-       "action-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤\98सà¤\95ाबी",
-       "action-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤\98सà¤\95ाबी",
-       "action-movefile": "à¤\88 à¤¸à¤\82à¤\9aिà¤\95ाà¤\95à¥\87à¤\81 à¤\98सà¤\95ाà¤\89",
+       "action-move": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-subpages": "à¤\88 à¤ªà¤¨à¥\8dना à¤\86 à¤\8fà¤\95र à¤\89पपनà¥\8dनाà¤\95à¥\87 à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-movefile": "à¤\88 à¤¸à¤\9eà¥\8dà¤\9aिà¤\95ा à¤¸à¥\8dथानानà¥\8dतरण à¤\95रà¥\80",
        "action-upload": "ई संचिकाकेँ उपारोपित करू",
        "action-reupload": "ई संचिकाक पुनर्लेखन करू",
        "action-reupload-shared": "ई संचिकाकेँ साझी बखारीमे नजरि नै दिअ",
        "statistics-users": "पञ्जीकृत [[Special:ListUsers|प्रयोक्ता]]",
        "statistics-users-active": "सक्रिय प्रयोक्ता",
        "statistics-users-active-desc": "प्रयोक्ता जे अन्तिम {{PLURAL:$1|दिन|$1 दिन}} मे कोनो काज केने छथि",
-       "pageswithprop-submit": "à¤\9cाà¤\8a",
+       "pageswithprop-submit": "à¤\9cाà¤\8f",
        "doubleredirects": "द्वितीयक लागएबला बदलेन",
        "doubleredirectstext": "ई पन्ना ओइ पन्ना सभक संकलन छी जे बदलेन करैए दोसर बदलेनबला पन्नासँ।\nप्रत्येक पाँती पहिल आ दोसर बदलेनक लागि रखने अछि आ संगे दोसर बदलेनक लक्ष्य सेहो, जे वास्तवमे \"वास्तव\" लक्ष्य पन्ना अछि, जकरापर पहिल बदलेनकेँ जेबाक चाही। \n <del>Crossed out</del> प्रविष्टिक हल भेटल अछि।",
        "double-redirect-fixed-move": "[[$1]] घसकाएल गेल।\nई आब [[$2]] दिस जा रहल अछि।",
        "newpages-username": "प्रयोक्तानाम:",
        "ancientpages": "सभसँ पुरान पन्नासभ",
        "move": "स्थानान्तरण",
-       "movethispage": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤\98सà¤\95ाबी",
+       "movethispage": "पà¥\83षà¥\8dठà¤\95 à¤¨à¤¾à¤® à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\95री",
        "unusedimagestext": "ई सभ संचिका अछि मुदा कोनो पन्नामे निवशित नै अछि।\nकृपा कऽ ई बुझू जे दोसर जालस्थल सभ सोझ सार्वत्रिक विभव संकेतबला कोनो संचिकासँ लागि बना सकैए, आ तँए सरिय प्रयोगक बादो अखनो एतए सूचित कएल जा सकैए।",
        "unusedcategoriestext": "ई संवर्ग पन्ना सभ अछि, ओना कोनो दोसर पन्ना वा संवर्ग ओकर प्रयोग करैत अछि।",
        "notargettitle": "बिन लक्ष्यक",
        "maximum-size": "अधिक आकार:",
        "pagesize": "(अष्टक)",
        "restriction-edit": "संपादन",
-       "restriction-move": "à¤\98सà¤\95ाà¤\89",
+       "restriction-move": "सà¥\8dथानानà¥\8dतरण",
        "restriction-create": "बनाउ",
        "restriction-upload": "उपारोपण",
        "restriction-level-sysop": "पूर्ण सुरक्षित",
        "restriction-level-autoconfirmed": "अर्ध-रक्षित",
        "restriction-level-all": "कोनो स्तर",
-       "undelete": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
-       "undeletepage": "दà¥\87à¤\96à¥\82 à¤\86 à¤«à¥\87रसà¤\81 à¤®à¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤\86नà¥\82",
+       "undelete": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
+       "undeletepage": "मà¥\87à¤\9fाà¤\8fल à¤\97à¥\87ल à¤ªà¥\83षà¥\8dठ à¤¦à¥\87à¤\96à¥\80 à¤\86 à¤ªà¥\81नरà¥\8dसà¥\8dथापित à¤\95रà¥\80",
        "undeletepagetitle": "''' ई मेटाएल संशोधन लेने अछि [[:$1|$1]]एकर''' ।",
-       "viewdeletedpage": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
+       "viewdeletedpage": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
        "undeletepagetext": "ई {{PLURAL:$1|page has been deleted but is|$1 pages have been deleted but are}} अखनो जोगाएल पेटारमे अछि आ फेरसँ आनल नै जा सकैए।\nई जोगाएल पेटार बीच-बीचमे साफ करबाक चाही।",
        "undelete-fieldset-title": "संशोधन सभकेँ घुराउ",
        "undeleteextrahelp": "'''''{{int:undeletebtn}}''''' केँ क्लिक करू पन्नाक पूर्ण इतिहास अनबा लेल, सभटा विकल्पबक्सासँ चेन्ह हटाउ।\n'''''{{int:undeletebtn}}''''' क्लिक करू छाँटल मौलिक आकारमे अनबा लेल, संशोधन सभकेँ अनबा लेल सम्बन्धित बक्सा सभमे चेन्ह लगाउ।",
        "cannotundelete": "फेरसँ नै आबि सकल:\n$",
        "undeletedpage": "'''$1 के पुनर्स्थापित करल गेल अछि'''\n\nलग पास में हटाओल गेल आ पुनर्स्थापित कएल गेल पन्ना सभके जानकारी के लेल [[Special:Log/delete|हटाओल गेल लग]] देखु।",
        "undelete-header": "हालक मेटाएल पन्ना के लेल [[Special:Log/delete|हटाएल लग]] देखू।",
-       "undelete-search-title": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dनाà¤\95à¥\87à¤\81 à¤¤à¤¾à¤\95à¥\82",
+       "undelete-search-title": "मà¥\87à¤\9fाà¤\8fल à¤\97à¥\87ल à¤ªà¥\83षà¥\8dठ à¤¤à¤¾à¤\95à¥\80",
        "undelete-search-box": "मेटाएल पन्ना सभकेँ ताकू",
        "undelete-search-prefix": "से शुरु भेल पन्ना देखाबू.",
        "undelete-search-submit": "ताकू",
        "lockfilenotwritable": "दत्तांशनिधि प्रतिबन्ध संचिका लिखबा योग्य नै अछि।\nदत्तांशनिधिकेँ प्रतिबन्धित वा अप्रतिबन्धित करबा लेल एकरा जाल वितरक द्वारा लिखबा योग्य हेबाक चाही।",
        "databasenotlocked": "दत्तांशनिधि प्रतिबन्धित नै अछि।",
        "lockedbyandtime": "(द्वारा {{GENDER:$1|$1}} केँ $2 बजे $3)",
-       "move-page": "$1हटाउ",
-       "move-page-legend": "पनà¥\8dना à¤\98सà¤\95ाà¤\89",
+       "move-page": "$1 स्थानान्तरित करी",
+       "move-page-legend": "पà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरण",
        "movepagetext": "नीचाँक फॉर्मक प्रयोग पन्नाक नाम बदलि देत, एकर सभटा इतिहासकेँ नव नामक अन्तर्गत राखि देत।\nपुरान शीर्षक नव पन्ना लेल एकटा घुरबैबला पन्ना बनि जाएत।\nअहाँ घुरबैबला पन्नाकेँ अद्यतन कऽ सकै छी जे मूल शीर्षकपर स्वचालित रूपेँ जाइत अछि।\nजौं अहाँ ई नै करबाक निर्णय करै छी, निश्चय करू तकबा लेल [[Special:DoubleRedirects|double]] वा\n[[Special:BrokenRedirects|broken redirects]]\nअहाँ ऐ लेल जिम्मीदार छी जे सम्बन्धित लिंक ओतै जाए जतए ओकरा जेबाक चाही।\n\nमोन राखू कि पन्ना '''नै''' घसकाउ जौं नव शीर्षकपर पहिनहियेसँ पन्ना अछि, आ तखने ई करू जखन ओ खाली हुअए वा ओ एकटा घुमबैबला पन्ना हुअए वा ओइ पन्नाक कोनो भूतकालक सम्पादन इतिहास नै हुअए।\nएकर माने भेल जे अहाँ कोनो पन्नाक नाम परिवर्तन कऽ पाछाँ लऽ जा सकै छी जतए एकर नाममे परिवर्तन कएल गेल रहए जौं अहाँसँ गलती भेल अछि, आ अहाँ ओइ पन्नाकेँ फेरसँ दोबारा नै लिख सकै छी।\n\n\n'''चेतौनी!'''\nई एकटा लोकप्रिय पन्नाक लेल एकटा भयंकर आ बिना आशाक कएल परिवर्तन भऽ सकैए।\nआगाँ बढ़ैसँ पहिने अहाँ ई सुनिश्चित करू जे अहाँ एकर परिणाम बुझै छी।",
        "movepagetext-noredirectfixer": "नीचाँक फॉर्मक प्रयोग पन्नाक नाम बदलि देत, एकर सभटा इतिहासकेँ नव नामक अन्तर्गत राखि देत।\nपुरान शीर्षक नव पन्ना लेल एकटा घुरबैबला पन्ना बनि जाएत।\nनिश्चय करू तकबा लेल [[Special:DoubleRedirects|double]] वा[[Special:BrokenRedirects|broken redirects]]।\nअहाँ ऐ लेल जिम्मीदार छी जे सम्बन्धित लिंक ओतै जाए जतए ओकरा जेबाक चाही।\n\nमोन राखू कि पन्ना '''नै''' घसकत जौं नव शीर्षकपर पहिनहियेसँ पन्ना अछि, आ तखने ई करू जखन ओ खाली हुअए वा ओ एकटा घुमबैबला पन्ना हुअए वा ओइ पन्नाक कोनो भूतकालक सम्पादन इतिहास नै हुअए।\nएकर माने भेल जे अहाँ कोनो पन्नाक नाम परिवर्तन कऽ पाछाँ लऽ जा सकै छी जतए एकर नाममे परिवर्तन कएल गेल रहए जौं अहाँसँ गलती भेल अछि, आ अहाँ ओइ पन्नाकेँ फेरसँ दोबारा नै लिख सकै छी।\n\n\n'''चेतौनी!'''\nई एकटा लोकप्रिय पन्नाक लेल एकटा भयंकर आ बिना आशाक कएल परिवर्तन भऽ सकैए।\nआगाँ बढ़ैसँ पहिने अहाँ ई सुनिश्चित करू जे अहाँ एकर परिणाम बुझै छी।",
        "movepagetalktext": "सम्बन्धित चौबटिया पन्ना स्वचालित रूपेँ घसकत एकर संग '''जौं:'''\n*एकटा खाली-नै चौबटिया पन्ना पहिनहियेसँ नव नामक संग अछि, वा\n*अहाँ नीचाँक बॉक्स टिक हटा दी।\n\nताइ परिस्थितिमे, अहाँकेँ अपनेसँ पन्नाकेँ, आवश्यकतानुसार, घसकाबऽ वा मिज्झर करऽ पड़त।",
        "cant-move-user-page": "अहाँकेँ प्रयोक्ता पन्ना सभकेँ घसकेबाक अधिकार नै अछि (उपपन्ना सभकेँ छोड़ि कऽ)।",
        "cant-move-to-user-page": "अहाँकेँ कोनो पन्नाकेँ प्रयोक्ता पन्ना लग घसकेबाक अधिकार नै अछि (प्रयोक्ता उपपन्ना लग छोड़ि कऽ)।",
        "newtitle": "नव शीर्षकपर:",
-       "move-watch": "à¤\9cड़ि à¤ªà¤¨à¥\8dना à¤\86 à¤\9bà¥\80प à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
-       "movepagebtn": "पनà¥\8dना à¤\98सà¤\95ाबी",
+       "move-watch": "लिà¤\99à¥\8dà¤\95 à¤ªà¤¨à¥\8dना à¤\86 à¤²à¤\95à¥\8dषित à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
+       "movepagebtn": "नाम à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\95री",
        "pagemovedsub": "घसकल",
        "movepage-moved": "'''\"$1\" घसकाएल गेल \"$2\"''' पर",
        "movepage-moved-redirect": "एकटा पुनर्निर्देशन बनाओल गेल छै.",
        "movepage-moved-noredirect": "पुनर्निर्देशन नहि बनाओल गेल छै.",
        "articleexists": "ओइ नामक एकटा पन्ना पहिनहियेसँ अछि, वा जे नाम अहाँ चयन केने छी से वांछित नै अछि। \nकृपा कऽ दोसर नामक चयन करू।",
        "cantmove-titleprotected": "नब शीर्षक बनाबै  सें रोकहि के कारण, अहां अहि ठाम पर कोनो आन पृष्ठक ठाम बदलि नहि सकब.",
-       "movetalk": "सम्बन्धित वार्ता पृष्ठ सेहो घसकाबी",
+       "movetalk": "समà¥\8dबनà¥\8dधित à¤µà¤¾à¤°à¥\8dता à¤ªà¥\83षà¥\8dठ à¤¸à¥\87हà¥\8b à¤\98à¥\81सà¤\95ाबà¥\80",
        "move-subpages": "उपपृष्ठ सेहो लेल जाऊ ($1 धरि)",
        "move-talk-subpages": "वार्ता पृष्ठक उपपृष्ठ लेने जाऊ ($1 धरि)",
        "movepage-page-exists": "पन्ना $1 पहिनहियेसँ अछि आ स्वचालित रूपेँ मेटाएल नै जा सकैए।",
        "imagetypemismatch": "नव संचिका विस्तारक अपन प्रकारसँ मेल नै खाइए।",
        "imageinvalidfilename": "लक्ष्यित संचिकाक नाम अवैध अछि",
        "fix-double-redirects": "मूल शीर्षक धरि जाहि बला सभटा पुनर्निर्देशनों के सेहो बदलु.",
-       "move-leave-redirect": "एकटा बदलेन के पांछा छोडि के जाऊ",
+       "move-leave-redirect": "एक पुनर्निर्देशन पाछा छोडी",
        "protectedpagemovewarning": "''' चेतौनी: ई पन्ना संरक्षित अछि से खाली संचालन अधिकारयुक्त प्रयोक्ता एकरा घुसका सकैत छथि।'''\nनव वृतलेख उल्लेख नीचाँ सन्दर्भ लेल देल जा रहल अछि:",
        "semiprotectedpagemovewarning": "'''नोट:''' ई पन्ना संरक्षित अछि से खाली पंजीकृत प्रयोक्ता एकरा घुसका सकैत छथि।\nनव वृतलेख उल्लेख नीचाँ सन्दर्भ लेल देल जा रहल अछि:",
        "move-over-sharedrepo": "[[:$1]] अछि एकटा साझी बखारीमे। कोनो संचिकाकेँ ऐ नामसँ अनलापर साझीबला एकटा संचिका मेटा जाएत।",
        "imgmultigoto": "$1 पृष्ठ पर जाए",
        "img-lang-default": "(डिफल्ट भाषा)",
        "img-lang-info": "ई चित्र को $1. $2 में ढालु",
-       "img-lang-go": "à¤\9cाà¤\8a",
+       "img-lang-go": "à¤\9cाà¤\8f",
        "ascending_abbrev": "asc",
        "descending_abbrev": "जानकारी",
        "table_pager_next": "अगला पृष्ठ",
        "table_pager_limit_label": "सामग्री प्रति पृष्ठ",
        "table_pager_limit_submit": "जाए",
        "table_pager_empty": "कोनो परिणाम नहि",
-       "autosumm-blank": "पà¥\83षà¥\8dठ à¤\95à¥\87 à¤\96ालà¥\80 à¤\95रल गेल",
+       "autosumm-blank": "पà¥\83षà¥\8dठ à¤\96ालà¥\80 à¤\95à¤\8fल गेल",
        "autosumm-replace": "\"$1\" सहित पाठ परिवर्तित भेल",
        "autoredircomment": "[[$1]] के अनुप्रेषित",
        "autosumm-new": "'$1' संग नब पृष्ठ बनाओल गेल",
        "version-libraries-version": "संस्करण",
        "redirect": "अनुप्रेषित करु फ़ाइल, प्रयोगकर्ता, वा संशोधन पहीचान के आधार में",
        "redirect-summary": "ई विशेष पन्ना फ़ाइलनाम प्रदान करै पे फ़ाइल नाम के, पन्न आइ॰दी अथवा अवतरण आइ॰दी दुनु पे पन्ना के,आर साथी सदस्य आइ॰दी दुनु पे सदस्य पन्ना के पुनर्प्रेषित करएत अछि । उदाहरण: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], या [[{{#Special:Redirect}}/user/101]]।",
-       "redirect-submit": "à¤\9cाà¤\8a",
+       "redirect-submit": "à¤\9cाà¤\8f",
        "redirect-lookup": "ताकू:",
        "redirect-value": "मूल्य:",
        "redirect-user": "प्रयोक्ता आई॰डी॰",
        "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 केँ",
-       "logentry-delete-revision": "$1 {{GENDER:$2|परिवर्तन कियल गैल}} एकर दृश्य{{PLURAL:$5| एकटा संशोधन|$5 संशोधन}}  पन्ना $3: $4 पर",
+       "logentry-delete-revision": "$1 द्वारा $3 पृष्ठक {{PLURAL:$5|एक अवतरण|$5 अवतरणसभ}}क दृश्यता {{GENDER:$2|परिवर्तित}}: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|changed}}  $3 पर वृत्तलेख दृश्य",
        "logentry-delete-revision-legacy": "$1 {{GENDER:$2|changed}}  $3 पर वृत्तलेख संशोधन",
        "logentry-suppress-delete": "$1 {{GENDER:$2|दबाएल}} page $3",
        "logentry-import-interwiki": "$1 {{GENDER:$2|आयात केल गेल}} $3 कोनो और विकि सँ",
        "logentry-merge-merge": "$1 {{GENDER:$2|विलय केल गेल}} $3 के $4 में (संशोधन $5 धरि)",
        "logentry-move-move": "$1द्वारा $3 पृष्ठ $4 पर {{GENDER:$2|स्थानान्तरित}} कएलक",
-       "logentry-move-move-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआकेँ बिना छोड़ने",
-       "logentry-move-move_redir": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतिरिक्त",
-       "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतितिक्त घुमौआकेँ बिना छोड़ने",
+       "logentry-move-move-noredirect": "$1 द्वारा $3 पर पुनर्निर्देशन नै छोडि ओकरा $4 पर {{GENDER:$2|स्थानान्तरित}} केलक",
+       "logentry-move-move_redir": "$1 द्वारा $4 सँ पुनर्निर्देशन हटाए $3 क ओहिपर {{GENDER:$2|स्थानान्तरित}} केलक",
+       "logentry-move-move_redir-noredirect": "$1 द्वारा $4 सँ पुनार्निर्देश हटाए $3 पर पुनर्निर्देश नै छोडि $3 के $4 पर {{GENDER:$2|स्थानान्तरित}} केलक",
        "logentry-patrol-patrol": "$1 {{GENDER:$2|चिन्हित}} संशोधन $4 $3 पन्नाक निरीक्षित",
        "logentry-patrol-patrol-auto": "$1 स्वतः {{GENDER:$2|चिन्हित}} संशोधन $4 $3 पन्नाक निरीक्षित",
        "logentry-newusers-newusers": "$1 {{GENDER:$2|बनाएल}} एकटा प्रयोक्ता खाता",
index 71e1b0c..199eb7c 100644 (file)
        "talk": "Разговор",
        "views": "Посети",
        "toolbox": "Алатки",
+       "tool-link-userrights": "Смени ги {{GENDER:$1|корисничките}} групи",
+       "tool-link-emailuser": "Испрати е-пошта на {{GENDER:$1|корисников}}",
        "userpage": "Преглед на корисничката страница",
        "projectpage": "Преглед на проектната страница",
        "imagepage": "Преглед на страницата на податотеката",
index 45ba999..dc3b4ff 100644 (file)
@@ -49,7 +49,8 @@
                        "Matma Rex",
                        "SuperPotato",
                        "Nemo bis",
-                       "Telaneo"
+                       "Telaneo",
+                       "Jon Harald Søby"
                ]
        },
        "tog-underline": "Strek under lenker:",
index e9cfa37..f794610 100644 (file)
        "addedwatchtext": "\"[[:$1]]\" en de bijhorende overlegpagina zijn toegevoegd aan uw [[Special:Watchlist|volglijst]].",
        "addedwatchtext-short": "De pagina \"$1\" is aan uw volglijst toegevoegd.",
        "removewatch": "Verwijderen uit volglijst",
-       "removedwatchtext": "\"[[:$1]]\" en de overlegpagina zijn verwijderd van [[Special:Watchlist|uw volglijst]].",
+       "removedwatchtext": "\"[[:$1]]\" en de bijhorende overlegpagina zijn verwijderd van uw [[Special:Watchlist|volglijst]].",
+       "removedwatchtext-talk": "\"[[:$1]]\" en de bijhorende pagina zijn verwijderd van uw [[Special:Watchlist|volglijst]].",
        "removedwatchtext-short": "De pagina \"$1\" is van uw volglijst verwijderd.",
        "watch": "Volgen",
        "watchthispage": "Pagina volgen",
index ba5db3b..52740a6 100644 (file)
        "exif-xresolution": "ଭୂସମାନ୍ତର ରେଜଲୁସନ",
        "exif-yresolution": "ଭୁଲମ୍ବ ରେଜଲୁସନ",
        "exif-stripoffsets": "ଛବି ଡାଟା ଅବସ୍ଥାନ",
-       "exif-rowsperstrip": "ପà¬\9fି à¬ªà¬¿à¬\9bା à¬¸à­\8dତମà­\8dଭ à¬¸à¬\99à­\8dଖ୍ୟା",
+       "exif-rowsperstrip": "ପà¬\9fି à¬ªà¬¿à¬\9bା à¬¸à­\8dତମà­\8dଭ à¬¸à¬\82ଖ୍ୟା",
        "exif-stripbytecounts": "ସଙ୍କୁଚିତ ପଟି ପିଛା ବାଇଟ",
        "exif-jpeginterchangeformat": "Offset ରୁ JPEG SOI",
        "exif-jpeginterchangeformatlength": "JPEG ଡାଟାର ବାଇଟ",
        "exif-subsectimedigitized": "DateTimeDigitized ସାନ ସେକେଣ୍ଡ",
        "exif-exposuretime": "ଏକ୍ସପୋଜର କାଳ",
        "exif-exposuretime-format": "$1 ସେକେଣ୍ଡ ($2)",
-       "exif-fnumber": "F à¬¸à¬\99à­\8dà¬\96à­\8dà­\9fା",
+       "exif-fnumber": "F à¬¨à¬®à­\8dବର",
        "exif-exposureprogram": "ଏକ୍ସପୋଜର ପ୍ରୋଗ୍ରାମ",
        "exif-spectralsensitivity": "ବର୍ଣ୍ଣାଳି ସମ୍ବେଦନଶୀଳତା",
        "exif-isospeedratings": "ISO ବେଗ ସୂଚାଙ୍କ",
        "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": "$1, $3 ପୃଷ୍ଠାଟି {{GENDER:$2|ପୁନସ୍ଥାପନ କଲେ}}",
        "logentry-delete-event": "$1 {{PLURAL:$5|ଲଗ ଘଟଣାଟିଏ|$5 ଗୋଟି ଲଗ ଘଟଣା}}ର ଦେଖଣା $3 ପୃଷ୍ଠାରେ {{GENDER:$2|ବଦଳାଇଲେ}}: $4",
index 737143f..0953ab2 100644 (file)
        "talk": "Dyskusja",
        "views": "Widok",
        "toolbox": "Narzędzia",
+       "tool-link-userrights": "Zmiana grup {{GENDER:$1|użytkownika|użytkowniczki}}",
+       "tool-link-emailuser": "Wyślij e-mail do {{GENDER:$1|tego użytkownika|tej użytkowniczki}}",
        "userpage": "Pokaż stronę użytkownika",
        "projectpage": "Pokaż stronę projektu",
        "imagepage": "Pokaż stronę pliku",
        "grant-group-high-volume": "Czynności na dużą skalę",
        "grant-group-customization": "Dostosowywanie i preferencje",
        "grant-group-administration": "Czynności administracyjne",
+       "grant-group-private-information": "Dostęp do prywatnych danych o tobie",
        "grant-group-other": "Różne czynności",
        "grant-blockusers": "Blokowanie i odblokowywanie użytkowników",
        "grant-createaccount": "Tworzenie kont",
        "grant-highvolume": "Masowe edytowanie",
        "grant-oversight": "Ukrywanie użytkowników i wersji stron",
        "grant-patrol": "Patrolować zmiany w stronach",
+       "grant-privateinfo": "Dostęp do prywatnych danych",
        "grant-protect": "Zabezpieczanie i odbezpieczanie stron",
        "grant-rollback": "Wycofywanie zmian na stronach",
        "grant-sendemail": "Wysyłanie e‐maili do innych użytkowników",
        "authform-notoken": "Brakujący token",
        "authform-wrongtoken": "Nieprawidłowy token",
        "specialpage-securitylevel-not-allowed": "Niestety, nie możesz korzystać z tej strony, ponieważ twoja tożsamość nie może zostać zweryfikowana.",
+       "authpage-cannot-login": "Nie można uruchomić logowania.",
        "authpage-cannot-login-continue": "Nie można kontynuować logowania. Sesja najprawdopodobniej wygasła.",
        "authpage-cannot-create": "Nie można rozpocząć tworzenie konta.",
        "authpage-cannot-create-continue": "Nie można kontynuować tworzenia konta. Twoja sesja najprawdopodobniej wygasła.",
index 4ec5cec..f852d1a 100644 (file)
        "talk": "Used as display name for the tab to all {{msg-mw|Talk}} pages. These pages accompany all content pages and can be used for discussing the content page. Example: [[Talk:Example]].\n\nSee also:\n* {{msg-mw|Talk}}\n* {{msg-mw|Accesskey-ca-talk}}\n* {{msg-mw|Tooltip-ca-talk}}\n{{Identical|Discussion}}",
        "views": "Subtitle for the list of available views, for the current page. In \"monobook\" skin the list of views are shown as tabs, so this sub-title is not shown. For an example, see [{{canonicalurl:Main_Page|useskin=simple}} Main Page using simple skin].\n\n'''Note:''' This is \"views\" as in \"appearances\"/\"representations\", '''not''' as in \"visits\"/\"accesses\".\n{{Identical|View}}",
        "toolbox": "The title of the toolbox below the search menu.\n{{Identical|Tool}}",
-       "tool-link-userrights": "Link to [[Special:UserRights]] (user rights management) in the sidebar toolbox.\n\nParameters:\n* $1 - Name of user who would have their rights changed",
+       "tool-link-userrights": "Link to [[Special:UserRights]] (user rights management) in the sidebar toolbox.\n\nParameters:\n* $1 - Name of user for the user group management (usable for GENDER)",
        "tool-link-emailuser": "Link to [[Special:EmailUser]] (email user tool) in the sidebar toolbox.\n\nParameters:\n* $1 - Name of user who would receive the email\n\nSee also:\n* {{msg-mw|Emailuser-title-target}}",
        "userpage": "Used in user talk pages as the text of the link to the user page, with the Cologne Blue skin.",
        "projectpage": "Used as link text in Talk page of project page with the Cologne Blue skin.",
index 8a7a974..c0db2ac 100644 (file)
@@ -58,7 +58,7 @@
        "tog-enotifminoredits": "Trimite-mi, de asemenea, un e-mail în caz de modificări minore asupra paginilor și fișierelor",
        "tog-enotifrevealaddr": "Afișează-mi adresa de e-mail în mesajele de notificare",
        "tog-shownumberswatching": "Arată numărul utilizatorilor care urmăresc",
-       "tog-oldsig": "Semnătură actuală:",
+       "tog-oldsig": "Semnătura actuală:",
        "tog-fancysig": "Tratează semnătura ca wikitext (fără o legătură automată)",
        "tog-uselivepreview": "Folosește previzualizarea în timp real",
        "tog-forceeditsummary": "Avertizează-mă când uit să descriu modificările",
        "newwindow": "(se deschide într-o fereastră nouă)",
        "cancel": "Revocare",
        "moredotdotdot": "Mai mult…",
-       "morenotlisted": "Această listă nu este completă.",
+       "morenotlisted": "Această listă ar putea fi incompletă.",
        "mypage": "Pagină",
        "mytalk": "Discuții",
        "anontalk": "Discuții",
        "talk": "Discuție",
        "views": "Vizualizări",
        "toolbox": "Unelte",
+       "tool-link-userrights": "Schimbă grupurile {{GENDER:$1|utilizatorului|utilizatoarei}}",
+       "tool-link-emailuser": "Trimiteți un mesaj {{GENDER:$1|acestui utilizator|acestei utilizatoare}}",
        "userpage": "Vizualizați pagina utilizatorului",
        "projectpage": "Vizualizați pagina proiectului",
        "imagepage": "Vizualizați pagina fișierului",
        "botpasswords-disabled": "Parolele de roboți sunt dezactivate.",
        "botpasswords-no-central-id": "Pentru a folosi parole pentru roboți, trebuie să fiți logat într-un cont centralizat.",
        "botpasswords-existing": "Parole de robot existente",
+       "botpasswords-createnew": "Creați o nouă parolă de bot",
+       "botpasswords-editexisting": "Editați o parolă de bot",
        "botpasswords-label-appid": "Numele robotului:",
        "botpasswords-label-create": "Creare",
        "botpasswords-label-update": "Actualizează",
        "grant-generic": "set de permisiuni „$1”",
        "grant-group-page-interaction": "Interacționează cu paginile",
        "grant-group-file-interaction": "Interacționează cu conținut media",
+       "grant-highvolume": "Volum mare de editare",
+       "grant-oversight": "Ascunde utilizatori și suprimă versiuni",
+       "grant-patrol": "Patrulează schimbările paginilor",
        "grant-basic": "Drepturi de bază",
        "newuserlogpage": "Jurnal utilizatori noi",
        "newuserlogpagetext": "Acesta este jurnalul creărilor conturilor de utilizator.",
        "uploadstash-badtoken": "Execuția acestei acțiuni nu a reușit, probabil deoarece informațiile dumneavoastră de identificare au expirat. Încercați din nou.",
        "uploadstash-errclear": "Golirea fișierelor nu a reușit.",
        "uploadstash-refresh": "Reîmprospătează lista de fișiere",
+       "uploadstash-thumbnail": "arată miniatura",
+       "uploadstash-exception": "Nu pot stoca încărcare în spațiul temporar ($1): \"$2\"",
        "invalid-chunk-offset": "Decalaj de segment nevalid",
        "img-auth-accessdenied": "Acces interzis",
        "img-auth-nopathinfo": "PATH_INFO lipsește.\nServerul dumneavoastră nu a fost setat pentru a trece aceste informații.\nS-ar putea să fie bazat pe CGI și să nu suporte img_auth.\nVedeți https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "delete-toobig": "Această pagină are un istoric al modificărilor important, cu mai mult de $1 {{PLURAL:$1|versiune|versiuni|de versiuni}}.\nȘtergerea unei astfel de pagini a fost restricționată pentru a preveni apariția unor erori în {{SITENAME}}.",
        "delete-warning-toobig": "Această pagină are un istoric al modificărilor mult prea mare, cu mai mult de $1 {{PLURAL:$1|versiune|versiuni|de versiuni}}.\nȘtergerea sa poate afecta baza de date a sitului {{SITENAME}};\nacționați cu precauție.",
        "deleteprotected": "Nu puteți șterge această pagină, deoarece este protejată.",
-       "deleting-backlinks-warning": "'''Atenție:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Alte pagini]] se leagă sau transclud pagina pe care doriți să o ștergeți.",
+       "deleting-backlinks-warning": "<strong>Atenție:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Alte pagini]] se leagă sau transclud pagina pe care doriți să o ștergeți.",
        "rollback": "Editări de revenire",
        "rollbacklink": "revenire",
        "rollbacklinkcount": "revenire asupra {{PLURAL:$1|unei modificări|a $1 modificări|a $1 de modificări}}",
        "log-action-filter-rights-rights": "Modificare manuală",
        "log-action-filter-rights-autopromote": "Schimbare automată",
        "log-action-filter-upload-upload": "Încărcare nouă",
-       "log-action-filter-upload-overwrite": "Reîncărcare"
+       "log-action-filter-upload-overwrite": "Reîncărcare",
+       "linkaccounts-submit": "Leagă conturile",
+       "unlinkaccounts": "Dezleagă conturile",
+       "unlinkaccounts-success": "Contul a fost dezlegat"
 }
index 7d136f6..87c43ed 100644 (file)
        "talk": "Обсуждение",
        "views": "Просмотры",
        "toolbox": "Инструменты",
+       "tool-link-userrights": "Изменить группы {{GENDER:$1|участника|участницы}}",
+       "tool-link-emailuser": "Написать письмо {{GENDER:$1|участнику|участнице}}",
        "userpage": "Просмотреть страницу участника",
        "projectpage": "Просмотреть страницу проекта",
        "imagepage": "Просмотреть страницу файла",
index 4dca72d..18b68d6 100644 (file)
@@ -15,7 +15,8 @@
                        "Macofe",
                        "Matma Rex",
                        "Мария Олесова",
-                       "Ай-Куо"
+                       "Ай-Куо",
+                       "Туллук"
                ]
        },
        "tog-underline": "Сигэлэри аннынан тардыы:",
@@ -42,7 +43,7 @@
        "tog-enotifminoredits": "Кыра да уларытыы киирдэҕинэ эл. почтанан биллэрээр",
        "tog-enotifrevealaddr": "Мин почтам аадырыһын биллэриилэргэ көрдөр",
        "tog-shownumberswatching": "Сирэйи кэтээн көрөр дьон ахсаанын көрдөр",
-       "tog-oldsig": "Билиҥҥи илии баттааһын:",
+       "tog-oldsig": "Билигин туттар илии баттааһынын",
        "tog-fancysig": "Бэйэ илии баттааһына (сигэтэ суох)",
        "tog-uselivepreview": "Хайдах буолуохтааҕын тутатына эрдэ көрүүнү туттуу",
        "tog-forceeditsummary": "Тугу уларыппытым туһунан суруйбатахпына сэрэт",
        "yourpasswordagain": "Киирии тылгын хатылаа:",
        "createacct-yourpasswordagain": "Киирии тылгын бигэргэт",
        "createacct-yourpasswordagain-ph": "Киирии тылгын хатылаа",
-       "remembermypassword": "Миигин бу көмпүүтэргэ сигээ ($1 {{PLURAL:$1|күн|күнтэн ордуга суох}})",
        "userlogin-remembermypassword": "Тиһиликтэн тахсыма",
        "userlogin-signwithsecure": "Бигэ холбонуу",
        "cannotloginnow-title": "Сип-билигин киирэр кыах суох",
        "tag-filter": "[[Special:Tags|Бэлиэлэр]] фильтрдара:",
        "tag-filter-submit": "Фильтр",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тиэк|Тиэктэр}}]]: $2)",
+       "tag-mw-contentmodelchange": "Иһинээҕи киэбин уларытыы сурунаала",
        "tags-title": "Бэлиэлэр (тиэктэр)",
        "tags-intro": "Бу сирэйгэ бырагыраамма уларытыылары бэлиэтиир анал бэлиэлэрин (тиэктэрин) тиһиктэрэ уонна ол бэлиэлэр суолталара көстөр.",
        "tags-tag": "Бэлиэ (тиэк) аата",
        "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 соппут",
        "logentry-delete-restore": "$3 сирэйи $1 сөргүппүт",
        "logentry-delete-event": "Сурунаал $5 суругун көстүүтүн манна $3 $1 уларыппыт: $4",
index eee92a6..12edaa7 100644 (file)
        "talk": "Pogovor",
        "views": "Pogled",
        "toolbox": "Orodja",
+       "tool-link-userrights": "Spremeni {{GENDER:$1|uporabnikove|uporabničine}} skupine",
+       "tool-link-emailuser": "Pošlji e-pošto {{GENDER:$1|uporabniku|uporabnici}}",
        "userpage": "Prikaži uporabnikovo stran",
        "projectpage": "Prikaži projektno stran",
        "imagepage": "Pokaži stran z datoteko",
        "passwordreset-emailelement": "Uporabniško ime: \n$1\n\nZačasno geslo: \n$2",
        "passwordreset-emailsentemail": "Če je e-poštni naslov povezan z vašim računom, vam bomo poslali e-pošto za postavitev gesla.",
        "passwordreset-emailsentusername": "Če obstaja e-poštni naslov, povezan s tem uporabniškim imenom, vam bomo poslali e-pošto za postavitev gesla.",
-       "passwordreset-emailsent-capture2": "Poslali smo {{PLURAL:$1|e-pošto|e-pošti|e-pošte}} za ponastavitev gesla. {{PLURAL:$1|Uporabniško ime in geslo sta navedena spodaj.|Seznam uporabniških imen in gesel je naveden spodaj.}}",
-       "passwordreset-emailerror-capture2": "Pošiljanje e-pošte {{GENDER:$2|uporabniku|uporabnici}} je spodletelo: $1 {{PLURAL:$3|Uporabniško ime in geslo sta navedena spodaj.|Seznam uporabniških imen in gesel je naveden spodaj.}}",
+       "passwordreset-emailsent-capture2": "Poslali smo {{PLURAL:$1|e-pošto|e-pošti|e-pošte}} za ponastavitev gesla. {{PLURAL:$1|Uporabniško ime in geslo sta navedena tukaj.|Seznam uporabniških imen in gesel je naveden tukaj.}}",
+       "passwordreset-emailerror-capture2": "Pošiljanje e-pošte {{GENDER:$2|uporabniku|uporabnici}} je spodletelo: $1 {{PLURAL:$3|Uporabniško ime in geslo sta navedena tukaj.|Seznam uporabniških imen in gesel je naveden tukaj.}}",
        "passwordreset-nocaller": "Podati morate klicatelja",
        "passwordreset-nosuchcaller": "Klicatelj ne obstaja: $1",
        "passwordreset-ignored": "Ponastavitve gesla nismo izvedli. Morda ni nastavljen noben ponudnik?",
index e392e0d..e2e04db 100644 (file)
        "talk": "Diskussion",
        "views": "Visningar",
        "toolbox": "Verktyg",
+       "tool-link-userrights": "Ändra {{GENDER:$1|användargrupper}}",
+       "tool-link-emailuser": "Skicka e-post till denna {{GENDER:$1|användare}}",
        "userpage": "Visa användarsida",
        "projectpage": "Visa projektsida",
        "imagepage": "Visa filsida",
        "passwordreset-emailelement": "Användarnamn: \n$1\n\nTillfälligt lösenord: \n$2",
        "passwordreset-emailsentemail": "Om denna e-postadress är associerad med ditt konto kommer en lösenordsåterställning skickas via e-post.",
        "passwordreset-emailsentusername": "Om det finns en e-postadress som associeras med detta användarnamn kommer en lösenordsåterställning skickas via e-post.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-postmeddelande|E-postmeddelanden}} för återställning av lösenord har skickats. {{PLURAL:$1|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} visas nedan.",
-       "passwordreset-emailerror-capture2": "Kunde inte skicka e-post till {{GENDER:$2|användaren}}: $1 {{PLURAL:$3|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} listas nedan.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-postmeddelande|E-postmeddelanden}} för återställning av lösenord har skickats. {{PLURAL:$1|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} visas här.",
+       "passwordreset-emailerror-capture2": "Kunde inte skicka e-post till {{GENDER:$2|användaren}}: $1 {{PLURAL:$3|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} listas här.",
        "passwordreset-nocaller": "En användare måste anges",
        "passwordreset-nosuchcaller": "Användare finns inte: $1",
        "passwordreset-ignored": "Lösenordsåterställningen hanterades inte. Kanske ingen leverantör har konfigurerats?",
index 1f7fa45..315a732 100644 (file)
        "recentchangeslinked-summary": "ಒಂಜಿ ನಿರ್ದಿಸ್ಟೊ ಪುಟೊರ್ದು (ಅತ್ತ್’ನ್ಡ ನಿರ್ದಿಸ್ಟೊ ವರ್ಗೊಗು ಸೇರ್ದಿನ ಪುಟೊಲೆರ್ದ್) ಸಂಪರ್ಕೊ ಉಪ್ಪುನ ಪುಟೊಲೆಡ್ ಇಂಚಿಪ ಮಲ್ತಿನಂಚಿನ ಬದಲಾವಣೆಲೆನ್ ತಿರ್ತ್ ಪಟ್ಟಿ ಮಲ್ಪೆರಾತ್ಂಡ್.\n[[Special:Watchlist|ಇರೆನ ವೀಕ್ಷಣೆ ಪಟ್ಟಿಡ್]] ಉಪ್ಪುನ ಪುಟೊಲು '''ದಪ್ಪ ಅಕ್ಷರೊಡು''' ಉಂಡು.",
        "recentchangeslinked-page": "ಪುಟೊತ ಪುದರ್:",
        "recentchangeslinked-to": "ಇಂದೆತ ಬದಲ್‍ಗ್ ಕೊರ್ತ್‍ನ ಪುಟೊಗು ಕೊಂಡಿ ಉಪ್ಪುನಂಚಿನ ಪುಟೊಲೆದ ಬದಲಾವಣೆಲೆನ್ ತೋಜಾವು",
-       "upload": "ಫೈಲ್ ಅಪ್ಲೋಡ್",
+       "upload": "ಫೈಲ್’ನ್ ಅಪ್ಲೋಡ್ ಮಲ್ಪುಲೆ",
        "uploadbtn": "ಫೈಲ್’ನ್ ಅಪ್ಲೋಡ್ ಮಲ್ಪುಲೆ",
        "uploadnologin": "ಲಾಗಿನ್ ಆತ್‘ಜ್ಜರ್",
        "uploadlogpage": "ಅಪ್ಲೋಡ್ ದಾಖಲೆ",
index f5d2beb..b708f01 100644 (file)
        "talk": "Обговорення",
        "views": "Перегляди",
        "toolbox": "Інструменти",
+       "tool-link-userrights": "Змінити групи {{GENDER:$1|користувачів}}",
+       "tool-link-emailuser": "Надіслати електронного листа {{GENDER:$1|цьому користувачеві|цій користувачці}}",
        "userpage": "Переглянути сторінку користувача",
        "projectpage": "Переглянути сторінку проекту",
        "imagepage": "Переглянути сторінку файлу",
index 791e6e4..1cdb51b 100644 (file)
                        "Macofe",
                        "Hindustanilanguage",
                        "امین اکبر",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "قیصرانی"
                ]
        },
        "tog-underline": "ربط کی خط کشیدگی:",
        "tog-hideminor": "حالیہ تبدیلیوں میں معمولی ترامیم چھپائیں",
-       "tog-hidepatrolled": "حالیہ تبدیلیوں میں گشتی ترامیم چھپائیں",
+       "tog-hidepatrolled": "حالیہ تبدیلیوں میں مراجعت شدہ ترامیم چھپائیں",
        "tog-newpageshidepatrolled": "جدید صفحات کی فہرست میں مراجعت شدہ صفحات چھپائیں",
        "tog-hidecategorization": "صفحات کی زمرہ بندی چھپائیں",
        "tog-extendwatchlist": "حالیہ ترین تبدیلیوں کی بجائے تمام تبدیلیاں دیکھنے کے لیے زیر نظر فہرست کو وسیع کریں",
        "tog-usenewrc": "حالیہ تبدیلیاں اور زیر نظر فہرست میں تبدیلیوں کو بلحاظ صفحہ گروہ بند کریں",
        "tog-numberheadings": "سرخیوں کو خودکار نمبر دیں",
        "tog-showtoolbar": "آلات ترمیم دکھائیں",
-       "tog-editondblclick": "دو کلک پر صفحات کی ترمیم کریں",
+       "tog-editondblclick": "دہرے کلک پر صفحات کی ترمیم کریں",
        "tog-editsectiononrightclick": "قطعہ کے عنوانات پر رائیٹ کلک کے ذریعے قطعہ کی ترمیم کاری فعال کریں",
        "tog-watchcreations": "میرے تخلیق کردہ صفحات اور اپلوڈ کردہ فائلوں کو میری زیر نظر فہرست میں شامل کریں",
        "tog-watchdefault": "میرے ترمیم شدہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
        "hidden-categories": "{{PLURAL:$1|پوشیدہ زمرہ|پوشیدہ زمرہ جات}}",
        "hidden-category-category": "پوشیدہ زمرہ جات",
        "category-subcat-count": "{{PLURAL:$2|اِس زمرہ میں محض درج ذیل ذیلی زمرہ موجود ہے.|اِس زمرہ میں کل $2 میں سے درج ذیل {{PLURAL:$1|ذیلی زمرہ|$1 ذیلی زمرہ جات}} موجود ہیں۔}}",
-       "category-subcat-count-limited": "اِس زمرہ میں درج ذیل {{PLURAL:$1|ذیلی زمرہ ہے|$1 ذیلی زمرہ جات ہیں}}.",
+       "category-subcat-count-limited": "اِس زمرہ میں درج ذیل {{PLURAL:$1|ذیلی زمرہ ہے|$1 ذیلی زمرہ جات ہیں}}۔",
        "category-article-count": "{{PLURAL:$2|اس زمرہ میں محض درج ذیل صفحہ موجود ہے۔|اس زمرہ کے کل $2 صفحات میں سے $1 {{PLURAL:$1|صفحہ|صفحات}} درج ذیل {{PLURAL:$1|ہے|ہیں}}}}۔",
        "category-article-count-limited": "درج ذیل {{PLURAL:$1|صفحہ|$1 صفحات}} اس زمرہ میں شامل {{PLURAL:$1|ہے|ہیں}}۔",
        "category-file-count": "{{PLURAL:$2|اس زمرہ میں صرف درج ذیل فائل موجود ہے۔|اس زمرہ کی کل $2 فائلوں میں سے $1 {{PLURAL:$1|فائل|فائلیں}} درج ذیل {{PLURAL:$1|ہے|ہیں}}}}۔",
        "listingcontinuesabbrev": "جاری۔",
        "index-category": "فہرست شدہ صفحات",
        "noindex-category": "غیر فہرست شدہ صفحات",
-       "broken-file-category": "صفحات مع شکستہ فائل روابط",
+       "broken-file-category": "فائل کے شکستہ روابط کے حامل صفحات",
        "categoryviewer-pagedlinks": "($1) ($2)",
        "about": "تعارف",
        "article": "صفحہ مواد",
        "qbpageoptions": "یہ صفحہ",
        "qbmyoptions": "میرے صفحات",
        "faq": "عام طور پر پوچھے جانے والے سوالات",
-       "faqpage": "Project:معلوماتِ عامہ",
-       "actions": "ایکشنز",
-       "namespaces": "جائے نام",
+       "faqpage": "Project:عمومی سوالات",
+       "actions": "اقدامات",
+       "namespaces": "نام فضا",
        "variants": "متغیرات",
        "navigation-heading": "قائمہ رہنمائی",
-       "errorpagetitle": "خطاء",
-       "returnto": "واپس $1۔",
+       "errorpagetitle": "نقص",
+       "returnto": "واپس $1 پر جائیں",
        "tagline": "{{SITENAME}} سے",
        "help": "معاونت",
        "search": "تلاش",
        "search-ignored-headings": " #<!-- اس سطر کو ہو بہو اپنی حالت پر چھوڑ دیں --> <pre>\n# سرخیاں جو تلاش کے دوران میں نظر انداز کر دی جائیں گی۔\n# سرخی پر مشتمل صفحہ کی فہرست سازی مکمل ہوتے ہی تبدیلیاں نافذ ہو جائیں گی۔\n# ایک خالی ترمیم کر کے آپ صفحہ کی دوبارہ فہرست سازی کر سکتے ہیں۔\n# صیغہ حسب ذیل ہے:\n# * ہر چیز جو \"#\" علامت کے بعد آخری سطر تک ہو اسے تبصرہ سمجھا جائے گا۔\n#* ہر وہ سطر جو خالی نہ ہو عنوان ہوگا اور اسے نظر انداز کر دیا جائے گا (نیز جس طرح درج ہے اسی طرح استعمال کیا جائے گا)۔\nحوالہ جات\nبیرونی روابط\nمزید دیکھیے\n #<!-- اس سطر کو ہو بہو اپنی حالت پر چھوڑ دیں --> <pre>",
        "searchbutton": "تلاش",
-       "go": "چلو",
-       "searcharticle": "چلو",
-       "history": "تارÛ\8cØ®Ú\86Û\81 Ø¡ صفحہ",
+       "go": "چلیں",
+       "searcharticle": "چلیں",
+       "history": "تارÛ\8cØ®Ú\86Û\82 صفحہ",
        "history_short": "تاریخچہ",
-       "updatedmarker": "میری آخری آمد تک جدید",
+       "updatedmarker": "میری آخری آمد کے بعد تجدید شدہ",
        "printableversion": "قابل طبع نسخہ",
        "permalink": "مستقل ربط",
        "print": "طباعت",
-       "view": "منظر",
+       "view": "مطالعہ",
        "view-foreign": "$1 پر دیکھیں",
        "edit": "ترمیم",
-       "edit-local": "ترمیم مقامی وضاحت",
+       "edit-local": "مقامی وضاحت کی ترمیم",
        "create": "تخلیق",
-       "create-local": "ادخال مقامی وضاحت",
+       "create-local": "مقامی وضاحت کا اندراج",
        "editthispage": "اس صفحہ میں ترمیم کریں",
-       "create-this-page": "صÙ\81Ø­Û\81 Û\81ٰذا ØªØ®Ù\84Û\8cÙ\82 Ú©Û\8cجئÛ\92",
+       "create-this-page": "اس ØµÙ\81Ø­Û\81 Ú©Ù\88 ØªØ®Ù\84Û\8cÙ\82 Ú©Ø±Û\8cÚº",
        "delete": "حذف",
        "deletethispage": "یہ صفحہ حذف کریں",
        "undeletethispage": "یہ صفحہ بحال کریں",
        "undelete_short": "بحال {{PLURAL:$1|ایک ترمیم|$1 ترامیم}}",
-       "viewdeleted_short": "{{PLURAL:$1|ایک ترمیم حذف ہوچکی|$1 ترامیم حذف ہوچکیں}}",
+       "viewdeleted_short": "{{PLURAL:$1|ایک ترمیم حذف ہو چکی|$1 ترامیم حذف ہو چکیں}} دیکھیں",
        "protect": "محفوظ",
-       "protect_change": "تبدیل کرو",
-       "protectthispage": "اس صفحےکومحفوظ کریں",
+       "protect_change": "تبدیل کریں",
+       "protectthispage": "اس صفحے کو محفوظ کریں",
        "unprotect": "حفاظت میں تبدیلی",
-       "unprotectthispage": "اÙ\90سÛ\92 ØµÙ\81Ø­Û\92 Ú©Û\8c ØªØ­Ù\81ظ ØªØ¨Ø¯Û\8cÙ\84 Ú©Ø±یں",
+       "unprotectthispage": "اÙ\90سÛ\92 ØµÙ\81Ø­Û\92 Ú©Û\8c Ø­Ù\81اظت Ø¨Ø¯Ù\84یں",
        "newpage": "نیا صفحہ",
        "talkpage": "اس صفحہ پر تبادلۂ خیال کریں",
        "talkpagelinktext": "تبادلۂ خیال",
        "specialpage": "خصوصی صفحہ",
-       "personaltools": "ذاتÛ\8c Ø§Ù\88زار",
-       "articlepage": "Ù\85Ù\86درجاتÛ\8c ØµÙ\81Ø­Û\81 Ø¯Û\8cÚ©Ú¾Û\8cÛ\93",
+       "personaltools": "ذاتÛ\8c Ø¢Ù\84ات",
+       "articlepage": "Ù\85Ù\86درجاتÛ\8c ØµÙ\81Ø­Û\81 Ø¯Û\8cÚ©Ú¾Û\8cÛ\92",
        "talk": "تبادلہٴ خیال",
-       "views": "خیالات",
+       "views": "مناظر",
        "toolbox": "آلات",
-       "userpage": "صفحۂ صارف دیکھئے",
-       "projectpage": "صفحۂ منصوبہ دیکھئے",
-       "imagepage": "صفحۂ مسل دیکھئے",
-       "mediawikipage": "صفحۂ پیغام دیکھئے",
-       "templatepage": "صفحۂ سانچہ دیکھئے",
-       "viewhelppage": "صفحۂ معاونت دیکھیے",
-       "categorypage": "زمرہ‌جاتی صفحہ دیکھئے",
-       "viewtalkpage": "تبادلۂ خیال دیکھئے",
+       "tool-link-userrights": "{{GENDER:$1|صارف}} کے گروہوں میں تبدیلی کریں",
+       "tool-link-emailuser": "اس {{GENDER:$1|صارف}} کو برقی خط لکھیں",
+       "userpage": "صارف کا صفحہ دیکھیے",
+       "projectpage": "منصوبہ کا صفحہ دیکھیے",
+       "imagepage": "فائل کا صفحہ دیکھیے",
+       "mediawikipage": "پیغام کا صفحہ دیکھیے",
+       "templatepage": "سانچہ کا صفحہ دیکھیے",
+       "viewhelppage": "معاونت کا صفحہ دیکھیے",
+       "categorypage": "زمرہ‌ جاتی صفحہ دیکھیے",
+       "viewtalkpage": "تبادلۂ خیال دیکھیں",
        "otherlanguages": "دیگر زبانوں میں",
-       "redirectedfrom": "($1 سے پلٹایا گیا)",
+       "redirectedfrom": "($1 سے رجوع مکرر)",
        "redirectpagesub": "لوٹایا گیا صفحہ",
-       "redirectto": "لوٹایا گیا صفحہ:",
+       "redirectto": "رجوعِ مکرر از:",
        "lastmodifiedat": "اس صفحہ میں آخری بار مورخہ $1ء کو $2 بجے ترمیم کی گئی۔",
        "viewcount": "اِس صفحہ تک {{PLURAL:$1|ایک‌بار|$1 مرتبہ}} رسائی کی گئی",
        "protectedpage": "محفوظ شدہ صفحہ",
        "copyrightpage": "{{ns:project}}:حقوق تصانیف",
        "currentevents": "حالیہ واقعات",
        "currentevents-url": "Project:حالیہ واقعات",
-       "disclaimers": "اعÙ\84اÙ\86ات",
+       "disclaimers": "اظÛ\81ار Ù\84ا ØªØ¹Ù\84Ù\82Û\8c",
        "disclaimerpage": "Project:عام اعلان",
        "edithelp": "معاونت براۓ ترمیم",
        "helppage-top-gethelp": "مدد",
        "portal-url": "Project:دیوان عام",
        "privacy": "اخفائے راز کے اصول",
        "privacypage": "Project:اصولِ اخفائے راز",
-       "badaccess": "خطائے اجازت",
+       "badaccess": "نقص اجازت",
        "badaccess-group0": "آپ متمنی عمل کا اجراء کرنے کے مُجاز نہیں۔",
        "badaccess-groups": "آپ کا درخواست‌کردہ عمل {{PLURAL:$2|گروہ|گروہوں میں سے ایک}}: $1 کے صارفین تک محدود ہے.",
        "versionrequired": "میڈیا ویکی کا $1 نسخہ لازمی چاہئیے.",
        "versionrequiredtext": "اِس صفحہ کو استعمال کرنے کیلئے میڈیاویکی کا $1 نسخہ چاہئیے.\n\n\nدیکھئے [[خاص:نسخہ|صفحۂ نسخہ]]",
        "ok": "ٹھیک ہے",
        "pagetitle-view-mainpage": "{{SITENAME}}",
-       "retrievedfrom": "‘‘$1’’ مستعادہ منجانب",
+       "backlinksubtitle": "→ $1",
+       "retrievedfrom": "اخذ کردہ از «$1»",
        "youhavenewmessages": "آپکے لیۓ ایک $1 ہے۔ ($2)",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|آپ کے لیے}} {{PLURAL:$3|کسی دوسرے صارف|$3 صارفین}} کی جانب سے $1 ($2)۔",
        "youhavenewmessagesmanyusers": "آپ کے لیے متعدد صارفین کی جانب سے $1 ($2)۔",
-       "newmessageslinkplural": "{{PLURAL:$1|نیا پیغام|999=نئے پیغاماتs}}",
+       "newmessageslinkplural": "{{PLURAL:$1|نیا پیغام|999=نئے پیغامات}}",
        "newmessagesdifflinkplural": "آخری {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "youhavenewmessagesmulti": "ء$1 پر آپ کیلئے نئے پیغامات ہیں",
+       "youhavenewmessagesmulti": "$1 پر آپ کے لیے نئے پیغامات ہیں",
        "editsection": "ترمیم",
        "editold": "ترمیم",
        "viewsourceold": "مآخذ دیکھئے",
-       "editlink": "تدÙ\88Û\8cÙ\86 Ú©Ø±Û\8cÚº",
-       "viewsourcelink": "Ù\85آخذ Ø¯Û\8cکھئÛ\92",
-       "editsectionhint": "تدÙ\88Û\8cÙ\86Ù\90 Ø­ØµÙ\91ہ: $1",
+       "editlink": "ترÙ\85Û\8cÙ\85",
+       "viewsourcelink": "Ù\85اخذ Ø¯Û\8cÚ©Ú¾Û\8cÚº",
+       "editsectionhint": "ترÙ\85Û\8cÙ\85 Ù\82طعہ: $1",
        "toc": "فہرست",
        "showtoc": "دکھائیں",
        "hidetoc": "چھپائیں",
        "sort-ascending": "ترتیب صعودی",
        "nstab-main": "صفحہ",
        "nstab-user": "صفحۂ صارف",
-       "nstab-media": "صÙ\81Ø­Û\82 Ù\88سÛ\8cØ·",
-       "nstab-special": "خاص صفحہ",
+       "nstab-media": "صÙ\81Ø­Û\82 Ù\85Û\8cÚ\88Û\8cا",
+       "nstab-special": "خصÙ\88صÛ\8c صفحہ",
        "nstab-project": "صفحۂ منصوبہ",
-       "nstab-image": "Ù\85سل",
+       "nstab-image": "Ù\81ائل",
        "nstab-mediawiki": "پیغام",
        "nstab-template": "سانچہ",
        "nstab-help": "معاونت",
        "nstab-category": "زمرہ",
        "mainpage-nstab": "صفحۂ اول",
-       "nosuchaction": "کوئی سا عمل نہیں",
+       "nosuchaction": "مطلوبہ اقدام موجود نہیں",
        "nosuchactiontext": "URL کی جانب سے مختص کیا گیا عمل درست نہیں.\nآپ نے شاید URL غلط لکھا، یا کسی غیر صحیح ربط کی پیروی کی ہے.\n{{اِس سے SITENAME کے زیرِ استعمال مصنع لطیف میں کھٹمل کی نشاندہی کا بھی اندیشہ ہے}}.",
        "nosuchspecialpage": "کوئی ایسا خاص صفحہ نہیں",
        "nospecialpagetext": "<strong>آپ نے ایک غیر موجود خصوصی صفحہ کی درخواست کی ہے۔</strong>\n\nدرست خاص صفحات کی ایک فہرست [[Special:SpecialPages|{{int:specialpages}}]] پر دیکھی جاسکتی ہے۔",
-       "error": "خطاء",
-       "databaseerror": "خطائے ڈیٹابیس",
+       "error": "نقص",
+       "databaseerror": "ڈیٹابیس کا نقص",
        "databaseerror-text": "ڈیٹا بیس کیوری میں خامی پیدا ہوگئی ہے.\nیہ سافٹ ویئر میں ایک مسئلے (بگ) کی نشاندہی کر سکتے ہیں.",
        "databaseerror-textcl": "ڈیٹا بیس کیوری میں خامی پیدا ہوگئی ہے.",
        "databaseerror-query": "کیوری: $1",
        "databaseerror-function": "فنکشن: $ 1",
-       "databaseerror-error": "خرابی: $ 1",
+       "databaseerror-error": "نقص: $1",
        "transaction-duration-limit-exceeded": "زیادہ تاخیر سے بچنے کے لیے اس اقدام کو منسوخ کر دیا گیا ہے کیونکہ مدت تحریر ($1) اپنی حد $2 سیکنڈ سے تجاوز کر چکی ہے۔\nاگر آپ ایک ہی وقت میں کئی چیزیں تبدیل کر رہے ہیں تو بہتر ہوگا کہ اس تبدیلی کو متعدد قسطوں میں انجام دیں۔",
-       "laggedslavemode": "انتباہ: ممکن ہے کہ صفحہ میں حالیہ بتاریخہ جات شامل نہ ہوں.\n\nWarning: Page may not contain recent updates.",
+       "laggedslavemode": "<strong>انتباہ:</strong> شاید اس صفحہ میں تازہ ترین معلومات موجود نہیں۔",
        "readonly": "ڈیٹابیس مقفل ہے",
        "enterlockreason": "قفل کیلئے کوئی وجہ درج کیجئے، بشمولِ تخمینہ کہ قفل کب کھولا جائے گا.",
        "readonlytext": "ڈیٹابیس  شاید معمول کی اصلاح کے لیے نئے اندراجات اور دوسری ترمیمات کیلئے مقفل ہے، جس کے بعد یہ عام حالت پر آجائے گا۔\nمنتظم، جس نے قفل لگایا، یہ تفصیل فراہم کی ہے: $1",
        "missing-article": "ڈیٹابیس نے کسی صفحے کا متن بنام \"$1\" $2  نہیں پایا جو اِسے پانا چاہئے تھا.\n\nیہ عموماً کسی صفحے کے تاریخی یا پرانے حذف شدہ ربط کی وجہ سے ہوسکتا ہے.\n\nاگر یہ وجہ نہیں، تو آپ نے مصنع‌لطیف میں کھٹمل پایا ہے.\nبرائے مہربانی، URL کی نشاندہی کرتے ہوئے کسی [[Special:ListUsers/sysop|منتظم]] کو اِس کا سندیس کیجئے.",
-       "missingarticle-rev": "(Ù\86ظرثاÙ\86Û\8c#: $1)",
+       "missingarticle-rev": "(Ù\86سخÛ\81#: $1)",
        "missingarticle-diff": "(فرق: $1، $2)",
        "readonly_lag": "ڈیٹابیس خودکار طور پر مقفل ہوچکا ہے تاکہ ماتحت ڈیٹابیسی معیلات کا درجہ آقا کا ہوجائے.",
        "nonwrite-api-promise-error": "ایچ ٹی ٹی پی سرنامہ 'Promise-Non-Write-API-Action' بھیجا گیا لیکن یہ ایک اے پی آئی نویس ماڈیول کو بھیجی گئی درخواست تھی۔",
        "unexpected": "غیرمتوقع قدر: \"$1\"=\"$2\"",
        "formerror": "خطا: ورقہ بھیجا نہ جاسکا.",
        "badarticleerror": "اس صفحہ پر یہ عمل انجام نہیں دیا جاسکتا۔",
-       "cannotdelete": "صÙ\81Ø­Û\81 Û\8cا Ù\85Ù\84Ù\81 $1 Ú©Ù\88 Ø­Ø°Ù\81 Ù\86Û\81Û\8cÚº Ú©Û\8cا Ø¬Ø§Ø³Ú©ØªØ§.\nÛ\81Ù\88سکتا Û\81Û\92 Ú©Û\81 Ø§Ø³Û\92 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ú©Ø³Û\8c Ù\86Û\92 Ø­Ø°Ù\81 Ú©Ø±Ø¯Û\8cا Û\81Ù\88.",
-       "cannotdelete-title": "صفحہ ھذف نہیں کیا جا سکتا \"$1\"",
+       "cannotdelete": "صÙ\81Ø­Û\81 Û\8cا Ù\81ائÙ\84 Â«$1» Ú©Ù\88 Ø­Ø°Ù\81 Ù\86Û\81Û\8cÚº Ú©Û\8cا Ø¬Ø§ Ø³Ú©Ø§Û\94\nÙ\85Ù\85Ú©Ù\86 Û\81Û\92 Ú©Ø³Û\8c Ù\86Û\92 Ø§Ø³Û\92 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ø­Ø°Ù\81 Ú©Ø± Ø¯Û\8cا Û\81Ù\88Û\94",
+       "cannotdelete-title": "صفحہ «$1» حذف نہیں کیا جا سکا",
        "delete-hook-aborted": "حذف شدگی روک دی گئی\nوضاحت نہیں کی گئی",
        "no-null-revision": "صفحہ \"$1\" کے لیے نیا خالی نسخہ نہیں بنایا جا سکتا",
        "badtitle": "خراب عنوان",
        "title-invalid-too-long": "درخواست شدہ عنوان بے حد طویل ہے۔ عنوان کی طوالت یونیکوڈ کے $1 {{PLURAL:$1|بائٹ}} سے کم ہونی چاہیے۔",
        "title-invalid-leading-colon": "درخواست شدہ عنوان کے شروع میں ایک نادرست رابطہ موجود ہے۔",
        "perfcached": "ذیلی ڈیٹا ابطن شدہ (cached) ہے اور اِس کے پُرانے ہونے کا امکان ہے. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
-       "perfcachedts": "ذیلی ڈیٹا ابطن شدہ ہے (cached) اور آخری بار اِس کی بتاریخیت $1 کو ہوئی. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
+       "perfcachedts": "ذیل میں درج معلومات کیشے شدہ ہے اور آخری بار اس کی تجدید $1 کو کی گئی تھی۔ کیشے میں زیادہ سے زیادہ {{PLURAL:$4|ایک نتیجہ دستیاب ہے|$4 دستیاب ہیں}}۔",
        "querypage-no-updates": "اِس صفحہ کیلئے بتاریخات فی الحال ناقابل بنائی گئی ہیں.\nیہاں کا ڈیٹا ابھی تازہ نہیں کیا جائے گا.",
        "viewsource": "مسودہ",
        "viewsource-title": "$1 کا مسودہ دیکھیں",
        "viewsourcetext": "آپ صرف مسودہ دیکھ سکتے ہیں اور اسکی نقل اتار سکتے ہیں۔",
        "viewyourtext": "آپ اس مواد کو دیکھ سکتے ہیں اور اٹھا (کاپی) سکتے ہیں <strong>آپ کی ترامیم</strong> اس صفحہ پر۔",
        "protectedinterface": "یہ صفحہ سافٹ ویئر کا انٹرفیس متن فراہم کرتا ہے اور غلط استعمال سے بچنے کے لیے اسے محفوظ رکھا گیا ہے۔\nتمام ویکیوں میں ترجمہ شامل کرنے یا اس میں تبدیلی کرنے کے لیے میڈیاویکی دار الترجمہ [https://translatewiki.net/ translatewiki.net]کو استعمال کریں۔",
-       "editinginterface": "<strong>انتباہ: </strong> آپ ایک ایسے صفحے میں ترمیم کر رہے ہیں جو سوفٹ ویئر کے لیے انٹرفیس متن فراہم کرتا ہے۔ اس صفحہ میں کی جانے والی تبدیلی سے اس ویکی پر دیگر صارفین کے لیے انٹرفیس متاثر ہوگی۔",
-       "translateinterface": "تÙ\85اÙ\85 Ù\88Û\8cÚ©Û\8cÙ\88Úº Ù\85Û\8cÚº ØªØ¨Ø¯Û\8cÙ\84Û\8c Û\8cا Ø´Ø§Ù\85Ù\84 Ú©Ø±Ù\86Û\92 Ú©Û\92 Ù\84Û\8cÛ\92Ø\8c [https://translatewiki.net/ translatewiki.net]Ú©Ù\88 Ø§Ø³ØªØ¹Ù\85اÙ\84 Ú©Ø±Û\8cÚº Ø\8c Ù\85Û\8cÚ\88Û\8cا Ù\88Û\8cÚ©Û\8c Ø¯Ø§Ø±Ø§Ù\84ترجÙ\85Û\81.",
+       "editinginterface": "<strong>انتباہ:</strong> آپ ایک ایسے صفحے میں ترمیم کر رہے ہیں جو سافٹ ویئر کا انٹرفیس متن فراہم کرتا ہے۔ اس صفحہ میں کی جانے والی ترمیم، دیگر صارفین کے انٹرفیس کو تبدیل کردے گی۔",
+       "translateinterface": "تÙ\85اÙ\85 Ù\88Û\8cÚ©Û\8cÙ\88Úº Ù\85Û\8cÚº ØªØ±Ø§Ø¬Ù\85 Ú©Ù\88 ØªØ¨Ø¯Û\8cÙ\84 Û\8cا Ø´Ø§Ù\85Ù\84 Ú©Ø±Ù\86Û\92 Ú©Û\92 Ù\84Û\8cÛ\92  Ù\85Û\8cÚ\88Û\8cاÙ\88Û\8cÚ©Û\8c Ú©Û\92 Ø¯Ø§Ø± Ø§Ù\84ترجÙ\85Û\81 [https://translatewiki.net/ translatewiki.net] Ú©Ù\88 Ø§Ø³ØªØ¹Ù\85اÙ\84 Ú©Ø±Û\8cÚºÛ\94",
        "cascadeprotected": "درج ذیل محفوظ کردہ {{PLURAL:$1|صفحہ|صفحات}} کی «آبشاری» حفاظت میں شامل ہونے کی وجہ سے یہ صفحہ بھی محفوظ ہے:\n$2",
        "namespaceprotected": "آپ کو '''$1''' فضائے نام میں صفحات تدوین کرنے کی اِجازت نہیں ہے.",
        "customcssprotected": "آپ کو اس سی ایس ایس میں ترمیم کرنے کی اجازت نہیں کیونکہ اس میں کسی دوسرے صارف کی ذاتی ترتیبات موجود ہیں۔",
        "userlogin-yourpassword-ph": "اپنا کلمہ شناخت دیں",
        "createacct-yourpassword-ph": "ایک پاس ورڈ داخل کریں",
        "yourpasswordagain": "کلمۂ شناخت دوبارہ لکھیں",
-       "createacct-yourpasswordagain": "کلمۂ اجازت تصدیق کریں",
+       "createacct-yourpasswordagain": "پاس ورڈ کی تصدیق کریں",
        "createacct-yourpasswordagain-ph": "پاس ورڈ پھر داخل کریں",
        "userlogin-remembermypassword": "مجھے داخل رکھے",
        "userlogin-signwithsecure": "محفوظ رابطہ (کنکشن) استعمال کریں",
        "summary": "خلاصہ:",
        "subject": "عنوان:",
        "minoredit": "معمولی ترمیم",
-       "watchthis": "یہ صفحہ زیر نظر کیجیۓ",
+       "watchthis": "اس صفحہ کو زیر نظر کیحیے",
        "savearticle": "محفوظ",
        "savechanges": "تبدیلیاں محفوظ کریں",
        "publishpage": "شائع کریں",
        "nosuchsectiontitle": "قطعہ نہیں ملا",
        "nosuchsectiontext": "آپ نے ایسے قطعہ میں ترمیم کی کوشش کی ہے جو کہ موجود نہیں.\nہوسکتا ہے کہ جب آپ صفحہ ملاحظہ فرمارہے تھے اُسی اثناء مذکورہ قطعہ کو منتقل یا حذف کردیا گیا ہو.",
        "loginreqtitle": "داخلہ / اندراج لازم",
-       "loginreqlink": "داخلہ",
+       "loginreqlink": "لاگ ان",
        "loginreqpagetext": "دوسرے صفحات ملاحظہ کرنے کیلئے آپکا $1 ضروری ہے.",
        "accmailtitle": "کلمہ شناخت بھیج دیا گیا۔",
        "accmailtext": "[[User talk:$1|$1]] کے لیے خودکار طریقے سے تخلیق کیا گیا پاسورڈ $2 کو بھیج دیا گیا ہے.\n\nلاگ ان ہونے کے بعد <em>[[Special:ChangePassword|اسے تبدیل]]</em> کیا جا سکتا ہے۔",
        "moveddeleted-notice": "اس صفحہ کو حذف کر دیا گیا ہے۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف شدگی اور نوشتہ منتقلی درج ہے۔",
        "moveddeleted-notice-recent": "معذرت، اس صفحہ کو حال ہی میں حذف کیا گیا ہے (گزشتہ چوبیس گھنٹوں میں)۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف اور نوشتہ منتقلی موجود ہے۔",
        "log-fulllog": "پورا نوشتہ دیکھئے",
+       "edit-hook-aborted": "کسی رکاوٹ کی وجہ سے ترمیم کاری منسوخ کر دی گئی ہے۔\nاور کوئی وضاحت نہیں دی گئی۔",
        "edit-gone-missing": "صفحہ تجدید نہیں کیا جاسکتا.\nلگتا ہے یہ حذف ہوچکا ہے.",
        "edit-conflict": "تنازعۂ تدوین.",
        "edit-no-change": "آپ کی تدوین کو نظرانداز کردیا گیا، کیونکہ متن میں کوئی تبدیلی نہیں ہوئی تھی.",
        "mergehistory-fail-no-change": "ضم تاریخچہ نے کسی بھی نسخے کو ضم نہیں کیا۔ براہ کرم صفحہ اور وقت کے پیرامیٹر کو دوبارہ جانچ لیں۔",
        "mergehistory-fail-permission": "ناکافی اختیارات برائے ضم تاریخچہ۔",
        "mergehistory-fail-self-merge": "ماخذ و مقصود صفحات یکساں ہیں۔",
+       "mergehistory-fail-toobig": "تاریخچے کو ضم نہیں کیا جا سکتا، کیونکہ {{PLURAL:$1|نسخے|نسخوں}} کی مقررہ حد  $1 سے تجاوز کرنا ہوگا۔",
        "mergehistory-no-source": "مآخذ صفحہ $1 موجود نہیں.",
        "mergehistory-no-destination": "مقصود صفحہ $1 موجود نہیں.",
        "mergehistory-invalid-source": "مآخذ صفحہ کا عنوان صحیح ہونا چاہئے.",
        "editundo": "رد ترمیم",
        "diff-empty": "(کوئی فرق نہیں)",
        "diff-multi-sameuser": "(ایک ہی صارف کا {{PLURAL: $1 |ایک درمیانی نسخہ نہیں دکھایا گیا| $1 درمیانی نسخے نہیں دکھائے گئے}})",
+       "diff-multi-otherusers": "({{PLURAL:$2|ایک دوسرے صارف|$2 صارفین}} {{PLURAL:$1|کا ایک درمیانی نسخہ نہیں دکھایا گیا|$1 کے درمیانی نسخے نہیں دکھائے گئے}})",
+       "diff-multi-manyusers": "($2 سے زیادہ {{PLURAL:$2|صارف|صارفین}} {{PLURAL:$1|کا ایک درمیانی نسخہ نہیں دکھایا گیا|$1 کے درمیانی نسخے نہیں دکھائے گئے}})",
+       "difference-missing-revision": "اس فرق ($1) {{PLURAL:$2|کا ایک نسخہ نہیں ملا|$2 کے نسخے نہیں ملے}}۔\n\nعموماً ایسا اس وقت ہوتا ہے جب کسی حذف شدہ صفحہ کے نسخوں کے درمیان میں فرق تلاش کرنے کی کوشش کی جائے۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
        "searchresults": "تلاش کا نتیجہ",
        "searchresults-title": "نتائجِ تلاش برائے \"$1\"",
        "titlematches": "عنوان صفحہ سے ملتا ہے",
        "prevn-title": "پچھلے $1 {{PLURAL:$1|نتیجہ|نتائج}}",
        "nextn-title": "آگے $1 {{PLURAL:$1|نتیجہ|نتائج}}",
        "shown-title": "فی صفحہ $1 {{PLURAL:$1|نتیجہ|نتائج}} دِکھاؤ",
-       "viewprevnext": "دیکھیں($1 {{int:pipe-separator}} $2) ($3)۔",
+       "viewprevnext": "($1 {{int:pipe-separator}} $2) دیکھیں ($3)",
        "searchmenu-exists": "<strong>اِس ویکی پر «[[:$1]]» نامی ایک صفحہ موجود ہے۔</strong> {{PLURAL:$2|0=|تلاش کے دیگر نتائج بھی ملاحظہ فرمائیں۔}}",
        "searchmenu-new": "<strong>صفحہ \"[[:$1]]\" کو اس ویکی پر تخلیق کریں</strong> {{PLURAL:$2|0=|وہ صفحہ بھی دیکھے جو ٓپ کے تلاش میں پایا گیا|ان نتائج کو بھی دیکھے جو پائے گئے}}",
        "searchprofile-articles": "مشمولاتی صفحات",
        "search-relatedarticle": "متعلقہ",
        "searchrelated": "متعلقہ",
        "searchall": "تمام",
-       "search-showingresults": "{{PLURAL:$4|نتائج <strong>$1</strong> از <strong>$3</strong>|نتائج <strong>$1 - $2</strong> از <strong>$3</strong>}}",
+       "showingresults": "ذیل میں #<strong>$2</strong> سے {{PLURAL:$1|<strong>1</strong> تک نتیجہ دکھایا گیا ہے|<strong>$1</strong> تک نتائج دکھائے گئے ہیں}}۔",
+       "showingresultsinrange": "ذیل میں #<strong>$2</strong> سے #<strong>$3</strong> تک {{PLURAL:$1|<strong>1</strong> نتیجہ|<strong>$1</strong> نتائج}} ایک قطار میں درج {{PLURAL:$1|ہے|ہیں}}۔",
+       "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> میں سے <strong>$1</strong> نتیجہ|<strong>$3</strong> میں سے <strong>$1 تا $2</strong> نتائج}}",
        "search-nonefound": "استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
        "search-nonefound-thiswiki": "اس سائٹ پر استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
        "powersearch-legend": "پیشرفتہ تلاش",
        "userrights-lookup-user": "گروہائے صارف کا انتظام",
        "userrights-user-editname": "کوئی اسم‌صارف داخل کیجئے:",
        "editusergroup": "{{GENDER:$1|صارف}} کے گروہوں میں ترمیم کریں",
-       "editinguser": "{{GENDER:$1|صارف}} <strong>[[صارف:$1|$1]]</strong> $2 کے اختیارات میں تبدیلی",
+       "editinguser": "{{GENDER:$1|صارف}} <strong>[[User:$1|$1]]</strong> $2 کے اختیارات میں تبدیلی",
        "userrights-editusergroup": "ترمیم گروہائے صارف",
        "saveusergroups": "{{GENDER:$1|صارف}} کے گروہوں کو محفوظ کریں",
        "userrights-groupsmember": "رکنِ:",
        "grant-editprotected": "محفوظ صفحات میں ترمیم",
        "grant-highvolume": "اعلی حجم کی تدوین",
        "grant-oversight": "صارفین چھپائیے اور نظرثانی دبائیے",
+       "grant-patrol": "صفحات کی مراجعتی تبدیلیاں",
        "grant-privateinfo": "ذاتی معلومات تک رسائی",
        "grant-protect": "صفحات کو محفوظ اور غیر محفوظ کریں",
+       "grant-rollback": "صفحات کی تبدیلیوں کا استرجع",
        "grant-sendemail": "دیگر صارفین کو برقی خط کی ترسیل",
        "grant-uploadeditmovefile": "فائلوں کی تبدیلی، اپلوڈ اور منتقلی",
        "grant-uploadfile": "نئی فائلوں کی اپلوڈ کاری",
        "badfilename": "ملف (فائل) کا نام \"$1\" ، تبدیل کردیا گیا۔",
        "filetype-mime-mismatch": "فائل کی توسیع «$1.‎» فائل کی MIME قسم ($2) کے مطابق نہیں۔",
        "filetype-badmime": "MIME قسم \"$1\" کی فائلوں کو اپلوڈ کرنے کی اجازت نہیں ہے۔",
+       "filetype-bad-ie-mime": "اس فائل کو اپلوڈ نہیں کیا جا سکتا کیونکہ انٹرنیٹ ایکسپلورر اسے «$1» سمجھے گا جس کی اجازت نہیں اور اس نوع کی فائل کے خطرناک ہونے کا احتمال ہے۔",
+       "filetype-unwanted-type": "<strong>\".$1\"</strong> ناپسندیدہ نوعیت کی فائل ہے۔\nراجح نوعیت کی {{PLURAL:$3|فائل|فائلیں}} $2 {{PLURAL:$3|ہے|ہیں}}۔",
+       "filetype-banned-type": "<strong>\".$1\"</strong> نوعیت کی {{PLURAL:$4|فائل|فائلوں}} کی اجازت نہیں۔\nاجازت یافتہ نوعیت کی {{PLURAL:$3|فائل|فائلیں}} $2 {{PLURAL:$3|ہے|ہیں}}۔",
        "filetype-missing": "اس فائل کی کوئی توسیع نہیں ہے (مثلاً  \".jpg\")۔",
        "empty-file": "آپ کی ارسال کردہ فائل خالی تھی۔",
        "file-too-large": "آپ کی ارسال کردہ فائل بہت بڑی تھی",
        "emptyfile": "لگتا ہے آپ کی اپلوڈ کردہ فائل خالی ہے۔\nایسا ٹائپنگ میں کوئی غلطی کی وجہ سے ہو سکتا ہے۔\nبرائے مہربانی جانچ کر لیں کہ آیا آپ واقعی اس فائل کو اپلوڈ کرنا چاہتے ہیں۔",
        "windows-nonascii-filename": "یہ ویکی خاص حروف کے ساتھ فائل کا نام تسلیم نہیں کرتا۔",
        "fileexists": "اس نام سے ایک فائل پہلے سے موجود ہے، اگر آپ کو یقین نہ ہو کہ اسے حذف کردیا جانا چاہیے تو براہ کرم  <strong>[[:$1]]</strong> کو ایک نظر دیکھ لیجیے۔ [[$1|thumb]]",
+       "filepageexists": "اس فائل کا صفحۂ وضاحت پہلے ہی <strong>[[:$1]]</strong> پر بنا دیا گیا ہے، تاہم فی الحال اس نام سے کوئی فائل موجود نہیں۔\nجو خلاصہ آپ درج کر رہے ہیں یہ صفحۂ وضاحت میں نظر نہیں آئے گا۔\nاگر آپ اپنے خلاصے کو صفحہ وضاحت میں دیکھنا چاہیں تو وہاں دستی طور پر شامل کریں۔\n[[$1|thumb]]",
+       "fileexists-extension": "اسی نام سے ایک فائل موجود ہے: [[$2|thumb]]\n* اپلوڈ کی جانے والی فائل کا نام: <strong>[[:$1]]</strong>\n* پہلے سے موجود فائل کا نام: <strong>[[:$2]]</strong>\nبراہ کرم فائل کا منفرد نام رکھیں۔",
+       "fileexists-thumbnail-yes": "ایسا معلوم ہوتا ہے کہ یہ فائل کم حجم <em>(تھمب نیل)</em> کی تصویر ہے۔\n[[$1|thumb]]\nبراہ کرم فائل <strong>[[:$1]]</strong> کو جانچ لیں۔\nاگر وہ اپنے اصل حجم کے ساتھ یہی فائل ہے تو کسی اضافی تھمب نیل کے اپلوڈ کرنے کی ضرورت نہیں۔",
+       "file-thumbnail-no": "اس فائل کا نام <strong>$1</strong> سے شروع ہوتا ہے۔\nایسا معلوم ہوتا ہے کہ یہ فائل کم حجم <em>(تھمب نیل)</em> کی تصویر ہے۔\nاگر آپ کے پاس یہ تصویر مکمل ریزولیوشن میں موجود ہو تو اسے اپلوڈ کریں، ورنہ اس فائل کا نام تبدیل کر دیں۔",
+       "fileexists-forbidden": "اس نام سے پہلے ہی ایک فائل موجود ہے اور اب اسے برتحریر نہیں کیا جا سکتا۔\nاگر آپ بہرصورت اپنی فائل کو اپلوڈ کرنا چاہتے ہیں تو براہ کرم واپس جائیں اور فائل کا نام تبدیل کریں۔\n[[File:$1|thumb|center|$1]]",
+       "fileexists-shared-forbidden": "فائلوں کے مشترکہ ذخیرے میں اس نام سے پہلے ہی ایک فائل موجود ہے۔\nاگر آپ بہرصورت اپنی فائل کو اپلوڈ کرنا چاہتے ہیں تو براہ کرم واپس جائیں اور فائل کا نام تبدیل کریں۔\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "اپلوڈ کردہ فائل <strong>[[:$1]]</strong> کے موجودہ نسخے کی واضح نقل ہے۔",
+       "fileexists-duplicate-version": "اپلوڈ کردہ فائل <strong>[[:$1]]</strong> کے {{PLURAL:$2|پرانے نسخے|پرانے نسخوں}} کی واضح نقل ہے۔",
+       "file-exists-duplicate": "پیش نظر فائل درج ذیل {{PLURAL:$1|فائل|فائلوں}} کی نقل ہے:",
+       "file-deleted-duplicate": "اس فائل ([[:$1]]) سے ملتی جلتی دوسری فائل کو پہلے حذف کیا جا چکا ہے۔\nچنانچہ اسے دوبارہ اپلوڈ کرنے سے قبل اُس پرانی فائل کے حذف کا تاریخچہ جانچ لیں۔",
+       "file-deleted-duplicate-notitle": "اس فائل سے ملتی جلتی دوسری فائل کو پہلے حذف کیا اور اس عنوان کو ممنوع قرار دیا جا چکا ہے۔\nاسے دوبارہ اپلوڈ کرنے سے قبل کسی ایسے شخص سے اس صورت حال کا جائزہ لینے کی درخواست کریں جسے ممنوع فائلوں کی معلومات تک رسائی حاصل ہو۔",
        "uploadwarning": "انتباہ بہ سلسلۂ زبراثقال",
        "uploadwarning-text": "ذیل میں موجود فائل کی وضاحت میں تبدیلی کریں اور دوبارہ کوشش کریں۔",
        "savefile": "فائل محفوظ کریں",
        "uploaddisabled": "اپلوڈ غیر فعال ہے۔",
        "copyuploaddisabled": "بذریعہ یوآرایل اپلوڈ غیر فعال ہے۔",
        "uploaddisabledtext": "فائل اپلوڈ غیر فعال ہے۔",
+       "php-uploaddisabledtext": "پی ایچ پی کی فائلیں اپلوڈ نہیں کی جا سکتیں۔\nبراہ کرم  file_uploads کی ترتیبات جانچ لیں۔",
+       "uploadscripted": "اس فائل میں ایچ ٹی ایم ایل یا اسکرپٹ کوڈ کا استعمال کیا گیا ہے لہذا عین ممکن ہے کہ کوئی ویب براؤزر اس کی غلط تشریح کرے۔",
+       "upload-scripted-pi-callback": "ایسی کسی فائل کو اپلوڈ نہیں کیا جا سکتا جس میں ایکس ایم ایل اسٹائل شیٹ پر عمل کرنے کی ہدایت ہو۔",
        "uploaded-hostile-svg": "اپلوڈ کردہ ایس وی جی فائل کے اسٹائل عنصر میں غیر محفوظ سی ایس ایس دریافت ہوئی ہے۔",
        "uploadscriptednamespace": "اس ایس وی جی فائل میں غیر قانونی نام فضا \"$1\" موجود ہے۔",
        "uploadinvalidxml": "اپلوڈ کردہ فائل میں موجود ایکس ایم ایل کا تجزیہ نہیں کیا جا سکا۔",
        "backend-fail-maxsize": "فائل $1 کی معلومات نہیں لکھی جا سکی کیونکہ اس کا حجم {{PLURAL:$2|ایک بائٹ|$2 بائٹ}} سے زیادہ ہے۔",
        "backend-fail-readonly": "فی الحال ذخیرہ کا پس منظر $1 فقط خواندگی حالت میں ہے۔ اس کی وجہ حسب ذیل ہے:\n\n\n<em>«$2»</em>",
        "backend-fail-synced": "اس وقت فائل $1 داخلی ذخیرہ کے پس منظر کے اندر ناپائیدار حالت میں ہے۔",
+       "lockmanager-notlocked": "«$1» کو کھولا نہیں جا سکا؛ کیونکہ یہ مقفل نہیں ہے۔",
+       "lockmanager-fail-closelock": "«$1» کا فائل لاک بند نہیں کیا جا سکا۔",
+       "lockmanager-fail-deletelock": "«$1» کا فائل لاک حذف نہیں کیا جا سکا۔",
+       "lockmanager-fail-acquirelock": "«$1» کا قفل حاصل نہیں کیا جا سکا۔",
+       "lockmanager-fail-openlock": "«$1» کا فائل لاک کھولا نہیں جا سکا۔",
+       "lockmanager-fail-releaselock": "«$1» کا قفل کھولا نہ جا سکا۔",
+       "lockmanager-fail-db-release": "$1 ڈیٹابیس سے قفل نہیں ہٹائے جا سکے۔",
+       "lockmanager-fail-svr-acquire": "$1 سرور کے قفل حاصل نہیں کیے جا سکے۔",
+       "lockmanager-fail-svr-release": "$1 سرور کے قفل ہٹائے نہیں جا سکے۔",
+       "zip-file-open-error": "مواد کی جانچ کے لیے زپ فائل کھولنے کے دوران میں کوئی نقص واقع ہوا۔",
        "zip-wrong-format": "یہ زپ فائل نہیں تھی۔",
        "uploadstash-errclear": "فائل کی صفائی ناکام۔",
        "uploadstash-refresh": "فائلوں کی فہرست کو تازہ کریں",
        "listduplicatedfiles": "مکررات کے ساتھ فائلوں کی فہرست",
        "unusedtemplates": "غیر استعمال شدہ سانچے",
        "unusedtemplateswlh": "دیگر روابط",
-       "randompage": "بےترتیب صفحہ",
+       "randompage": "جستہ جستہ",
+       "randompage-nopages": "{{PLURAL:$2|اس نام فضا|ان نام فضا}} میں کوئی صفحہ موجود نہیں ہے: $1",
        "randomincategory": "زمرہ میں بے ترتیب صفحہ",
        "randomincategory-invalidcategory": "عنوان «$1» زمرے کا درست نام نہیں ہے۔",
        "randomincategory-nopages": "[[:Category:$1|$1]] زمرہ میں کوئی صفحہ نہیں ہے۔",
        "uncategorizedtemplates": "غیر زمرہ بند سانچہ جات",
        "unusedcategories": "غیر استعمال شدہ زمرہ جات",
        "unusedimages": "غیر استعمال شدہ فائلیں",
-       "wantedcategories": "طلب شدہ زمرہ جات",
+       "wantedcategories": "مطلوبہ زمرہ جات",
        "wantedpages": "درخواست شدہ مضامین",
        "wantedpages-summary": "ذیل میں ان غیر موجود صفحات کی فہرست ہے جن سے بہت سارے روابط مربوط ہیں، البتہ ان میں وہ صفحات شامل نہیں جن میں محض ان سے مربوط رجوع مکررات موجود ہیں۔ ان صفحوں کو دیکھنے کے لیے [[{{#special:BrokenRedirects}}|شکستہ روابط کی فہرست]] ملاحظہ فرمائیں۔",
        "wantedpages-badtitle": "نتائج میں نادرست عنوان: $1",
        "notargettext": "اس اقدام کی تکمیل کے لیے آپ نے کسی صفحہ یا صارف کا تعین نہیں کیا ہے۔",
        "nopagetitle": "ایسا کوئی صفحہ موجود نہیں",
        "nopagetext": "آپ کا درج کردہ ہدف صفحہ موجود نہیں ہے۔",
-       "pager-newer-n": "{{PLURAL:$1|جدید 1|جدید $1}}",
-       "pager-older-n": "{{PLURAL:$1|Ù¾Ù\8fراÙ\86ا 1|Ù¾Ù\8fراÙ\86Û\92 $1}}",
+       "pager-newer-n": "{{PLURAL:$1|جدید $1}}",
+       "pager-older-n": "{{PLURAL:$1|Ù\82دÛ\8cÙ\85}} $1",
        "suppress": "دبائیں",
        "querypage-disabled": "اس خصوصی صفحہ کو بوجوہ غیر فعال کر دیا گیا ہے۔",
        "apihelp": "معاونت اے پی آئی",
        "trackingcategories-name": "پیغام کا عنوان",
        "trackingcategories-desc": "زمرہ کی شمولیت کا معیار",
        "restricted-displaytitle-ignored": "صفحات مع نظرانداز کردہ عناوین",
+       "broken-file-category-desc": "اس صفحہ میں کسی فائل کا شکستہ ربط موجود ہے (یعنی ایسی فائل کا ربط دیا گیا ہے جو موجود نہیں)۔",
+       "hidden-category-category-desc": "اس زمرہ میں ابتدائی طور پر <code><nowiki>__HIDDENCAT__</nowiki></code> کا کوڈ شامل ہے جو اس زمرہ کو صفحات میں ظاہر ہونے سے روکتا ہے۔",
        "trackingcategories-nodesc": "کوئی وضاحت دستیاب نہیں۔",
        "trackingcategories-disabled": "زمرہ غیر فعال ہے",
        "mailnologin": "بھیجنے کے لیے کوئی پتہ نہیں",
        "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": "صÙ\81Ø­Û\81 Ø¶Ø§Ø¦Ø¹ کریں",
+       "deletepage": "حذÙ\81 کریں",
        "confirm": "یقین",
        "excontent": "'$1':مواد تھا",
        "excontentauthor": "حذف شدہ مواد: «$1» اور صرف «[[Special:Contributions/$2|$2]]» ([[User talk:$2|تبادلۂ خیال]]) نے اس میں ترمیم کی",
        "exbeforeblank": "خالی کرنے سے قبل موجود مواد: «$1»",
        "delete-confirm": "حذف ''$1''",
        "delete-legend": "حذف",
-       "historywarning": "<strong>اÙ\86تباÛ\81</strong>: Ø¢Ù¾ Ø§Ø³ ØµÙ\81Ø­Û\81 Ú©Ù\88 $1 {{PLURAL:$1|Ù\86ظر Ø«Ø§Ù\86Û\8c\86ظر Ø«Ø§Ù\86Û\8cوں}} کے تاریخچہ کے ساتھ حذف کر رہے ہیں:",
+       "historywarning": "<strong>اÙ\86تباÛ\81</strong>: Ø¢Ù¾ Ø§Ø³ ØµÙ\81Ø­Û\81 Ú©Ù\88 $1 {{PLURAL:$1|Ù\86سخÛ\81\86سخوں}} کے تاریخچہ کے ساتھ حذف کر رہے ہیں:",
        "historyaction-submit": "دکھائیں",
-       "confirmdeletetext": "آپ نے اس صفحے کو اس سے ملحقہ تاریخچہ سمیت حذف کرنے کا ارادہ کیا ہے۔ براۓ مہربانی تصدیق کرلیجیۓ کہ آپ اس عمل کے نتائج سے بخوبی آگاہ ہیں، اور یہ بھی یقین کرلیجیۓ کہ آپ ایسا [[{{MediaWiki:Policy-url}}|ویکیپیڈیا کی حکمت عملی]] کے دائرے میں رہ کر کر رہے ہیں۔",
+       "confirmdeletetext": "آپ اس صفحے کو اس سے ملحقہ تاریخچہ سمیت حذف کر رہے ہیں۔ براہ مہربانی اس بات کی تصدیق کر لیں کہ آپ اس عمل کے نتائج سے بخوبی آگاہ ہیں، اور یہ بھی جانچ لیں کہ آیا آپ کا یہ اقدام [[{{MediaWiki:Policy-url}}|حکمت عملی]] کے دائرے میں ہے یا نہیں۔",
        "actioncomplete": "اقدام تکمیل کو پہنچا",
        "actionfailed": "عمل ناکام",
        "deletedtext": "\"$1\" کو حذف کر دیا گیا ہے ۔\nحالیہ حذف شدگی کے تاریخ نامہ کیلیۓ  $2  دیکھیۓ",
        "rollback-missingrevision": "نسخہ کی معلومات لوڈ نہیں ہو سکتی۔",
        "cantrollback": "تدوین ثانی کا اعادہ نہیں کیا جاسکتا؛ کیونکہ اس میں آخری بار حصہ لینے والا ہی اس صفحہ کا واحد کاتب ہے۔",
        "editcomment": "خلاصہ ترمیم یہ تھا: <em>«$1»</em>.",
-       "revertpage": "[[خاص:شراکتیں/$2|$2]] ([[تبادلۂ خیال:$2|تبادلۂ خیال]]) کی ترامیم [[صارف:$1|$1]] کی گذشتہ ترمیم کی جانب واپس پھیر دی گئیں۔",
+       "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|تبادلۂ خیال]]) کی ترامیم [[User:$1|$1]] کی گذشتہ ترمیم کی جانب واپس پھیر دی گئیں۔",
        "revertpage-nouser": "(حذف شدہ صارف نام) کی ترامیم {{GENDER:$1|[[User:$1|$1]]}} کی گذشتہ ترمیم کی جانب واپس پھیر دی گئیں",
        "rollback-success": "$1 کی ترامیم واپس پھیر دی گئیں؛\nصفحہ واپس $2 کی آخری ترمیم کی جانب منتقل کر دیا گیا۔",
        "rollback-success-notify": "$1 کی ترامیم واپس پھیر دی گئیں؛\nصفحہ واپس $2 کی آخری ترمیم کی جانب منتقل کر دیا گیا۔ [$3 تبدیلیاں دکھائیں]",
        "undeletepagetitle": "'''ذیل میں [[:$1|$1]] کے حذف شدہ ترامیم درج ہیں۔'''",
        "viewdeletedpage": "حذف شدہ صفحات دیکھیے",
        "undelete-fieldset-title": "ترامیم بحال کریں",
-       "undeletehistory": "اگر آپ اس صفحہ کو بحال کرتے ہیں، تو اس صفحہ کے تاریخچہ میں تمام ترامیم بھی بحال ہوجائیگی۔\nاگر حذف شدگی کے بعد کوئی نیا صفحہ اسی نام سے تخلیق کیا گیا ہو، تو تمام بحال شدہ ترامیم گذشتہ تاریخچہ میں ظاہر ہوگی۔",
-       "undeleterevdel": "بحالیٔ صفحہ کا اقدام مکمل نہیں ہوگا اگر اس کا تنیجہ صفحہ کے اوپر کے حصہ کی ترمیم یا ملف کا اعادہ جزوی طور پر حذف کیا جارہا ہو۔\nایسی صورت میں لازمی طور آپ حالیہ حذف شدہ اعادے کو ظاہر کریں۔",
+       "undeleterevisions": "$1 {{PLURAL:$1|نسخہ حذف کیا گیا|نسخے حذف کیے گئے}}",
+       "undeletehistory": "اگر آپ اس صفحہ کو بحال کرتے ہیں، تو اس صفحہ کے تاریخچہ میں موجود تمام ترامیم بھی بحال ہو جائیں گی۔\nاگر حذف شدگی کے بعد کوئی نیا صفحہ اسی نام سے بنایا گیا ہو، تو تمام بحال شدہ ترامیم گذشتہ تاریخچہ میں ظاہر ہوگی۔",
+       "undeleterevdel": "اگر صفحہ یا فائل کے آخری نسخے کو جزوی طور پر حذف کیا جا رہا ہو تو بحالیٔ صفحہ کا اقدام مکمل نہیں ہوگا۔\nایسی صورت میں لازمی طور آپ حالیہ حذف شدہ نسخے کو بھی بحال کریں۔",
+       "undeletehistorynoadmin": "اس صفحہ کو حذف کر دیا گیا ہے۔\nذیل میں صفحہ حذف کرنے کی وجہ درج ہے، اور ساتھ ہی ان صارفین کی تفصیلات بھی موجود ہیں جنہوں نے صفحہ حذف ہونے سے قبل اس میں ترمیم کی تھی۔\nحذف شدہ نسخوں کا اصل متن محض منتظمین کے لیے دستیاب ہے۔",
+       "undelete-revision": "$3 کی جانب سے (مورخہ $4 بوقت $5 بجے) تحریر کردہ $1 کا حذف شدہ نسخہ:",
+       "undelete-nodiff": "کوئی پرانا نسخہ نہیں ملا۔",
        "undeletebtn": "بحال",
        "undeletelink": "دیکھو/بحال کرو",
        "undeleteviewlink": "دکھاؤ",
        "undeletedrevisions": "{{PLURAL:$1|1 نظر ثانی|$1 نظر ثانیاں}} بحال",
        "undeletedrevisions-files": "{{PLURAL:$1|1 نظر ثانی|$1 نظر ثانیاں}} اور {{PLURAL:$2|1 ملف|$2 املاف}} بحال",
        "undeletedfiles": "{{PLURAL:$1|1 ملف|$1 املاف}} بحال",
+       "cannotundelete": "کلی یا جزوی طور پر بحالی کا اقدام ناکام رہا:\n$1",
+       "undeletedpage": "<strong>$1 کو بحال کر دیا گیا</strong>\n\nحالیہ حذف شدگیوں اور بحالیوں کا نوشتہ دیکھنے کے لیے [[Special:Log/delete|نوشتہ حذف شدگی]] ملاحظہ فرمائیں۔",
        "undelete-header": "حالیہ حذف شدہ صفحات کے لیے [[Special:Log/delete|نوشتۂ حذف شدگی]] دیکھیں۔",
        "undelete-search-title": "حذف شدہ صفحات میں تلاش کریں",
        "undelete-search-box": "حذف شدہ صفحات میں تلاش کریں",
        "undelete-search-prefix": "اظہار صفحات بآغاز از:",
        "undelete-search-submit": "تلاش",
        "undelete-no-results": "حذف شدہ صفحات میں ایسا کوئی صفحہ نہیں ملا",
+       "undelete-cleanup-error": "غیر مستعمل تاریخچہ «$1» کو حذف کرنے کے دوران میں نقص۔",
+       "undelete-error": "صفحہ کی بحالی کے دوران میں نقص",
+       "undelete-error-short": "فائل کی بحالی کے دوران میں نقص: $1",
+       "undelete-error-long": "فائل کی بحالی کے دوران میں نقص واقع ہوا:\n\n$1",
+       "undelete-show-file-confirm": "کیا آپ واقعی فائل <nowiki>$1</nowiki> کا مورخہ $2 بوقت $3 بجے کا حذف شدہ نسخہ دیکھنا چاہتے ہیں؟",
        "undelete-show-file-submit": "ہاں",
        "namespace": "نام فضا:",
-       "invert": "انتخاب بالعکس",
-       "tooltip-invert": "منتخب شدہ فضائے نام (اور مُلحقہ فضائے نام) میں شامل صفحات کی تبدیلیوں کو چُھپانے کیلئے اِس خانہ کو ٹِک کریں۔",
-       "namespace_association": "متعلقہ نام فضا",
+       "invert": "انتخاب معکوس",
+       "tooltip-invert": "منتخب نام فضا (اور مُلحقہ نام فضا) میں شامل صفحات کی تبدیلیوں کو چھپانے کے لیے اس خانہ کو نشان زد کریں۔",
+       "tooltip-whatlinkshere-invert": "منتخب نام فضا میں موجود صفحات کے روابط چھپانے کے لیے اس خانہ کو نشان زد کریں۔",
+       "namespace_association": "ملحقہ نام فضا",
+       "tooltip-namespace_association": "منتخب نام فضا سے منسلک تبادلۂ خیال یا ذیلی نام فضا کو شامل کرنے کے لیے اس خانہ کو نشان زد کریں",
        "blanknamespace": "(مرکز)",
-       "contributions": "{{GENDER:$1|صارف}} شراکتیں",
+       "contributions": "{{GENDER:$1|صارف}} کی شراکتیں",
        "contributions-title": "صارف $1 کی شراکتیں",
        "mycontris": "شراکت",
        "anoncontribs": "شراکتیں",
        "contribsub2": "برائے {{GENDER:$3|$1}} ($2)",
+       "contributions-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ مندرج نہیں ہے۔",
+       "nocontribs": "اس معیار کے مطابق کوئی ترمیم دستیاب نہیں ہوئی۔",
        "uctop": "(موجودہ)",
        "month": "مہینہ (اور اُس سے قبل):",
        "year": "سال (اور اُس سے قبل):",
-       "sp-contributions-newbies": "صرف نئے کھاتوں کے مساہمات دکھاؤ",
+       "sp-contributions-newbies": "محض جدید صارفین کی شراکتیں دکھائیں",
+       "sp-contributions-newbies-sub": "جدید صارفین کے",
+       "sp-contributions-newbies-title": "جدید صارفین کی شراکتیں",
        "sp-contributions-blocklog": "نوشتۂ پابندی",
-       "sp-contributions-uploads": "اثقالات",
+       "sp-contributions-suppresslog": "{{GENDER:$1|صارف}} کی پوشیدہ شراکتیں",
+       "sp-contributions-deleted": "{{GENDER:$1|صارف}} کی حذف شدہ شراکتیں",
+       "sp-contributions-uploads": "اپلوڈ کردہ",
        "sp-contributions-logs": "نوشتہ جات",
-       "sp-contributions-talk": "گفتگو",
+       "sp-contributions-talk": "تبادلۂ خیال",
        "sp-contributions-userrights": "انتظام اختیارات صارف",
-       "sp-contributions-search": "تلاش برائے مساہمات",
-       "sp-contributions-username": "آئی.پی پتہ یا اسمِ صارف:",
-       "sp-contributions-toponly": "صرف حالیہ ترین نظرثانی ترمیمات دِکھاؤ",
+       "sp-contributions-blocked-notice": "اس صارف پر پابندی لگائی گئی ہے۔\nحوالہ کے لیے نوشتہ پابندی کا تازہ ترین اندراج ذیل میں دستیاب ہے:",
+       "sp-contributions-blocked-notice-anon": "اس آئی پی پتے پر پابندی لگا دی گئی ہے۔\nحوالہ کے لیے نوشتہ پابندی کا تازہ ترین اندراج ذیل میں دستیاب ہے:",
+       "sp-contributions-search": "شراکتوں میں تلاش کریں",
+       "sp-contributions-username": "آئی پی پتا یا صارف نام:",
+       "sp-contributions-toponly": "محض نئے نسخوں پر مشتمل ترامیم دکھائیں",
+       "sp-contributions-newonly": "محض نئے صفحات دکھائیں",
        "sp-contributions-hideminor": "معمولی ترامیم چھپائیں",
        "sp-contributions-submit": "تلاش",
        "whatlinkshere": "ادھر کونسا ربط ہے",
        "whatlinkshere-title": "\"$1\" سے مربوط صفحات",
        "whatlinkshere-page": "صفحہ:",
-       "linkshere": "'''[[:$1]]''' سے درج ذیل صفحات مربوط ہیں:",
+       "linkshere": "<strong>[[:$1]]</strong> سے درج ذیل صفحات مربوط ہیں:",
        "nolinkshere": "'''[[:$1]]''' سے کوئی روابط نہیں۔",
-       "isredirect": "لوٹایا گیا صفحہ",
+       "isredirect": "رجوع مکرر صفحہ",
        "istemplate": "شامل شدہ",
        "isimage": "فائل کا ربط",
        "whatlinkshere-prev": "{{PLURAL:$1|پچھلا|پچھلے $1}}",
        "whatlinkshere-hideimages": "تصویر کے روابط $1",
        "whatlinkshere-filters": "فلٹرذ",
        "whatlinkshere-submit": "ٹھیک",
+       "autoblockid": "خودکار پابندی #$1",
        "block": "صارف مسدود کریں",
        "unblock": "صارف سے پابندی ہٹائیں",
        "blockip": "داخلہ ممنوع برائے صارف",
        "contribslink": "شراکتیں",
        "emaillink": "ای میل بھیجیں",
        "blocklogpage": "نوشتۂ پابندی",
+       "unblocklogentry": "$1 سے پابندی ہٹائی گئی",
        "block-log-flags-anononly": "محض نامعلوم صارفین",
        "block-log-flags-nocreate": "کھاتے کی تخلیق غیرفعال",
        "block-log-flags-noautoblock": "خودکار پابندی غیر فعال",
        "block-log-flags-nousertalk": "اپنے ذاتی تبادلۂ خیال میں ترمیم نہیں کر سکتا",
        "block-log-flags-angry-autoblock": "پیشرفتہ خودکار پابندی فعال",
        "block-log-flags-hiddenname": "صارف نام پوشیدہ ہے",
+       "range_block_disabled": "منتظمین سے رینج پر پابندی لگانے کا اختیار واپس لے لیا گیا ہے۔",
        "ipb_expiry_invalid": "وقت اختتام نادرست ہے۔",
        "ipb_expiry_old": "وقت اختتام گزر چکا ہے۔",
+       "ipb_expiry_temp": "پوشیدہ صارف نام پر عائد کی جانے والی پابندیاں مستقل ہونا لازمی ہے۔",
+       "ipb_hide_invalid": "اس کھاتے کو دبایا نہیں جا سکا؛ اس کھاتے سے {{PLURAL:$1|ایک ترمیم کی گئی ہے|$1 ترامیم کی گئی ہیں}}۔",
        "ipb_already_blocked": "«$1» پر پہلے ہی پابندی لگا دی گئی ہے۔",
        "ipb-needreblock": "«$1» پر پہلے ہی پابندی لگا دی گئی ہے۔ کیا آپ ان ترتیبات کو تبدیل کرنا چاہتے ہیں؟",
        "ipb-otherblocks-header": "دیگر {{PLURAL:$1|پابندی|پابندیاں}}",
        "unblock-hideuser": "چونکہ اس صارف کا نام پوشیدہ ہے لہذا آپ اس صارف سے پابندی نہیں ہٹا سکتے۔",
+       "ip_range_invalid": "آئی پی پتے کی رینج نادرست ہے۔",
+       "ip_range_toolarge": "/$1 سے زیادہ بڑی رینج پابندیوں کی اجازت نہیں ہے۔",
+       "proxyblocker": "پراکسی مسدود کنندہ",
+       "proxyblockreason": "آپ کے آئی پی پتے پر پابندی لگا دی گئی ہے کیونکہ یہ اوپن پراکسی ہے۔\nبراہ کرم انٹرنیٹ خدمات فراہم کرنے والے یا اپنی تنظیم کے تکنیکی معاون سے رابطہ کریں اور انہیں اس سنجیدہ مسئلہ سے آگاہ کریں۔",
+       "sorbsreason": "{{SITENAME}} کے زیر استعمال DNSBL میں آپ کا آئی پی پتا اوپن پراکسی کے طور پر درج فہرست ہے۔",
+       "sorbs_create_account_reason": "{{SITENAME}} کے زیر استعمال DNSBL میں آپ کا آئی پی پتا اوپن پراکسی کے طور پر درج فہرست ہے۔\nآپ کھاتہ نہیں بنا سکتے۔",
        "ipbblocked": "آپ دیگر صارفین پر پابندی لگا یا ہٹا نہیں سکتے کیونکہ خود آپ پر پابندی عائد کی گئی ہے۔",
        "ipbnounblockself": "آپ کو اپنی ذات سے پابندی ہٹانے کی اجازت نہیں ہے۔",
        "lockdb": "ڈیٹابیس مقفل کریں",
        "importsuccess": "درآمد مکمل!",
        "importnofile": "کسی درآمد فائل کو اپلوڈ نہیں کیا گیا۔",
        "import-noarticle": "درآمد کرنے کے لیے کوئی صفحہ موجود نہیں!",
+       "import-invalid-interwiki": "اس ویکی سے درآمد نہیں کیا جا سکتا۔",
+       "import-error-edit": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ آپ کو اس میں ترمیم کرنے کی اجازت نہیں ہے۔",
+       "import-error-create": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ آپ کو اسے تخلیق کرنے کی اجازت نہیں ہے۔",
+       "import-error-interwiki": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ اس کا نام بیرونی ربط (بین الویکی) کے لیے محفوظ ہے۔",
+       "import-error-special": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ یہ اس خصوصی نام فضا سے متعلق ہے جس میں صفحات بنانے کی اجازت نہیں۔",
+       "import-error-invalid": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ درآمد کے بعد اس صفحہ کا جو نام ہوگا وہ اس ویکی پر نادرست ہے۔",
+       "import-options-wrong": "غلط {{PLURAL:$2|اختیار|اختیارات}}: <nowiki>$1</nowiki>",
+       "import-rootpage-invalid": "درج کردہ ماخذی صفحہ کا عنوان نادرست ہے۔",
        "importlogpage": "نوشتہ درآمد",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|نسخہ|نسخے}} درآمد {{PLURAL:$1|کیا گیا|کیے گئے}}",
        "import-logentry-interwiki-detail": "$2 سے $1 {{PLURAL:$1|نسخہ|نسخے}} درآمد {{PLURAL:$1|کیا گیا|کیے گئے}}",
        "javascripttest": "جاوا اسکرپٹ کی آزمائش",
+       "javascripttest-pagetext-unknownaction": "نامعلوم اقدام \"$1\"",
        "tooltip-pt-userpage": "آپ کا صارف صفحہ",
+       "tooltip-pt-anonuserpage": "آپ جس آئی پی سے ترمیم کاری کر رہے ہیں اس کا صارف صفحہ",
        "tooltip-pt-mytalk": "آپ کا تبادلہ خیال صفحہ",
+       "tooltip-pt-anontalk": "اس آئی پی پتے کی ترامیم سے متعلق گفتگو",
        "tooltip-pt-preferences": "آپ کی ترجیحات",
        "tooltip-pt-watchlist": "اُن صفحات کی فہرست جن کی تبدیلیاں آپ کی زیرِنظر ہیں",
        "tooltip-pt-mycontris": "آپ کی شراکتوں کی فہرست",
        "tooltip-n-portal": "منصوبہ کے متعلق، آپ کیا کرسکتے ہیں، چیزیں کہاں ڈھونڈنی ہیں",
        "tooltip-n-currentevents": "حالیہ واقعات پر پس منظری معلومات دیکھیئے",
        "tooltip-n-recentchanges": "ویکی میں حالیہ تبدیلیوں کی فہرست",
-       "tooltip-n-randompage": "اÛ\8cÚ© ØªØµØ§Ø¯Ù\81Û\8c ØµÙ\81Ø­Û\81 Ù\84ائÛ\8cÛ\92",
+       "tooltip-n-randompage": "صÙ\81حات Ú©Ø§ Ø¬Ø³ØªÛ\81 Ø¬Ø³ØªÛ\81 Ù\85طاÙ\84عÛ\81 Ú©Ø±Û\8cÚº",
        "tooltip-n-help": "ڈھونڈ نکالنے کی جگہ",
        "tooltip-t-whatlinkshere": "اُن تمام ویکی صفحات کی فہرست جن کا یہاں ربط ہے",
        "tooltip-t-recentchangeslinked": "اِس صفحہ سے مربوط صفحات میں حالیہ تبدیلیاں",
        "tooltip-summary": "مختصر خلاصہ درج کریں",
        "common.css": "body,\ntextarea {\n    font-family: Amiri;\n}",
        "anonymous": "{{SITENAME}} {{PLURAL:$1|کا|کے}} گمنام {{PLURAL:$1|صارف|صارفین}}",
-       "siteuser": "{SITENAME}} کا صارف، $1",
-       "anonuser": "{SITENAME}} کا گمنام صارف، $1",
+       "siteuser": "{{SITENAME}} $1 صارف",
+       "anonuser": "{{SITENAME}} کا گمنام صارف $1",
        "lastmodifiedatby": "مورخہ $1 کو $2 بجے $3 نے اس صفحہ میں آخری بار تبدیلی کی۔",
        "othercontribs": "$1 کے کام کے مطابق۔",
        "others": "دیگر",
        "siteusers": "{{SITENAME}} {{PLURAL:$2|کا|کے}} {{PLURAL:$2|{{GENDER:$1|صارف}}|صارفین}} $1",
        "anonusers": "{{SITENAME}} {{PLURAL:$2|کا|کے}} گمنام {{PLURAL:$2|{{GENDER:$1|صارف}}|صارفین}} $1",
+       "creditspage": "صفحہ کے انتسابات",
+       "nocredits": "اس صفحہ کے انتسابات سے متعلق معلومات دستیاب نہیں ہیں۔",
        "pageinfo-title": "«$1» کی معلومات",
        "pageinfo-not-current": "معذرت، پرانی ترامیم کی ان معلومات کو فراہم کرنا ناممکن ہے۔",
        "pageinfo-header-basic": "بنیادی معلومات",
        "pageinfo-redirectsto-info": "معلومات",
        "pageinfo-contentpage": "شمار بطور صفحہ",
        "pageinfo-contentpage-yes": "ہاں",
+       "pageinfo-protect-cascading": "آبشاری حفاظت کا ماخذ",
        "pageinfo-protect-cascading-yes": "ہاں",
+       "pageinfo-protect-cascading-from": "آبشاری حفاظت از",
        "pageinfo-category-info": "زمرے کی معلومات",
        "pageinfo-category-total": "اراکین کی مجموعی تعداد",
        "pageinfo-category-pages": "تعداد صفحات",
        "markaspatrolledtext": "اس صفحہ کو بطور مراجعت شدہ نشان زد کریں",
        "markaspatrolledtext-file": "فائل کے اس نسخے کو مراجعت شدہ نشان زد کریں",
        "markedaspatrolled": "مراجعت شدہ نشان زد کر دیا گیا",
+       "markedaspatrolledtext": "[[:$1]] کی منتخب ترمیم کو مراجعت شدہ نشان زد کر دیا گیا۔",
+       "rcpatroldisabled": "حالیہ تبدیلیوں کی مراجعت غیر فعال ہے",
+       "rcpatroldisabledtext": "فی الحال حالیہ تبدیلیوں کی مراجعت کی سہولت غیر فعال ہے۔",
+       "markedaspatrollederror": "مراجعت شدہ نشان زد نہیں کیا جا سکا",
+       "markedaspatrollederrortext": "مراجعت شدہ نشان زد کرنے کے لیے کسی ترمیم کو منتخب کرنا ضروری ہے۔",
+       "markedaspatrollederror-noautopatrol": "آپ کو اپنی ذاتی تبدیلیاں مراجعت شدہ نشان زد کرنے کی اجازت نہیں ہے۔",
+       "markedaspatrollednotify": "$1 کی اس تبدیلی کو نشان زد کر دیا گیا۔",
        "markedaspatrollederrornotify": "بطور مراجعت نشان زد نہیں کیا جا سکا۔",
+       "patrol-log-page": "نوشتہ مراجعت",
+       "patrol-log-header": "ذیل میں مراجعت شدہ ترامیم کا نوشتہ ہے۔",
+       "log-show-hide-patrol": "$1 نوشتہ مراجعت",
+       "log-show-hide-tag": "$1 نوشتہ ٹیگ",
        "deletedrevision": "حذف شدہ پرانی ترمیم $1۔",
+       "filedeleteerror-short": "فائل حذف کاری میں نقص: $1",
+       "filedeleteerror-long": "فائل حذف کرنے کے دوران میں نقص واقع ہوا:\n\n$1",
+       "filedelete-missing": "فائل «$1» کو حذف نہیں کیا جا سکتا کیونکہ یہ موجود نہیں ہے۔",
+       "filedelete-old-unregistered": "فائل «$1» کا منتخب نسخہ ڈیٹابیس میں موجود نہیں ہے۔",
+       "filedelete-current-unregistered": "«$1» کے عنوان سے کوئی فائل ڈیٹابیس میں موجود نہیں ہے۔",
        "previousdiff": "← پُرانی تدوین",
        "nextdiff": "صفحہ کا نام:",
        "imagemaxsize": "تصویر کی جسامت کی حد:<br /><em>(فائل کے توضیحی صفحات کے لیے)</em>",
        "thumbsize": "تھمب نیل کی جسامت:",
+       "widthheightpage": "$1×$2، $3 {{PLURAL:$3|صفحہ|صفحات}}",
+       "file-info": "فائل کا حجم: $1، MIME قسم: $2",
        "file-info-size": "\n$1 × $2 عکصر (پکسلز)، حجم ملف: $3، MIME قسم: $4",
+       "file-info-size-pages": "$1 × $2 پکسل، فائل کا حجم: $3، MIME قسم: $4، $5 {{PLURAL:$5|صفحہ|صفحات}}",
        "file-nohires": "اس سے بڑی تصمیم دستیاب نہیں۔",
+       "svg-long-desc": "ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل کا حجم: $3",
+       "svg-long-desc-animated": "متحرک ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل کا حجم: $3",
+       "svg-long-error": "نادرست ایس وی جی فائل: $1",
        "show-big-image": "اصل ملف",
        "show-big-image-preview": "اس نمائش کا حجم:$1",
+       "show-big-image-preview-differ": "اس $2 فائل کی $3 نمائش کا حجم: $1",
        "show-big-image-other": "دیگر {{PLURAL:$2|تجویز|تجویزیں}}: $1۔",
        "show-big-image-size": "$1 × $2 pixels",
+       "file-info-gif-looped": "چکردار",
+       "file-info-gif-frames": "$1 {{PLURAL:$1|چوکھٹا|چوکھٹے}}",
+       "file-info-png-looped": "چکردار",
+       "file-info-png-repeat": "$1 {{PLURAL:$1|مرتبہ}} دکھائی گئی",
+       "file-info-png-frames": "$1 {{PLURAL:$1|چوکھٹا|چوکھٹے}}",
+       "file-no-thumb-animation": "<strong>اطلاع: تکنیکی پابندیوں کی وجہ سے اس فائل کے تھمب نیل غیر متحرک ہونگے۔</strong>",
+       "file-no-thumb-animation-gif": "<strong>اطلاع: تکنیکی پابندیوں کی وجہ سے اس طرح کی زیادہ ریزولیوشن والی جی آئی ایف تصویروں کے تھمب نیل غیر متحرک ہونگے۔</strong>",
        "newimages": "نئی فائلوں کی گیلری",
+       "imagelisttext": "ذیل میں $2 <strong>$1</strong> {{PLURAL:$1|فائل|فائلوں}} کی فہرست موجود ہے۔",
+       "newimages-summary": "اس خصوصی صفحہ میں تازہ ترین اپلوڈ شدہ فائلوں کی فہرست موجود ہے۔",
+       "newimages-legend": "مقطار",
+       "newimages-label": "فائل کا نام (یا اس کا جزو):",
+       "newimages-showbots": "روبہ جات کے ذریعہ اپلوڈ کردہ فائلیں دکھائیں",
+       "newimages-hidepatrolled": "مراجعت شدہ اپلوڈ چھپائیں",
+       "noimages": "دیکھنے کیلئے کچھ نہیں ہے۔",
        "ilsubmit": "تلاش",
-       "bydate": "بالحاظ تاریخ",
+       "bydate": "بلحاظ تاریخ",
+       "sp-newimages-showfrom": "$2، $1 کے بعد اپلوڈ کی جانے والی فائلیں دکھائیں",
        "seconds": "{{PLURAL:$1|$1 سیکنڈ}}",
        "minutes": "{{PLURAL:$1|$1 منٹ}}",
        "hours": "{{PLURAL:$1|گھنٹہ|گھنٹے}}",
        "metadata-fields": "تصویر کے میٹاڈیٹا کے وہ خانے جو اس پیغام میں درج ہیں وہ تصویر کے صفحے پر شامل ہوتے ہیں نیز یہ اس وقت ظاہر ہوتے ہیں جب میٹاڈیٹا کو وسیع کیا جائے۔\nالبتہ دیگر خانے ابتدائی طور پر پوشیدہ ہوتے ہیں۔\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-imagewidth": "چوڑائی",
        "exif-imagelength": "لمبائی",
+       "exif-bitspersample": "بٹ فی جزو",
+       "exif-compression": "نظام کمپریشن",
+       "exif-photometricinterpretation": "پکسل کی ترکیب",
        "exif-orientation": "پیشکش",
+       "exif-samplesperpixel": "اجزا کی تعداد",
+       "exif-planarconfiguration": "ڈیٹا کی ترتیب",
+       "exif-ycbcrsubsampling": "Y کا C سے ذیلی نمونہ ساز تناسب",
+       "exif-ycbcrpositioning": "Y اور C کی جگہ",
        "exif-xresolution": "چھوڑاوی دکھاوت",
        "exif-yresolution": "لمباوی دکھاوت",
+       "exif-stripoffsets": "تصویر کے دیٹا کا محل وقوع",
+       "exif-rowsperstrip": "فی پٹی تعداد قطار",
+       "exif-stripbytecounts": "بائٹ فی کمپریس شدہ پٹی",
+       "exif-jpeginterchangeformat": "JPEG SOI کا آفسیٹ",
+       "exif-jpeginterchangeformatlength": "JPEG ڈیٹا کے بائٹ",
+       "exif-whitepoint": "سفید نقطہ کے رنگ",
+       "exif-primarychromaticities": "اساسیات کے رنگ",
        "exif-datetime": "ملف کے تبدیلی کا تاریخ او وقت",
        "exif-imagedescription": "تصویر کا عنوان",
        "exif-make": "کیمرے کا صانع",
        "exif-usercomment": "صارف کے تبصرے",
        "exif-datetimeoriginal": "ڈیٹا بنانے کا تاریخ اور وقت",
        "exif-datetimedigitized": "معددی کا تاریخ اور وقت",
+       "exif-exposuretime": "نمائش کا وقت",
        "exif-exposuretime-format": "$1 سیکنڈ ($2)",
        "exif-fnumber": "ایف نمبر",
+       "exif-exposureprogram": "نمائش کا پروگرام",
+       "exif-spectralsensitivity": "طیفی حساسیت",
+       "exif-isospeedratings": "آئیسو کی رفتار کی درجہ بندی",
+       "exif-shutterspeedvalue": "APEX شٹر کی رفتار",
+       "exif-aperturevalue": "APEX اپرچر",
+       "exif-brightnessvalue": "APEX کی چمک",
+       "exif-exposurebiasvalue": "APEX نمائش کا نقص",
+       "exif-maxaperturevalue": "زیادہ سے زیادہ لینڈ اپرچر",
+       "exif-subjectdistance": "شئی کا فاصلہ",
+       "exif-lightsource": "روشنی کا ماخذ",
+       "exif-flash": "فلیش",
+       "exif-focallength": "عدسہ کی ماسکی لمبائی",
+       "exif-subjectarea": "شئی کی مساحت",
+       "exif-flashenergy": "فلیش توانائی",
+       "exif-focalplanexresolution": "X کی تحلیل کا درجہ ماسکہ",
+       "exif-focalplaneyresolution": "Y کی تحلیل کا درجہ ماسکہ",
+       "exif-focalplaneresolutionunit": "درجہ ماسکہ کی تحلیل کی اکائی",
+       "exif-subjectlocation": "شئی کا محل وقوع",
+       "exif-exposureindex": "نمائش کا اشاریہ",
        "exif-filesource": "فائل کا ماخذ",
+       "exif-scenetype": "منظر کی نوعیت",
+       "exif-customrendered": "تصویر کی شخصی پروسیسینگ",
+       "exif-exposuremode": "نمائش کی حالت",
+       "exif-whitebalance": "وائٹ بیلنس",
+       "exif-digitalzoomratio": "ڈیجیٹل زوم کا تناسب",
+       "exif-focallengthin35mmfilm": "35 ایم ایم کی فلم میں ماسکی لمبائی",
+       "exif-scenecapturetype": "منظر کے گرفت کی نوعیت",
+       "exif-gaincontrol": "منظر کنٹرول",
+       "exif-contrast": "امتیاز",
+       "exif-saturation": "پُری",
+       "exif-sharpness": "تیزی",
+       "exif-gpslatituderef": "شمالی یا جنوبی عرض البلد",
        "exif-gpslatitude": "عرض البلد",
+       "exif-gpslongituderef": "مشرقی یا مغربی طول البلد",
+       "exif-gpslongitude": "طول البلد",
+       "exif-gpsaltituderef": "اتفاع کا حوالہ",
+       "exif-gpsaltitude": "ارتفاع",
+       "exif-gpstimestamp": "جی پی ایس وقت (جوہری گھڑی)",
+       "exif-gpssatellites": "پیمائش کے لیے مستعمل مصنوعی سیارے",
+       "exif-gpsstatus": "ریسیور کی صورت حال",
+       "exif-gpsmeasuremode": "حالت پیمائش",
+       "exif-gpsdop": "پیمائش کی درستی",
+       "exif-gpsspeedref": "رفتار کی اکائی",
+       "exif-gpsspeed": "جی پی ایس ریسیور کی رفتار",
+       "exif-gpstrackref": "حرکت کی سمت کا حوالہ",
+       "exif-gpstrack": "حرکت کی سمت",
+       "exif-gpsimgdirectionref": "تصویر کی سمت کا حوالہ",
+       "exif-gpsimgdirection": "تصویر کی سمت",
+       "exif-gpsmapdatum": "زیر استعمال جیو ڈیٹک سروے",
+       "exif-gpsdestlatituderef": "منزل کے عرض البلد کا حوالہ",
+       "exif-gpsdestlatitude": "منزل کا عرض البلد",
+       "exif-gpsdestlongituderef": "منزل کے طول البلد کا حوالہ",
+       "exif-gpsdestlongitude": "منزل کا طول البلد",
+       "exif-gpsdestbearingref": "منزل کی سمت کا حوالہ",
+       "exif-gpsdestbearing": "منزل کی سمت",
+       "exif-gpsdestdistanceref": "منزل کی مسافت کا حوالہ",
+       "exif-gpsdestdistance": "منزل کی مسافت",
+       "exif-gpsprocessingmethod": "جی پی ایس پراسیسنگ کے طریقہ کا نام",
+       "exif-gpsareainformation": "جی پی ایس کے علاقے کا نام",
+       "exif-gpsdatestamp": "جی پی ایس کی تاریخ",
+       "exif-gpsdifferential": "جی پی ایس کی تفریقی درستی",
+       "exif-jpegfilecomment": "JPEG فائل کا تبصرہ",
+       "exif-keywords": "کلیدی الفاظ",
+       "exif-worldregioncreated": "دنیا کا وہ خطہ جہاں یہ تصویر کھینچی گئی",
+       "exif-countrycreated": "وہ ملک جہاں یہ تصویر کھینچی گئی",
+       "exif-countrycodecreated": "اس ملک کا کوڈ جہاں یہ تصویر کھینچی گئی",
+       "exif-provinceorstatecreated": "وہ ریاست یا صوبہ جہاں یہ تصویر کھینچی گئی",
+       "exif-citycreated": "وہ شہر جہاں یہ تصویر کھینچی گئی",
+       "exif-sublocationcreated": "شہر کا وہ مقام جہاں یہ تصویر کھینچی گئی",
+       "exif-worldregiondest": "دنیا کا دکھایا گیا خطہ",
+       "exif-countrydest": "دکھایا گیا ملک",
+       "exif-countrycodedest": "دکھائے گئے ملک کا کوڈ",
+       "exif-provinceorstatedest": "دکھایا گیا صوبہ یا ریاست",
+       "exif-citydest": "دکھایا گیا شہر",
+       "exif-sublocationdest": "شہر کا دکھایا گیا مقام",
+       "exif-objectname": "مختصر عنوان",
+       "exif-specialinstructions": "خصوصی ہدایات",
+       "exif-headline": "سرخی",
+       "exif-credit": "کریڈٹ/مہیا کار",
+       "exif-source": "ماخذ",
+       "exif-editstatus": "تصویر کی ادارتی کیفیت",
+       "exif-urgency": "فوری طور پر",
        "exif-writer": "مصنف",
        "exif-languagecode": "زبان",
+       "exif-iimversion": "IIM نسخہ",
        "exif-iimcategory": "زمرہ",
+       "exif-iimsupplementalcategory": "تکمیلی زمرہ جات",
+       "exif-datetimeexpires": "اس تاریخ کے بعد استعمال نہ کریں",
+       "exif-datetimereleased": "جاری کردہ بتاریخ",
+       "exif-originaltransmissionref": "منتقلی کے اصل محل وقوع کا کوڈ",
+       "exif-identifier": "شناخت کنندہ",
+       "exif-lens": "زیر استعمال عدسے",
+       "exif-serialnumber": "کیمرے کا نمبر شمار",
+       "exif-cameraownername": "کیمرے کا مالک",
+       "exif-label": "لیبل",
+       "exif-datetimemetadata": "میٹاڈیٹا میں تبدیلی کی آخری تاریخ",
+       "exif-nickname": "تصویر کا غیر رسمی نام",
+       "exif-rating": "درجہ بندی (5 میں سے)",
+       "exif-rightscertificate": "حقوق کے انتظام کا تصدیق نامہ",
+       "exif-copyrighted": "کاپی رائٹ کی صورت حال",
+       "exif-copyrightowner": "کاپی رائٹ کا حامل",
+       "exif-usageterms": "استعمال کے شرائط",
+       "exif-webstatement": "آن لائن موجود کاپی رائٹ کا اعلامیہ",
+       "exif-originaldocumentid": "اصل دستاویز کی منفرد شناخت",
+       "exif-licenseurl": "کاپی رائٹ کے اجازت نامے کا یوآرایل",
+       "exif-morepermissionsurl": "متبادل اجازت ناموں کی معلومات",
+       "exif-attributionurl": "اس کام کو دوربارہ استعمال کرنے کے وقت اس کا ربط دیں",
+       "exif-preferredattributionname": "اس کام کو دوربارہ استعمال کرنے کے وقت اس سے منسوب کریں",
+       "exif-pngfilecomment": "پی این جی فائل کا تبصرہ",
+       "exif-disclaimer": "اظہار لا تعلقی",
+       "exif-contentwarning": "مواد سے متعلق انتباہ",
+       "exif-giffilecomment": "جی آئی ایف فائل کا تبصرہ",
+       "exif-intellectualgenre": "شئی کی قسم",
+       "exif-subjectnewscode": "موضوع کا کوڈ",
+       "exif-compression-1": "غیر کمپریس شدہ",
+       "exif-copyrighted-true": "کاپی رائٹ شدہ",
+       "exif-copyrighted-false": "کاپی رائٹ کی صورت حال متعین نہیں کی گئی",
+       "exif-photometricinterpretation-1": "سیاہ اور سفید (سیاہ 0 ہے)",
+       "exif-unknowndate": "نامعلوم تاریخ",
        "exif-orientation-1": "عام",
+       "exif-componentsconfiguration-0": "موجود نہیں",
+       "exif-exposureprogram-0": "غیر متعین",
+       "exif-exposureprogram-1": "دستی",
+       "exif-exposureprogram-2": "عام پروگرام",
+       "exif-exposureprogram-4": "شٹر کی ترجیح",
+       "exif-subjectdistance-value": "$1 میٹر",
        "exif-meteringmode-0": "نامعلوم",
+       "exif-meteringmode-1": "اوسط",
+       "exif-meteringmode-2": "مرکز کی حجم شدہ اوسط",
+       "exif-meteringmode-3": "داغ",
+       "exif-meteringmode-4": "کثیر داغ",
+       "exif-meteringmode-5": "طرز",
+       "exif-meteringmode-6": "جزوی",
+       "exif-meteringmode-255": "دیگر",
+       "exif-lightsource-0": "نامعلوم",
+       "exif-lightsource-1": "روشنی روز",
+       "exif-lightsource-2": "فلورسینٹ",
+       "exif-lightsource-4": "فلیش",
+       "exif-lightsource-9": "اچھا موسم",
+       "exif-lightsource-10": "ابرآلود موسم",
+       "exif-lightsource-11": "سایہ",
+       "exif-focalplaneresolutionunit-2": "انچ",
+       "exif-sensingmethod-1": "غیر وضاحتی",
+       "exif-customrendered-0": "عام عمل",
+       "exif-customrendered-1": "اپنی مرضی کے مطابق عمل",
+       "exif-scenecapturetype-0": "معیاری",
+       "exif-contrast-0": "عام",
+       "exif-contrast-1": "نرم",
+       "exif-contrast-2": "سخت",
+       "exif-saturation-0": "عام",
+       "exif-sharpness-0": "عام",
+       "exif-sharpness-1": "نرم",
+       "exif-sharpness-2": "سخت",
+       "exif-subjectdistancerange-0": "نامعلوم",
+       "exif-subjectdistancerange-1": "میکرو",
+       "exif-subjectdistancerange-2": "قریبی منظر",
+       "exif-subjectdistancerange-3": "دور سے دیکھیں",
+       "exif-gpslatitude-n": "شمالی عرض البلد",
+       "exif-gpslatitude-s": "جنوبی عرض البلد",
+       "exif-gpslongitude-e": "مشرقی طول البلد",
+       "exif-gpslongitude-w": "مغربی طول البلد",
+       "exif-gpsaltitude-above-sealevel": "سطح سمندر سے $1 {{PLURAL:$1|میٹر}} بلند",
+       "exif-gpsaltitude-below-sealevel": "سطح سمندر سے $1 {{PLURAL:$1|میٹر}} نیچے",
+       "exif-gpsstatus-a": "پیمائش جاری ہے",
+       "exif-gpsspeed-k": "کلو میٹر فی گھنٹہ",
+       "exif-gpsspeed-m": "میل فی گھنٹہ",
+       "exif-gpsdestdistance-k": "کلومیٹر",
+       "exif-gpsdestdistance-m": "میل",
+       "exif-gpsdestdistance-n": "سمندری میل",
+       "exif-gpsdop-excellent": "بہترین ($1)",
+       "exif-gpsdop-good": "بہترین ($1)",
+       "exif-gpsdop-moderate": "معتدل ($1)",
+       "exif-gpsdop-fair": "کمتر ($1)",
+       "exif-gpsdop-poor": "کمزور ($1)",
+       "exif-objectcycle-a": "صرف صبح",
+       "exif-objectcycle-p": "صرف شام",
+       "exif-objectcycle-b": "صبح و شام",
+       "exif-gpsdirection-t": "اصلی سمت",
+       "exif-gpsdirection-m": "مقناطیسی سمت",
+       "exif-ycbcrpositioning-1": "وسط",
        "exif-dc-contributor": "ترمیم کنندگان",
+       "exif-dc-date": "تاریخ",
+       "exif-dc-publisher": "ناشر",
+       "exif-dc-relation": "متعلقہ میڈیا",
+       "exif-dc-rights": "حقوق",
+       "exif-dc-source": "ماخذ میڈیا",
+       "exif-dc-type": "میڈیا کی قسم",
+       "exif-rating-rejected": "مسترد شدہ",
+       "exif-isospeedratings-overflow": "65535 سے بڑا",
+       "exif-iimcategory-ace": "فنون لطیفہ، ثقافت اور تفریح",
+       "exif-iimcategory-clj": "جرم اور قانون",
+       "exif-iimcategory-dis": "آفات اور حادثات",
+       "exif-iimcategory-fin": "معیشت اور کاروبار",
+       "exif-iimcategory-edu": "تعلیم",
+       "exif-iimcategory-evn": "ماحول",
+       "exif-iimcategory-hth": "صحت",
+       "exif-iimcategory-hum": "انسانی دلچسپی",
+       "exif-iimcategory-lab": "مزدوری",
+       "exif-iimcategory-lif": "طرز زندگی اور تفریح",
+       "exif-iimcategory-pol": "سیاست",
+       "exif-iimcategory-rel": "مذہب اور عقیدہ",
+       "exif-iimcategory-sci": "سائنس اور ٹیکنالوجی",
+       "exif-iimcategory-soi": "سماجی مسائل",
+       "exif-iimcategory-spo": "کھیل",
+       "exif-iimcategory-war": "جنگ، تصادم اور بدامنی",
+       "exif-iimcategory-wea": "موسم",
+       "exif-urgency-normal": "عام ($1)",
+       "exif-urgency-low": "کم ($1)",
+       "exif-urgency-high": "اعلیٰ ($1)",
        "namespacesall": "تمام",
        "monthsall": "تمام",
-       "deletedwhileediting": "انتباہ: آپ کے ترمیم شروع کرنے کے بعد یہ صفحہ حذف کیا جا چکا ہے!",
+       "confirmemail": "اپنے برقی پتہ کی تصدیق کریں",
+       "confirmemail_noemail": "آپ نے [[Special:Preferences|اپنی ترجیحات]] میں درست برقی ڈاک پتا نہیں دیا ہے۔",
+       "confirmemail_send": "تصدیقی کوڈ بھیجیں",
+       "confirmemail_sent": "تصدیقی برقی خط بھیجا گیا ہے۔",
+       "confirmemail_oncreate": "آپ کے برقی ڈاک پتے پر تصدیقی کوڈ بھیجا گیا ہے۔\nیہ کوڈ داخل ہونے کے لیے ضروری نہیں، تاہم اس ویکی میں برقی ڈاک پر مبنی کسی سہولت کو فعال کرنے سے قبل آپ کو اس کوڈ کی ضرورت پڑے گی۔",
+       "confirmemail_sendfailed": "{{SITENAME}} آپ کو تصدیقی کوڈ نہیں بھیج سکا۔\nبراہ کرم اپنا برقی ڈاک پتا جانچ لیں کہ کہیں اس میں نادرست حروف تو موجود نہیں۔\n\nڈاک رساں کا جواب: $1",
+       "confirmemail_invalid": "نادرست تصدیقی کوڈ۔\nممکن ہے اس کوڈ کی مدت ختم ہو چکی ہو۔",
+       "confirmemail_needlogin": "اپنے برقی ڈاک پتے کی تصدیق کے لیے براہ کرم $1 ہوں۔",
+       "confirmemail_success": "آپ کے برقی ڈاک پتے کی تصدیق ہو چکی ہے۔\nاب آپ اپنے کھاتے میں [[Special:UserLogin|داخل ہو سکتے ہیں]]۔",
+       "confirmemail_loggedin": "اب آپ کے برقی ڈاک پتے کی تصدیق ہو چکی ہے۔",
+       "confirmemail_subject": "{{SITENAME}} کی جانب سے برقی ڈاک پتے کا تصدیقی پیغام",
+       "confirmemail_invalidated": "برقی ڈاک پتے کی تصدیق منسوخ ہو گئی",
+       "invalidateemail": "برقی ڈاک کی تصدیق منسوخ کریں",
+       "scarytranscludetoolong": "[یوآرایل بہت طویل ہے]",
+       "deletedwhileediting": "<strong>انتباہ:</strong>: آپ کے ترمیم شروع کرنے کے بعد یہ صفحہ حذف کیا جا چکا ہے!",
+       "recreate": "دوبارہ تخلیق کریں",
        "confirm_purge_button": "جی!",
        "confirm-watch-button": "ٹھیک",
        "confirm-watch-top": "اس صفحہ کو آپ کی زیر نظر فہرست میں شامل کریں؟",
+       "confirm-unwatch-button": "ٹھیک ہے",
+       "confirm-unwatch-top": "اس صفحہ کو آپ کی زیرِنظرفہرست سے حذف کر دیا جائے؟",
        "confirm-rollback-button": "ٹھیک ہے",
+       "confirm-rollback-top": "اس صفحے پر ترمیم کو استرجع کیا جائے؟",
        "semicolon-separator": "؛&#32;",
        "comma-separator": "،&#32;",
+       "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← پچھلا",
        "imgmultipagenext": "اگلا →",
        "imgmultigo": "جائیں!",
        "imgmultigoto": "$1 صفحہ پر جائیں",
+       "img-lang-default": "(طے شدہ زبان)",
+       "img-lang-go": "ٹھیک",
+       "ascending_abbrev": "صعودی",
+       "descending_abbrev": "نزولی",
        "table_pager_next": "اگلا صفحہ",
        "table_pager_prev": "پچھلا صفحہ",
        "table_pager_first": "پہلا صفحہ",
        "table_pager_last": "آخری صفحہ",
+       "table_pager_limit": "فی صفحہ $1 آئٹم دکھائیں",
+       "table_pager_limit_label": "آئٹم فی صفحہ:",
+       "table_pager_limit_submit": "چلیں",
+       "table_pager_empty": "کوئی نتیجہ برآمد نہیں ہوا",
        "autosumm-blank": "تمام مندرجات حذف",
+       "autosumm-replace": "\"$1\" سے مواد کی تبدیلی",
        "autoredircomment": "[[$1]] سے رجوع مکرر",
-       "autosumm-new": "نیا صفحہ: $1",
+       "autosumm-new": "«$1» پر مشتمل نیا صفحہ بنایا",
+       "autosumm-newblank": "خالی صفحہ بنایا",
        "size-bytes": "$1 بائٹ",
+       "watchlistedit-normal-title": "زیر نظر فہرست میں ترمیم کریں",
+       "watchlistedit-normal-legend": "زیرنظر فہرست سے عناوین نکالیں",
+       "watchlistedit-normal-submit": "عناوین نکالیں",
+       "watchlistedit-raw-title": "خام زیرِنظرفہرست میں ترمیم کریں",
+       "watchlistedit-raw-legend": "خام زیرِنظرفہرست میں ترمیم کریں",
+       "watchlistedit-raw-titles": "عناوین:",
+       "watchlistedit-raw-submit": "زیرنظر فہرست کی تجدید کریں",
+       "watchlistedit-raw-done": "آپ کی زیرنظر فہرست کی تجدید ہو چکی ہے۔",
+       "watchlistedit-clear-title": "اپنی زیر نظر فہرست صاف کریں",
+       "watchlistedit-clear-legend": "اپنی زیر نظر فہرست صاف کریں",
+       "watchlistedit-clear-titles": "عناوین:",
+       "watchlisttools-clear": "زیرنظر فہرست کی صفائی",
        "watchlisttools-view": "متعلقہ تبدیلیاں دیکھیں",
        "watchlisttools-edit": "زیرِنظرفہرست دیکھیں اور تدوین کریں",
        "watchlisttools-raw": "خام زیرِنظرفہرست تدوین کریں",
        "hijri-calendar-m10": "شوال",
        "hijri-calendar-m11": "ذوالقعدہ",
        "hijri-calendar-m12": "ذوالحجہ",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|تبادلۂ خیال]])",
+       "timezone-local": "مقامی",
        "restricted-displaytitle": "<strong>انتباہ!:</strong> عنوان \"$1\" کو نظر انداز کر دیا گیا ہے کیونکہ یہ متعلقہ صفحہ کے عنوان کا حقیقی متبادل نہیں ہے۔",
-       "version": "ورژن",
+       "version": "نسخہ",
+       "version-extensions": "نصب شدہ توسیعات",
+       "version-skins": "نصب شدہ پوشاکیں",
        "version-specialpages": "خاص صفحات",
+       "version-parserhooks": "تجزیہ کار ہک",
+       "version-variables": "متغیرات",
+       "version-antispam": "فاضل کاری کی روک تھام",
        "version-other": "دیگر",
+       "version-mediahandlers": "میڈیا ناظمین",
+       "version-hooks": "ہک",
+       "version-parser-extensiontags": "پارسر توسیع کے ٹیگ",
+       "version-parser-function-hooks": "پارسر فنکشن کے ہک",
+       "version-hook-name": "ہک کا نام",
+       "version-hook-subscribedby": "مستعمل بذریعہ",
+       "version-no-ext-name": "[کوئی نام نہیں]",
+       "version-license": "میڈیاویکی کا اجازت نامہ",
+       "version-ext-license": "اجازت نامہ",
+       "version-ext-colheader-name": "توسیع",
+       "version-skin-colheader-name": "پوشاک",
+       "version-ext-colheader-version": "نسخہ",
+       "version-ext-colheader-license": "اجازت نامہ",
+       "version-ext-colheader-description": "وضاحت",
        "version-ext-colheader-credits": "مصنف",
+       "version-license-title": "$1 کا اجازت نامہ",
+       "version-license-not-found": "اس توسیع کے اجازت نامے سے متعلق تفصیلی معلومات دستیاب نہین ہوئی۔",
+       "version-credits-title": "$1 کے انتسابات",
+       "version-credits-not-found": "اس توسیع کے انتسابات سے متعلق تفصیلی معلومات دستیاب نہین ہوئی۔",
+       "version-poweredby-credits": "پیش نظر ویکی <strong>[https://www.mediawiki.org/ میڈیاویکی]</strong> کی تقویت یافتہ ہے، جملہ حقوق محفوظ © 2001-$1 $2 بنام",
        "version-poweredby-others": "دیگر",
+       "version-poweredby-translators": "translatewiki.net کے مترجمین",
+       "version-credits-summary": "ہم درج ذیل اشخاص کی [[Special:Version|میڈیاویکی]] کی تعمیر میں شرکت کرنے کا اعتراف کرتے ہیں۔",
+       "version-license-info": "میڈیاویکی ایک آزاد سافٹ ویئر ہے؛ آپ اسے آزاد سافٹ ویئر فاؤنڈیش کی جانب سے شائع کردہ گنو عام عوامی اجازت نامہ کی شرائط کے تحت دوبارہ شائع/یا اس میں تبدیلی کر سکتے ہیں؛ خواہ مذکورہ اجازت نامہ کے نسخہ دوم کے تحت شائع کریں یا (حسب منشا) کسی جدید نسخے کے تحت۔\n\nیقیناً میڈیاویکی کو اس امید کے ساتھ شائع کیا گیا ہے کہ یہ مفید ثابت ہوگا، لیکن کوئی ضمانت نہیں ہے؛ نہ قابل تجارت ہونے کی اطلاقی ضمانت ہے اور نہ کسی مخصوص مقصد کے لیے موزوں ہونے کی۔ مزید تفصیلات کے لئے گنو کا عام عوامی اجازت نامہ ملاحظہ فرمائیں۔\n\nآپ کو اس پروگرام کے ساتھ  [{{SERVER}}{{SCRIPTPATH}}/COPYING گنو عام عوامی اجازت نامہ کا ایک نسخہ] بھی موصول ہوگا؛ اگر یہ نسخہ نہ ملے تو Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA پتے پر خط و کتابت کریں یا [//www.gnu.org/licenses/old-licenses/gpl-2.0.html اسے آن لائن پڑھیں]۔",
+       "version-software": "نصب شدہ سافٹ ویئر",
+       "version-software-product": "مصنوعات",
+       "version-software-version": "نسخہ",
+       "version-entrypoints": "یوآرایل کا نقطہ آمد",
+       "version-entrypoints-header-entrypoint": "نقطہ آمد",
+       "version-entrypoints-header-url": "یوآرایل",
+       "version-libraries": "نصب شدہ کتب خانے",
+       "version-libraries-library": "کتب خانہ",
+       "version-libraries-version": "نسخہ",
+       "version-libraries-license": "اجازت نامہ",
+       "version-libraries-description": "وضاحت",
        "version-libraries-authors": "مصنف",
+       "redirect": "فائل، صارف، صفحہ، نسخہ، یا نوشتہ کی شناخت سے رجوع مکرر",
+       "redirect-summary": "اس خصوصی صفحہ کے ذریعہ فائل (درج کردہ فائل نام)، صفحہ (صفحہ یا نسخہ کا درج کردہ شناختی نمبر)، صفحہ صارف (صارف کا درج کردہ شناختی نمبر) یا اندراج نوشتہ (نوشتہ کا درج کردہ شناختی نمبر) کا رجوع مکرر حاصل کیا جا سکتا ہے۔ طریقہ استعمال: [[{{#Special:Redirect}}/file/Example.jpg]]، \n[[{{#Special:Redirect}}/page/64308]]، [[{{#Special:Redirect}}/revision/328429]] یا [[{{#Special:Redirect}}/user/101]] یا \n[[{{#Special:Redirect}}/logid/186]]۔",
+       "redirect-submit": "چلیں",
+       "redirect-lookup": "تلاش:",
+       "redirect-value": "قدر:",
+       "redirect-user": "صارف کی شناخت",
+       "redirect-page": "صفحہ کی شناخت",
+       "redirect-revision": "صفحہ کا نسخہ",
+       "redirect-file": "فائل کا نام",
+       "redirect-logid": "نوشتہ کی شناخت",
+       "redirect-not-exists": "یہ قدر نہیں ملی",
+       "fileduplicatesearch": "مکرر فائلوں کی تلاش",
+       "fileduplicatesearch-summary": "ہیش قدروں کے مطابق مکرر فائلوں کو تلاش کریں۔",
+       "fileduplicatesearch-filename": "فائل کا نام:",
        "fileduplicatesearch-submit": "تلاش",
+       "fileduplicatesearch-info": "$1 × $2 پکسل <br />فائل کا حجم: $3<br />MIME قسم: $4",
+       "fileduplicatesearch-result-1": "فائل «$1» کی کوئی نقل نہیں ہے۔",
+       "fileduplicatesearch-result-n": "فائل «$1» کی {{PLURAL:$2|1 نقل ہے|$2 نقلیں ہیں}}۔",
+       "fileduplicatesearch-noresults": "«$1» کے نام سے کوئی فائل نہیں مل سکی۔",
        "specialpages": "خصوصی صفحات",
+       "specialpages-note-top": "وضاحت",
+       "specialpages-note": "* عام خصوصی صفحات۔\n* <span class=\"mw-specialpagerestricted\">ممنوع خصوصی صفحات</span>",
+       "specialpages-group-maintenance": "نگہداشت کی رپورٹیں",
+       "specialpages-group-other": "دیگر خصوصی صفحات",
+       "specialpages-group-login": "کھاتہ کھولیں یا اندراج کریں",
+       "specialpages-group-changes": "حالیہ تبدیلیاں اور نوشتہ جات",
+       "specialpages-group-media": "میڈیا رپورٹیں اور اپلوڈ کردہ",
+       "specialpages-group-users": "صارفین اور اختیارات",
+       "specialpages-group-highuse": "کثیر مستعمل صفحات",
        "specialpages-group-pages": "فہارست صفحات",
+       "specialpages-group-pagetools": "آلات صفحہ",
+       "specialpages-group-wiki": "ڈیٹا اور آلات",
+       "specialpages-group-redirects": "رجوع مکرر کے حامل خصوصی صفحات",
+       "specialpages-group-spam": "آلات فاضل کاری",
+       "specialpages-group-developer": "آلات ترقی دہندہ",
+       "blankpage": "خالی صفحہ",
+       "intentionallyblankpage": "اس صفحہ کو دانستہ خالی چھوڑا گیا ہے۔",
+       "external_image_whitelist": "#اس سطر کو ہو بہو ایسا ہی رہنے دیں<pre>\n#ذیل میں ریجیکس کی عبارتیں درج کریں (محض // کے درمیان)\n#ان عبارتوں کی بیرونی تصویروں کے روابط سے مطابقت کی جائے گی\n#جو مطابق ہو جائیں وہ تصویر کے طور پر نظر آئیں گے ورنہ محض تصویر کا ربط ظاہر ہوگا\n# علامت # سے شرع ہونے والی سطروں کو تبصرہ سمجھا جائے گا\n#چھوٹے بڑے حروف کو نظر انداز کیا جائے گا\n\nریجیکس کی تمام عبارتوں کو اس سطر کے اوپر رکھیں۔ اس سطر کو ہو بہو ایسا ہی رہنے دیں</pre>",
+       "tags": "تبدیلی کے درست ٹیگ",
        "tag-filter": "[[Special:Tags|لوحہ]] فلٹر:",
-       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ|ٹیگ}}]]: $2)",
+       "tag-filter-submit": "مقطار",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ}}]]: $2)",
+       "tag-mw-contentmodelchange": "مواد کے ماڈل میں تبدیلی",
+       "tag-mw-contentmodelchange-description": "ترامیم جو صفحہ کے [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel مواد کے ماڈل کو تبدیل کرتی ہیں]",
+       "tags-title": "ٹیگ",
+       "tags-intro": "اس صفحہ میں ان تمام ٹیگوں کی فہرست درج ہے، جنہیں سافٹ ویئر کسی ترمیم پر مفہوم کے ساتھ نشان زد کرتا ہے۔",
+       "tags-tag": "ٹیگ کا نام",
+       "tags-display-header": "تبدیلی کی فہرستوں میں نمائش",
+       "tags-description-header": "مکمل وضاحت",
        "tags-source-header": "ماخذ",
        "tags-active-header": "فعال؟",
+       "tags-hitcount-header": "ٹیگ شدہ تبدیلیاں",
+       "tags-actions-header": "اقدامات",
        "tags-active-yes": "ہاں",
        "tags-active-no": "نہیں",
+       "tags-source-extension": "سافٹ ویئر کے وضاحت کردہ",
+       "tags-source-manual": "صارفین اور روبہ جات کی جانب سے دستی طور پر لگائے گئے",
+       "tags-source-none": "اب مستعمل نہیں",
+       "tags-edit": "ترمیم",
        "tags-delete": "حذف",
        "tags-activate": "فعال کریں",
        "tags-deactivate": "غیر فعال  کریں",
        "tags-hitcount": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "tags-create-submit": "تخلیق",
+       "tags-manage-no-permission": "آپ کو تبدیلی کے ٹیگوں کے انتظام کی اجازت نہیں ہے۔",
+       "tags-manage-blocked": "آپ بحالت پابندی تبدیلی کے ٹیگوں کا انتظام نہیں کر سکتے۔",
+       "tags-create-heading": "نیا ٹیگ بنائیں",
+       "tags-create-explanation": "ابتدائی طور پر نو تخلیق شدہ ٹیگ صارفین اور روبہ جات کے استعمال کے لیے دستیاب ہونگے۔",
+       "tags-create-tag-name": "ٹیگ کا نام:",
+       "tags-create-reason": "وجہ:",
+       "tags-create-submit": "بنائیں",
+       "tags-create-no-name": "آپ نے ایک ٹیگ کا نام دینا ہو گا۔",
+       "tags-create-invalid-chars": "ٹیگوں کے نام میں فاصلے (<code>,</code>) یا فارورڈ سلیش (<code>/</code>) نہیں ہونے چاہئیں۔",
+       "tags-create-invalid-title-chars": "ٹیگوں کے نام میں ایسے حروف کے استعمال کی اجازت نہیں جنہیں صفحات کے عناوین میں استعمال نہیں کیا جا سکتا۔",
+       "tags-create-already-exists": "ٹیگ \"$1\" پہلے سے موجود ہے۔",
+       "tags-create-warnings-above": " \"$1\" نامی ٹیگ بنانے کے دوران میں درج ذیل {{PLURAL:$2|انتباہ دیا گیا|انتباہات دیے گئے}}:",
+       "tags-create-warnings-below": "کیا آپ واقعی ٹیگ سازی جاری رکھنا چاہتے ہیں؟",
        "tags-delete-title": "حذف ٹیگ",
+       "tags-delete-explanation-initial": "آپ «$1» ٹیگ کو ڈیٹابیس سے حذف کرنے جا رہے ہیں۔",
        "tags-delete-reason": "وجہ:",
+       "tags-delete-not-found": "«$1» ٹیگ موجود نہیں ہے۔",
+       "tags-delete-no-permission": "آپ کو تبدیلی کے ٹیگ حذف کرنے کی اجازت نہیں۔",
        "tags-activate-title": "ٹیگ فعال",
+       "tags-activate-question": "آپ «$1» ٹیگ کو فعال کرنے جا رہے ہیں۔",
        "tags-activate-reason": "وجہ:",
+       "tags-activate-not-allowed": "«$1» ٹیگ کو فعال کرنا ممکن نہیں۔",
+       "tags-activate-not-found": "«$1» ٹیگ موجود نہیں ہے۔",
        "tags-activate-submit": "فعال",
        "tags-deactivate-title": "ٹیگ غیر فعال",
+       "tags-deactivate-question": "آپ «$1» ٹیگ کو غیر فعال کرنے جا رہے ہیں۔",
        "tags-deactivate-reason": "وجہ:",
+       "tags-deactivate-not-allowed": "«$1» ٹیگ کو غیر فعال کرنا ممکن نہیں۔",
        "tags-deactivate-submit": "غیر فعال",
+       "tags-apply-no-permission": "آپ کو اپنی تبدیلیوں پر تبدیلی کے ٹیگوں کو نافذ کرنے کی اجازت نہیں ہے۔",
+       "tags-apply-blocked": "بحالت پابندی آپ اپنی تبدیلیوں پر تبدیلی کے ٹیگ نافذ نہیں کر سکتے۔",
+       "tags-apply-not-allowed-one": "«$1» ٹیگ کو دستی طور پر نافذ کرنے کی اجازت نہیں ہے۔",
+       "tags-apply-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر نافذ کرنے کی اجازت نہیں ہے: $1",
+       "tags-update-no-permission": "آپ کو انفرادی نسخوں یا نوشتہ کے اندراجات سے تبدیلی کے ٹیگوں کو ہٹانے یا ان میں لگانے کی اجازت نہیں ہے۔",
+       "tags-update-blocked": "بحالت پابندی آپ تبدیلی کے ٹیگوں کو لگا یا ہٹا نہیں سکتے۔",
+       "tags-update-add-not-allowed-one": "«$1» ٹیگ کو دستی طور پر لگانے کی اجازت نہیں ہے۔",
+       "tags-update-add-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر لگانے کی اجازت نہیں ہے: $1",
+       "tags-update-remove-not-allowed-one": "«$1» کو ہٹانے کی اجازت نہیں ہے۔",
+       "tags-update-remove-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر ہٹانے کی اجازت نہیں ہے: $1",
        "tags-edit-title": "ٹیگ میں ترمیم",
+       "tags-edit-manage-link": "ٹیگ کا انتظام",
+       "tags-edit-revision-selected": "[[:$2]] {{PLURAL:$1|کا منتخب نسخہ|کے منتخب نسخے}}:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|منتخب واقعۂ نوشتہ|منتخب واقعاتِ نوشتہ}}:",
+       "tags-edit-revision-legend": "{{PLURAL:$1|اس نسخے|ان تمام $1 نسخوں}} میں ٹیگ لگائیں یا ان سے ہٹائیں",
+       "tags-edit-logentry-legend": "{{PLURAL:$1|اس اندراج نوشتہ|ان تمام $1 اندراجات نوشتہ}} میں ٹیگ لگائیں یا ان سے ہٹائیں",
+       "tags-edit-existing-tags": "موجودہ ٹیگ:",
+       "tags-edit-existing-tags-none": "<em>کوئی نہیں</em>",
+       "tags-edit-new-tags": "نئے ٹیگ:",
+       "tags-edit-add": "ان ٹیگ کا اضافہ کریں:",
+       "tags-edit-remove": "یہ ٹیگ نکالیں:",
+       "tags-edit-remove-all-tags": "(تمام ٹیگ نکال دیں)",
+       "tags-edit-chosen-placeholder": "کچھ ٹیگ منتخب کریں",
+       "tags-edit-chosen-no-results": "اس کے مشابہ کوئی ٹیگ نہیں ملا",
        "tags-edit-reason": "وجہ:",
+       "tags-edit-revision-submit": "{{PLURAL:$1|اس نسخے|$1 نسخوں}} میں تبدیلیاں نافذ کریں",
+       "tags-edit-logentry-submit": "{{PLURAL:$1|اس اندراج نوشتہ|$1 اندراجات نوشتہ}} میں تبدیلیاں نافذ کریں",
+       "tags-edit-success": "تبدیلیاں نافذ کر دی گئیں۔",
+       "tags-edit-failure": "تبدیلیاں نافذ نہیں کی جا سکیں:\n$1",
+       "tags-edit-nooldid-title": "نادرست ہدف نسخہ",
+       "comparepages": "صفحات کا موازنہ کریں",
+       "compare-page1": "صفحہ 1",
+       "compare-page2": "صفحہ 2",
+       "compare-rev1": "نظرثانی 1",
+       "compare-rev2": "نظرثانی 2",
+       "compare-submit": "موازنہ",
+       "compare-invalid-title": "آپ کا اختصاصی عنوان غلط ہے۔",
+       "compare-title-not-exists": "آپ کا اختصاصی عنوان موجود نہیں۔",
+       "compare-revision-not-exists": "آپ کی اختصاصی نظرثانی موجود نہیں۔",
+       "dberr-problems": "افسوس! اس ویب سائٹ کو تکنیکی مشکلات کا سامنا ہے۔",
+       "dberr-again": "چند منٹ انتظار کے بعد دوبارہ کوشش کریں۔",
+       "htmlform-invalid-input": "آپ کے اندراج میں کچھ مسائل ہیں۔",
+       "htmlform-select-badoption": "آپ کی درج کردہ قدر درست اختیار نہیں ہے۔",
+       "htmlform-int-invalid": "آپ کی درج کردہ قدر عدد صحیح نہیں ہے۔",
+       "htmlform-float-invalid": "آپ کی درج کردہ قدر عدد نہیں ہے۔",
+       "htmlform-int-toolow": "آپ کی درج کردہ قدر کمترین حد یعنی $1 سے کم ہے۔",
+       "htmlform-int-toohigh": "آپ کی درج کردہ قدر $1 سے زیادہ ہے۔",
+       "htmlform-required": "یہ قدر درکار ہے۔",
+       "htmlform-submit": "ٹھیک ہے",
+       "htmlform-reset": "رد ترامیم",
        "htmlform-selectorother-other": "دیگر",
        "htmlform-no": "نہیں",
        "htmlform-yes": "ہاں",
+       "htmlform-chosen-placeholder": "ایک اختیار منتخب کریں",
+       "htmlform-cloner-create": "مزید اضافہ کریں",
+       "htmlform-cloner-delete": "حذف",
+       "htmlform-cloner-required": "کم ازکم ایک قدر درکار ہے۔",
+       "htmlform-title-badnamespace": "[[:$1]] صفحہ \"{{ns:$2}}\" نام فضا میں موجود نہیں۔",
+       "htmlform-title-not-creatable": "«$1» عنوان قابل تخلیق نہیں",
+       "htmlform-title-not-exists": "$1 موجود نہیں ہے۔",
+       "htmlform-user-not-exists": "<strong>$1</strong> موجود نہیں ہے۔",
+       "htmlform-user-not-valid": "<strong>$1</strong> درست صارف نام نہیں ہے۔",
        "logentry-delete-delete": "$1 {{GENDER:$2|حذف کیا گیا}} صفحہ $3",
+       "logentry-delete-restore": "$1 نے صفحہ $3 کو {{GENDER:$2|بحال کیا}}",
+       "logentry-delete-event": "$1 نے $3 میں موجود {{PLURAL:$5|ایک واقعۂ نوشتہ|$5 واقعات نوشتہ}} کی مرئیت کو {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-delete-revision": "$1 نے $3 میں موجود {{PLURAL:$5|ایک نسخے|$5 نسخوں}} کی مرئیت کو {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-delete-event-legacy": "$1 نے $3 میں موجود واقعات نوشتہ کی مرئیت کو {{GENDER:$2|تبدیل کیا}}",
+       "logentry-delete-revision-legacy": "$1 نے $3 میں موجود نسخوں کی مرئیت کو {{GENDER:$2|تبدیل کیا}}",
+       "logentry-suppress-delete": "$1 نے صفحہ $3 کو {{GENDER:$2|پوشیدہ کیا}}",
+       "logentry-suppress-event": "$1 نے $3 میں موجود {{PLURAL:$5|ایک واقعۂ نوشتہ|$5 واقعات نوشتہ}} کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-suppress-revision": "$1 نے $3 میں موجود {{PLURAL:$5|ایک نسخے|$5 نسخوں}} کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-suppress-event-legacy": "$1 نے $3 میں موجود واقعات نوشتہ کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}",
+       "logentry-suppress-revision-legacy": "$1 نے $3 میں موجود نسخوں کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}",
+       "revdelete-content-hid": "مواد کو پوشیدہ کرد یا گیا",
+       "revdelete-summary-hid": "خلاصہ ترمیم کو پوشیدہ کر دیا گیا",
+       "revdelete-uname-hid": "صارف نام کو پوشیدہ کر دیا گیا",
+       "revdelete-content-unhid": "مواد کی پوشیدگی ختم کی گئی",
+       "revdelete-summary-unhid": "خلاصہ ترمیم کی پوشیدگی ختم کی گئی",
+       "revdelete-uname-unhid": "صارف نام کی پوشیدگی ختم کی گئی",
+       "revdelete-restricted": "منتظمین کو محدود کر دیا گیا",
+       "revdelete-unrestricted": "منتظمین کے لیے کھول دیا گیا",
+       "logentry-block-block": "$1 نے {{GENDER:$4|$3}} پر $5 کے وقت اختتام تک {{GENDER:$2|پابندی لگائی}} $6",
+       "logentry-block-unblock": "$1 نے {{GENDER:$4|$3}} سے {{GENDER:$2|پابندی اٹھائی}}",
        "logentry-move-move": "$1 نے صفحہ $3 کو $4 کی جانب منتقل کیا",
        "logentry-move-move_redir-noredirect": "$1 نے صفحہ $3 کو رجوع مکرر چھوڑے بغیر $4 کی جانب جو رجوع مکر تھا {{GENDER:$2|منتقل کیا}}",
        "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
        "logentry-protect-modify": "$1 نے $3 کا درجۂ حفاظت {{GENDER:$2|تبدیل کیا}} $4",
        "logentry-rights-rights": "$1 نے {{GENDER:$6|$3}} کی گروہی رکنیت از $4 تا $5 {{GENDER:$2|تبدیل کی}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
+       "log-name-tag": "نوشتہ ٹیگ",
        "rightsnone": "(کچھ نہیں)",
        "revdelete-summary": "خلاصۂ تدوین",
+       "feedback-back": "واپس",
+       "feedback-cancel": "منسوخ",
+       "feedback-close": "مکمل",
+       "feedback-dialog-title": "تبصرہ روانہ کریں",
+       "feedback-error-title": "نقص",
+       "feedback-error1": "نقص: اے پی آئی کی جانب سے غیر معروف نتیجہ",
+       "feedback-error2": "نقص: ترمیم ناکام ہو گئی",
+       "feedback-error3": "نقص: اے پی آئی سے کوئی جواب نہیں",
+       "feedback-message": "پیغام:",
+       "feedback-subject": "موضوع:",
+       "feedback-submit": "روانہ کریں",
        "feedback-thanks-title": "شکریہ!",
        "searchsuggest-search": "تلاش",
        "searchsuggest-containing": "نتائج...",
+       "api-error-file-too-large": "آپ کی ارسال کردہ فائل بہت بڑی تھی۔",
+       "api-error-filename-tooshort": "فائل کا نام انتہائی مختصر ہے۔",
+       "api-error-filetype-banned": "فائل کی اس قسم پر پابندی عائد ہے۔",
+       "api-error-filetype-missing": "فائل کی توسیع موجود نہیں",
+       "api-error-illegal-filename": "اس نام کی فائل ممنوع ہے۔",
+       "api-error-unclassified": "نامعلوم نقص واقع ہوا۔",
+       "api-error-unknown-code": "نامعلوم نقص: \"$1\" ۔",
+       "api-error-uploaddisabled": "اس ویکی پر اپلوڈ کی سہولت غیر فعال ہے۔",
+       "api-error-verification-error": "شاید فائل خراب ہے یا غلط توسیع کی حامل ہے۔",
+       "duration-seconds": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "duration-minutes": "$1 {{PLURAL:$1|منٹ}}",
+       "duration-hours": "$1 {{PLURAL:$1|گھنٹہ|گھنٹے}}",
        "duration-days": "$1 {{PLURAL:$1|دن}}",
+       "duration-weeks": "$1 {{PLURAL:$1|ہفتہ|ہفتے}}",
+       "duration-years": "$1 {{PLURAL:$1|سال}}",
+       "duration-decades": "$1 {{PLURAL:$1|دہائی|دہائیاں}}",
+       "duration-centuries": "$1 {{PLURAL:$1|صدی|صدیاں}}",
+       "duration-millennia": "$1 {{PLURAL:$1|ہزاریے|ہزاریہ}}",
+       "limitreport-cputime-value": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "limitreport-walltime-value": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|بائٹ}}",
+       "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|بائٹ}}",
        "expandtemplates": "سانچے کو وسیع کریں",
        "expand_templates_input": "ان پٹ متن:",
        "expand_templates_output": "نتیجہ",
        "expand_templates_ok": "ٹھیک ہے",
        "expand_templates_remove_comments": "تبصرے حذف کریں",
+       "expand_templates_generate_rawhtml": "خام اہچ ٹی ایم ایل دکھائیں",
        "expand_templates_preview": "پیش نظارہ",
+       "pagelanguage": "صفحے کی زبان تبدیل کریں",
        "pagelang-name": "صفحہ",
        "pagelang-language": "زبان",
+       "pagelang-use-default": "طے شدہ زبان استعمال کرتا ہے",
+       "pagelang-select-lang": "زبان کا انتخاب کریں",
+       "pagelang-submit": "ٹھیک ہے",
+       "right-pagelang": "صفحے کی زبان تبدیل کریں",
+       "action-pagelang": "صفحے کی زبان تبدیل کریں",
+       "log-name-pagelang": "نوشتہ تبدیلی زبان",
+       "log-description-pagelang": "ذیل میں زبانوں کے صفحہ میں ہونے والی تبدیلیوں کا نوشتہ ہے۔",
+       "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (فعال)",
+       "mediastatistics": "میڈیا کے اعداد و شمار",
+       "mediastatistics-table-mimetype": "MIME قسم",
+       "mediastatistics-table-extensions": "ممکنہ توسیعات",
+       "mediastatistics-table-count": "فائلوں کی تعداد",
+       "mediastatistics-table-totalbytes": "مشترکہ حجم",
+       "mediastatistics-header-unknown": "نامعلوم",
+       "mediastatistics-header-bitmap": "بٹ میپ تصویریں",
+       "mediastatistics-header-drawing": "خاکے (ویکٹر تصویریں)",
+       "mediastatistics-header-audio": "آڈیو",
+       "mediastatistics-header-video": "ویڈیو",
+       "mediastatistics-header-office": "دفتر",
+       "mediastatistics-header-text": "متنی",
+       "mediastatistics-header-executable": "قابل تنفیذ",
+       "mediastatistics-header-archive": "کمپریس شدہ فارمیٹ",
+       "mediastatistics-header-total": "تمام فائلیں",
+       "json-error-unknown": "جے سن میں کوئی مسئلہ تھا۔ نقص: $1",
+       "json-error-syntax": "نحوی غلطی",
+       "headline-anchor-title": "اس قطعہ کا ربط",
        "special-characters-group-latin": "لاطینی محارف",
        "special-characters-group-latinextended": "وسیع لاطینی",
+       "special-characters-group-ipa": "آئی پی اے",
        "special-characters-group-symbols": "علامات",
        "special-characters-group-greek": "یونانی",
+       "special-characters-group-greekextended": "وسیع یونانی",
+       "special-characters-group-cyrillic": "سیریلیائی",
        "special-characters-group-arabic": "عربی",
        "special-characters-group-arabicextended": "عربی توسیع شدہ",
        "special-characters-group-persian": "فارسی",
        "special-characters-group-telugu": "تلگو",
        "special-characters-group-sinhala": "سنگھالی",
        "special-characters-group-gujarati": "گجراتی",
+       "special-characters-group-devanagari": "دیوناگری",
        "special-characters-group-thai": "سیامی",
        "special-characters-group-lao": "لاوسی",
-       "special-characters-group-khmer": "کھمیری"
+       "special-characters-group-khmer": "کھمیری",
+       "special-characters-title-endash": "خط فاصلہ",
+       "special-characters-title-emdash": "خط فاصل کشیدہ",
+       "special-characters-title-minus": "علامت وضع",
+       "mw-widgets-dateinput-no-date": "کسی تاریخ کو منتخب نہیں کیا گیا",
+       "mw-widgets-titleinput-description-new-page": "صفحہ ابھی تک موجود نہیں",
+       "mw-widgets-titleinput-description-redirect": "$1 کا رجوع مکرر",
+       "sessionprovider-generic": "$1 کی نشستیں",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "کوکی پر مبنی نشستیں",
+       "sessionprovider-nocookies": "شاید کوکی غیر فعال ہے۔ براہ کرم کوکی فعال کرنے کے بعد دوبارہ کوشش کریں۔",
+       "log-action-filter-block": "پابندی کی نوعیت:",
+       "log-action-filter-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-protect": "حفاظت کی نوعیت:",
+       "log-action-filter-rights": "تبدیلیٔ اختیار کی نوعیت:",
+       "log-action-filter-suppress": "پوشیدگی کی نوعیت:",
+       "log-action-filter-upload": "اپلوڈ کی نوعیت:",
+       "log-action-filter-all": "تمام",
+       "log-action-filter-block-block": "پابندی",
+       "log-action-filter-block-reblock": "تبدیلیٔ پابندی",
+       "log-action-filter-block-unblock": "پابندی ختم",
+       "log-action-filter-contentmodel-change": "مواد کے ماڈل کی تبدیلی",
+       "log-action-filter-contentmodel-new": "غیر معیاری contentmodel پر مشتمل صفحہ سازی",
+       "log-action-filter-delete-delete": "صفحہ کی حذف شدگی",
+       "log-action-filter-delete-restore": "صفحہ کی بحالی",
+       "log-action-filter-delete-event": "نوشتہ کی حذف شدگی",
+       "log-action-filter-delete-revision": "ترمیم کی حذف شدگی",
+       "log-action-filter-import-interwiki": "ماورائے ویکی درآمد",
+       "log-action-filter-import-upload": "ایکس ایم ایل اپلوڈ کی مدد سے درآمد",
+       "log-action-filter-managetags-create": "ٹیگ سازی",
+       "log-action-filter-managetags-delete": "ٹیگ کی حذف شدگی",
+       "log-action-filter-managetags-activate": "ٹیگ کی فعالی",
+       "log-action-filter-managetags-deactivate": "ٹیگ کی غیر فعالی",
+       "log-action-filter-move-move": "رجوع مکررات کو برتحریر کیے بغیر منتقلی",
+       "log-action-filter-move-move_redir": "رجوع مکررات کی برتحریری کے ساتھ منتقلی",
+       "log-action-filter-newusers-create": "گمنام صارف کی تخلیق",
+       "log-action-filter-newusers-create2": "مندرج صارف کی تخلیق",
+       "log-action-filter-newusers-autocreate": "خودکار تخلیق",
+       "log-action-filter-newusers-byemail": "برقی خط کے ذریعہ بھیجے گئے پاس ورڈ کی مدد سے تخلیق",
+       "log-action-filter-patrol-patrol": "دستی مراجعت",
+       "log-action-filter-patrol-autopatrol": "خودکار مراجعت",
+       "log-action-filter-protect-protect": "حفاظت",
+       "log-action-filter-protect-modify": "تبدیلیٔ حفاظت",
+       "log-action-filter-protect-unprotect": "اختتام حفاظت",
+       "log-action-filter-protect-move_prot": "منتقلیٔ حفاظت",
+       "log-action-filter-rights-rights": "دستی تبدیلی",
+       "log-action-filter-rights-autopromote": "خود کار تبدیلی",
+       "log-action-filter-suppress-event": "نوشتہ کی پوشیدگی",
+       "log-action-filter-suppress-revision": "نسخے کی پوشیدگی",
+       "log-action-filter-suppress-delete": "صفحہ کی پوشیدگی",
+       "log-action-filter-suppress-block": "بذریعہ پابندی صارف کی پوشیدگی",
+       "log-action-filter-suppress-reblock": "بذریعہ باز پابندی صارف کی پوشیدگی",
+       "log-action-filter-upload-upload": "نیا اپلوڈ",
+       "log-action-filter-upload-overwrite": "دوبارہ لوڈ",
+       "authmanager-authn-autocreate-failed": "خودکار مقامی کھاتہ سازی ناکام: $1",
+       "authmanager-create-disabled": "کھاتہ سازی غیر فعال ہے۔",
+       "authmanager-authplugin-setpass-failed-title": "پاس ورڈ کی تبدیلی ناکام رہی",
+       "authmanager-authplugin-setpass-bad-domain": "نادرست ڈومین۔",
+       "authmanager-autocreate-noperm": "خودکار کھاتہ سازی کی اجازت نہیں ہے۔",
+       "authmanager-autocreate-exception": "سابقہ نقص کی وجہ سے عارضی طور پر خودکار کھاتہ سازی غیر فعال ہے۔",
+       "authmanager-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ مندرج نہیں ہے۔",
+       "authmanager-username-help": "صارف نام برائے تصدیق",
+       "authmanager-password-help": "پاس ورڈ برائے تصدیق",
+       "authmanager-domain-help": "ڈومین برائے خارجی تصدیق",
+       "authmanager-retype-help": "تصدیق کے لیے دوبارہ پاس ورڈ درج کریں",
+       "authmanager-email-label": "برقی خط",
+       "authmanager-email-help": "برقی ڈاک پتا",
+       "authmanager-realname-label": "حقیقی نام",
+       "authmanager-realname-help": "صارف کا حقیقی نام",
+       "authmanager-provider-password": "پاس ورڈ پر مبنی تصدیق",
+       "authmanager-provider-password-domain": "پاس ورڈ اور ڈومین پر مبنی تصدیق",
+       "authmanager-provider-temporarypassword": "عارضی پاس ورڈ",
+       "authprovider-confirmlink-request-label": "جن کھاتوں کو مربوط کرنا ہو",
+       "authprovider-confirmlink-success-line": "$1: کامیابی سے مربوط کر دیے گئے۔",
+       "authprovider-resetpass-skip-label": "آگے بڑھیں",
+       "authprovider-resetpass-skip-help": "پاس ورڈ کی ترتیب نو کو رہنے دیں",
+       "authform-notoken": "ٹوکن مفقود",
+       "authform-wrongtoken": "غلط ٹوکن",
+       "specialpage-securitylevel-not-allowed-title": "اجازت نہیں",
+       "cannotauth-not-allowed-title": "اجازت رد کر دی گئی",
+       "cannotauth-not-allowed": "آپ کو اس صفحہ کے استعمال کی اجازت نہیں",
+       "changecredentials": "وثیقوں کو تبدیل کریں",
+       "changecredentials-submit": "وثیقوں کو تبدیل کریں",
+       "changecredentials-success": "آپ کے وثیقے تبدیل کر دیے گئے۔",
+       "removecredentials": "وثیقے حذف کریں",
+       "removecredentials-submit": "وثیقے حذف کریں",
+       "removecredentials-success": "آپ کے وثیقے حذف کر دیے گئے۔",
+       "credentialsform-provider": "وثیقوں کی نوعیت:",
+       "credentialsform-account": "کھاتے کا نام:",
+       "cannotlink-no-provider-title": "قابل ربط کھاتے موجود نہیں ہیں",
+       "cannotlink-no-provider": "قابل ربط کھاتے موجود نہیں ہیں۔",
+       "linkaccounts": "کھاتوں کو مربوط کریں",
+       "linkaccounts-success-text": "کھاتے کو مربوط کر دیا گیا۔",
+       "linkaccounts-submit": "کھاتوں کو مربوط کریں",
+       "unlinkaccounts": "مربوط کھاتوں کو علاحدہ کریں",
+       "unlinkaccounts-success": "مربوط کھاتہ علاحدہ کر دیا گیا۔",
+       "userjsispublic": "براہ کرم اس بات کا خیال رکھیں کہ جاوا اسکرپٹ کے ذیلی صفحات میں خفیہ معلومات نہیں رکھی جانی چاہئیں کیونکہ ان صفحات کو دیگر صارفین بھی دیکھ سکتے ہیں۔",
+       "usercssispublic": "براہ کرم اس بات کا خیال رکھیں کہ سی ایس ایس کے ذیلی صفحات میں خفیہ معلومات نہیں رکھی جانی چاہئیں کیونکہ ان صفحات کو دیگر صارفین بھی دیکھ سکتے ہیں۔"
 }
index ea738ac..bf09c1b 100644 (file)
        "talk": "讨论",
        "views": "视图",
        "toolbox": "工具",
+       "tool-link-userrights": "更改{{GENDER:$1|用户}}组",
+       "tool-link-emailuser": "电邮联系该{{GENDER:$1|用户}}",
        "userpage": "查看用户页面",
        "projectpage": "查看项目页面",
        "imagepage": "查看文件页面",
        "passwordreset-emailelement": "用户名:\n$1\n\n临时密码:\n$2",
        "passwordreset-emailsentemail": "如果此邮件地址与您的账户相关联的话,将发送一封密码重置邮件。",
        "passwordreset-emailsentusername": "如果有邮件地址与此用户名相关联的话,将发送一封密码重置邮件。",
-       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在下方显示。",
-       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在显示。",
+       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在显示。",
        "passwordreset-nocaller": "必须提供一个调用方",
        "passwordreset-nosuchcaller": "调用方不存在:$1",
        "passwordreset-ignored": "密码重置没有处理。也许没有配置提供者?",
        "shown-title": "每页显示$1项结果",
        "viewprevnext": "查看($1{{int:pipe-separator}}$2)($3)",
        "searchmenu-exists": "<strong>本wiki上有名为“[[:$1]]”的页面。</strong>{{PLURAL:$2|0=|另请查看找到的其他搜索结果。}}",
-       "searchmenu-new": "<strong>在本Wiki上新建名为“[[:$1]]”的页面!</strong>{{PLURAL:$2|0=|另请查看您的搜索找的结果。|另请查看搜索结果。}}",
+       "searchmenu-new": "<strong>在本Wiki上新建名为“[[:$1]]”的页面!</strong>{{PLURAL:$2|0=|另请查看您的搜索找的结果。|另请查看搜索结果。}}",
        "searchprofile-articles": "内容页面",
        "searchprofile-images": "多媒体",
        "searchprofile-everything": "全部",
index ac700ef..5a4ab39 100644 (file)
@@ -245,7 +245,8 @@ if ( $count > 0 ) {
                if ( isset( $options['dry'] ) ) {
                        echo " publishing {$file} by '" . $wgUser->getName() . "', comment '$commentText'... ";
                } else {
-                       $props = FSFile::getPropsFromPath( $file );
+                       $mwProps = new MWFileProps( MimeMagic::singleton() );
+                       $props = $mwProps->getPropsFromPath( $file, true );
                        $flags = 0;
                        $publishOptions = [];
                        $handler = MediaHandler::getHandler( $props['mime'] );
index 106be1f..e7a4d06 100644 (file)
@@ -90,7 +90,7 @@ class RefreshLinks extends Maintenance {
                $end = null, $redirectsOnly = false, $oldRedirectsOnly = false
        ) {
                $reportingInterval = 100;
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
 
                if ( $start === null ) {
                        $start = 1;
@@ -282,7 +282,7 @@ class RefreshLinks extends Maintenance {
        ) {
                wfWaitForSlaves();
                $this->output( "Deleting illegal entries from the links tables...\n" );
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
                do {
                        // Find the start of the next chunk. This is based only
                        // on existent page_ids.
@@ -324,7 +324,7 @@ class RefreshLinks extends Maintenance {
         */
        private function dfnCheckInterval( $start = null, $end = null, $batchSize = 100 ) {
                $dbw = $this->getDB( DB_MASTER );
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
 
                $linksTables = [ // table name => page_id field
                        'pagelinks' => 'pl_from',
index 3cc94b8..780b372 100644 (file)
 .button-colors-quiet( @textColor, @highlightColor, @activeColor ) {
        // Quiet buttons all start gray, and reveal
        // constructive/progressive/destructive color on hover and active.
-       color: @textColor;
+       color: @colorButtonText;
 
        &:hover {
                background-color: transparent;
index 2a985fe..a19fea1 100644 (file)
@@ -147,6 +147,7 @@ $wgAutoloadClasses += [
        # tests/phpunit/mocks
        'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
        'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
+       'MockLocalRepo' => "$testDir/phpunit/mocks/filerepo/MockLocalRepo.php",
        'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
        'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
        'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
index 4ef778d..d2968a1 100644 (file)
@@ -348,7 +348,8 @@ class ParserTestRunner {
                        $backend = new FSFileBackend( [
                                'name' => 'local-backend',
                                'wikiId' => wfWikiID(),
-                               'basePath' => $this->uploadDir
+                               'basePath' => $this->uploadDir,
+                               'tmpDirectory' => wfTempDir()
                        ] );
                } elseif ( $this->fileBackendName ) {
                        global $wgFileBackends;
@@ -379,7 +380,7 @@ class ParserTestRunner {
 
                return new RepoGroup(
                        [
-                               'class' => 'LocalRepo',
+                               'class' => 'MockLocalRepo',
                                'name' => 'local',
                                'url' => 'http://example.com/images',
                                'hashLevels' => 2,
index cfeb44f..43577ca 100644 (file)
@@ -56,7 +56,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        private static $useTemporaryTables = true;
        private static $reuseDB = false;
        private static $dbSetup = false;
-       private static $oldTablePrefix = false;
+       private static $oldTablePrefix = '';
 
        /**
         * Original value of PHP's error_reporting setting.
index 41f516a..a05e39d 100644 (file)
@@ -320,7 +320,8 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
                        'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
                        'TitleParser' => [ 'TitleParser', TitleParser::class ],
-                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ]
+                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
+                       'ProxyLookup' => [ 'ProxyLookup', ProxyLookup::class ]
                ];
        }
 
index ebc2d10..7e56ebf 100644 (file)
@@ -57,9 +57,11 @@ class StatusTest extends MediaWikiLangTestCase {
        }
 
        /**
+        * Test 'ok' and 'errors' getters.
         *
+        * @covers Status::__get
         */
-       public function testOkAndErrors() {
+       public function testOkAndErrorsGetters() {
                $status = Status::newGood( 'foo' );
                $this->assertTrue( $status->ok );
                $status = Status::newFatal( 'foo', 1, 2 );
@@ -76,6 +78,19 @@ class StatusTest extends MediaWikiLangTestCase {
                );
        }
 
+       /**
+        * Test 'ok' setter.
+        *
+        * @covers Status::__set
+        */
+       public function testOkSetter() {
+               $status = new Status();
+               $status->ok = false;
+               $this->assertFalse( $status->isOK() );
+               $status->ok = true;
+               $this->assertTrue( $status->isOK() );
+       }
+
        /**
         * @dataProvider provideSetResult
         * @covers Status::setResult
@@ -98,11 +113,12 @@ class StatusTest extends MediaWikiLangTestCase {
 
        /**
         * @dataProvider provideIsOk
-        * @covers Status::isOk
+        * @covers Status::setOK
+        * @covers Status::isOK
         */
        public function testIsOk( $ok ) {
                $status = new Status();
-               $status->ok = $ok;
+               $status->setOK( $ok );
                $this->assertEquals( $ok, $status->isOK() );
        }
 
@@ -128,7 +144,7 @@ class StatusTest extends MediaWikiLangTestCase {
         */
        public function testIsGood( $ok, $errors, $expected ) {
                $status = new Status();
-               $status->ok = $ok;
+               $status->setOK( $ok );
                foreach ( $errors as $error ) {
                        $status->warning( $error );
                }
@@ -171,6 +187,7 @@ class StatusTest extends MediaWikiLangTestCase {
         * @covers Status::error
         * @covers Status::getErrorsArray
         * @covers Status::getStatusArray
+        * @covers Status::getErrors
         */
        public function testErrorWithMessage( $mockDetails ) {
                $status = new Status();
@@ -361,7 +378,7 @@ class StatusTest extends MediaWikiLangTestCase {
                ];
 
                $status = new Status();
-               $status->ok = false;
+               $status->setOK( false );
                $testCases['GoodButNoError'] = [
                        $status,
                        "Internal error: Status::getWikiText: Invalid result object: no error text but not OK\n",
@@ -475,7 +492,7 @@ class StatusTest extends MediaWikiLangTestCase {
                ];
 
                $status = new Status();
-               $status->ok = false;
+               $status->setOK( false );
                $testCases['GoodButNoError'] = [
                        $status,
                        [ "Status::getMessage: Invalid result object: no error text but not OK\n" ],
@@ -647,8 +664,8 @@ class StatusTest extends MediaWikiLangTestCase {
 
        /**
         * @dataProvider provideErrorsWarningsOnly
-        * @covers Status::getErrorsOnlyStatus
-        * @covers Status::getWarningsOnlyStatus
+        * @covers Status::splitByErrorType
+        * @covers StatusValue::splitByErrorType
         */
        public function testGetErrorsWarningsOnlyStatus( $errorText, $warningText, $type, $errorResult,
                $warningResult
index aade490..041e7e3 100644 (file)
@@ -10,12 +10,10 @@ class WebRequestTest extends MediaWikiTestCase {
                parent::setUp();
 
                $this->oldServer = $_SERVER;
-               IP::clearCaches();
        }
 
        protected function tearDown() {
                $_SERVER = $this->oldServer;
-               IP::clearCaches();
 
                parent::tearDown();
        }
@@ -367,7 +365,6 @@ class WebRequestTest extends MediaWikiTestCase {
        public function testGetIP( $expected, $input, $squid, $xffList, $private, $description ) {
                $_SERVER = $input;
                $this->setMwGlobals( [
-                       'wgSquidServersNoPurge' => $squid,
                        'wgUsePrivateIPs' => $private,
                        'wgHooks' => [
                                'IsTrustedProxy' => [
@@ -379,6 +376,8 @@ class WebRequestTest extends MediaWikiTestCase {
                        ]
                ] );
 
+               $this->setService( 'ProxyLookup', new ProxyLookup( [], $squid ) );
+
                $request = new WebRequest();
                $result = $request->getIP();
                $this->assertEquals( $expected, $result, $description );
@@ -564,6 +563,7 @@ class WebRequestTest extends MediaWikiTestCase {
                        'wgUsePrivateIPs' => false,
                        'wgHooks' => [],
                ] );
+               $this->setService( 'ProxyLookup', new ProxyLookup( [], [] ) );
 
                $request = new WebRequest();
                # Next call throw an exception about lacking an IP
index f13ead4..9480c2d 100644 (file)
@@ -33,6 +33,13 @@ class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
        function __construct() {
                $this->profiler = new ProfilerStub( [] );
                $this->trxProfiler = new TransactionProfiler();
+               $this->cliMode = true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        protected function closeConnection() {
index 0013685..68bc964 100644 (file)
@@ -827,4 +827,42 @@ class DatabaseSQLTest extends MediaWikiTestCase {
                        ],
                ];
        }
+
+       public function testSessionTempTables() {
+               $temp1 = $this->database->tableName( 'tmp_table_1' );
+               $temp2 = $this->database->tableName( 'tmp_table_2' );
+               $temp3 = $this->database->tableName( 'tmp_table_3' );
+
+               $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->dropTable( 'tmp_table_1', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_2', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_3', __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+       }
 }
index caa29bd..31b692b 100644 (file)
@@ -36,6 +36,10 @@ class DatabaseTestHelper extends DatabaseBase {
                $this->cliMode = isset( $opts['cliMode'] ) ? $opts['cliMode'] : true;
                $this->connLogger = new \Psr\Log\NullLogger();
                $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        /**
@@ -94,6 +98,11 @@ class DatabaseTestHelper extends DatabaseBase {
        }
 
        public function tableExists( $table, $fname = __METHOD__ ) {
+               $tableRaw = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+                       return true; // already known to exist
+               }
+
                $this->checkFunctionName( $fname );
 
                return in_array( $table, (array)$this->tablesExists );
index 81c9faf..f01c47d 100644 (file)
@@ -21,6 +21,13 @@ class FakeDatabase extends DatabaseBase {
        public $lastInsertData;
 
        function __construct() {
+               $this->cliMode = true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        function clearFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
diff --git a/tests/phpunit/includes/libs/IPTest.php b/tests/phpunit/includes/libs/IPTest.php
new file mode 100644 (file)
index 0000000..307652d
--- /dev/null
@@ -0,0 +1,670 @@
+<?php
+/**
+ * Tests for IP validity functions.
+ *
+ * Ported from /t/inc/IP.t by avar.
+ *
+ * @group IP
+ * @todo Test methods in this call should be split into a method and a
+ * dataprovider.
+ */
+
+class IPTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers IP::isIPAddress
+        * @dataProvider provideInvalidIPs
+        */
+       public function isNotIPAddress( $val, $desc ) {
+               $this->assertFalse( IP::isIPAddress( $val ), $desc );
+       }
+
+       /**
+        * Provide a list of things that aren't IP addresses
+        */
+       public function provideInvalidIPs() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Garbage IP string' ],
+                       [ ':', 'Single ":" is not an IP' ],
+                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
+                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
+                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPAddress
+        */
+       public function testisIPAddress() {
+               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
+               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
+               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20' ), 'IPv4 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
+
+               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
+                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
+               foreach ( $validIPs as $ip ) {
+                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
+               }
+       }
+
+       /**
+        * @covers IP::isIPv6
+        */
+       public function testisIPv6() {
+               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
+               $this->assertFalse(
+                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+
+               $this->assertFalse( IP::isIPv6( ':::' ) );
+               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
+
+               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
+               $this->assertTrue( IP::isIPv6( '::0' ) );
+               $this->assertTrue( IP::isIPv6( '::fc' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
+
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
+
+               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ), 'IPv6 with "::" and 4 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideInvalidIPv4Addresses
+        */
+       public function testisNotIPv4( $bogusIP, $desc ) {
+               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
+       }
+
+       public function provideInvalidIPv4Addresses() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Letters are not an IP' ],
+                       [ ':', 'A colon is not an IP' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideValidIPv4Address
+        */
+       public function testIsIPv4( $ip, $desc ) {
+               $this->assertTrue( IP::isIPv4( $ip ), $desc );
+       }
+
+       /**
+        * Provide some IPv4 addresses and ranges
+        */
+       public function provideValidIPv4Address() {
+               return [
+                       [ '124.24.52.13', 'Valid IPv4 address' ],
+                       [ '1.24.52.13', 'Another valid IPv4 address' ],
+                       [ '74.24.52.13/20', 'An IPv4 range' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testValidIPs() {
+               foreach ( range( 0, 255 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
+                       $a = sprintf( "%04x", $i );
+                       $b = sprintf( "%03x", $i );
+                       $c = sprintf( "%02x", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
+                       }
+               }
+               // test with some abbreviations
+               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isValid( 'fc:100::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
+                       'IPv6 with 8 words ending with "::"'
+               );
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testInvalidIPs() {
+               // Out of range...
+               foreach ( range( 256, 999 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 'g', 'z' ) as $i ) {
+                       $a = sprintf( "%04s", $i );
+                       $b = sprintf( "%03s", $i );
+                       $c = sprintf( "%02s", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
+                       }
+               }
+               // Have CIDR
+               $ipCIDRs = [
+                       '212.35.31.121/32',
+                       '212.35.31.121/18',
+                       '212.35.31.121/24',
+                       '::ff:d:321:5/96',
+                       'ff::d3:321:5/116',
+                       'c:ff:12:1:ea:d:321:5/120',
+               ];
+               foreach ( $ipCIDRs as $i ) {
+                       $this->assertFalse( IP::isValid( $i ),
+                               "$i is an invalid IP address because it is a block" );
+               }
+               // Incomplete/garbage
+               $invalid = [
+                       'www.xn--var-xla.net',
+                       '216.17.184.G',
+                       '216.17.184.1.',
+                       '216.17.184',
+                       '216.17.184.',
+                       '256.17.184.1'
+               ];
+               foreach ( $invalid as $i ) {
+                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
+               }
+       }
+
+       /**
+        * Provide some valid IP blocks
+        */
+       public function provideValidBlocks() {
+               return [
+                       [ '116.17.184.5/32' ],
+                       [ '0.17.184.5/30' ],
+                       [ '16.17.184.1/24' ],
+                       [ '30.242.52.14/1' ],
+                       [ '10.232.52.13/8' ],
+                       [ '30.242.52.14/0' ],
+                       [ '::e:f:2001/96' ],
+                       [ '::c:f:2001/128' ],
+                       [ '::10:f:2001/70' ],
+                       [ '::fe:f:2001/1' ],
+                       [ '::6d:f:2001/8' ],
+                       [ '::fe:f:2001/0' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideValidBlocks
+        */
+       public function testValidBlocks( $block ) {
+               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideInvalidBlocks
+        */
+       public function testInvalidBlocks( $invalid ) {
+               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
+       }
+
+       public function provideInvalidBlocks() {
+               return [
+                       [ '116.17.184.5/33' ],
+                       [ '0.17.184.5/130' ],
+                       [ '16.17.184.1/-1' ],
+                       [ '10.232.52.13/*' ],
+                       [ '7.232.52.13/ab' ],
+                       [ '11.232.52.13/' ],
+                       [ '::e:f:2001/129' ],
+                       [ '::c:f:2001/228' ],
+                       [ '::10:f:2001/-1' ],
+                       [ '::6d:f:2001/*' ],
+                       [ '::86:f:2001/ab' ],
+                       [ '::23:f:2001/' ],
+               ];
+       }
+
+       /**
+        * @covers IP::sanitizeIP
+        * @dataProvider provideSanitizeIP
+        */
+       public function testSanitizeIP( $expected, $input ) {
+               $result = IP::sanitizeIP( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testSanitizeIP()
+        */
+       public static function provideSanitizeIP() {
+               return [
+                       [ '0.0.0.0', '0.0.0.0' ],
+                       [ '0.0.0.0', '00.00.00.00' ],
+                       [ '0.0.0.0', '000.000.000.000' ],
+                       [ '141.0.11.253', '141.000.011.253' ],
+                       [ '1.2.4.5', '1.2.4.5' ],
+                       [ '1.2.4.5', '01.02.04.05' ],
+                       [ '1.2.4.5', '001.002.004.005' ],
+                       [ '10.0.0.1', '010.0.000.1' ],
+                       [ '80.72.250.4', '080.072.250.04' ],
+                       [ 'Foo.1000.00', 'Foo.1000.00' ],
+                       [ 'Bar.01', 'Bar.01' ],
+                       [ 'Bar.010', 'Bar.010' ],
+                       [ null, '' ],
+                       [ null, ' ' ]
+               ];
+       }
+
+       /**
+        * @covers IP::toHex
+        * @dataProvider provideToHex
+        */
+       public function testToHex( $expected, $input ) {
+               $result = IP::toHex( $input );
+               $this->assertTrue( $result === false || is_string( $result ) );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testToHex()
+        */
+       public static function provideToHex() {
+               return [
+                       [ '00000001', '0.0.0.1' ],
+                       [ '01020304', '1.2.3.4' ],
+                       [ '7F000001', '127.0.0.1' ],
+                       [ '80000000', '128.0.0.0' ],
+                       [ 'DEADCAFE', '222.173.202.254' ],
+                       [ 'FFFFFFFF', '255.255.255.255' ],
+                       [ '8D000BFD', '141.000.11.253' ],
+                       [ false, 'IN.VA.LI.D' ],
+                       [ 'v6-00000000000000000000000000000001', '::1' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
+                       [ false, 'IN:VA::LI:D' ],
+                       [ false, ':::1' ]
+               ];
+       }
+
+       /**
+        * @covers IP::isPublic
+        * @dataProvider provideIsPublic
+        */
+       public function testIsPublic( $expected, $input ) {
+               $result = IP::isPublic( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testIsPublic()
+        */
+       public static function provideIsPublic() {
+               return [
+                       [ false, 'fc00::3' ], # RFC 4193 (local)
+                       [ false, 'fc00::ff' ], # RFC 4193 (local)
+                       [ false, '127.1.2.3' ], # loopback
+                       [ false, '::1' ], # loopback
+                       [ false, 'fe80::1' ], # link-local
+                       [ false, '169.254.1.1' ], # link-local
+                       [ false, '10.0.0.1' ], # RFC 1918 (private)
+                       [ false, '172.16.0.1' ], # RFC 1918 (private)
+                       [ false, '192.168.0.1' ], # RFC 1918 (private)
+                       [ true, '2001:5c0:1000:a::133' ], # public
+                       [ true, 'fc::3' ], # public
+                       [ true, '00FC::' ] # public
+               ];
+       }
+
+       // Private wrapper used to test CIDR Parsing.
+       private function assertFalseCIDR( $CIDR, $msg = '' ) {
+               $ff = [ false, false ];
+               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
+       }
+
+       // Private wrapper to test network shifting using only dot notation
+       private function assertNet( $expected, $CIDR ) {
+               $parse = IP::parseCIDR( $CIDR );
+               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
+       }
+
+       /**
+        * @covers IP::hexToQuad
+        * @dataProvider provideIPsAndHexes
+        */
+       public function testHexToQuad( $ip, $hex ) {
+               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
+       }
+
+       /**
+        * Provide some IP addresses and their equivalent hex representations
+        */
+       public function provideIPsandHexes() {
+               return [
+                       [ '0.0.0.1', '00000001' ],
+                       [ '255.0.0.0', 'FF000000' ],
+                       [ '255.255.255.255', 'FFFFFFFF' ],
+                       [ '10.188.222.255', '0ABCDEFF' ],
+                       // hex not left-padded...
+                       [ '0.0.0.0', '0' ],
+                       [ '0.0.0.1', '1' ],
+                       [ '0.0.0.255', 'FF' ],
+                       [ '0.0.255.0', 'FF00' ],
+               ];
+       }
+
+       /**
+        * @covers IP::hexToOctet
+        * @dataProvider provideOctetsAndHexes
+        */
+       public function testHexToOctet( $octet, $hex ) {
+               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
+       }
+
+       /**
+        * Provide some hex and octet representations of the same IPs
+        */
+       public function provideOctetsAndHexes() {
+               return [
+                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
+                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
+                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
+                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
+                       // hex not left-padded...
+                       [ '0:0:0:0:0:0:0:0', '0' ],
+                       [ '0:0:0:0:0:0:0:1', '1' ],
+                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
+                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
+                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
+               ];
+       }
+
+       /**
+        * IP::parseCIDR() returns an array containing a signed IP address
+        * representing the network mask and the bit mask.
+        * @covers IP::parseCIDR
+        */
+       public function testCIDRParsing() {
+               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
+               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
+
+               // Verify if statement
+               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
+               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
+               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
+               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
+
+               // Check internal logic
+               # 0 mask always result in array(0,0)
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
+
+               // @todo FIXME: Add more tests.
+
+               # This part test network shifting
+               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
+               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
+               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
+               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
+               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
+               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
+               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeOnValidIp() {
+               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
+                       'Canonicalization of a valid IP returns it unchanged' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeMappedAddress() {
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::ffff:192.0.2.152' )
+               );
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::192.0.2.152' )
+               );
+       }
+
+       /**
+        * Issues there are most probably from IP::toHex() or IP::parseRange()
+        * @covers IP::isInRange
+        * @dataProvider provideIPsAndRanges
+        */
+       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
+               $this->assertEquals(
+                       $expected,
+                       IP::isInRange( $addr, $range ),
+                       $message
+               );
+       }
+
+       /** Provider for testIPIsInRange() */
+       public static function provideIPsAndRanges() {
+               # Format: (expected boolean, address, range, optional message)
+               return [
+                       # IPv4
+                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
+                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
+                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
+
+                       [ false, '0.0.0.0', '192.0.2.0/24' ],
+                       [ false, '255.255.255', '192.0.2.0/24' ],
+
+                       # IPv6
+                       [ false, '::1', '2001:DB8::/32' ],
+                       [ false, '::', '2001:DB8::/32' ],
+                       [ false, 'FE80::1', '2001:DB8::/32' ],
+
+                       [ true, '2001:DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+                               '2001:DB8::/32' ],
+
+                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
+               ];
+       }
+
+       /**
+        * Test for IP::splitHostAndPort().
+        * @dataProvider provideSplitHostAndPort
+        */
+       public function testSplitHostAndPort( $expected, $input, $description ) {
+               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::splitHostAndPort()
+        */
+       public static function provideSplitHostAndPort() {
+               return [
+                       [ false, '[', 'Unclosed square bracket' ],
+                       [ false, '[::', 'Unclosed square bracket 2' ],
+                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
+                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
+                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
+                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
+                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
+                       [ false, '::x', 'Double colon but no IPv6' ],
+                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
+                       [ false, 'x:x', 'Hostname and invalid port' ],
+                       [ [ 'x', false ], 'x', 'Plain hostname' ]
+               ];
+       }
+
+       /**
+        * Test for IP::combineHostAndPort()
+        * @dataProvider provideCombineHostAndPort
+        */
+       public function testCombineHostAndPort( $expected, $input, $description ) {
+               list( $host, $port, $defaultPort ) = $input;
+               $this->assertEquals(
+                       $expected,
+                       IP::combineHostAndPort( $host, $port, $defaultPort ),
+                       $description );
+       }
+
+       /**
+        * Provider for IP::combineHostAndPort()
+        */
+       public static function provideCombineHostAndPort() {
+               return [
+                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
+                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
+                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
+                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
+               ];
+       }
+
+       /**
+        * Test for IP::sanitizeRange()
+        * @dataProvider provideIPCIDRs
+        */
+       public function testSanitizeRange( $input, $expected, $description ) {
+               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::testSanitizeRange()
+        */
+       public static function provideIPCIDRs() {
+               return [
+                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
+                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
+                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
+                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
+                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
+                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
+                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
+                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
+               ];
+       }
+
+       /**
+        * Test for IP::prettifyIP()
+        * @dataProvider provideIPsToPrettify
+        */
+       public function testPrettifyIP( $ip, $prettified ) {
+               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
+       }
+
+       /**
+        * Provider for IP::testPrettifyIP()
+        */
+       public static function provideIPsToPrettify() {
+               return [
+                       [ '0:0:0:0:0:0:0:0', '::' ],
+                       [ '0:0:0::0:0:0', '::' ],
+                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
+                       [ '0:0::f', '::f' ],
+                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
+                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
+                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
+                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
+                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
+                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
+                       [ '0:0:0::0:0:0/64', '::/64' ],
+                       [ '0:0::f/52', '::f/52' ],
+                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
+                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
+                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
+                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
+                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/libs/time/ConvertableTimestampTest.php b/tests/phpunit/includes/libs/time/ConvertableTimestampTest.php
deleted file mode 100644 (file)
index 88c2989..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-
-/**
- * Tests timestamp parsing and output.
- */
-class ConvertableTimestampTest extends PHPUnit_Framework_TestCase {
-       /**
-        * @covers ConvertableTimestamp::__construct
-        */
-       public function testConstructWithNoTimestamp() {
-               $timestamp = new ConvertableTimestamp();
-               $this->assertInternalType( 'string', $timestamp->getTimestamp() );
-               $this->assertNotEmpty( $timestamp->getTimestamp() );
-               $this->assertNotEquals( false, strtotime( $timestamp->getTimestamp( TS_MW ) ) );
-       }
-
-       /**
-        * @covers ConvertableTimestamp::__toString
-        */
-       public function testToString() {
-               $timestamp = new ConvertableTimestamp( '1406833268' ); // Equivalent to 20140731190108
-               $this->assertEquals( '1406833268', $timestamp->__toString() );
-       }
-
-       public static function provideValidTimestampDifferences() {
-               return [
-                       [ '1406833268', '1406833269', '00 00 00 01' ],
-                       [ '1406833268', '1406833329', '00 00 01 01' ],
-                       [ '1406833268', '1406836929', '00 01 01 01' ],
-                       [ '1406833268', '1406923329', '01 01 01 01' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideValidTimestampDifferences
-        * @covers ConvertableTimestamp::diff
-        */
-       public function testDiff( $timestamp1, $timestamp2, $expected ) {
-               $timestamp1 = new ConvertableTimestamp( $timestamp1 );
-               $timestamp2 = new ConvertableTimestamp( $timestamp2 );
-               $diff = $timestamp1->diff( $timestamp2 );
-               $this->assertEquals( $expected, $diff->format( '%D %H %I %S' ) );
-       }
-
-       /**
-        * Test parsing of valid timestamps and outputing to MW format.
-        * @dataProvider provideValidTimestamps
-        * @covers ConvertableTimestamp::getTimestamp
-        */
-       public function testValidParse( $format, $original, $expected ) {
-               $timestamp = new ConvertableTimestamp( $original );
-               $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) );
-       }
-
-       /**
-        * Test outputting valid timestamps to different formats.
-        * @dataProvider provideValidTimestamps
-        * @covers ConvertableTimestamp::getTimestamp
-        */
-       public function testValidOutput( $format, $expected, $original ) {
-               $timestamp = new ConvertableTimestamp( $original );
-               $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) );
-       }
-
-       /**
-        * Test an invalid timestamp.
-        * @expectedException TimestampException
-        * @covers ConvertableTimestamp
-        */
-       public function testInvalidParse() {
-               new ConvertableTimestamp( "This is not a timestamp." );
-       }
-
-       /**
-        * Test an out of range timestamp
-        * @dataProvider provideOutOfRangeTimestamps
-        * @expectedException TimestampException
-        * @covers ConvertableTimestamp
-        */
-       public function testOutOfRangeTimestamps( $format, $input ) {
-               $timestamp = new ConvertableTimestamp( $input );
-               $timestamp->getTimestamp( $format );
-       }
-
-       /**
-        * Test requesting an invalid output format.
-        * @expectedException TimestampException
-        * @covers ConvertableTimestamp::getTimestamp
-        */
-       public function testInvalidOutput() {
-               $timestamp = new ConvertableTimestamp( '1343761268' );
-               $timestamp->getTimestamp( 98 );
-       }
-
-       /**
-        * Returns a list of valid timestamps in the format:
-        * [ type, timestamp_of_type, timestamp_in_MW ]
-        */
-       public static function provideValidTimestamps() {
-               return [
-                       // Various formats
-                       [ TS_UNIX, '1343761268', '20120731190108' ],
-                       [ TS_MW, '20120731190108', '20120731190108' ],
-                       [ TS_DB, '2012-07-31 19:01:08', '20120731190108' ],
-                       [ TS_ISO_8601, '2012-07-31T19:01:08Z', '20120731190108' ],
-                       [ TS_ISO_8601_BASIC, '20120731T190108Z', '20120731190108' ],
-                       [ TS_EXIF, '2012:07:31 19:01:08', '20120731190108' ],
-                       [ TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ],
-                       [ TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ],
-                       [ TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ],
-                       // Some extremes and weird values
-                       [ TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ],
-                       [ TS_UNIX, '-62135596801', '00001231235959' ]
-               ];
-       }
-
-       /**
-        * Returns a list of out of range timestamps in the format:
-        * [ type, timestamp_of_type ]
-        */
-       public static function provideOutOfRangeTimestamps() {
-               return [
-                       // Various formats
-                       [ TS_MW, '-62167219201' ], // -0001-12-31T23:59:59Z
-                       [ TS_MW, '253402300800' ], // 10000-01-01T00:00:00Z
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php b/tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php
new file mode 100644 (file)
index 0000000..d48caf3
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * Tests timestamp parsing and output.
+ */
+class ConvertibleTimestampTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers ConvertibleTimestamp::__construct
+        */
+       public function testConstructWithNoTimestamp() {
+               $timestamp = new ConvertibleTimestamp();
+               $this->assertInternalType( 'string', $timestamp->getTimestamp() );
+               $this->assertNotEmpty( $timestamp->getTimestamp() );
+               $this->assertNotEquals( false, strtotime( $timestamp->getTimestamp( TS_MW ) ) );
+       }
+
+       /**
+        * @covers ConvertibleTimestamp::__toString
+        */
+       public function testToString() {
+               $timestamp = new ConvertibleTimestamp( '1406833268' ); // Equivalent to 20140731190108
+               $this->assertEquals( '1406833268', $timestamp->__toString() );
+       }
+
+       public static function provideValidTimestampDifferences() {
+               return [
+                       [ '1406833268', '1406833269', '00 00 00 01' ],
+                       [ '1406833268', '1406833329', '00 00 01 01' ],
+                       [ '1406833268', '1406836929', '00 01 01 01' ],
+                       [ '1406833268', '1406923329', '01 01 01 01' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideValidTimestampDifferences
+        * @covers ConvertibleTimestamp::diff
+        */
+       public function testDiff( $timestamp1, $timestamp2, $expected ) {
+               $timestamp1 = new ConvertibleTimestamp( $timestamp1 );
+               $timestamp2 = new ConvertibleTimestamp( $timestamp2 );
+               $diff = $timestamp1->diff( $timestamp2 );
+               $this->assertEquals( $expected, $diff->format( '%D %H %I %S' ) );
+       }
+
+       /**
+        * Test parsing of valid timestamps and outputing to MW format.
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testValidParse( $format, $original, $expected ) {
+               $timestamp = new ConvertibleTimestamp( $original );
+               $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) );
+       }
+
+       /**
+        * Test outputting valid timestamps to different formats.
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testValidOutput( $format, $expected, $original ) {
+               $timestamp = new ConvertibleTimestamp( $original );
+               $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) );
+       }
+
+       /**
+        * Test an invalid timestamp.
+        * @expectedException TimestampException
+        * @covers ConvertibleTimestamp
+        */
+       public function testInvalidParse() {
+               new ConvertibleTimestamp( "This is not a timestamp." );
+       }
+
+       /**
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::convert
+        */
+       public function testConvert( $format, $expected, $original ) {
+               $this->assertSame( $expected, ConvertibleTimestamp::convert( $format, $original ) );
+       }
+
+       /**
+        * Format an invalid timestamp.
+        * @covers ConvertibleTimestamp::convert
+        */
+       public function testConvertInvalid() {
+               $this->assertSame( false, ConvertibleTimestamp::convert( 'Not a timestamp', 0 ) );
+       }
+
+       /**
+        * Test an out of range timestamp
+        * @dataProvider provideOutOfRangeTimestamps
+        * @expectedException TimestampException
+        * @covers       ConvertibleTimestamp
+        */
+       public function testOutOfRangeTimestamps( $format, $input ) {
+               $timestamp = new ConvertibleTimestamp( $input );
+               $timestamp->getTimestamp( $format );
+       }
+
+       /**
+        * Test requesting an invalid output format.
+        * @expectedException TimestampException
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testInvalidOutput() {
+               $timestamp = new ConvertibleTimestamp( '1343761268' );
+               $timestamp->getTimestamp( 98 );
+       }
+
+       /**
+        * Returns a list of valid timestamps in the format:
+        * [ type, timestamp_of_type, timestamp_in_MW ]
+        */
+       public static function provideValidTimestamps() {
+               return [
+                       // Various formats
+                       [ TS_UNIX, '1343761268', '20120731190108' ],
+                       [ TS_MW, '20120731190108', '20120731190108' ],
+                       [ TS_DB, '2012-07-31 19:01:08', '20120731190108' ],
+                       [ TS_ISO_8601, '2012-07-31T19:01:08Z', '20120731190108' ],
+                       [ TS_ISO_8601_BASIC, '20120731T190108Z', '20120731190108' ],
+                       [ TS_EXIF, '2012:07:31 19:01:08', '20120731190108' ],
+                       [ TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ],
+                       [ TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ],
+                       [ TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ],
+                       // Some extremes and weird values
+                       [ TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ],
+                       [ TS_UNIX, '-62135596801', '00001231235959' ]
+               ];
+       }
+
+       /**
+        * Returns a list of out of range timestamps in the format:
+        * [ type, timestamp_of_type ]
+        */
+       public static function provideOutOfRangeTimestamps() {
+               return [
+                       // Various formats
+                       [ TS_MW, '-62167219201' ], // -0001-12-31T23:59:59Z
+                       [ TS_MW, '253402300800' ], // 10000-01-01T00:00:00Z
+               ];
+       }
+}
index 5042121..e854ab5 100644 (file)
@@ -26,7 +26,8 @@ abstract class MediaWikiMediaTestCase extends MediaWikiTestCase {
                $this->backend = new FSFileBackend( [
                        'name' => 'localtesting',
                        'wikiId' => wfWikiID(),
-                       'containerPaths' => $containers
+                       'containerPaths' => $containers,
+                       'tmpDirectory' => $this->getNewTempDirectory()
                ] );
                $this->repo = new FSRepo( $this->getRepoOptions() );
        }
diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php
deleted file mode 100644 (file)
index 5e0626b..0000000
+++ /dev/null
@@ -1,670 +0,0 @@
-<?php
-/**
- * Tests for IP validity functions.
- *
- * Ported from /t/inc/IP.t by avar.
- *
- * @group IP
- * @todo Test methods in this call should be split into a method and a
- * dataprovider.
- */
-
-class IPTest extends PHPUnit_Framework_TestCase {
-       /**
-        * @covers IP::isIPAddress
-        * @dataProvider provideInvalidIPs
-        */
-       public function isNotIPAddress( $val, $desc ) {
-               $this->assertFalse( IP::isIPAddress( $val ), $desc );
-       }
-
-       /**
-        * Provide a list of things that aren't IP addresses
-        */
-       public function provideInvalidIPs() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Garbage IP string' ],
-                       [ ':', 'Single ":" is not an IP' ],
-                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
-                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
-                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPAddress
-        */
-       public function testisIPAddress() {
-               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
-               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
-               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
-               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
-               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
-
-               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
-                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
-               foreach ( $validIPs as $ip ) {
-                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
-               }
-       }
-
-       /**
-        * @covers IP::isIPv6
-        */
-       public function testisIPv6() {
-               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
-               $this->assertFalse(
-                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-
-               $this->assertFalse( IP::isIPv6( ':::' ) );
-               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
-               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
-               $this->assertTrue( IP::isIPv6( '::0' ) );
-               $this->assertTrue( IP::isIPv6( '::fc' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
-               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideInvalidIPv4Addresses
-        */
-       public function testisNotIPv4( $bogusIP, $desc ) {
-               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
-       }
-
-       public function provideInvalidIPv4Addresses() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Letters are not an IP' ],
-                       [ ':', 'A colon is not an IP' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideValidIPv4Address
-        */
-       public function testIsIPv4( $ip, $desc ) {
-               $this->assertTrue( IP::isIPv4( $ip ), $desc );
-       }
-
-       /**
-        * Provide some IPv4 addresses and ranges
-        */
-       public function provideValidIPv4Address() {
-               return [
-                       [ '124.24.52.13', 'Valid IPv4 address' ],
-                       [ '1.24.52.13', 'Another valid IPv4 address' ],
-                       [ '74.24.52.13/20', 'An IPv4 range' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testValidIPs() {
-               foreach ( range( 0, 255 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
-                       $a = sprintf( "%04x", $i );
-                       $b = sprintf( "%03x", $i );
-                       $c = sprintf( "%02x", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
-                       }
-               }
-               // test with some abbreviations
-               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isValid( 'fc:100::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
-                       'IPv6 with 8 words ending with "::"'
-               );
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testInvalidIPs() {
-               // Out of range...
-               foreach ( range( 256, 999 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 'g', 'z' ) as $i ) {
-                       $a = sprintf( "%04s", $i );
-                       $b = sprintf( "%03s", $i );
-                       $c = sprintf( "%02s", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
-                       }
-               }
-               // Have CIDR
-               $ipCIDRs = [
-                       '212.35.31.121/32',
-                       '212.35.31.121/18',
-                       '212.35.31.121/24',
-                       '::ff:d:321:5/96',
-                       'ff::d3:321:5/116',
-                       'c:ff:12:1:ea:d:321:5/120',
-               ];
-               foreach ( $ipCIDRs as $i ) {
-                       $this->assertFalse( IP::isValid( $i ),
-                               "$i is an invalid IP address because it is a block" );
-               }
-               // Incomplete/garbage
-               $invalid = [
-                       'www.xn--var-xla.net',
-                       '216.17.184.G',
-                       '216.17.184.1.',
-                       '216.17.184',
-                       '216.17.184.',
-                       '256.17.184.1'
-               ];
-               foreach ( $invalid as $i ) {
-                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
-               }
-       }
-
-       /**
-        * Provide some valid IP blocks
-        */
-       public function provideValidBlocks() {
-               return [
-                       [ '116.17.184.5/32' ],
-                       [ '0.17.184.5/30' ],
-                       [ '16.17.184.1/24' ],
-                       [ '30.242.52.14/1' ],
-                       [ '10.232.52.13/8' ],
-                       [ '30.242.52.14/0' ],
-                       [ '::e:f:2001/96' ],
-                       [ '::c:f:2001/128' ],
-                       [ '::10:f:2001/70' ],
-                       [ '::fe:f:2001/1' ],
-                       [ '::6d:f:2001/8' ],
-                       [ '::fe:f:2001/0' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideValidBlocks
-        */
-       public function testValidBlocks( $block ) {
-               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideInvalidBlocks
-        */
-       public function testInvalidBlocks( $invalid ) {
-               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
-       }
-
-       public function provideInvalidBlocks() {
-               return [
-                       [ '116.17.184.5/33' ],
-                       [ '0.17.184.5/130' ],
-                       [ '16.17.184.1/-1' ],
-                       [ '10.232.52.13/*' ],
-                       [ '7.232.52.13/ab' ],
-                       [ '11.232.52.13/' ],
-                       [ '::e:f:2001/129' ],
-                       [ '::c:f:2001/228' ],
-                       [ '::10:f:2001/-1' ],
-                       [ '::6d:f:2001/*' ],
-                       [ '::86:f:2001/ab' ],
-                       [ '::23:f:2001/' ],
-               ];
-       }
-
-       /**
-        * @covers IP::sanitizeIP
-        * @dataProvider provideSanitizeIP
-        */
-       public function testSanitizeIP( $expected, $input ) {
-               $result = IP::sanitizeIP( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testSanitizeIP()
-        */
-       public static function provideSanitizeIP() {
-               return [
-                       [ '0.0.0.0', '0.0.0.0' ],
-                       [ '0.0.0.0', '00.00.00.00' ],
-                       [ '0.0.0.0', '000.000.000.000' ],
-                       [ '141.0.11.253', '141.000.011.253' ],
-                       [ '1.2.4.5', '1.2.4.5' ],
-                       [ '1.2.4.5', '01.02.04.05' ],
-                       [ '1.2.4.5', '001.002.004.005' ],
-                       [ '10.0.0.1', '010.0.000.1' ],
-                       [ '80.72.250.4', '080.072.250.04' ],
-                       [ 'Foo.1000.00', 'Foo.1000.00' ],
-                       [ 'Bar.01', 'Bar.01' ],
-                       [ 'Bar.010', 'Bar.010' ],
-                       [ null, '' ],
-                       [ null, ' ' ]
-               ];
-       }
-
-       /**
-        * @covers IP::toHex
-        * @dataProvider provideToHex
-        */
-       public function testToHex( $expected, $input ) {
-               $result = IP::toHex( $input );
-               $this->assertTrue( $result === false || is_string( $result ) );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testToHex()
-        */
-       public static function provideToHex() {
-               return [
-                       [ '00000001', '0.0.0.1' ],
-                       [ '01020304', '1.2.3.4' ],
-                       [ '7F000001', '127.0.0.1' ],
-                       [ '80000000', '128.0.0.0' ],
-                       [ 'DEADCAFE', '222.173.202.254' ],
-                       [ 'FFFFFFFF', '255.255.255.255' ],
-                       [ '8D000BFD', '141.000.11.253' ],
-                       [ false, 'IN.VA.LI.D' ],
-                       [ 'v6-00000000000000000000000000000001', '::1' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
-                       [ false, 'IN:VA::LI:D' ],
-                       [ false, ':::1' ]
-               ];
-       }
-
-       /**
-        * @covers IP::isPublic
-        * @dataProvider provideIsPublic
-        */
-       public function testIsPublic( $expected, $input ) {
-               $result = IP::isPublic( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testIsPublic()
-        */
-       public static function provideIsPublic() {
-               return [
-                       [ false, 'fc00::3' ], # RFC 4193 (local)
-                       [ false, 'fc00::ff' ], # RFC 4193 (local)
-                       [ false, '127.1.2.3' ], # loopback
-                       [ false, '::1' ], # loopback
-                       [ false, 'fe80::1' ], # link-local
-                       [ false, '169.254.1.1' ], # link-local
-                       [ false, '10.0.0.1' ], # RFC 1918 (private)
-                       [ false, '172.16.0.1' ], # RFC 1918 (private)
-                       [ false, '192.168.0.1' ], # RFC 1918 (private)
-                       [ true, '2001:5c0:1000:a::133' ], # public
-                       [ true, 'fc::3' ], # public
-                       [ true, '00FC::' ] # public
-               ];
-       }
-
-       // Private wrapper used to test CIDR Parsing.
-       private function assertFalseCIDR( $CIDR, $msg = '' ) {
-               $ff = [ false, false ];
-               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
-       }
-
-       // Private wrapper to test network shifting using only dot notation
-       private function assertNet( $expected, $CIDR ) {
-               $parse = IP::parseCIDR( $CIDR );
-               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
-       }
-
-       /**
-        * @covers IP::hexToQuad
-        * @dataProvider provideIPsAndHexes
-        */
-       public function testHexToQuad( $ip, $hex ) {
-               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
-       }
-
-       /**
-        * Provide some IP addresses and their equivalent hex representations
-        */
-       public function provideIPsandHexes() {
-               return [
-                       [ '0.0.0.1', '00000001' ],
-                       [ '255.0.0.0', 'FF000000' ],
-                       [ '255.255.255.255', 'FFFFFFFF' ],
-                       [ '10.188.222.255', '0ABCDEFF' ],
-                       // hex not left-padded...
-                       [ '0.0.0.0', '0' ],
-                       [ '0.0.0.1', '1' ],
-                       [ '0.0.0.255', 'FF' ],
-                       [ '0.0.255.0', 'FF00' ],
-               ];
-       }
-
-       /**
-        * @covers IP::hexToOctet
-        * @dataProvider provideOctetsAndHexes
-        */
-       public function testHexToOctet( $octet, $hex ) {
-               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
-       }
-
-       /**
-        * Provide some hex and octet representations of the same IPs
-        */
-       public function provideOctetsAndHexes() {
-               return [
-                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
-                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
-                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
-                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
-                       // hex not left-padded...
-                       [ '0:0:0:0:0:0:0:0', '0' ],
-                       [ '0:0:0:0:0:0:0:1', '1' ],
-                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
-                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
-                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
-               ];
-       }
-
-       /**
-        * IP::parseCIDR() returns an array containing a signed IP address
-        * representing the network mask and the bit mask.
-        * @covers IP::parseCIDR
-        */
-       public function testCIDRParsing() {
-               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
-               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
-
-               // Verify if statement
-               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
-               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
-               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
-               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
-
-               // Check internal logic
-               # 0 mask always result in array(0,0)
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
-
-               // @todo FIXME: Add more tests.
-
-               # This part test network shifting
-               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
-               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
-               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
-               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
-               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
-               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
-               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeOnValidIp() {
-               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
-                       'Canonicalization of a valid IP returns it unchanged' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeMappedAddress() {
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::ffff:192.0.2.152' )
-               );
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::192.0.2.152' )
-               );
-       }
-
-       /**
-        * Issues there are most probably from IP::toHex() or IP::parseRange()
-        * @covers IP::isInRange
-        * @dataProvider provideIPsAndRanges
-        */
-       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
-               $this->assertEquals(
-                       $expected,
-                       IP::isInRange( $addr, $range ),
-                       $message
-               );
-       }
-
-       /** Provider for testIPIsInRange() */
-       public static function provideIPsAndRanges() {
-               # Format: (expected boolean, address, range, optional message)
-               return [
-                       # IPv4
-                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
-                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
-                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
-
-                       [ false, '0.0.0.0', '192.0.2.0/24' ],
-                       [ false, '255.255.255', '192.0.2.0/24' ],
-
-                       # IPv6
-                       [ false, '::1', '2001:DB8::/32' ],
-                       [ false, '::', '2001:DB8::/32' ],
-                       [ false, 'FE80::1', '2001:DB8::/32' ],
-
-                       [ true, '2001:DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
-                               '2001:DB8::/32' ],
-
-                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
-               ];
-       }
-
-       /**
-        * Test for IP::splitHostAndPort().
-        * @dataProvider provideSplitHostAndPort
-        */
-       public function testSplitHostAndPort( $expected, $input, $description ) {
-               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::splitHostAndPort()
-        */
-       public static function provideSplitHostAndPort() {
-               return [
-                       [ false, '[', 'Unclosed square bracket' ],
-                       [ false, '[::', 'Unclosed square bracket 2' ],
-                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
-                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
-                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
-                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
-                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
-                       [ false, '::x', 'Double colon but no IPv6' ],
-                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
-                       [ false, 'x:x', 'Hostname and invalid port' ],
-                       [ [ 'x', false ], 'x', 'Plain hostname' ]
-               ];
-       }
-
-       /**
-        * Test for IP::combineHostAndPort()
-        * @dataProvider provideCombineHostAndPort
-        */
-       public function testCombineHostAndPort( $expected, $input, $description ) {
-               list( $host, $port, $defaultPort ) = $input;
-               $this->assertEquals(
-                       $expected,
-                       IP::combineHostAndPort( $host, $port, $defaultPort ),
-                       $description );
-       }
-
-       /**
-        * Provider for IP::combineHostAndPort()
-        */
-       public static function provideCombineHostAndPort() {
-               return [
-                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
-                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
-                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
-                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
-               ];
-       }
-
-       /**
-        * Test for IP::sanitizeRange()
-        * @dataProvider provideIPCIDRs
-        */
-       public function testSanitizeRange( $input, $expected, $description ) {
-               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::testSanitizeRange()
-        */
-       public static function provideIPCIDRs() {
-               return [
-                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
-                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
-                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
-                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
-                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
-                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
-                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
-                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
-               ];
-       }
-
-       /**
-        * Test for IP::prettifyIP()
-        * @dataProvider provideIPsToPrettify
-        */
-       public function testPrettifyIP( $ip, $prettified ) {
-               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
-       }
-
-       /**
-        * Provider for IP::testPrettifyIP()
-        */
-       public static function provideIPsToPrettify() {
-               return [
-                       [ '0:0:0:0:0:0:0:0', '::' ],
-                       [ '0:0:0::0:0:0', '::' ],
-                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
-                       [ '0:0::f', '::f' ],
-                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
-                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
-                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
-                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
-                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
-                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
-                       [ '0:0:0::0:0:0/64', '::/64' ],
-                       [ '0:0::f/52', '::f/52' ],
-                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
-                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
-                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
-                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
-                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/mocks/filerepo/MockLocalRepo.php b/tests/phpunit/mocks/filerepo/MockLocalRepo.php
new file mode 100644 (file)
index 0000000..eeaf05a
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Class simulating a local file repo.
+ *
+ * @ingroup FileRepo
+ * @since 1.28
+ */
+class MockLocalRepo extends LocalRepo {
+       function getLocalCopy( $virtualUrl ) {
+               return new MockFSFile( wfTempDir() . '/' . wfRandomString( 32 ) );
+       }
+
+       function getLocalReference( $virtualUrl ) {
+               return new MockFSFile( wfTempDir() . '/' . wfRandomString( 32 ) );
+       }
+
+       function getFileProps( $virtualUrl ) {
+               $fsFile = $this->getLocalReference( $virtualUrl );
+
+               return $fsFile->getProps();
+       }
+}