Merge "Add an option to the title widget to exclude the current page"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 4 Oct 2016 22:45:07 +0000 (22:45 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 4 Oct 2016 22:45:07 +0000 (22:45 +0000)
193 files changed:
RELEASE-NOTES-1.28
autoload.php
composer.json
includes/DefaultSettings.php
includes/EditPage.php
includes/HttpFunctions.php [deleted file]
includes/MediaWikiServices.php
includes/OutputPage.php
includes/ServiceWiring.php
includes/Xml.php
includes/actions/HistoryAction.php
includes/auth/AuthManager.php
includes/cache/HTMLFileCache.php
includes/cache/MessageCache.php
includes/context/RequestContext.php
includes/db/MWLBFactory.php [new file with mode: 0644]
includes/db/loadbalancer/LBFactoryMW.php [deleted file]
includes/debug/logger/LegacyLogger.php
includes/exception/MWException.php
includes/exception/MWExceptionHandler.php
includes/exception/MWExceptionRenderer.php
includes/filebackend/FileBackendGroup.php
includes/htmlform/HTMLForm.php
includes/htmlform/fields/HTMLSubmitField.php
includes/http/CurlHttpRequest.php [new file with mode: 0644]
includes/http/Http.php [new file with mode: 0644]
includes/http/MWHttpRequest.php [new file with mode: 0644]
includes/http/PhpHttpRequest.php [new file with mode: 0644]
includes/installer/Installer.php
includes/installer/MssqlUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/bn.json
includes/installer/i18n/cs.json
includes/installer/i18n/de.json
includes/installer/i18n/en.json
includes/installer/i18n/fr.json
includes/installer/i18n/it.json
includes/installer/i18n/lv.json
includes/installer/i18n/nl.json
includes/installer/i18n/pl.json
includes/installer/i18n/qqq.json
includes/installer/i18n/ru.json
includes/installer/i18n/sv.json
includes/installer/i18n/zh-hans.json
includes/installer/i18n/zh-hant.json
includes/libs/CryptRand.php [new file with mode: 0644]
includes/libs/MWCryptHash.php [new file with mode: 0644]
includes/libs/MemoizedCallable.php
includes/libs/MultiHttpClient.php
includes/libs/WaitConditionLoop.php [deleted file]
includes/libs/lockmanager/LockManager.php
includes/libs/lockmanager/MemcLockManager.php
includes/libs/objectcache/APCBagOStuff.php
includes/libs/objectcache/APCUBagOStuff.php [new file with mode: 0644]
includes/libs/objectcache/BagOStuff.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/IMaintainableDatabase.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/ILoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php
includes/libs/rdbms/loadmonitor/LoadMonitorNull.php
includes/media/TransformationalImageHandler.php
includes/objectcache/ObjectCache.php
includes/page/Article.php
includes/page/WikiPage.php
includes/profiler/Profiler.php
includes/profiler/SectionProfiler.php
includes/registration/ExtensionRegistry.php
includes/skins/SkinTemplate.php
includes/specials/SpecialBotPasswords.php
includes/specials/SpecialImport.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialNewpages.php [changed mode: 0644->0755]
includes/user/User.php
includes/utils/MWCryptHKDF.php
includes/utils/MWCryptHash.php [deleted file]
includes/utils/MWCryptRand.php
includes/utils/UIDGenerator.php
languages/i18n/ace.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/bs.json
languages/i18n/cdo.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/egl.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/eu.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gor.json
languages/i18n/got.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/ia.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/li.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/lzz.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/mr.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/pl.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/shn.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/so.json
languages/i18n/sv.json
languages/i18n/tt-cyrl.json
languages/i18n/ur.json
languages/i18n/uz.json
languages/i18n/vi.json
languages/i18n/zh-hans.json
languages/messages/MessagesOlo.php
maintenance/archives/patch-change_tag-ct_id.sql [new file with mode: 0644]
maintenance/archives/patch-tag_summary-ts_id.sql [new file with mode: 0644]
maintenance/interwiki.list
maintenance/interwiki.sql
maintenance/mssql/archives/patch-change_tag-ct_id.sql [new file with mode: 0644]
maintenance/mssql/archives/patch-tag_summary-ts_id.sql [new file with mode: 0644]
maintenance/mssql/tables.sql
maintenance/oracle/archives/patch-change_tag-ct_id.sql [new file with mode: 0644]
maintenance/oracle/archives/patch-tag_summary-ts_id.sql [new file with mode: 0644]
maintenance/oracle/tables.sql
maintenance/postgres/tables.sql
maintenance/sql.php
maintenance/sqlite/archives/patch-change_tag-ct_id.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-tag_summary-ts_id.sql [new file with mode: 0644]
maintenance/tables.sql
resources/Resources.php
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki.ui/components/buttons.less
resources/src/mediawiki.ui/components/forms.less
resources/src/mediawiki.ui/components/inputs.less
resources/src/mediawiki.ui/components/text.less
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.Upload.Dialog.js
resources/src/mediawiki/mediawiki.feedback.js
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/libs/MemoizedCallableTest.php
tests/phpunit/includes/libs/WaitConditionLoopTest.php [deleted file]
tests/phpunit/includes/libs/composer/ComposerJsonTest.php
tests/phpunit/includes/libs/composer/ComposerLockTest.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/mocks/media/MockDjVuHandler.php

index 4f687a1..8b7dced 100644 (file)
@@ -6,6 +6,7 @@ MediaWiki 1.28 is an alpha-quality branch and is not recommended for use in
 production.
 
 === Configuration changes in 1.28 ===
+* $wgSend404Code now affects status code of action=history if the page is not there.
 * BREAKING CHANGE: $wgHTTPProxy is now *required* for all external requests
   made by MediaWiki via a proxy. Relying on the http_proxy environment
   variable is no longer supported.
@@ -65,13 +66,17 @@ production.
 
 ==== Upgraded external libraries ====
 * Updated es5-shim from v4.1.5 to v4.5.8
+* Updated composer/semver from v1.4.1 to v1.4.2
+* Updated wikimedia/php-session-serializer from v1.0.3 to v1.0.4
 
 ==== New external libraries ====
 * Added wikimedia/scoped-callback v1.0.0
+* Added wikimedia/wait-condition-loop v1.0.1
 
 ==== Removed and replaced external libraries ====
 
 === Bug fixes in 1.28 ===
+* (T146496) action=history pages should return 404 HTTP error code if the page does not exist
 * (T137264) SECURITY: XSS in unclosed internal links
 * (T133147) SECURITY: Escape '<' and ']]>' in inline <style> blocks
 * (T133147) SECURITY: Require login to preview user CSS pages
index 5b99fac..ffd6557 100644 (file)
@@ -5,6 +5,7 @@ global $wgAutoloadLocalClasses;
 
 $wgAutoloadLocalClasses = [
        'APCBagOStuff' => __DIR__ . '/includes/libs/objectcache/APCBagOStuff.php',
+       'APCUBagOStuff' => __DIR__ . '/includes/libs/objectcache/APCUBagOStuff.php',
        'AbstractContent' => __DIR__ . '/includes/content/AbstractContent.php',
        'Action' => __DIR__ . '/includes/actions/Action.php',
        'ActiveUsersPager' => __DIR__ . '/includes/specials/pagers/ActiveUsersPager.php',
@@ -294,10 +295,11 @@ $wgAutoloadLocalClasses = [
        'CreateAndPromote' => __DIR__ . '/maintenance/createAndPromote.php',
        'CreateFileOp' => __DIR__ . '/includes/libs/filebackend/fileop/CreateFileOp.php',
        'CreditsAction' => __DIR__ . '/includes/actions/CreditsAction.php',
+       'CryptRand' => __DIR__ . '/includes/libs/CryptRand.php',
        'CssContent' => __DIR__ . '/includes/content/CssContent.php',
        'CssContentHandler' => __DIR__ . '/includes/content/CssContentHandler.php',
        'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
-       'CurlHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
+       'CurlHttpRequest' => __DIR__ . '/includes/http/CurlHttpRequest.php',
        'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
        'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBAccessError.php',
        'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
@@ -575,7 +577,7 @@ $wgAutoloadLocalClasses = [
        'Html' => __DIR__ . '/includes/Html.php',
        'HtmlArmor' => __DIR__ . '/includes/libs/HtmlArmor.php',
        'HtmlFormatter' => __DIR__ . '/includes/HtmlFormatter.php',
-       'Http' => __DIR__ . '/includes/HttpFunctions.php',
+       'Http' => __DIR__ . '/includes/http/Http.php',
        'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
        'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php',
        'IApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
@@ -663,7 +665,6 @@ $wgAutoloadLocalClasses = [
        'KkConverter' => __DIR__ . '/languages/classes/LanguageKk.php',
        'KuConverter' => __DIR__ . '/languages/classes/LanguageKu.php',
        'LBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactory.php',
-       'LBFactoryMW' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMW.php',
        'LBFactoryMulti' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactoryMulti.php',
        'LBFactorySimple' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySimple.php',
        'LBFactorySingle' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySingle.php',
@@ -771,7 +772,7 @@ $wgAutoloadLocalClasses = [
        'MWCallableUpdate' => __DIR__ . '/includes/deferred/MWCallableUpdate.php',
        'MWContentSerializationException' => __DIR__ . '/includes/content/ContentHandler.php',
        'MWCryptHKDF' => __DIR__ . '/includes/utils/MWCryptHKDF.php',
-       'MWCryptHash' => __DIR__ . '/includes/utils/MWCryptHash.php',
+       'MWCryptHash' => __DIR__ . '/includes/libs/MWCryptHash.php',
        'MWCryptRand' => __DIR__ . '/includes/utils/MWCryptRand.php',
        'MWDebug' => __DIR__ . '/includes/debug/MWDebug.php',
        'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
@@ -780,7 +781,8 @@ $wgAutoloadLocalClasses = [
        'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
        'MWFileProps' => __DIR__ . '/includes/utils/MWFileProps.php',
        'MWGrants' => __DIR__ . '/includes/utils/MWGrants.php',
-       'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
+       'MWHttpRequest' => __DIR__ . '/includes/http/MWHttpRequest.php',
+       'MWLBFactory' => __DIR__ . '/includes/db/MWLBFactory.php',
        'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
        'MWMessagePack' => __DIR__ . '/includes/libs/MWMessagePack.php',
        'MWNamespace' => __DIR__ . '/includes/MWNamespace.php',
@@ -1056,7 +1058,7 @@ $wgAutoloadLocalClasses = [
        'Pbkdf2Password' => __DIR__ . '/includes/password/Pbkdf2Password.php',
        'PerRowAugmentor' => __DIR__ . '/includes/search/PerRowAugmentor.php',
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
-       'PhpHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
+       'PhpHttpRequest' => __DIR__ . '/includes/http/PhpHttpRequest.php',
        'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
        'Pingback' => __DIR__ . '/includes/Pingback.php',
        'PoolCounter' => __DIR__ . '/includes/poolcounter/PoolCounter.php',
@@ -1516,7 +1518,6 @@ $wgAutoloadLocalClasses = [
        'VirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTService.php',
        'VirtualRESTServiceClient' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTServiceClient.php',
        'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/WANObjectCache.php',
-       'WaitConditionLoop' => __DIR__ . '/includes/libs/WaitConditionLoop.php',
        'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
        'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
        'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
index c827c0f..884b64f 100644 (file)
@@ -16,7 +16,7 @@
                "wiki": "https://www.mediawiki.org/"
        },
        "require": {
-               "composer/semver": "1.4.1",
+               "composer/semver": "1.4.2",
                "cssjanus/cssjanus": "1.1.2",
                "ext-ctype": "*",
                "ext-iconv": "*",
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.9",
+               "oojs/oojs-ui": "0.17.10",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
                "wikimedia/composer-merge-plugin": "1.3.1",
                "wikimedia/html-formatter": "1.0.1",
                "wikimedia/ip-set": "1.1.0",
-               "wikimedia/php-session-serializer": "1.0.3",
+               "wikimedia/php-session-serializer": "1.0.4",
                "wikimedia/relpath": "1.0.3",
                "wikimedia/running-stat": "1.1.0",
                "wikimedia/scoped-callback": "1.0.0",
                "wikimedia/utfnormal": "1.0.3",
+               "wikimedia/wait-condition-loop": "1.0.1",
                "wikimedia/wrappedstring": "2.2.0",
                "zordius/lightncandy": "0.23"
        },
index f8626e9..2ae33b2 100644 (file)
@@ -2211,7 +2211,7 @@ $wgCacheDirectory = false;
  *   - CACHE_NONE:       Do not cache
  *   - CACHE_DB:         Store cache objects in the DB
  *   - CACHE_MEMCACHED:  MemCached, must specify servers in $wgMemCachedServers
- *   - CACHE_ACCEL:      APC, XCache or WinCache
+ *   - CACHE_ACCEL:      APC, APCU, XCache or WinCache
  *   - (other):          A string may be used which identifies a cache
  *                       configuration in $wgObjectCaches.
  *
@@ -2288,6 +2288,7 @@ $wgObjectCaches = [
        ],
 
        'apc' => [ 'class' => 'APCBagOStuff', 'reportDupes' => false ],
+       'apcu' => [ 'class' => 'APCUBagOStuff', 'reportDupes' => false ],
        'xcache' => [ 'class' => 'XCacheBagOStuff', 'reportDupes' => false ],
        'wincache' => [ 'class' => 'WinCacheBagOStuff', 'reportDupes' => false ],
        'memcached-php' => [ 'class' => 'MemcachedPhpBagOStuff', 'loggroup' => 'memcached' ],
@@ -5532,13 +5533,7 @@ $wgApplyIpBlocksToXff = false;
  * elapses.
  *
  * @par Example:
- * To set a generic maximum of 4 hits in 60 seconds:
- * @code
- *     $wgRateLimits = [ 4, 60 ];
- * @endcode
- *
- * @par Example:
- * You could also limit per action and then type of users.
+ * Limits per configured per action and then type of users.
  * @code
  *     $wgRateLimits = [
  *         'edit' => [
@@ -5547,8 +5542,20 @@ $wgApplyIpBlocksToXff = false;
  *             'newbie' => [ x, y ], // each new autoconfirmed accounts; overrides 'user'
  *             'ip' => [ x, y ], // each anon and recent account
  *             'subnet' => [ x, y ], // ... within a /24 subnet in IPv4 or /64 in IPv6
+ *             'groupName' => [ x, y ], // by group membership
  *         ]
- *     ]
+ *     ];
+ * @endcode
+ *
+ * @par Normally, the 'noratelimit' right allows a user to bypass any rate
+ * limit checks. This can be disabled on a per-action basis by setting the
+ * special '&can-bypass' key to false in that action's configuration.
+ * @code
+ *     $wgRateLimits = [
+ *         'some-action' => [
+ *             '&can-bypass' => false,
+ *             'user' => [ x, y ],
+ *     ];
  * @endcode
  *
  * @warning Requires that $wgMainCacheType is set to something persistent
index 38f8ab6..c0c0048 100644 (file)
@@ -4155,7 +4155,7 @@ HTML
                        'name' => 'wpSave',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'save' );
-               $buttons['save'] = Html::submitButton( $buttonLabel, $attribs, [ 'mw-ui-constructive' ] );
+               $buttons['save'] = Html::submitButton( $buttonLabel, $attribs, [ 'mw-ui-progressive' ] );
 
                ++$tabindex; // use the same for preview and live preview
                $attribs = [
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
deleted file mode 100644 (file)
index 2ca5e1b..0000000
+++ /dev/null
@@ -1,1122 +0,0 @@
-<?php
-/**
- * Various HTTP related functions.
- *
- * 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 HTTP
- */
-
-/**
- * @defgroup HTTP HTTP
- */
-
-use MediaWiki\Logger\LoggerFactory;
-
-/**
- * Various HTTP related functions
- * @ingroup HTTP
- */
-class Http {
-       static public $httpEngine = false;
-
-       /**
-        * Perform an HTTP request
-        *
-        * @param string $method HTTP method. Usually GET/POST
-        * @param string $url Full URL to act on. If protocol-relative, will be expanded to an http:// URL
-        * @param array $options Options to pass to MWHttpRequest object.
-        *      Possible keys for the array:
-        *    - timeout             Timeout length in seconds
-        *    - connectTimeout      Timeout for connection, in seconds (curl only)
-        *    - postData            An array of key-value pairs or a url-encoded form data
-        *    - proxy               The proxy to use.
-        *                          Otherwise it will use $wgHTTPProxy (if set)
-        *                          Otherwise it will use the environment variable "http_proxy" (if set)
-        *    - noProxy             Don't use any proxy at all. Takes precedence over proxy value(s).
-        *    - sslVerifyHost       Verify hostname against certificate
-        *    - sslVerifyCert       Verify SSL certificate
-        *    - caInfo              Provide CA information
-        *    - maxRedirects        Maximum number of redirects to follow (defaults to 5)
-        *    - followRedirects     Whether to follow redirects (defaults to false).
-        *                                  Note: this should only be used when the target URL is trusted,
-        *                                  to avoid attacks on intranet services accessible by HTTP.
-        *    - userAgent           A user agent, if you want to override the default
-        *                          MediaWiki/$wgVersion
-        * @param string $caller The method making this request, for profiling
-        * @return string|bool (bool)false on failure or a string on success
-        */
-       public static function request( $method, $url, $options = [], $caller = __METHOD__ ) {
-               wfDebug( "HTTP: $method: $url\n" );
-
-               $options['method'] = strtoupper( $method );
-
-               if ( !isset( $options['timeout'] ) ) {
-                       $options['timeout'] = 'default';
-               }
-               if ( !isset( $options['connectTimeout'] ) ) {
-                       $options['connectTimeout'] = 'default';
-               }
-
-               $req = MWHttpRequest::factory( $url, $options, $caller );
-               $status = $req->execute();
-
-               if ( $status->isOK() ) {
-                       return $req->getContent();
-               } else {
-                       $errors = $status->getErrorsByType( 'error' );
-                       $logger = LoggerFactory::getInstance( 'http' );
-                       $logger->warning( $status->getWikiText( false, false, 'en' ),
-                               [ 'error' => $errors, 'caller' => $caller, 'content' => $req->getContent() ] );
-                       return false;
-               }
-       }
-
-       /**
-        * Simple wrapper for Http::request( 'GET' )
-        * @see Http::request()
-        * @since 1.25 Second parameter $timeout removed. Second parameter
-        * is now $options which can be given a 'timeout'
-        *
-        * @param string $url
-        * @param array $options
-        * @param string $caller The method making this request, for profiling
-        * @return string|bool false on error
-        */
-       public static function get( $url, $options = [], $caller = __METHOD__ ) {
-               $args = func_get_args();
-               if ( isset( $args[1] ) && ( is_string( $args[1] ) || is_numeric( $args[1] ) ) ) {
-                       // Second was used to be the timeout
-                       // And third parameter used to be $options
-                       wfWarn( "Second parameter should not be a timeout.", 2 );
-                       $options = isset( $args[2] ) && is_array( $args[2] ) ?
-                               $args[2] : [];
-                       $options['timeout'] = $args[1];
-                       $caller = __METHOD__;
-               }
-               return Http::request( 'GET', $url, $options, $caller );
-       }
-
-       /**
-        * Simple wrapper for Http::request( 'POST' )
-        * @see Http::request()
-        *
-        * @param string $url
-        * @param array $options
-        * @param string $caller The method making this request, for profiling
-        * @return string|bool false on error
-        */
-       public static function post( $url, $options = [], $caller = __METHOD__ ) {
-               return Http::request( 'POST', $url, $options, $caller );
-       }
-
-       /**
-        * A standard user-agent we can use for external requests.
-        * @return string
-        */
-       public static function userAgent() {
-               global $wgVersion;
-               return "MediaWiki/$wgVersion";
-       }
-
-       /**
-        * Checks that the given URI is a valid one. Hardcoding the
-        * protocols, because we only want protocols that both cURL
-        * and php support.
-        *
-        * file:// should not be allowed here for security purpose (r67684)
-        *
-        * @todo FIXME this is wildly inaccurate and fails to actually check most stuff
-        *
-        * @param string $uri URI to check for validity
-        * @return bool
-        */
-       public static function isValidURI( $uri ) {
-               return preg_match(
-                       '/^https?:\/\/[^\/\s]\S*$/D',
-                       $uri
-               );
-       }
-
-       /**
-        * Gets the relevant proxy from $wgHTTPProxy
-        *
-        * @return mixed The proxy address or an empty string if not set.
-        */
-       public static function getProxy() {
-               global $wgHTTPProxy;
-
-               if ( $wgHTTPProxy ) {
-                       return $wgHTTPProxy;
-               }
-
-               return "";
-       }
-}
-
-/**
- * This wrapper class will call out to curl (if available) or fallback
- * to regular PHP if necessary for handling internal HTTP requests.
- *
- * Renamed from HttpRequest to MWHttpRequest to avoid conflict with
- * PHP's HTTP extension.
- */
-class MWHttpRequest {
-       const SUPPORTS_FILE_POSTS = false;
-
-       protected $content;
-       protected $timeout = 'default';
-       protected $headersOnly = null;
-       protected $postData = null;
-       protected $proxy = null;
-       protected $noProxy = false;
-       protected $sslVerifyHost = true;
-       protected $sslVerifyCert = true;
-       protected $caInfo = null;
-       protected $method = "GET";
-       protected $reqHeaders = [];
-       protected $url;
-       protected $parsedUrl;
-       protected $callback;
-       protected $maxRedirects = 5;
-       protected $followRedirects = false;
-
-       /**
-        * @var CookieJar
-        */
-       protected $cookieJar;
-
-       protected $headerList = [];
-       protected $respVersion = "0.9";
-       protected $respStatus = "200 Ok";
-       protected $respHeaders = [];
-
-       public $status;
-
-       /**
-        * @var Profiler
-        */
-       protected $profiler;
-
-       /**
-        * @var string
-        */
-       protected $profileName;
-
-       /**
-        * @param string $url Url to use. If protocol-relative, will be expanded to an http:// URL
-        * @param array $options (optional) extra params to pass (see Http::request())
-        * @param string $caller The method making this request, for profiling
-        * @param Profiler $profiler An instance of the profiler for profiling, or null
-        */
-       protected function __construct(
-               $url, $options = [], $caller = __METHOD__, $profiler = null
-       ) {
-               global $wgHTTPTimeout, $wgHTTPConnectTimeout;
-
-               $this->url = wfExpandUrl( $url, PROTO_HTTP );
-               $this->parsedUrl = wfParseUrl( $this->url );
-
-               if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
-                       $this->status = Status::newFatal( 'http-invalid-url', $url );
-               } else {
-                       $this->status = Status::newGood( 100 ); // continue
-               }
-
-               if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
-                       $this->timeout = $options['timeout'];
-               } else {
-                       $this->timeout = $wgHTTPTimeout;
-               }
-               if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) {
-                       $this->connectTimeout = $options['connectTimeout'];
-               } else {
-                       $this->connectTimeout = $wgHTTPConnectTimeout;
-               }
-               if ( isset( $options['userAgent'] ) ) {
-                       $this->setUserAgent( $options['userAgent'] );
-               }
-
-               $members = [ "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
-                               "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" ];
-
-               foreach ( $members as $o ) {
-                       if ( isset( $options[$o] ) ) {
-                               // ensure that MWHttpRequest::method is always
-                               // uppercased. Bug 36137
-                               if ( $o == 'method' ) {
-                                       $options[$o] = strtoupper( $options[$o] );
-                               }
-                               $this->$o = $options[$o];
-                       }
-               }
-
-               if ( $this->noProxy ) {
-                       $this->proxy = ''; // noProxy takes precedence
-               }
-
-               // Profile based on what's calling us
-               $this->profiler = $profiler;
-               $this->profileName = $caller;
-       }
-
-       /**
-        * Simple function to test if we can make any sort of requests at all, using
-        * cURL or fopen()
-        * @return bool
-        */
-       public static function canMakeRequests() {
-               return function_exists( 'curl_init' ) || wfIniGetBool( 'allow_url_fopen' );
-       }
-
-       /**
-        * Generate a new request object
-        * @param string $url Url to use
-        * @param array $options (optional) extra params to pass (see Http::request())
-        * @param string $caller The method making this request, for profiling
-        * @throws MWException
-        * @return CurlHttpRequest|PhpHttpRequest
-        * @see MWHttpRequest::__construct
-        */
-       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
-               if ( !Http::$httpEngine ) {
-                       Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
-               } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
-                               ' Http::$httpEngine is set to "curl"' );
-               }
-
-               switch ( Http::$httpEngine ) {
-                       case 'curl':
-                               return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() );
-                       case 'php':
-                               if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
-                                       throw new MWException( __METHOD__ . ': allow_url_fopen ' .
-                                               'needs to be enabled for pure PHP http requests to ' .
-                                               'work. If possible, curl should be used instead. See ' .
-                                               'http://php.net/curl.'
-                                       );
-                               }
-                               return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
-                       default:
-                               throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
-               }
-       }
-
-       /**
-        * Get the body, or content, of the response to the request
-        *
-        * @return string
-        */
-       public function getContent() {
-               return $this->content;
-       }
-
-       /**
-        * Set the parameters of the request
-        *
-        * @param array $args
-        * @todo overload the args param
-        */
-       public function setData( $args ) {
-               $this->postData = $args;
-       }
-
-       /**
-        * Take care of setting up the proxy (do nothing if "noProxy" is set)
-        *
-        * @return void
-        */
-       public function proxySetup() {
-               // If there is an explicit proxy set and proxies are not disabled, then use it
-               if ( $this->proxy && !$this->noProxy ) {
-                       return;
-               }
-
-               // Otherwise, fallback to $wgHTTPProxy if this is not a machine
-               // local URL and proxies are not disabled
-               if ( self::isLocalURL( $this->url ) || $this->noProxy ) {
-                       $this->proxy = '';
-               } else {
-                       $this->proxy = Http::getProxy();
-               }
-       }
-
-       /**
-        * Check if the URL can be served by localhost
-        *
-        * @param string $url Full url to check
-        * @return bool
-        */
-       private static function isLocalURL( $url ) {
-               global $wgCommandLineMode, $wgLocalVirtualHosts;
-
-               if ( $wgCommandLineMode ) {
-                       return false;
-               }
-
-               // Extract host part
-               $matches = [];
-               if ( preg_match( '!^https?://([\w.-]+)[/:].*$!', $url, $matches ) ) {
-                       $host = $matches[1];
-                       // Split up dotwise
-                       $domainParts = explode( '.', $host );
-                       // Check if this domain or any superdomain is listed as a local virtual host
-                       $domainParts = array_reverse( $domainParts );
-
-                       $domain = '';
-                       $countParts = count( $domainParts );
-                       for ( $i = 0; $i < $countParts; $i++ ) {
-                               $domainPart = $domainParts[$i];
-                               if ( $i == 0 ) {
-                                       $domain = $domainPart;
-                               } else {
-                                       $domain = $domainPart . '.' . $domain;
-                               }
-
-                               if ( in_array( $domain, $wgLocalVirtualHosts ) ) {
-                                       return true;
-                               }
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Set the user agent
-        * @param string $UA
-        */
-       public function setUserAgent( $UA ) {
-               $this->setHeader( 'User-Agent', $UA );
-       }
-
-       /**
-        * Set an arbitrary header
-        * @param string $name
-        * @param string $value
-        */
-       public function setHeader( $name, $value ) {
-               // I feel like I should normalize the case here...
-               $this->reqHeaders[$name] = $value;
-       }
-
-       /**
-        * Get an array of the headers
-        * @return array
-        */
-       public function getHeaderList() {
-               $list = [];
-
-               if ( $this->cookieJar ) {
-                       $this->reqHeaders['Cookie'] =
-                               $this->cookieJar->serializeToHttpRequest(
-                                       $this->parsedUrl['path'],
-                                       $this->parsedUrl['host']
-                               );
-               }
-
-               foreach ( $this->reqHeaders as $name => $value ) {
-                       $list[] = "$name: $value";
-               }
-
-               return $list;
-       }
-
-       /**
-        * Set a read callback to accept data read from the HTTP request.
-        * By default, data is appended to an internal buffer which can be
-        * retrieved through $req->getContent().
-        *
-        * To handle data as it comes in -- especially for large files that
-        * would not fit in memory -- you can instead set your own callback,
-        * in the form function($resource, $buffer) where the first parameter
-        * is the low-level resource being read (implementation specific),
-        * and the second parameter is the data buffer.
-        *
-        * You MUST return the number of bytes handled in the buffer; if fewer
-        * bytes are reported handled than were passed to you, the HTTP fetch
-        * will be aborted.
-        *
-        * @param callable $callback
-        * @throws MWException
-        */
-       public function setCallback( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new MWException( 'Invalid MwHttpRequest callback' );
-               }
-               $this->callback = $callback;
-       }
-
-       /**
-        * A generic callback to read the body of the response from a remote
-        * server.
-        *
-        * @param resource $fh
-        * @param string $content
-        * @return int
-        */
-       public function read( $fh, $content ) {
-               $this->content .= $content;
-               return strlen( $content );
-       }
-
-       /**
-        * Take care of whatever is necessary to perform the URI request.
-        *
-        * @return Status
-        */
-       public function execute() {
-
-               $this->content = "";
-
-               if ( strtoupper( $this->method ) == "HEAD" ) {
-                       $this->headersOnly = true;
-               }
-
-               $this->proxySetup(); // set up any proxy as needed
-
-               if ( !$this->callback ) {
-                       $this->setCallback( [ $this, 'read' ] );
-               }
-
-               if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
-                       $this->setUserAgent( Http::userAgent() );
-               }
-
-       }
-
-       /**
-        * Parses the headers, including the HTTP status code and any
-        * Set-Cookie headers.  This function expects the headers to be
-        * found in an array in the member variable headerList.
-        */
-       protected function parseHeader() {
-
-               $lastname = "";
-
-               foreach ( $this->headerList as $header ) {
-                       if ( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
-                               $this->respVersion = $match[1];
-                               $this->respStatus = $match[2];
-                       } elseif ( preg_match( "#^[ \t]#", $header ) ) {
-                               $last = count( $this->respHeaders[$lastname] ) - 1;
-                               $this->respHeaders[$lastname][$last] .= "\r\n$header";
-                       } elseif ( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
-                               $this->respHeaders[strtolower( $match[1] )][] = $match[2];
-                               $lastname = strtolower( $match[1] );
-                       }
-               }
-
-               $this->parseCookies();
-
-       }
-
-       /**
-        * Sets HTTPRequest status member to a fatal value with the error
-        * message if the returned integer value of the status code was
-        * not successful (< 300) or a redirect (>=300 and < 400).  (see
-        * RFC2616, section 10,
-        * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for a
-        * list of status codes.)
-        */
-       protected function setStatus() {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               if ( (int)$this->respStatus > 399 ) {
-                       list( $code, $message ) = explode( " ", $this->respStatus, 2 );
-                       $this->status->fatal( "http-bad-status", $code, $message );
-               }
-       }
-
-       /**
-        * Get the integer value of the HTTP status code (e.g. 200 for "200 Ok")
-        * (see RFC2616, section 10, http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-        * for a list of status codes.)
-        *
-        * @return int
-        */
-       public function getStatus() {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               return (int)$this->respStatus;
-       }
-
-       /**
-        * Returns true if the last status code was a redirect.
-        *
-        * @return bool
-        */
-       public function isRedirect() {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               $status = (int)$this->respStatus;
-
-               if ( $status >= 300 && $status <= 303 ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Returns an associative array of response headers after the
-        * request has been executed.  Because some headers
-        * (e.g. Set-Cookie) can appear more than once the, each value of
-        * the associative array is an array of the values given.
-        *
-        * @return array
-        */
-       public function getResponseHeaders() {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               return $this->respHeaders;
-       }
-
-       /**
-        * Returns the value of the given response header.
-        *
-        * @param string $header
-        * @return string|null
-        */
-       public function getResponseHeader( $header ) {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               if ( isset( $this->respHeaders[strtolower( $header )] ) ) {
-                       $v = $this->respHeaders[strtolower( $header )];
-                       return $v[count( $v ) - 1];
-               }
-
-               return null;
-       }
-
-       /**
-        * Tells the MWHttpRequest object to use this pre-loaded CookieJar.
-        *
-        * @param CookieJar $jar
-        */
-       public function setCookieJar( $jar ) {
-               $this->cookieJar = $jar;
-       }
-
-       /**
-        * Returns the cookie jar in use.
-        *
-        * @return CookieJar
-        */
-       public function getCookieJar() {
-               if ( !$this->respHeaders ) {
-                       $this->parseHeader();
-               }
-
-               return $this->cookieJar;
-       }
-
-       /**
-        * Sets a cookie. Used before a request to set up any individual
-        * cookies. Used internally after a request to parse the
-        * Set-Cookie headers.
-        * @see Cookie::set
-        * @param string $name
-        * @param mixed $value
-        * @param array $attr
-        */
-       public function setCookie( $name, $value = null, $attr = null ) {
-               if ( !$this->cookieJar ) {
-                       $this->cookieJar = new CookieJar;
-               }
-
-               $this->cookieJar->setCookie( $name, $value, $attr );
-       }
-
-       /**
-        * Parse the cookies in the response headers and store them in the cookie jar.
-        */
-       protected function parseCookies() {
-
-               if ( !$this->cookieJar ) {
-                       $this->cookieJar = new CookieJar;
-               }
-
-               if ( isset( $this->respHeaders['set-cookie'] ) ) {
-                       $url = parse_url( $this->getFinalUrl() );
-                       foreach ( $this->respHeaders['set-cookie'] as $cookie ) {
-                               $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
-                       }
-               }
-
-       }
-
-       /**
-        * Returns the final URL after all redirections.
-        *
-        * Relative values of the "Location" header are incorrect as
-        * stated in RFC, however they do happen and modern browsers
-        * support them.  This function loops backwards through all
-        * locations in order to build the proper absolute URI - Marooned
-        * at wikia-inc.com
-        *
-        * Note that the multiple Location: headers are an artifact of
-        * CURL -- they shouldn't actually get returned this way. Rewrite
-        * this when bug 29232 is taken care of (high-level redirect
-        * handling rewrite).
-        *
-        * @return string
-        */
-       public function getFinalUrl() {
-               $headers = $this->getResponseHeaders();
-
-               // return full url (fix for incorrect but handled relative location)
-               if ( isset( $headers['location'] ) ) {
-                       $locations = $headers['location'];
-                       $domain = '';
-                       $foundRelativeURI = false;
-                       $countLocations = count( $locations );
-
-                       for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
-                               $url = parse_url( $locations[$i] );
-
-                               if ( isset( $url['host'] ) ) {
-                                       $domain = $url['scheme'] . '://' . $url['host'];
-                                       break; // found correct URI (with host)
-                               } else {
-                                       $foundRelativeURI = true;
-                               }
-                       }
-
-                       if ( $foundRelativeURI ) {
-                               if ( $domain ) {
-                                       return $domain . $locations[$countLocations - 1];
-                               } else {
-                                       $url = parse_url( $this->url );
-                                       if ( isset( $url['host'] ) ) {
-                                               return $url['scheme'] . '://' . $url['host'] .
-                                                       $locations[$countLocations - 1];
-                                       }
-                               }
-                       } else {
-                               return $locations[$countLocations - 1];
-                       }
-               }
-
-               return $this->url;
-       }
-
-       /**
-        * Returns true if the backend can follow redirects. Overridden by the
-        * child classes.
-        * @return bool
-        */
-       public function canFollowRedirects() {
-               return true;
-       }
-}
-
-/**
- * MWHttpRequest implemented using internal curl compiled into PHP
- */
-class CurlHttpRequest extends MWHttpRequest {
-       const SUPPORTS_FILE_POSTS = true;
-
-       protected $curlOptions = [];
-       protected $headerText = "";
-
-       /**
-        * @param resource $fh
-        * @param string $content
-        * @return int
-        */
-       protected function readHeader( $fh, $content ) {
-               $this->headerText .= $content;
-               return strlen( $content );
-       }
-
-       public function execute() {
-
-               parent::execute();
-
-               if ( !$this->status->isOK() ) {
-                       return $this->status;
-               }
-
-               $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
-               $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
-
-               // Only supported in curl >= 7.16.2
-               if ( defined( 'CURLOPT_CONNECTTIMEOUT_MS' ) ) {
-                       $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000;
-               }
-
-               $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
-               $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
-               $this->curlOptions[CURLOPT_HEADERFUNCTION] = [ $this, "readHeader" ];
-               $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
-               $this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
-
-               $this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
-
-               $this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost ? 2 : 0;
-               $this->curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->sslVerifyCert;
-
-               if ( $this->caInfo ) {
-                       $this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
-               }
-
-               if ( $this->headersOnly ) {
-                       $this->curlOptions[CURLOPT_NOBODY] = true;
-                       $this->curlOptions[CURLOPT_HEADER] = true;
-               } elseif ( $this->method == 'POST' ) {
-                       $this->curlOptions[CURLOPT_POST] = true;
-                       $postData = $this->postData;
-                       // Don't interpret POST parameters starting with '@' as file uploads, because this
-                       // makes it impossible to POST plain values starting with '@' (and causes security
-                       // issues potentially exposing the contents of local files).
-                       // The PHP manual says this option was introduced in PHP 5.5 defaults to true in PHP 5.6,
-                       // but we support lower versions, and the option doesn't exist in HHVM 5.6.99.
-                       if ( defined( 'CURLOPT_SAFE_UPLOAD' ) ) {
-                               $this->curlOptions[CURLOPT_SAFE_UPLOAD] = true;
-                       } elseif ( is_array( $postData ) ) {
-                               // In PHP 5.2 and later, '@' is interpreted as a file upload if POSTFIELDS
-                               // is an array, but not if it's a string. So convert $req['body'] to a string
-                               // for safety.
-                               $postData = wfArrayToCgi( $postData );
-                       }
-                       $this->curlOptions[CURLOPT_POSTFIELDS] = $postData;
-
-                       // Suppress 'Expect: 100-continue' header, as some servers
-                       // will reject it with a 417 and Curl won't auto retry
-                       // with HTTP 1.0 fallback
-                       $this->reqHeaders['Expect'] = '';
-               } else {
-                       $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
-               }
-
-               $this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
-
-               $curlHandle = curl_init( $this->url );
-
-               if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
-                       throw new MWException( "Error setting curl options." );
-               }
-
-               if ( $this->followRedirects && $this->canFollowRedirects() ) {
-                       MediaWiki\suppressWarnings();
-                       if ( !curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
-                               wfDebug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
-                                       "Probably open_basedir is set.\n" );
-                               // Continue the processing. If it were in curl_setopt_array,
-                               // processing would have halted on its entry
-                       }
-                       MediaWiki\restoreWarnings();
-               }
-
-               if ( $this->profiler ) {
-                       $profileSection = $this->profiler->scopedProfileIn(
-                               __METHOD__ . '-' . $this->profileName
-                       );
-               }
-
-               $curlRes = curl_exec( $curlHandle );
-               if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
-                       $this->status->fatal( 'http-timed-out', $this->url );
-               } elseif ( $curlRes === false ) {
-                       $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
-               } else {
-                       $this->headerList = explode( "\r\n", $this->headerText );
-               }
-
-               curl_close( $curlHandle );
-
-               if ( $this->profiler ) {
-                       $this->profiler->scopedProfileOut( $profileSection );
-               }
-
-               $this->parseHeader();
-               $this->setStatus();
-
-               return $this->status;
-       }
-
-       /**
-        * @return bool
-        */
-       public function canFollowRedirects() {
-               $curlVersionInfo = curl_version();
-               if ( $curlVersionInfo['version_number'] < 0x071304 ) {
-                       wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
-                       return false;
-               }
-
-               if ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
-                       if ( strval( ini_get( 'open_basedir' ) ) !== '' ) {
-                               wfDebug( "Cannot follow redirects when open_basedir is set\n" );
-                               return false;
-                       }
-               }
-
-               return true;
-       }
-}
-
-class PhpHttpRequest extends MWHttpRequest {
-
-       private $fopenErrors = [];
-
-       /**
-        * @param string $url
-        * @return string
-        */
-       protected function urlToTcp( $url ) {
-               $parsedUrl = parse_url( $url );
-
-               return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
-       }
-
-       /**
-        * Returns an array with a 'capath' or 'cafile' key
-        * that is suitable to be merged into the 'ssl' sub-array of
-        * a stream context options array.
-        * Uses the 'caInfo' option of the class if it is provided, otherwise uses the system
-        * default CA bundle if PHP supports that, or searches a few standard locations.
-        * @return array
-        * @throws DomainException
-        */
-       protected function getCertOptions() {
-               $certOptions = [];
-               $certLocations = [];
-               if ( $this->caInfo ) {
-                       $certLocations = [ 'manual' => $this->caInfo ];
-               } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
-                       // @codingStandardsIgnoreStart Generic.Files.LineLength
-                       // Default locations, based on
-                       // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/
-                       // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves.
-                       // PHP 5.6+ gets the CA location from OpenSSL as long as it is not set manually,
-                       // so we should leave capath/cafile empty there.
-                       // @codingStandardsIgnoreEnd
-                       $certLocations = array_filter( [
-                               getenv( 'SSL_CERT_DIR' ),
-                               getenv( 'SSL_CERT_PATH' ),
-                               '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al
-                               '/etc/ssl/certs',  # Debian et al
-                               '/etc/pki/tls/certs/ca-bundle.trust.crt',
-                               '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem',
-                               '/System/Library/OpenSSL', # OSX
-                       ] );
-               }
-
-               foreach ( $certLocations as $key => $cert ) {
-                       if ( is_dir( $cert ) ) {
-                               $certOptions['capath'] = $cert;
-                               break;
-                       } elseif ( is_file( $cert ) ) {
-                               $certOptions['cafile'] = $cert;
-                               break;
-                       } elseif ( $key === 'manual' ) {
-                               // fail more loudly if a cert path was manually configured and it is not valid
-                               throw new DomainException( "Invalid CA info passed: $cert" );
-                       }
-               }
-
-               return $certOptions;
-       }
-
-       /**
-        * Custom error handler for dealing with fopen() errors.
-        * fopen() tends to fire multiple errors in succession, and the last one
-        * is completely useless (something like "fopen: failed to open stream")
-        * so normal methods of handling errors programmatically
-        * like get_last_error() don't work.
-        */
-       public function errorHandler( $errno, $errstr ) {
-               $n = count( $this->fopenErrors ) + 1;
-               $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ];
-       }
-
-       public function execute() {
-
-               parent::execute();
-
-               if ( is_array( $this->postData ) ) {
-                       $this->postData = wfArrayToCgi( $this->postData );
-               }
-
-               if ( $this->parsedUrl['scheme'] != 'http'
-                       && $this->parsedUrl['scheme'] != 'https' ) {
-                       $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
-               }
-
-               $this->reqHeaders['Accept'] = "*/*";
-               $this->reqHeaders['Connection'] = 'Close';
-               if ( $this->method == 'POST' ) {
-                       // Required for HTTP 1.0 POSTs
-                       $this->reqHeaders['Content-Length'] = strlen( $this->postData );
-                       if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
-                               $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
-                       }
-               }
-
-               // Set up PHP stream context
-               $options = [
-                       'http' => [
-                               'method' => $this->method,
-                               'header' => implode( "\r\n", $this->getHeaderList() ),
-                               'protocol_version' => '1.1',
-                               'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0,
-                               'ignore_errors' => true,
-                               'timeout' => $this->timeout,
-                               // Curl options in case curlwrappers are installed
-                               'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0,
-                               'curl_verify_ssl_peer' => $this->sslVerifyCert,
-                       ],
-                       'ssl' => [
-                               'verify_peer' => $this->sslVerifyCert,
-                               'SNI_enabled' => true,
-                               'ciphers' => 'HIGH:!SSLv2:!SSLv3:-ADH:-kDH:-kECDH:-DSS',
-                               'disable_compression' => true,
-                       ],
-               ];
-
-               if ( $this->proxy ) {
-                       $options['http']['proxy'] = $this->urlToTcp( $this->proxy );
-                       $options['http']['request_fulluri'] = true;
-               }
-
-               if ( $this->postData ) {
-                       $options['http']['content'] = $this->postData;
-               }
-
-               if ( $this->sslVerifyHost ) {
-                       // PHP 5.6.0 deprecates CN_match, in favour of peer_name which
-                       // actually checks SubjectAltName properly.
-                       if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) {
-                               $options['ssl']['peer_name'] = $this->parsedUrl['host'];
-                       } else {
-                               $options['ssl']['CN_match'] = $this->parsedUrl['host'];
-                       }
-               }
-
-               $options['ssl'] += $this->getCertOptions();
-
-               $context = stream_context_create( $options );
-
-               $this->headerList = [];
-               $reqCount = 0;
-               $url = $this->url;
-
-               $result = [];
-
-               if ( $this->profiler ) {
-                       $profileSection = $this->profiler->scopedProfileIn(
-                               __METHOD__ . '-' . $this->profileName
-                       );
-               }
-               do {
-                       $reqCount++;
-                       $this->fopenErrors = [];
-                       set_error_handler( [ $this, 'errorHandler' ] );
-                       $fh = fopen( $url, "r", false, $context );
-                       restore_error_handler();
-
-                       if ( !$fh ) {
-                               // HACK for instant commons.
-                               // If we are contacting (commons|upload).wikimedia.org
-                               // try again with CN_match for en.wikipedia.org
-                               // as php does not handle SubjectAltName properly
-                               // prior to "peer_name" option in php 5.6
-                               if ( isset( $options['ssl']['CN_match'] )
-                                       && ( $options['ssl']['CN_match'] === 'commons.wikimedia.org'
-                                               || $options['ssl']['CN_match'] === 'upload.wikimedia.org' )
-                               ) {
-                                       $options['ssl']['CN_match'] = 'en.wikipedia.org';
-                                       $context = stream_context_create( $options );
-                                       continue;
-                               }
-                               break;
-                       }
-
-                       $result = stream_get_meta_data( $fh );
-                       $this->headerList = $result['wrapper_data'];
-                       $this->parseHeader();
-
-                       if ( !$this->followRedirects ) {
-                               break;
-                       }
-
-                       # Handle manual redirection
-                       if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
-                               break;
-                       }
-                       # Check security of URL
-                       $url = $this->getResponseHeader( "Location" );
-
-                       if ( !Http::isValidURI( $url ) ) {
-                               wfDebug( __METHOD__ . ": insecure redirection\n" );
-                               break;
-                       }
-               } while ( true );
-               if ( $this->profiler ) {
-                       $this->profiler->scopedProfileOut( $profileSection );
-               }
-
-               $this->setStatus();
-
-               if ( $fh === false ) {
-                       if ( $this->fopenErrors ) {
-                               LoggerFactory::getInstance( 'http' )->warning( __CLASS__
-                                       . ': error opening connection: {errstr1}', $this->fopenErrors );
-                       }
-                       $this->status->fatal( 'http-request-error' );
-                       return $this->status;
-               }
-
-               if ( $result['timed_out'] ) {
-                       $this->status->fatal( 'http-timed-out', $this->url );
-                       return $this->status;
-               }
-
-               // If everything went OK, or we received some error code
-               // get the response body content.
-               if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
-                       while ( !feof( $fh ) ) {
-                               $buf = fread( $fh, 8192 );
-
-                               if ( $buf === false ) {
-                                       $this->status->fatal( 'http-read-error' );
-                                       break;
-                               }
-
-                               if ( strlen( $buf ) ) {
-                                       call_user_func( $this->callback, $fh, $buf );
-                               }
-                       }
-               }
-               fclose( $fh );
-
-               return $this->status;
-       }
-}
index b16044e..f91bbae 100644 (file)
@@ -3,6 +3,7 @@ namespace MediaWiki;
 
 use Config;
 use ConfigFactory;
+use CryptRand;
 use EventRelayerGroup;
 use GenderCache;
 use GlobalVarConfig;
@@ -522,6 +523,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'WatchedItemQueryService' );
        }
 
+       /**
+        * @since 1.28
+        * @return CryptRand
+        */
+       public function getCryptRand() {
+               return $this->getService( 'CryptRand' );
+       }
+
        /**
         * @since 1.28
         * @return MediaHandlerFactory
@@ -589,6 +598,30 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'TitleParser' );
        }
 
+       /**
+        * @since 1.28
+        * @return \BagOStuff
+        */
+       public function getMainObjectStash() {
+               return $this->getService( 'MainObjectStash' );
+       }
+
+       /**
+        * @since 1.28
+        * @return \WANObjectCache
+        */
+       public function getMainWANObjectCache() {
+               return $this->getService( 'MainWANObjectCache' );
+       }
+
+       /**
+        * @since 1.28
+        * @return \BagOStuff
+        */
+       public function getLocalServerObjectCache() {
+               return $this->getService( 'LocalServerObjectCache' );
+       }
+
        /**
         * @since 1.28
         * @return VirtualRESTServiceClient
index b2e7d94..a69c0e6 100644 (file)
@@ -2215,6 +2215,8 @@ class OutputPage extends ContextSource {
         * @throws MWException
         */
        public function output( $return = false ) {
+               global $wgContLang;
+
                if ( $this->mDoNothing ) {
                        return $return ? '' : null;
                }
@@ -2261,7 +2263,7 @@ class OutputPage extends ContextSource {
                ob_start();
 
                $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
-               $response->header( 'Content-language: ' . $config->get( 'ContLang' )->getHtmlCode() );
+               $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
 
                // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
                // jQuery etc. can work correctly.
index 6044911..42b75f0 100644 (file)
 
 use MediaWiki\Interwiki\ClassicInterwikiLookup;
 use MediaWiki\Linker\LinkRendererFactory;
+use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 
 return [
        'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
                $mainConfig = $services->getMainConfig();
 
-               $lbConf = LBFactoryMW::applyDefaultConfig(
+               $lbConf = MWLBFactory::applyDefaultConfig(
                        $mainConfig->get( 'LBFactoryConf' ),
                        $mainConfig
                );
-               $class = LBFactoryMW::getLBFactoryClass( $lbConf );
+               $class = MWLBFactory::getLBFactoryClass( $lbConf );
 
                return new $class( $lbConf );
        },
@@ -158,6 +159,31 @@ return [
                return new WatchedItemQueryService( $services->getDBLoadBalancer() );
        },
 
+       'CryptRand' => function( MediaWikiServices $services ) {
+               $secretKey = $services->getMainConfig()->get( 'SecretKey' );
+               return new CryptRand(
+                       [
+                               // To try vary the system information of the state a bit more
+                               // by including the system's hostname into the state
+                               'wfHostname',
+                               // It's mostly worthless but throw the wiki's id into the data
+                               // for a little more variance
+                               'wfWikiID',
+                               // If we have a secret key set then throw it into the state as well
+                               function() use ( $secretKey ) {
+                                       return $secretKey ?: '';
+                               }
+                       ],
+                       // The config file is likely the most often edited file we know should
+                       // be around so include its stat info into the state.
+                       // The constant with its location will almost always be defined, as
+                       // WebStart.php defines MW_CONFIG_FILE to $IP/LocalSettings.php unless
+                       // being configured with MW_CONFIG_CALLBACK (e.g. the installer).
+                       defined( 'MW_CONFIG_FILE' ) ? [ MW_CONFIG_FILE ] : [],
+                       LoggerFactory::getInstance( 'CryptRand' )
+               );
+       },
+
        'MediaHandlerFactory' => function( MediaWikiServices $services ) {
                return new MediaHandlerFactory(
                        $services->getMainConfig()->get( 'MediaHandlers' )
@@ -218,6 +244,61 @@ return [
                return $services->getService( '_MediaWikiTitleCodec' );
        },
 
+       'MainObjectStash' => function( MediaWikiServices $services ) {
+               $mainConfig = $services->getMainConfig();
+
+               $id = $mainConfig->get( 'MainStash' );
+               if ( !isset( $mainConfig->get( 'ObjectCaches' )[$id] ) ) {
+                       throw new UnexpectedValueException(
+                               "Cache type \"$id\" is not present in \$wgObjectCaches." );
+               }
+
+               return \ObjectCache::newFromParams( $mainConfig->get( 'ObjectCaches' )[$id] );
+       },
+
+       'MainWANObjectCache' => function( MediaWikiServices $services ) {
+               $mainConfig = $services->getMainConfig();
+
+               $id = $mainConfig->get( 'MainWANCache' );
+               if ( !isset( $mainConfig->get( 'WANObjectCaches' )[$id] ) ) {
+                       throw new UnexpectedValueException(
+                               "WAN cache type \"$id\" is not present in \$wgWANObjectCaches." );
+               }
+
+               $params = $mainConfig->get( 'WANObjectCaches' )[$id];
+               $objectCacheId = $params['cacheId'];
+               if ( !isset( $mainConfig->get( 'ObjectCaches' )[$objectCacheId] ) ) {
+                       throw new UnexpectedValueException(
+                               "Cache type \"$objectCacheId\" is not present in \$wgObjectCaches." );
+               }
+               $params['store'] = $mainConfig->get( 'ObjectCaches' )[$objectCacheId];
+
+               return \ObjectCache::newWANCacheFromParams( $params );
+       },
+
+       'LocalServerObjectCache' => function( MediaWikiServices $services ) {
+               $mainConfig = $services->getMainConfig();
+
+               if ( function_exists( 'apc_fetch' ) ) {
+                       $id = 'apc';
+               } elseif ( function_exists( 'apcu_fetch' ) ) {
+                       $id = 'apcu';
+               } elseif ( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
+                       $id = 'xcache';
+               } elseif ( function_exists( 'wincache_ucache_get' ) ) {
+                       $id = 'wincache';
+               } else {
+                       $id = CACHE_NONE;
+               }
+
+               if ( !isset( $mainConfig->get( 'ObjectCaches' )[$id] ) ) {
+                       throw new UnexpectedValueException(
+                               "Cache type \"$id\" is not present in \$wgObjectCaches." );
+               }
+
+               return \ObjectCache::newFromParams( $mainConfig->get( 'ObjectCaches' )[$id] );
+       },
+
        'VirtualRESTServiceClient' => function( MediaWikiServices $services ) {
                $config = $services->getMainConfig()->get( 'VirtualRestConfig' );
 
index 43f7217..b1bd098 100644 (file)
@@ -452,7 +452,7 @@ class Xml {
 
        /**
         * Convenience function to build an HTML submit button
-        * When $wgUseMediaWikiUIEverywhere is true it will default to a constructive button
+        * When $wgUseMediaWikiUIEverywhere is true it will default to a progressive button
         * @param string $value Label text for the button
         * @param array $attribs Optional custom attributes
         * @return string HTML
@@ -467,7 +467,7 @@ class Xml {
                // some submit forms
                // might need to be mw-ui-destructive (e.g. delete a page)
                if ( $wgUseMediaWikiUIEverywhere ) {
-                       $baseAttrs['class'] = 'mw-ui-button mw-ui-constructive';
+                       $baseAttrs['class'] = 'mw-ui-button mw-ui-progressive';
                }
                // Any custom attributes will take precendence of anything in baseAttrs e.g. override the class
                $attribs = $attribs + $baseAttrs;
index f3ef3b3..1e1bb39 100644 (file)
@@ -139,6 +139,10 @@ class HistoryAction extends FormlessAction {
 
                // Fail nicely if article doesn't exist.
                if ( !$this->page->exists() ) {
+                       global $wgSend404Code;
+                       if ( $wgSend404Code ) {
+                               $out->setStatusCode( 404 );
+                       }
                        $out->addWikiMsg( 'nohistory' );
                        # show deletion/move log if there is an entry
                        LogEventsList::showLogExtract(
index 51efe56..e223e16 100644 (file)
@@ -1694,9 +1694,9 @@ class AuthManager implements LoggerAwareInterface {
                                        $status = Status::newGood();
                                        $status->warning( 'userexists' );
                                } else {
-                                       $this->logger->error( __METHOD__ . ': {username} failed with message {message}', [
+                                       $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
                                                'username' => $username,
-                                               'message' => $status->getWikiText( null, null, 'en' )
+                                               'msg' => $status->getWikiText( null, null, 'en' )
                                        ] );
                                        $user->setId( 0 );
                                        $user->loadFromId();
index a85639f..ae8efa9 100644 (file)
@@ -157,6 +157,7 @@ class HTMLFileCache extends FileCacheBase {
         * @return void
         */
        public function loadFromFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
+               global $wgContLang;
                $config = MediaWikiServices::getInstance()->getMainConfig();
 
                wfDebug( __METHOD__ . "()\n" );
@@ -169,7 +170,7 @@ class HTMLFileCache extends FileCacheBase {
 
                $context->getOutput()->sendCacheControl();
                header( "Content-Type: {$config->get( 'MimeType' )}; charset=UTF-8" );
-               header( "Content-Language: {$config->get( 'LanguageCode' )}" );
+               header( "Content-Language: {$wgContLang->getHtmlCode()}" );
                if ( $this->useGzip() ) {
                        if ( wfClientAcceptsGzip() ) {
                                header( 'Content-Encoding: gzip' );
index e871855..f393acd 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Cache
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * MediaWiki message cache structure version.
@@ -154,9 +155,9 @@ class MessageCache {
                $this->mExpiry = $expiry;
 
                if ( $wgUseLocalMessageCache ) {
-                       $this->localCache = ObjectCache::getLocalServerInstance( CACHE_NONE );
+                       $this->localCache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
                } else {
-                       $this->localCache = wfGetCache( CACHE_NONE );
+                       $this->localCache = new EmptyBagOStuff();
                }
 
                $this->wanCache = ObjectCache::getMainWANInstance();
index c87798e..a8cad9f 100644 (file)
@@ -160,7 +160,7 @@ class RequestContext implements IContextSource, MutableContext {
        /**
         * Set the Title object
         *
-        * @param Title $title
+        * @param Title|null $title
         */
        public function setTitle( Title $title = null ) {
                $this->title = $title;
diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php
new file mode 100644 (file)
index 0000000..bfdce39
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * MediaWiki-specific class for generating database load balancers
+ * @ingroup Database
+ */
+abstract class MWLBFactory {
+       /**
+        * @param array $lbConf Config for LBFactory::__construct()
+        * @param Config $mainConfig Main config object from MediaWikiServices
+        * @return array
+        */
+       public static function applyDefaultConfig( array $lbConf, Config $mainConfig ) {
+               global $wgCommandLineMode;
+
+               $lbConf += [
+                       'localDomain' => new DatabaseDomain(
+                               $mainConfig->get( 'DBname' ),
+                               null,
+                               $mainConfig->get( 'DBprefix' )
+                       ),
+                       'profiler' => Profiler::instance(),
+                       'trxProfiler' => Profiler::instance()->getTransactionProfiler(),
+                       'replLogger' => LoggerFactory::getInstance( 'DBReplication' ),
+                       'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ),
+                       'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
+                       'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
+                       'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
+                       'cliMode' => $wgCommandLineMode,
+                       'hostname' => wfHostname(),
+                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
+                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
+               ];
+
+               if ( $lbConf['class'] === 'LBFactorySimple' ) {
+                       if ( isset( $lbConf['servers'] ) ) {
+                               // Server array is already explicitly configured; leave alone
+                       } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
+                                       if ( $server['type'] === 'sqlite' ) {
+                                               $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
+                                       } elseif ( $server['type'] === 'postgres' ) {
+                                               $server += [ 'port' => $mainConfig->get( 'DBport' ) ];
+                                       }
+                                       $lbConf['servers'][$i] = $server + [
+                                               'schema' => $mainConfig->get( 'DBmwschema' ),
+                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                               'flags' => DBO_DEFAULT,
+                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                                       ];
+                               }
+                       } else {
+                               $flags = DBO_DEFAULT;
+                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
+                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+                               $server = [
+                                       'host' => $mainConfig->get( 'DBserver' ),
+                                       'user' => $mainConfig->get( 'DBuser' ),
+                                       'password' => $mainConfig->get( 'DBpassword' ),
+                                       'dbname' => $mainConfig->get( 'DBname' ),
+                                       'schema' => $mainConfig->get( 'DBmwschema' ),
+                                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                       'type' => $mainConfig->get( 'DBtype' ),
+                                       'load' => 1,
+                                       'flags' => $flags,
+                                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                       'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                               ];
+                               if ( $server['type'] === 'sqlite' ) {
+                                       $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
+                               } elseif ( $server['type'] === 'postgres' ) {
+                                       $server['port'] = $mainConfig->get( 'DBport' );
+                               }
+                               $lbConf['servers'] = [ $server ];
+                       }
+                       if ( !isset( $lbConf['externalClusters'] ) ) {
+                               $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
+                       }
+               } elseif ( $lbConf['class'] === 'LBFactoryMulti' ) {
+                       if ( isset( $lbConf['serverTemplate'] ) ) {
+                               $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' );
+                               $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
+                               $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get( 'DBmysql5' );
+                       }
+               }
+
+               // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
+               $sCache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
+               if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
+                       $lbConf['srvCache'] = $sCache;
+               }
+               $cCache = ObjectCache::getLocalClusterInstance();
+               if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) {
+                       $lbConf['memCache'] = $cCache;
+               }
+               $wCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
+                       $lbConf['wanCache'] = $wCache;
+               }
+
+               return $lbConf;
+       }
+
+       /**
+        * Returns the LBFactory class to use and the load balancer configuration.
+        *
+        * @todo instead of this, use a ServiceContainer for managing the different implementations.
+        *
+        * @param array $config (e.g. $wgLBFactoryConf)
+        * @return string Class name
+        */
+       public static function getLBFactoryClass( array $config ) {
+               // For configuration backward compatibility after removing
+               // underscores from class names in MediaWiki 1.23.
+               $bcClasses = [
+                       'LBFactory_Simple' => 'LBFactorySimple',
+                       'LBFactory_Single' => 'LBFactorySingle',
+                       'LBFactory_Multi' => 'LBFactoryMulti'
+               ];
+
+               $class = $config['class'];
+
+               if ( isset( $bcClasses[$class] ) ) {
+                       $class = $bcClasses[$class];
+                       wfDeprecated(
+                               '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
+                               '1.23'
+                       );
+               }
+
+               return $class;
+       }
+}
diff --git a/includes/db/loadbalancer/LBFactoryMW.php b/includes/db/loadbalancer/LBFactoryMW.php
deleted file mode 100644 (file)
index 9821da1..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-use MediaWiki\Logger\LoggerFactory;
-
-/**
- * Legacy MediaWiki-specific class for generating database load balancers
- * @ingroup Database
- */
-abstract class LBFactoryMW {
-       /**
-        * @param array $lbConf Config for LBFactory::__construct()
-        * @param Config $mainConfig Main config object from MediaWikiServices
-        * @return array
-        */
-       public static function applyDefaultConfig( array $lbConf, Config $mainConfig ) {
-               global $wgCommandLineMode;
-
-               $lbConf += [
-                       'localDomain' => new DatabaseDomain(
-                               $mainConfig->get( 'DBname' ),
-                               null,
-                               $mainConfig->get( 'DBprefix' )
-                       ),
-                       'profiler' => Profiler::instance(),
-                       'trxProfiler' => Profiler::instance()->getTransactionProfiler(),
-                       'replLogger' => LoggerFactory::getInstance( 'DBReplication' ),
-                       'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ),
-                       'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
-                       'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
-                       'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
-                       'cliMode' => $wgCommandLineMode,
-                       'hostname' => wfHostname(),
-                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
-                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
-               ];
-
-               if ( $lbConf['class'] === 'LBFactorySimple' ) {
-                       if ( isset( $lbConf['servers'] ) ) {
-                               // Server array is already explicitly configured; leave alone
-                       } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
-                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
-                                       if ( $server['type'] === 'sqlite' ) {
-                                               $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
-                                       } elseif ( $server['type'] === 'postgres' ) {
-                                               $server += [ 'port' => $mainConfig->get( 'DBport' ) ];
-                                       }
-                                       $lbConf['servers'][$i] = $server + [
-                                               'schema' => $mainConfig->get( 'DBmwschema' ),
-                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                               'flags' => DBO_DEFAULT,
-                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
-                                       ];
-                               }
-                       } else {
-                               $flags = DBO_DEFAULT;
-                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
-                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
-                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
-                               $server = [
-                                       'host' => $mainConfig->get( 'DBserver' ),
-                                       'user' => $mainConfig->get( 'DBuser' ),
-                                       'password' => $mainConfig->get( 'DBpassword' ),
-                                       'dbname' => $mainConfig->get( 'DBname' ),
-                                       'schema' => $mainConfig->get( 'DBmwschema' ),
-                                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
-                                       'type' => $mainConfig->get( 'DBtype' ),
-                                       'load' => 1,
-                                       'flags' => $flags,
-                                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
-                                       'utf8Mode' => $mainConfig->get( 'DBmysql5' )
-                               ];
-                               if ( $server['type'] === 'sqlite' ) {
-                                       $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
-                               } elseif ( $server['type'] === 'postgres' ) {
-                                       $server['port'] = $mainConfig->get( 'DBport' );
-                               }
-                               $lbConf['servers'] = [ $server ];
-                       }
-                       if ( !isset( $lbConf['externalClusters'] ) ) {
-                               $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
-                       }
-               } elseif ( $lbConf['class'] === 'LBFactoryMulti' ) {
-                       if ( isset( $lbConf['serverTemplate'] ) ) {
-                               $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' );
-                               $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
-                               $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get( 'DBmysql5' );
-                       }
-               }
-
-               // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
-               $sCache = ObjectCache::getLocalServerInstance();
-               if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
-                       $lbConf['srvCache'] = $sCache;
-               }
-               $cCache = ObjectCache::getLocalClusterInstance();
-               if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) {
-                       $lbConf['memCache'] = $cCache;
-               }
-               $wCache = ObjectCache::getMainWANInstance();
-               if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
-                       $lbConf['wanCache'] = $wCache;
-               }
-
-               return $lbConf;
-       }
-
-       /**
-        * Returns the LBFactory class to use and the load balancer configuration.
-        *
-        * @todo instead of this, use a ServiceContainer for managing the different implementations.
-        *
-        * @param array $config (e.g. $wgLBFactoryConf)
-        * @return string Class name
-        */
-       public static function getLBFactoryClass( array $config ) {
-               // For configuration backward compatibility after removing
-               // underscores from class names in MediaWiki 1.23.
-               $bcClasses = [
-                       'LBFactory_Simple' => 'LBFactorySimple',
-                       'LBFactory_Single' => 'LBFactorySingle',
-                       'LBFactory_Multi' => 'LBFactoryMulti'
-               ];
-
-               $class = $config['class'];
-
-               if ( isset( $bcClasses[$class] ) ) {
-                       $class = $bcClasses[$class];
-                       wfDeprecated(
-                               '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
-                               '1.23'
-                       );
-               }
-
-               return $class;
-       }
-}
index 3318ceb..4d7c84d 100644 (file)
@@ -94,6 +94,9 @@ class LegacyLogger extends AbstractLogger {
         * @return null
         */
        public function log( $level, $message, array $context = [] ) {
+               if ( is_string( $level ) ) {
+                       $level = self::$levelMapping[$level];
+               }
                if ( $this->channel === 'DBQuery' && isset( $context['method'] )
                        && isset( $context['master'] ) && isset( $context['runtime'] )
                ) {
@@ -102,8 +105,7 @@ class LegacyLogger extends AbstractLogger {
                }
 
                if ( isset( self::$dbChannels[$this->channel] )
-                       && isset( self::$levelMapping[$level] )
-                       && self::$levelMapping[$level] >= LogLevel::ERROR
+                       && $level >= self::$levelMapping[LogLevel::ERROR]
                ) {
                        // Format and write DB errors to the legacy locations
                        $effectiveChannel = 'wfLogDBError';
@@ -127,7 +129,7 @@ class LegacyLogger extends AbstractLogger {
         *
         * @param string $channel
         * @param string $message
-        * @param string|int $level \Psr\Log\LogEvent constant or Monlog level int
+        * @param string|int $level \Psr\Log\LogEvent constant or Monolog level int
         * @param array $context
         * @return bool True if message should be sent to disk/network, false
         * otherwise
@@ -135,6 +137,10 @@ class LegacyLogger extends AbstractLogger {
        public static function shouldEmit( $channel, $message, $level, $context ) {
                global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
 
+               if ( is_string( $level ) ) {
+                       $level = self::$levelMapping[$level];
+               }
+
                if ( $channel === 'wfLogDBError' ) {
                        // wfLogDBError messages are emitted if a database log location is
                        // specfied.
@@ -162,9 +168,6 @@ class LegacyLogger extends AbstractLogger {
                                }
 
                                if ( isset( $logConfig['level'] ) ) {
-                                       if ( is_string( $level ) ) {
-                                               $level = self::$levelMapping[$level];
-                                       }
                                        $shouldEmit = $level >= self::$levelMapping[$logConfig['level']];
                                }
                        } else {
index 5496cb6..e958c94 100644 (file)
@@ -199,7 +199,26 @@ class MWException extends Exception {
         * It will be either HTML or plain text based on isCommandLine().
         */
        public function report() {
-               MWExceptionRenderer::output( $this, MWExceptionRenderer::AS_PRETTY );
+               global $wgMimeType;
+
+               if ( defined( 'MW_API' ) ) {
+                       // Unhandled API exception, we can't be sure that format printer is alive
+                       self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
+                       wfHttpError( 500, 'Internal Server Error', $this->getText() );
+               } elseif ( self::isCommandLine() ) {
+                       $message = $this->getText();
+                       // T17602: STDERR may not be available
+                       if ( defined( 'STDERR' ) ) {
+                               fwrite( STDERR, $message );
+                       } else {
+                               echo $message;
+                       }
+               } else {
+                       self::statusHeader( 500 );
+                       self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+
+                       $this->reportHTML();
+               }
        }
 
        /**
index 8359846..4a1f190 100644 (file)
@@ -62,12 +62,19 @@ class MWExceptionHandler {
        protected static function report( $e ) {
                try {
                        // Try and show the exception prettily, with the normal skin infrastructure
-                       MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+                       if ( $e instanceof MWException ) {
+                               // Delegate to MWException until all subclasses are handled by
+                               // MWExceptionRenderer and MWException::report() has been
+                               // removed.
+                               $e->report();
+                       } else {
+                               MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+                       }
                } catch ( Exception $e2 ) {
                        // Exception occurred from within exception handler
                        // Show a simpler message for the original exception,
                        // don't try to invoke report()
-                       MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY, $e2 );
+                       MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_RAW, $e2 );
                }
        }
 
index aba131d..8fdc417 100644 (file)
@@ -35,11 +35,6 @@ class MWExceptionRenderer {
        public static function output( $e, $mode, $eNew = null ) {
                global $wgMimeType;
 
-               if ( $e instanceof DBConnectionError ) {
-                       self::reportOutageHTML( $e );
-                       return;
-               }
-
                if ( defined( 'MW_API' ) ) {
                        // Unhandled API exception, we can't be sure that format printer is alive
                        self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
@@ -47,9 +42,13 @@ class MWExceptionRenderer {
                } elseif ( self::isCommandLine() ) {
                        self::printError( self::getText( $e ) );
                } elseif ( $mode === self::AS_PRETTY ) {
-                       self::statusHeader( 500 );
-                       self::header( "Content-Type: $wgMimeType; charset=utf-8" );
-                       self::reportHTML( $e );
+                       if ( $e instanceof DBConnectionError ) {
+                               self::reportOutageHTML( $e );
+                       } else {
+                               self::statusHeader( 500 );
+                               self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+                               self::reportHTML( $e );
+                       }
                } else {
                        if ( $eNew ) {
                                $message = "MediaWiki internal error.\n\n";
index 87d9441..e65a594 100644 (file)
@@ -22,6 +22,7 @@
  * @author Aaron Schulz
  */
 use \MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class to handle file backend registration
@@ -192,7 +193,7 @@ class FileBackendGroup {
                        'streamMimeFunc' => [ 'StreamFile', 'contentTypeFromPath' ],
                        'tmpDirectory' => wfTempDir(),
                        'statusWrapper' => [ 'Status', 'wrap' ],
-                       'wanCache' => ObjectCache::getMainWANInstance(),
+                       'wanCache' => MediaWikiServices::getInstance()->getMainWANObjectCache(),
                        'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
                        'logger' => LoggerFactory::getInstance( 'FileOperation' ),
                        'profiler' => Profiler::instance()
index c65d97f..1f4d99e 100644 (file)
@@ -176,7 +176,7 @@ class HTMLForm extends ContextSource {
        protected $mFieldTree;
        protected $mShowReset = false;
        protected $mShowSubmit = true;
-       protected $mSubmitFlags = [ 'constructive', 'primary' ];
+       protected $mSubmitFlags = [ 'primary', 'progressive' ];
        protected $mShowCancel = false;
        protected $mCancelTarget;
 
index cb98549..0c33ad9 100644 (file)
@@ -7,7 +7,7 @@
 class HTMLSubmitField extends HTMLButtonField {
        protected $buttonType = 'submit';
 
-       protected $mFlags = [ 'primary', 'constructive' ];
+       protected $mFlags = [ 'primary', 'progressive' ];
 
        public function skipLoadData( $request ) {
                return !$request->getCheck( $this->mName );
diff --git a/includes/http/CurlHttpRequest.php b/includes/http/CurlHttpRequest.php
new file mode 100644 (file)
index 0000000..f58c3a9
--- /dev/null
@@ -0,0 +1,165 @@
+<?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
+ */
+
+/**
+ * MWHttpRequest implemented using internal curl compiled into PHP
+ */
+class CurlHttpRequest extends MWHttpRequest {
+       const SUPPORTS_FILE_POSTS = true;
+
+       protected $curlOptions = [];
+       protected $headerText = "";
+
+       /**
+        * @param resource $fh
+        * @param string $content
+        * @return int
+        */
+       protected function readHeader( $fh, $content ) {
+               $this->headerText .= $content;
+               return strlen( $content );
+       }
+
+       public function execute() {
+
+               parent::execute();
+
+               if ( !$this->status->isOK() ) {
+                       return $this->status;
+               }
+
+               $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
+               $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
+
+               // Only supported in curl >= 7.16.2
+               if ( defined( 'CURLOPT_CONNECTTIMEOUT_MS' ) ) {
+                       $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000;
+               }
+
+               $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+               $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
+               $this->curlOptions[CURLOPT_HEADERFUNCTION] = [ $this, "readHeader" ];
+               $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
+               $this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
+
+               $this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
+
+               $this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost ? 2 : 0;
+               $this->curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->sslVerifyCert;
+
+               if ( $this->caInfo ) {
+                       $this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
+               }
+
+               if ( $this->headersOnly ) {
+                       $this->curlOptions[CURLOPT_NOBODY] = true;
+                       $this->curlOptions[CURLOPT_HEADER] = true;
+               } elseif ( $this->method == 'POST' ) {
+                       $this->curlOptions[CURLOPT_POST] = true;
+                       $postData = $this->postData;
+                       // Don't interpret POST parameters starting with '@' as file uploads, because this
+                       // makes it impossible to POST plain values starting with '@' (and causes security
+                       // issues potentially exposing the contents of local files).
+                       // The PHP manual says this option was introduced in PHP 5.5 defaults to true in PHP 5.6,
+                       // but we support lower versions, and the option doesn't exist in HHVM 5.6.99.
+                       if ( defined( 'CURLOPT_SAFE_UPLOAD' ) ) {
+                               $this->curlOptions[CURLOPT_SAFE_UPLOAD] = true;
+                       } elseif ( is_array( $postData ) ) {
+                               // In PHP 5.2 and later, '@' is interpreted as a file upload if POSTFIELDS
+                               // is an array, but not if it's a string. So convert $req['body'] to a string
+                               // for safety.
+                               $postData = wfArrayToCgi( $postData );
+                       }
+                       $this->curlOptions[CURLOPT_POSTFIELDS] = $postData;
+
+                       // Suppress 'Expect: 100-continue' header, as some servers
+                       // will reject it with a 417 and Curl won't auto retry
+                       // with HTTP 1.0 fallback
+                       $this->reqHeaders['Expect'] = '';
+               } else {
+                       $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
+               }
+
+               $this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
+
+               $curlHandle = curl_init( $this->url );
+
+               if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
+                       throw new MWException( "Error setting curl options." );
+               }
+
+               if ( $this->followRedirects && $this->canFollowRedirects() ) {
+                       MediaWiki\suppressWarnings();
+                       if ( !curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
+                               $this->logger->debug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
+                                       "Probably open_basedir is set.\n" );
+                               // Continue the processing. If it were in curl_setopt_array,
+                               // processing would have halted on its entry
+                       }
+                       MediaWiki\restoreWarnings();
+               }
+
+               if ( $this->profiler ) {
+                       $profileSection = $this->profiler->scopedProfileIn(
+                               __METHOD__ . '-' . $this->profileName
+                       );
+               }
+
+               $curlRes = curl_exec( $curlHandle );
+               if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
+                       $this->status->fatal( 'http-timed-out', $this->url );
+               } elseif ( $curlRes === false ) {
+                       $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
+               } else {
+                       $this->headerList = explode( "\r\n", $this->headerText );
+               }
+
+               curl_close( $curlHandle );
+
+               if ( $this->profiler ) {
+                       $this->profiler->scopedProfileOut( $profileSection );
+               }
+
+               $this->parseHeader();
+               $this->setStatus();
+
+               return $this->status;
+       }
+
+       /**
+        * @return bool
+        */
+       public function canFollowRedirects() {
+               $curlVersionInfo = curl_version();
+               if ( $curlVersionInfo['version_number'] < 0x071304 ) {
+                       $this->logger->debug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
+                       return false;
+               }
+
+               if ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
+                       if ( strval( ini_get( 'open_basedir' ) ) !== '' ) {
+                               $this->logger->debug( "Cannot follow redirects when open_basedir is set\n" );
+                               return false;
+                       }
+               }
+
+               return true;
+       }
+}
diff --git a/includes/http/Http.php b/includes/http/Http.php
new file mode 100644 (file)
index 0000000..43ae2d0
--- /dev/null
@@ -0,0 +1,163 @@
+<?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 MediaWiki\Logger\LoggerFactory;
+
+/**
+ * Various HTTP related functions
+ * @ingroup HTTP
+ */
+class Http {
+       static public $httpEngine = false;
+
+       /**
+        * Perform an HTTP request
+        *
+        * @param string $method HTTP method. Usually GET/POST
+        * @param string $url Full URL to act on. If protocol-relative, will be expanded to an http:// URL
+        * @param array $options Options to pass to MWHttpRequest object.
+        *      Possible keys for the array:
+        *    - timeout             Timeout length in seconds
+        *    - connectTimeout      Timeout for connection, in seconds (curl only)
+        *    - postData            An array of key-value pairs or a url-encoded form data
+        *    - proxy               The proxy to use.
+        *                          Otherwise it will use $wgHTTPProxy (if set)
+        *                          Otherwise it will use the environment variable "http_proxy" (if set)
+        *    - noProxy             Don't use any proxy at all. Takes precedence over proxy value(s).
+        *    - sslVerifyHost       Verify hostname against certificate
+        *    - sslVerifyCert       Verify SSL certificate
+        *    - caInfo              Provide CA information
+        *    - maxRedirects        Maximum number of redirects to follow (defaults to 5)
+        *    - followRedirects     Whether to follow redirects (defaults to false).
+        *                                  Note: this should only be used when the target URL is trusted,
+        *                                  to avoid attacks on intranet services accessible by HTTP.
+        *    - userAgent           A user agent, if you want to override the default
+        *                          MediaWiki/$wgVersion
+        *    - logger              A \Psr\Logger\LoggerInterface instance for debug logging
+        * @param string $caller The method making this request, for profiling
+        * @return string|bool (bool)false on failure or a string on success
+        */
+       public static function request( $method, $url, $options = [], $caller = __METHOD__ ) {
+               wfDebug( "HTTP: $method: $url\n" );
+
+               $options['method'] = strtoupper( $method );
+
+               if ( !isset( $options['timeout'] ) ) {
+                       $options['timeout'] = 'default';
+               }
+               if ( !isset( $options['connectTimeout'] ) ) {
+                       $options['connectTimeout'] = 'default';
+               }
+
+               $req = MWHttpRequest::factory( $url, $options, $caller );
+               $status = $req->execute();
+
+               if ( $status->isOK() ) {
+                       return $req->getContent();
+               } else {
+                       $errors = $status->getErrorsByType( 'error' );
+                       $logger = LoggerFactory::getInstance( 'http' );
+                       $logger->warning( $status->getWikiText( false, false, 'en' ),
+                               [ 'error' => $errors, 'caller' => $caller, 'content' => $req->getContent() ] );
+                       return false;
+               }
+       }
+
+       /**
+        * Simple wrapper for Http::request( 'GET' )
+        * @see Http::request()
+        * @since 1.25 Second parameter $timeout removed. Second parameter
+        * is now $options which can be given a 'timeout'
+        *
+        * @param string $url
+        * @param array $options
+        * @param string $caller The method making this request, for profiling
+        * @return string|bool false on error
+        */
+       public static function get( $url, $options = [], $caller = __METHOD__ ) {
+               $args = func_get_args();
+               if ( isset( $args[1] ) && ( is_string( $args[1] ) || is_numeric( $args[1] ) ) ) {
+                       // Second was used to be the timeout
+                       // And third parameter used to be $options
+                       wfWarn( "Second parameter should not be a timeout.", 2 );
+                       $options = isset( $args[2] ) && is_array( $args[2] ) ?
+                               $args[2] : [];
+                       $options['timeout'] = $args[1];
+                       $caller = __METHOD__;
+               }
+               return Http::request( 'GET', $url, $options, $caller );
+       }
+
+       /**
+        * Simple wrapper for Http::request( 'POST' )
+        * @see Http::request()
+        *
+        * @param string $url
+        * @param array $options
+        * @param string $caller The method making this request, for profiling
+        * @return string|bool false on error
+        */
+       public static function post( $url, $options = [], $caller = __METHOD__ ) {
+               return Http::request( 'POST', $url, $options, $caller );
+       }
+
+       /**
+        * A standard user-agent we can use for external requests.
+        * @return string
+        */
+       public static function userAgent() {
+               global $wgVersion;
+               return "MediaWiki/$wgVersion";
+       }
+
+       /**
+        * Checks that the given URI is a valid one. Hardcoding the
+        * protocols, because we only want protocols that both cURL
+        * and php support.
+        *
+        * file:// should not be allowed here for security purpose (r67684)
+        *
+        * @todo FIXME this is wildly inaccurate and fails to actually check most stuff
+        *
+        * @param string $uri URI to check for validity
+        * @return bool
+        */
+       public static function isValidURI( $uri ) {
+               return preg_match(
+                       '/^https?:\/\/[^\/\s]\S*$/D',
+                       $uri
+               );
+       }
+
+       /**
+        * Gets the relevant proxy from $wgHTTPProxy
+        *
+        * @return mixed The proxy address or an empty string if not set.
+        */
+       public static function getProxy() {
+               global $wgHTTPProxy;
+
+               if ( $wgHTTPProxy ) {
+                       return $wgHTTPProxy;
+               }
+
+               return "";
+       }
+}
diff --git a/includes/http/MWHttpRequest.php b/includes/http/MWHttpRequest.php
new file mode 100644 (file)
index 0000000..458854a
--- /dev/null
@@ -0,0 +1,618 @@
+<?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 MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * This wrapper class will call out to curl (if available) or fallback
+ * to regular PHP if necessary for handling internal HTTP requests.
+ *
+ * Renamed from HttpRequest to MWHttpRequest to avoid conflict with
+ * PHP's HTTP extension.
+ */
+class MWHttpRequest implements LoggerAwareInterface {
+       const SUPPORTS_FILE_POSTS = false;
+
+       protected $content;
+       protected $timeout = 'default';
+       protected $headersOnly = null;
+       protected $postData = null;
+       protected $proxy = null;
+       protected $noProxy = false;
+       protected $sslVerifyHost = true;
+       protected $sslVerifyCert = true;
+       protected $caInfo = null;
+       protected $method = "GET";
+       protected $reqHeaders = [];
+       protected $url;
+       protected $parsedUrl;
+       protected $callback;
+       protected $maxRedirects = 5;
+       protected $followRedirects = false;
+
+       /**
+        * @var CookieJar
+        */
+       protected $cookieJar;
+
+       protected $headerList = [];
+       protected $respVersion = "0.9";
+       protected $respStatus = "200 Ok";
+       protected $respHeaders = [];
+
+       public $status;
+
+       /**
+        * @var Profiler
+        */
+       protected $profiler;
+
+       /**
+        * @var string
+        */
+       protected $profileName;
+
+       /**
+        * @var LoggerInterface;
+        */
+       protected $logger;
+
+       /**
+        * @param string $url Url to use. If protocol-relative, will be expanded to an http:// URL
+        * @param array $options (optional) extra params to pass (see Http::request())
+        * @param string $caller The method making this request, for profiling
+        * @param Profiler $profiler An instance of the profiler for profiling, or null
+        */
+       protected function __construct(
+               $url, $options = [], $caller = __METHOD__, $profiler = null
+       ) {
+               global $wgHTTPTimeout, $wgHTTPConnectTimeout;
+
+               $this->url = wfExpandUrl( $url, PROTO_HTTP );
+               $this->parsedUrl = wfParseUrl( $this->url );
+
+               if ( isset( $options['logger'] ) ) {
+                       $this->logger = $options['logger'];
+               } else {
+                       $this->logger = new NullLogger();
+               }
+
+               if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
+                       $this->status = Status::newFatal( 'http-invalid-url', $url );
+               } else {
+                       $this->status = Status::newGood( 100 ); // continue
+               }
+
+               if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
+                       $this->timeout = $options['timeout'];
+               } else {
+                       $this->timeout = $wgHTTPTimeout;
+               }
+               if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) {
+                       $this->connectTimeout = $options['connectTimeout'];
+               } else {
+                       $this->connectTimeout = $wgHTTPConnectTimeout;
+               }
+               if ( isset( $options['userAgent'] ) ) {
+                       $this->setUserAgent( $options['userAgent'] );
+               }
+
+               $members = [ "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
+                               "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" ];
+
+               foreach ( $members as $o ) {
+                       if ( isset( $options[$o] ) ) {
+                               // ensure that MWHttpRequest::method is always
+                               // uppercased. Bug 36137
+                               if ( $o == 'method' ) {
+                                       $options[$o] = strtoupper( $options[$o] );
+                               }
+                               $this->$o = $options[$o];
+                       }
+               }
+
+               if ( $this->noProxy ) {
+                       $this->proxy = ''; // noProxy takes precedence
+               }
+
+               // Profile based on what's calling us
+               $this->profiler = $profiler;
+               $this->profileName = $caller;
+       }
+
+       /**
+        * @param LoggerInterface $logger
+        */
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * Simple function to test if we can make any sort of requests at all, using
+        * cURL or fopen()
+        * @return bool
+        */
+       public static function canMakeRequests() {
+               return function_exists( 'curl_init' ) || wfIniGetBool( 'allow_url_fopen' );
+       }
+
+       /**
+        * Generate a new request object
+        * @param string $url Url to use
+        * @param array $options (optional) extra params to pass (see Http::request())
+        * @param string $caller The method making this request, for profiling
+        * @throws MWException
+        * @return CurlHttpRequest|PhpHttpRequest
+        * @see MWHttpRequest::__construct
+        */
+       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
+               if ( !Http::$httpEngine ) {
+                       Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
+               } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
+                       throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
+                               ' Http::$httpEngine is set to "curl"' );
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [];
+               }
+
+               if ( !isset( $options['logger'] ) ) {
+                       $options['logger'] = LoggerFactory::getInstance( 'http' );
+               }
+
+               switch ( Http::$httpEngine ) {
+                       case 'curl':
+                               return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() );
+                       case 'php':
+                               if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
+                                       throw new MWException( __METHOD__ . ': allow_url_fopen ' .
+                                               'needs to be enabled for pure PHP http requests to ' .
+                                               'work. If possible, curl should be used instead. See ' .
+                                               'http://php.net/curl.'
+                                       );
+                               }
+                               return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
+                       default:
+                               throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
+               }
+       }
+
+       /**
+        * Get the body, or content, of the response to the request
+        *
+        * @return string
+        */
+       public function getContent() {
+               return $this->content;
+       }
+
+       /**
+        * Set the parameters of the request
+        *
+        * @param array $args
+        * @todo overload the args param
+        */
+       public function setData( $args ) {
+               $this->postData = $args;
+       }
+
+       /**
+        * Take care of setting up the proxy (do nothing if "noProxy" is set)
+        *
+        * @return void
+        */
+       public function proxySetup() {
+               // If there is an explicit proxy set and proxies are not disabled, then use it
+               if ( $this->proxy && !$this->noProxy ) {
+                       return;
+               }
+
+               // Otherwise, fallback to $wgHTTPProxy if this is not a machine
+               // local URL and proxies are not disabled
+               if ( self::isLocalURL( $this->url ) || $this->noProxy ) {
+                       $this->proxy = '';
+               } else {
+                       $this->proxy = Http::getProxy();
+               }
+       }
+
+       /**
+        * Check if the URL can be served by localhost
+        *
+        * @param string $url Full url to check
+        * @return bool
+        */
+       private static function isLocalURL( $url ) {
+               global $wgCommandLineMode, $wgLocalVirtualHosts;
+
+               if ( $wgCommandLineMode ) {
+                       return false;
+               }
+
+               // Extract host part
+               $matches = [];
+               if ( preg_match( '!^https?://([\w.-]+)[/:].*$!', $url, $matches ) ) {
+                       $host = $matches[1];
+                       // Split up dotwise
+                       $domainParts = explode( '.', $host );
+                       // Check if this domain or any superdomain is listed as a local virtual host
+                       $domainParts = array_reverse( $domainParts );
+
+                       $domain = '';
+                       $countParts = count( $domainParts );
+                       for ( $i = 0; $i < $countParts; $i++ ) {
+                               $domainPart = $domainParts[$i];
+                               if ( $i == 0 ) {
+                                       $domain = $domainPart;
+                               } else {
+                                       $domain = $domainPart . '.' . $domain;
+                               }
+
+                               if ( in_array( $domain, $wgLocalVirtualHosts ) ) {
+                                       return true;
+                               }
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Set the user agent
+        * @param string $UA
+        */
+       public function setUserAgent( $UA ) {
+               $this->setHeader( 'User-Agent', $UA );
+       }
+
+       /**
+        * Set an arbitrary header
+        * @param string $name
+        * @param string $value
+        */
+       public function setHeader( $name, $value ) {
+               // I feel like I should normalize the case here...
+               $this->reqHeaders[$name] = $value;
+       }
+
+       /**
+        * Get an array of the headers
+        * @return array
+        */
+       public function getHeaderList() {
+               $list = [];
+
+               if ( $this->cookieJar ) {
+                       $this->reqHeaders['Cookie'] =
+                               $this->cookieJar->serializeToHttpRequest(
+                                       $this->parsedUrl['path'],
+                                       $this->parsedUrl['host']
+                               );
+               }
+
+               foreach ( $this->reqHeaders as $name => $value ) {
+                       $list[] = "$name: $value";
+               }
+
+               return $list;
+       }
+
+       /**
+        * Set a read callback to accept data read from the HTTP request.
+        * By default, data is appended to an internal buffer which can be
+        * retrieved through $req->getContent().
+        *
+        * To handle data as it comes in -- especially for large files that
+        * would not fit in memory -- you can instead set your own callback,
+        * in the form function($resource, $buffer) where the first parameter
+        * is the low-level resource being read (implementation specific),
+        * and the second parameter is the data buffer.
+        *
+        * You MUST return the number of bytes handled in the buffer; if fewer
+        * bytes are reported handled than were passed to you, the HTTP fetch
+        * will be aborted.
+        *
+        * @param callable $callback
+        * @throws MWException
+        */
+       public function setCallback( $callback ) {
+               if ( !is_callable( $callback ) ) {
+                       throw new MWException( 'Invalid MwHttpRequest callback' );
+               }
+               $this->callback = $callback;
+       }
+
+       /**
+        * A generic callback to read the body of the response from a remote
+        * server.
+        *
+        * @param resource $fh
+        * @param string $content
+        * @return int
+        */
+       public function read( $fh, $content ) {
+               $this->content .= $content;
+               return strlen( $content );
+       }
+
+       /**
+        * Take care of whatever is necessary to perform the URI request.
+        *
+        * @return Status
+        */
+       public function execute() {
+
+               $this->content = "";
+
+               if ( strtoupper( $this->method ) == "HEAD" ) {
+                       $this->headersOnly = true;
+               }
+
+               $this->proxySetup(); // set up any proxy as needed
+
+               if ( !$this->callback ) {
+                       $this->setCallback( [ $this, 'read' ] );
+               }
+
+               if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
+                       $this->setUserAgent( Http::userAgent() );
+               }
+
+       }
+
+       /**
+        * Parses the headers, including the HTTP status code and any
+        * Set-Cookie headers.  This function expects the headers to be
+        * found in an array in the member variable headerList.
+        */
+       protected function parseHeader() {
+
+               $lastname = "";
+
+               foreach ( $this->headerList as $header ) {
+                       if ( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
+                               $this->respVersion = $match[1];
+                               $this->respStatus = $match[2];
+                       } elseif ( preg_match( "#^[ \t]#", $header ) ) {
+                               $last = count( $this->respHeaders[$lastname] ) - 1;
+                               $this->respHeaders[$lastname][$last] .= "\r\n$header";
+                       } elseif ( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
+                               $this->respHeaders[strtolower( $match[1] )][] = $match[2];
+                               $lastname = strtolower( $match[1] );
+                       }
+               }
+
+               $this->parseCookies();
+
+       }
+
+       /**
+        * Sets HTTPRequest status member to a fatal value with the error
+        * message if the returned integer value of the status code was
+        * not successful (< 300) or a redirect (>=300 and < 400).  (see
+        * RFC2616, section 10,
+        * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for a
+        * list of status codes.)
+        */
+       protected function setStatus() {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               if ( (int)$this->respStatus > 399 ) {
+                       list( $code, $message ) = explode( " ", $this->respStatus, 2 );
+                       $this->status->fatal( "http-bad-status", $code, $message );
+               }
+       }
+
+       /**
+        * Get the integer value of the HTTP status code (e.g. 200 for "200 Ok")
+        * (see RFC2616, section 10, http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+        * for a list of status codes.)
+        *
+        * @return int
+        */
+       public function getStatus() {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               return (int)$this->respStatus;
+       }
+
+       /**
+        * Returns true if the last status code was a redirect.
+        *
+        * @return bool
+        */
+       public function isRedirect() {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               $status = (int)$this->respStatus;
+
+               if ( $status >= 300 && $status <= 303 ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Returns an associative array of response headers after the
+        * request has been executed.  Because some headers
+        * (e.g. Set-Cookie) can appear more than once the, each value of
+        * the associative array is an array of the values given.
+        *
+        * @return array
+        */
+       public function getResponseHeaders() {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               return $this->respHeaders;
+       }
+
+       /**
+        * Returns the value of the given response header.
+        *
+        * @param string $header
+        * @return string|null
+        */
+       public function getResponseHeader( $header ) {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               if ( isset( $this->respHeaders[strtolower( $header )] ) ) {
+                       $v = $this->respHeaders[strtolower( $header )];
+                       return $v[count( $v ) - 1];
+               }
+
+               return null;
+       }
+
+       /**
+        * Tells the MWHttpRequest object to use this pre-loaded CookieJar.
+        *
+        * @param CookieJar $jar
+        */
+       public function setCookieJar( $jar ) {
+               $this->cookieJar = $jar;
+       }
+
+       /**
+        * Returns the cookie jar in use.
+        *
+        * @return CookieJar
+        */
+       public function getCookieJar() {
+               if ( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+
+               return $this->cookieJar;
+       }
+
+       /**
+        * Sets a cookie. Used before a request to set up any individual
+        * cookies. Used internally after a request to parse the
+        * Set-Cookie headers.
+        * @see Cookie::set
+        * @param string $name
+        * @param mixed $value
+        * @param array $attr
+        */
+       public function setCookie( $name, $value = null, $attr = null ) {
+               if ( !$this->cookieJar ) {
+                       $this->cookieJar = new CookieJar;
+               }
+
+               $this->cookieJar->setCookie( $name, $value, $attr );
+       }
+
+       /**
+        * Parse the cookies in the response headers and store them in the cookie jar.
+        */
+       protected function parseCookies() {
+
+               if ( !$this->cookieJar ) {
+                       $this->cookieJar = new CookieJar;
+               }
+
+               if ( isset( $this->respHeaders['set-cookie'] ) ) {
+                       $url = parse_url( $this->getFinalUrl() );
+                       foreach ( $this->respHeaders['set-cookie'] as $cookie ) {
+                               $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
+                       }
+               }
+
+       }
+
+       /**
+        * Returns the final URL after all redirections.
+        *
+        * Relative values of the "Location" header are incorrect as
+        * stated in RFC, however they do happen and modern browsers
+        * support them.  This function loops backwards through all
+        * locations in order to build the proper absolute URI - Marooned
+        * at wikia-inc.com
+        *
+        * Note that the multiple Location: headers are an artifact of
+        * CURL -- they shouldn't actually get returned this way. Rewrite
+        * this when bug 29232 is taken care of (high-level redirect
+        * handling rewrite).
+        *
+        * @return string
+        */
+       public function getFinalUrl() {
+               $headers = $this->getResponseHeaders();
+
+               // return full url (fix for incorrect but handled relative location)
+               if ( isset( $headers['location'] ) ) {
+                       $locations = $headers['location'];
+                       $domain = '';
+                       $foundRelativeURI = false;
+                       $countLocations = count( $locations );
+
+                       for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
+                               $url = parse_url( $locations[$i] );
+
+                               if ( isset( $url['host'] ) ) {
+                                       $domain = $url['scheme'] . '://' . $url['host'];
+                                       break; // found correct URI (with host)
+                               } else {
+                                       $foundRelativeURI = true;
+                               }
+                       }
+
+                       if ( $foundRelativeURI ) {
+                               if ( $domain ) {
+                                       return $domain . $locations[$countLocations - 1];
+                               } else {
+                                       $url = parse_url( $this->url );
+                                       if ( isset( $url['host'] ) ) {
+                                               return $url['scheme'] . '://' . $url['host'] .
+                                                       $locations[$countLocations - 1];
+                                       }
+                               }
+                       } else {
+                               return $locations[$countLocations - 1];
+                       }
+               }
+
+               return $this->url;
+       }
+
+       /**
+        * Returns true if the backend can follow redirects. Overridden by the
+        * child classes.
+        * @return bool
+        */
+       public function canFollowRedirects() {
+               return true;
+       }
+}
diff --git a/includes/http/PhpHttpRequest.php b/includes/http/PhpHttpRequest.php
new file mode 100644 (file)
index 0000000..2af000f
--- /dev/null
@@ -0,0 +1,258 @@
+<?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
+ */
+
+class PhpHttpRequest extends MWHttpRequest {
+
+       private $fopenErrors = [];
+
+       /**
+        * @param string $url
+        * @return string
+        */
+       protected function urlToTcp( $url ) {
+               $parsedUrl = parse_url( $url );
+
+               return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
+       }
+
+       /**
+        * Returns an array with a 'capath' or 'cafile' key
+        * that is suitable to be merged into the 'ssl' sub-array of
+        * a stream context options array.
+        * Uses the 'caInfo' option of the class if it is provided, otherwise uses the system
+        * default CA bundle if PHP supports that, or searches a few standard locations.
+        * @return array
+        * @throws DomainException
+        */
+       protected function getCertOptions() {
+               $certOptions = [];
+               $certLocations = [];
+               if ( $this->caInfo ) {
+                       $certLocations = [ 'manual' => $this->caInfo ];
+               } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
+                       // @codingStandardsIgnoreStart Generic.Files.LineLength
+                       // Default locations, based on
+                       // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/
+                       // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves.
+                       // PHP 5.6+ gets the CA location from OpenSSL as long as it is not set manually,
+                       // so we should leave capath/cafile empty there.
+                       // @codingStandardsIgnoreEnd
+                       $certLocations = array_filter( [
+                               getenv( 'SSL_CERT_DIR' ),
+                               getenv( 'SSL_CERT_PATH' ),
+                               '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al
+                               '/etc/ssl/certs',  # Debian et al
+                               '/etc/pki/tls/certs/ca-bundle.trust.crt',
+                               '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem',
+                               '/System/Library/OpenSSL', # OSX
+                       ] );
+               }
+
+               foreach ( $certLocations as $key => $cert ) {
+                       if ( is_dir( $cert ) ) {
+                               $certOptions['capath'] = $cert;
+                               break;
+                       } elseif ( is_file( $cert ) ) {
+                               $certOptions['cafile'] = $cert;
+                               break;
+                       } elseif ( $key === 'manual' ) {
+                               // fail more loudly if a cert path was manually configured and it is not valid
+                               throw new DomainException( "Invalid CA info passed: $cert" );
+                       }
+               }
+
+               return $certOptions;
+       }
+
+       /**
+        * Custom error handler for dealing with fopen() errors.
+        * fopen() tends to fire multiple errors in succession, and the last one
+        * is completely useless (something like "fopen: failed to open stream")
+        * so normal methods of handling errors programmatically
+        * like get_last_error() don't work.
+        */
+       public function errorHandler( $errno, $errstr ) {
+               $n = count( $this->fopenErrors ) + 1;
+               $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ];
+       }
+
+       public function execute() {
+
+               parent::execute();
+
+               if ( is_array( $this->postData ) ) {
+                       $this->postData = wfArrayToCgi( $this->postData );
+               }
+
+               if ( $this->parsedUrl['scheme'] != 'http'
+                       && $this->parsedUrl['scheme'] != 'https' ) {
+                       $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
+               }
+
+               $this->reqHeaders['Accept'] = "*/*";
+               $this->reqHeaders['Connection'] = 'Close';
+               if ( $this->method == 'POST' ) {
+                       // Required for HTTP 1.0 POSTs
+                       $this->reqHeaders['Content-Length'] = strlen( $this->postData );
+                       if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
+                               $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
+                       }
+               }
+
+               // Set up PHP stream context
+               $options = [
+                       'http' => [
+                               'method' => $this->method,
+                               'header' => implode( "\r\n", $this->getHeaderList() ),
+                               'protocol_version' => '1.1',
+                               'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0,
+                               'ignore_errors' => true,
+                               'timeout' => $this->timeout,
+                               // Curl options in case curlwrappers are installed
+                               'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0,
+                               'curl_verify_ssl_peer' => $this->sslVerifyCert,
+                       ],
+                       'ssl' => [
+                               'verify_peer' => $this->sslVerifyCert,
+                               'SNI_enabled' => true,
+                               'ciphers' => 'HIGH:!SSLv2:!SSLv3:-ADH:-kDH:-kECDH:-DSS',
+                               'disable_compression' => true,
+                       ],
+               ];
+
+               if ( $this->proxy ) {
+                       $options['http']['proxy'] = $this->urlToTcp( $this->proxy );
+                       $options['http']['request_fulluri'] = true;
+               }
+
+               if ( $this->postData ) {
+                       $options['http']['content'] = $this->postData;
+               }
+
+               if ( $this->sslVerifyHost ) {
+                       // PHP 5.6.0 deprecates CN_match, in favour of peer_name which
+                       // actually checks SubjectAltName properly.
+                       if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) {
+                               $options['ssl']['peer_name'] = $this->parsedUrl['host'];
+                       } else {
+                               $options['ssl']['CN_match'] = $this->parsedUrl['host'];
+                       }
+               }
+
+               $options['ssl'] += $this->getCertOptions();
+
+               $context = stream_context_create( $options );
+
+               $this->headerList = [];
+               $reqCount = 0;
+               $url = $this->url;
+
+               $result = [];
+
+               if ( $this->profiler ) {
+                       $profileSection = $this->profiler->scopedProfileIn(
+                               __METHOD__ . '-' . $this->profileName
+                       );
+               }
+               do {
+                       $reqCount++;
+                       $this->fopenErrors = [];
+                       set_error_handler( [ $this, 'errorHandler' ] );
+                       $fh = fopen( $url, "r", false, $context );
+                       restore_error_handler();
+
+                       if ( !$fh ) {
+                               // HACK for instant commons.
+                               // If we are contacting (commons|upload).wikimedia.org
+                               // try again with CN_match for en.wikipedia.org
+                               // as php does not handle SubjectAltName properly
+                               // prior to "peer_name" option in php 5.6
+                               if ( isset( $options['ssl']['CN_match'] )
+                                       && ( $options['ssl']['CN_match'] === 'commons.wikimedia.org'
+                                               || $options['ssl']['CN_match'] === 'upload.wikimedia.org' )
+                               ) {
+                                       $options['ssl']['CN_match'] = 'en.wikipedia.org';
+                                       $context = stream_context_create( $options );
+                                       continue;
+                               }
+                               break;
+                       }
+
+                       $result = stream_get_meta_data( $fh );
+                       $this->headerList = $result['wrapper_data'];
+                       $this->parseHeader();
+
+                       if ( !$this->followRedirects ) {
+                               break;
+                       }
+
+                       # Handle manual redirection
+                       if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
+                               break;
+                       }
+                       # Check security of URL
+                       $url = $this->getResponseHeader( "Location" );
+
+                       if ( !Http::isValidURI( $url ) ) {
+                               $this->logger->debug( __METHOD__ . ": insecure redirection\n" );
+                               break;
+                       }
+               } while ( true );
+               if ( $this->profiler ) {
+                       $this->profiler->scopedProfileOut( $profileSection );
+               }
+
+               $this->setStatus();
+
+               if ( $fh === false ) {
+                       if ( $this->fopenErrors ) {
+                               $this->logger->warning( __CLASS__
+                                       . ': error opening connection: {errstr1}', $this->fopenErrors );
+                       }
+                       $this->status->fatal( 'http-request-error' );
+                       return $this->status;
+               }
+
+               if ( $result['timed_out'] ) {
+                       $this->status->fatal( 'http-timed-out', $this->url );
+                       return $this->status;
+               }
+
+               // If everything went OK, or we received some error code
+               // get the response body content.
+               if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
+                       while ( !feof( $fh ) ) {
+                               $buf = fread( $fh, 8192 );
+
+                               if ( $buf === false ) {
+                                       $this->status->fatal( 'http-read-error' );
+                                       break;
+                               }
+
+                               if ( strlen( $buf ) ) {
+                                       call_user_func( $this->callback, $fh, $buf );
+                               }
+                       }
+               }
+               fclose( $fh );
+
+               return $this->status;
+       }
+}
index eafb9d4..03f9974 100644 (file)
@@ -244,6 +244,7 @@ abstract class Installer {
        protected $objectCaches = [
                'xcache' => 'xcache_get',
                'apc' => 'apc_fetch',
+               'apcu' => 'apcu_fetch',
                'wincache' => 'wincache_ucache_get'
        ];
 
index 770d3bf..1175e9e 100644 (file)
@@ -92,6 +92,8 @@ class MssqlUpdater extends DatabaseUpdater {
                        // 1.28
                        [ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
+                       [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
+                       [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
                ];
        }
 
index 693b6ff..497f273 100644 (file)
@@ -288,6 +288,8 @@ class MysqlUpdater extends DatabaseUpdater {
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
                        [ 'doRevisionPageRevIndexNonUnique' ],
                        [ 'doNonUniquePlTlIl' ],
+                       [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
+                       [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
                ];
        }
 
index 8075aac..e1e0d0f 100644 (file)
@@ -116,6 +116,8 @@ class OracleUpdater extends DatabaseUpdater {
                        // 1.28
                        [ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
+                       [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
+                       [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
 
                        // KEEP THIS AT THE BOTTOM!!
                        [ 'doRebuildDuplicateFunction' ],
index be94d91..f3d2860 100644 (file)
@@ -68,6 +68,8 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addSequence', 'archive', false, 'archive_ar_id_seq' ],
                        [ 'addSequence', 'externallinks', false, 'externallinks_el_id_seq' ],
                        [ 'addSequence', 'watchlist', false, 'watchlist_wl_id_seq' ],
+                       [ 'addSequence', 'change_tag', false, 'change_tag_ct_id_seq' ],
+                       [ 'addSequence', 'tag_summary', false, 'tag_summary_ts_id_seq' ],
 
                        # new tables
                        [ 'addTable', 'category', 'patch-category.sql' ],
@@ -437,6 +439,10 @@ class PostgresUpdater extends DatabaseUpdater {
                        // 1.28
                        [ 'addPgIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
                                '( rc_namespace, rc_type, rc_patrolled, rc_timestamp )' ],
+                       [ 'addPgField', 'change_tag', 'ct_id',
+                               "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('change_tag_ct_id_seq')" ],
+                       [ 'addPgField', 'tag_summary', 'ts_id',
+                               "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('tag_summary_ts_id_seq')" ],
                ];
        }
 
index 1c6e6eb..388c034 100644 (file)
@@ -156,6 +156,8 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.28
                        [ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
                                'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
+                       [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
+                       [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
                ];
        }
 
index fb23d01..6926650 100644 (file)
@@ -6,7 +6,8 @@
                        "Aftab1995",
                        "Tauhid16",
                        "Aftabuzzaman",
-                       "Hasive"
+                       "Hasive",
+                       "আজিজ"
                ]
        },
        "config-desc": "মিডিয়াউইকির জন্য ইন্সটলার",
@@ -94,7 +95,9 @@
        "config-admin-name": "আপনার ব্যবহারকারী নাম:",
        "config-admin-password": "পাসওয়ার্ড:",
        "config-admin-password-confirm": "পাসওয়ার্ড আবারও প্রবেশ করান:",
+       "config-admin-help": "এখানে আপনার পছন্দের ব্যবহারকারী নাম লিখুন, উদাহরণস্বরূপ \"আজিজ\"। এই নামটি উইকিতে প্রবেশের সময় ব্যবহার করতে হবে।",
        "config-admin-name-blank": "একটি প্রশাসক ব্যবহারকারী নাম প্রবেশ করান",
+       "config-admin-name-invalid": "উল্লেখিত ব্যবহারকারী নাম \"<nowiki>$1</nowiki>\" অবৈধ। একটি ভিন্ন নাম উল্লেখ করুন।",
        "config-admin-password-blank": "প্রশাসক অ্যাকাউন্টের জন্য পাসওয়ার্ড প্রবেশ করান।",
        "config-admin-password-mismatch": "আপনি যে দুটি পাসওয়ার্ড দিয়েছেন তারা পরস্পর মেলেনি।",
        "config-admin-email": "ইমেইল ঠিকানা:",
index 68497c5..c696650 100644 (file)
@@ -66,6 +66,7 @@
        "config-memory-bad": "<strong>Upozornění:</strong> <code>memory_limit</code> je v PHP nastaven na $1.\nTo je pravděpodobně příliš málo.\nInstalace může selhat!",
        "config-xcache": "Je nainstalována [http://xcache.lighttpd.net/ XCache]",
        "config-apc": "Je nainstalováno [http://www.php.net/apc APC]",
+       "config-apcu": "Je nainstalováno [http://www.php.net/apcu APCu]",
        "config-wincache": "Je nainstalována [http://www.iis.net/download/WinCacheForPhp WinCache]",
        "config-no-cache-apcu": "<strong>Upozornění:</strong> Nebylo nalezeno [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache], ani [http://www.iis.net/download/WinCacheForPhp WinCache].\nKešování objektů bude vypnuto.",
        "config-mod-security": "<strong>Upozornění:</strong> váš webový server má zapnuto [http://modsecurity.org/ mod_security]/mod_security2. Mnoho běžných konfigurací bude způsobovat potíže MediaWiki a dalším programům, které umožňují ukládat libovolný obsah.\nPokud je to možné, mělo by se to vypnout. Jinak se v případě, že narazíte na náhodné chyby, podívejte do [http://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
        "config-cache-options": "Nastavení cachování objektů:",
        "config-cache-help": "Cachování objektů se používá pro vylepšení rychlosti MediaWiki tím, že se cachují často používaná data.\nStředním až velkým serverům se jeho zapnutí důrazně doporučuje, i menší servery pocítí jeho výhody.",
        "config-cache-none": "Bez cachování (o žádnou funkcionalitu nepřijdete, na větších wiki však může dojít ke zhoršení rychlosti)",
-       "config-cache-accel": "Cachování PHP objektů (APC, XCache nebo WinCache)",
+       "config-cache-accel": "Cachování PHP objektů (APC, APCu, XCache nebo WinCache)",
        "config-cache-memcached": "Použít Memcached (vyžaduje další nastavení a konfiguraci)",
        "config-memcached-servers": "Servery Memcached:",
        "config-memcached-help": "Seznam IP adres, které se mají používat pro Memcached.\nUveďte jednu na řádek spolu s portem. Například:\n 127.0.0.1:11211\n 192.168.1.25:1234",
index e704141..2783bca 100644 (file)
@@ -74,6 +74,7 @@
        "config-memory-bad": "'''Warnung:''' Der PHP-Parameter <code>memory_limit</code> beträgt $1.\nDieser Wert ist wahrscheinlich zu niedrig.\nDer Installationsvorgang könnte eventuell scheitern!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] ist installiert",
        "config-apc": "[http://www.php.net/apc APC] ist installiert",
+       "config-apcu": "[http://www.php.net/apcu APCu] ist installiert",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert",
        "config-no-cache-apcu": "<strong>Warnung:</strong> [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] konnten nicht gefunden werden.\nDer Objektcache ist nicht aktiviert.",
        "config-mod-security": "'''Warnung:''' Auf dem Webserver wurde [http://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann dies zu Problemen mit MediaWiki sowie anderer Software auf dem Server führen und es Benutzern ermöglichen, beliebige Inhalte im Wiki einzustellen.\nFür weitere Informationen empfehlen wir die [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
        "config-cache-options": "Einstellungen für die Zwischenspeicherung von Objekten:",
        "config-cache-help": "Das Objektcaching wird dazu genutzt, die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.\nEs wird sehr empfohlen, es für mittelgroße bis große Wikis zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Geschwindigkeitsverbesserungen.",
        "config-cache-none": "Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann dies die Leistungsfähigkeit größerer Wikis negativ beeinflussen)",
-       "config-cache-accel": "Objektcaching von PHP (APC, XCache oder WinCache)",
+       "config-cache-accel": "Objektcaching von PHP (APC, APCu, XCache oder WinCache)",
        "config-cache-memcached": "Memcached Cacheserver (erfordert einen zusätzlichen Installationsvorgang mitsamt Konfiguration)",
        "config-memcached-servers": "Memcached Cacheserver",
        "config-memcached-help": "Liste der für Memcached nutzbaren IP-Adressen.\nEs sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:\n127.0.0.1:11211 oder\n192.168.1.25:1234 usw.",
index 3f3032b..6a6c0ff 100644 (file)
@@ -57,6 +57,7 @@
        "config-memory-bad": "<strong>Warning:</strong> PHP's <code>memory_limit</code> is $1.\nThis is probably too low.\nThe installation may fail!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] is installed",
        "config-apc": "[http://www.php.net/apc APC] is installed",
+       "config-apcu": "[http://www.php.net/apcu APCu] is installed",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] is installed",
        "config-no-cache-apcu": "<strong>Warning:</strong> Could not find [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].\nObject caching is not enabled.",
        "config-mod-security": "<strong>Warning:</strong> Your web server has [http://modsecurity.org/ mod_security]/mod_security2 enabled. Many common configurations of this will cause problems for MediaWiki and other software that allows users to post arbitrary content.\nIf possible, this should be disabled. Otherwise, refer to [http://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
        "config-cache-options": "Settings for object caching:",
        "config-cache-help": "Object caching is used to improve the speed of MediaWiki by caching frequently used data.\nMedium to large sites are highly encouraged to enable this, and small sites will see benefits as well.",
        "config-cache-none": "No caching (no functionality is removed, but speed may be impacted on larger wiki sites)",
-       "config-cache-accel": "PHP object caching (APC, XCache or WinCache)",
+       "config-cache-accel": "PHP object caching (APC, APCu, XCache or WinCache)",
        "config-cache-memcached": "Use Memcached (requires additional setup and configuration)",
        "config-memcached-servers": "Memcached servers:",
        "config-memcached-help": "List of IP addresses to use for Memcached.\nShould specify one per line and specify the port to be used. For example:\n 127.0.0.1:11211\n 192.168.1.25:1234",
index 3e7f8f3..95224e9 100644 (file)
@@ -85,6 +85,7 @@
        "config-memory-bad": "'''Attention :''' Le paramètre <code>memory_limit</code> de PHP est à $1.\nCette valeur est probablement trop faible.\nIl est possible que l’installation échoue !",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] est installé",
        "config-apc": "[http://www.php.net/apc APC] est installé",
+       "config-apcu": "[http://www.php.net/apcu APCu] est installé",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] est installé",
        "config-no-cache-apcu": "'''Attention :''' Impossible de trouver [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].\nLa mise en cache d'objets n'est pas activée.",
        "config-mod-security": "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S'il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.\nReportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
        "config-cache-options": "Paramètres pour la mise en cache des objets:",
        "config-cache-help": "La mise en cache des objets améliore la vitesse de MediaWiki en mettant en cache les données fréquemment utilisées.\nLes sites de taille moyenne à grande sont fortement encouragés à l'activer. Les petits sites y verront également des avantages.",
        "config-cache-none": "Pas de mise en cache (aucune fonctionnalité n'a été supprimée, mais la vitesse peut changer sur les wikis importants)",
-       "config-cache-accel": "Mise en cache des objets PHP (APC, XCache ou WinCache)",
+       "config-cache-accel": "Mise en cache des objets PHP (APC, APCu, XCache ou WinCache)",
        "config-cache-memcached": "Utiliser Memcached (nécessite une installation et une configuration supplémentaires)",
        "config-memcached-servers": "serveurs pour Memcached :",
        "config-memcached-help": "Liste des adresses IP à utiliser pour Memcached.\nUne par ligne, en indiquant le port à utiliser. Par exemple :\n  127.0.0.1:11211\n  192.168.1.25:1234",
index dc24830..0c3b2b4 100644 (file)
@@ -19,7 +19,8 @@
                        "Macofe",
                        "Matteocng",
                        "Einreiher",
-                       "Tosky"
+                       "Tosky",
+                       "Selven"
                ]
        },
        "config-desc": "Programma di installazione per MediaWiki",
@@ -77,6 +78,7 @@
        "config-memory-bad": "''Attenzione:''' Il valore di <code>memory_limit</code> di PHP è $1.\nProbabilmente è troppo basso.\nL'installazione potrebbe non riuscire!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] è installato",
        "config-apc": "[http://www.php.net/apc APC] è installato",
+       "config-apcu": "[http://www.php.net/apc APC] è installato",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] è installato",
        "config-no-cache-apcu": "'''Attenzione:''' [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache] non sono stati trovati.\nLa caching degli oggetti non è attivata.",
        "config-mod-security": "<strong>Attenzione:</strong> Il tuo server web ha il [http://modsecurity.org/ mod_security] abilitato. Se non correttamente configurato, può creare problemi a MediaWiki o ad altro software che permette agli utenti di pubblicare contenuto.\nFai riferimento alla [http://modsecurity.org/documentation/ documentazione sul mod_security] o contatta il supporto tecnico del tuo provider di hosting se si verificano errori.",
        "config-subscribe-help": "Si tratta di una mailing list a basso traffico dedicata agli annunci di nuove versioni, compresi importanti segnalazioni riguardanti la sicurezza.\nÈ consigliato iscriversi e aggiornare la propria installazione di MediaWiki quando una nuova versione viene resa pubblica.",
        "config-subscribe-noemail": "Hai provato ad iscriverti alla mailing list dedicata agli annunci delle nuove versioni senza fornire un indirizzo email.\nInserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
        "config-pingback": "Condividi i dati su questa installazione con gli sviluppatori di MediaWiki.",
+       "config-pingback-help": "Se si seleziona questa opzione, MediaWiki contatterà periodicamente https://www.mediawiki.org con i dati base su questa istanza MediaWiki. In questa categoria di dati rientrano, per esempio, il tipo di sistema, la versione di PHP e database di backend scelto. La Wikimedia Foundation condivide questi dati con gli sviluppatori Mediawiki per aiutarla a guidare i futuri sforzi di sviluppo. Per il tuo sistema saranno inviati i seguenti dati:\n<pre>$1</pre>",
        "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare il wiki.",
        "config-optional-continue": "Fammi altre domande.",
        "config-optional-skip": "Sono già stanco, installa solo il wiki.",
        "config-cache-options": "Impostazioni per la cache di oggetti:",
        "config-cache-help": "La memorizzazione di oggetti nella cache è utilizzata per migliorare la velocità di MediaWiki attraverso l'allocazione nella cache dei dati utilizzati di frequente.\nPer siti di dimensioni medie e grandi, è caldamente consigliato attivare la cache, ma anche per piccoli siti se ne vedranno i benefici.",
        "config-cache-none": "Nessuna memorizzazione in cache (nessuna funzionalità viene impedita, ma sui siti wiki più grandi la velocità potrebbe risentirne)",
-       "config-cache-accel": "Mettere in cache oggetti PHP (APC, XCache o WinCache)",
+       "config-cache-accel": "Mettere in cache oggetti PHP (APC, APCu, XCache o WinCache)",
        "config-cache-memcached": "Usa Memcached (richiede ulteriori attività di installazione e configurazione)",
        "config-memcached-servers": "Server di memcached:",
        "config-memcached-help": "Elenco di indirizzi IP da utilizzare per Memcached.\nDovresti specificarne uno per riga e indicare la porta da utilizzare. Per esempio:\n 127.0.0.1:11211\n 192.168.1.25:1234",
        "config-install-extension-tables": "Creazione delle tabelle per le estensioni attivate",
        "config-install-mainpage-failed": "Impossibile inserire la pagina principale: $1",
        "config-install-done": "<strong>Complimenti!</strong>\nHai installato MediaWiki.\n\nIl programma di installazione ha generato un file <code>LocalSettings.php</code> che contiene tutte le impostazioni.\n\nDevi scaricarlo ed inserirlo nella directory base del tuo wiki (la stessa dove è presente index.php). Il download dovrebbe partire automaticamente.\n\nSe il download non si avvia, o se è stato annullato, puoi riavviarlo cliccando sul collegamento di seguito:\n\n$3\n\n<strong>Nota:</strong> se esci ora dall'installazione senza scaricare il file di configurazione che è stato generato, questo poi non sarà più disponibile in seguito.\n\nQuando hai fatto, puoi <strong>[$2 entrare nel tuo wiki]</strong>.",
+       "config-install-done-path": "<strong>Complimenti!</strong>\nHai installato MediaWiki.\n\nIl programma di installazione ha generato un file <code>LocalSettings.php</code> che contiene tutte le impostazioni.\n\nDevi scaricarlo ed inserirlo in <code>$4</code>. Il download dovrebbe partire automaticamente.\n\nSe il download non si avvia, o se è stato annullato, puoi riavviarlo cliccando sul collegamento seguente:\n\n$3\n\n<strong>Nota:</strong> se esci ora dall'installazione senza scaricare il file di configurazione che è stato generato, questo poi non sarà più disponibile in seguito.\n\nQuando hai fatto, puoi <strong>[$2 entrare nel tuo wiki]</strong>.",
        "config-download-localsettings": "Scarica <code>LocalSettings.php</code>",
        "config-help": "aiuto",
        "config-help-tooltip": "fai clic per espandere",
index 7fe3a64..23f3de1 100644 (file)
@@ -26,6 +26,8 @@
        "config-page-copying": "Kopē",
        "config-restart": "Jā, restartēt",
        "config-env-php": "PHP $1 ir uzstādīts.",
+       "config-env-hhvm": "HHVM $1 ir uzstādīts.",
+       "config-apcu": "[http://www.php.net/apcu APCu] ir uzstādīts",
        "config-diff3-bad": "GNU diff3 nav atrasts.",
        "config-db-name": "Datubāzes nosaukums:",
        "config-db-username": "Datubāzes lietotājvārds:",
        "config-email-settings": "E-pasta iestatījumi",
        "config-logo": "Logo URL:",
        "config-cc-again": "Izvēlies vēlreiz...",
+       "config-memcached-servers": "Memcached serveri:",
        "config-extensions": "Paplašinājumi",
        "config-install-step-done": "Gatavs",
+       "config-install-user": "Veido datu bāzes lietotāju",
        "config-help": "palīdzība",
        "config-help-tooltip": "uzspiediet, lai izvērstu",
        "mainpagetext": "<strong>MediaWiki veiksmīgi instalēts.</strong>",
index 78649ce..0a77232 100644 (file)
@@ -57,7 +57,7 @@
        "config-restart": "Ja, opnieuw starten",
        "config-welcome": "=== Controle omgeving ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nLever deze gegevens aan als u ondersteuning vraagt bij de installatie.",
        "config-copyright": "=== Auteursrechten en voorwaarden ===\n\n$1\n\nDit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.\n\nDit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.\nZie de GNU General Public License voor meer informatie.\n\nSamen met dit programma hoort u een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
-       "config-sidebar": "* [https://www.mediawiki.org MediaWiki thuispagina]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding] (Engelstalig)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen] (Engelstalig)\n----\n* <doclink href=Readme>Leesmij</doclink> (Engelstalig)\n* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)\n* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)\n* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)",
+       "config-sidebar": "* [https://www.mediawiki.org MediaWiki-thuispagina]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding] (Engelstalig)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding] (Engelstalig)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen] (Engelstalig)\n----\n* <doclink href=Readme>Leesmij</doclink> (Engelstalig)\n* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)\n* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)\n* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)",
        "config-env-good": "De omgeving is gecontroleerd.\nU kunt MediaWiki installeren.",
        "config-env-bad": "De omgeving is gecontroleerd.\nU kunt MediaWiki niet installeren.",
        "config-env-php": "PHP $1 is op dit moment geïnstalleerd.",
        "config-ns-site-name": "Zelfde als de wiki: $1",
        "config-ns-other": "Andere (geef aan welke)",
        "config-ns-other-default": "MijnWiki",
-       "config-project-namespace-help": "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".\nAlle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt opgeven.\nDit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+       "config-project-namespace-help": "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een '''projectnaamruimte'''.\nAlle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt opgeven.\nDit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
        "config-ns-invalid": "De opgegeven naamruimte \"<nowiki>$1</nowiki>\" is ongeldig.\nGeef een andere naamruimte op.",
        "config-ns-conflict": "De opgegeven naamruimte \"<nowiki>$1</nowiki>\" conflicteert met een standaard naamruimte in MediaWiki.\nGeef een andere naam op voor de projectnaamruimte.",
        "config-admin-box": "Beheerdersgebruiker",
        "config-subscribe": "Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].",
        "config-subscribe-help": "Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.\nAbonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.",
        "config-subscribe-noemail": "U hebt geprobeerd zich te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.\nGeef een e-mailadres op als u zich wilt abonneren op de mailinglijst.",
+       "config-pingback": "Gegevens over deze installatie delen met MediaWiki-ontwikkelaars.",
        "config-almost-done": "U bent bijna klaar!\nAls u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.",
        "config-optional-continue": "Stel me meer vragen.",
        "config-optional-skip": "Laat maar zitten, installeer gewoon de wiki.",
        "config-logo": "URL voor logo:",
        "config-logo-help": "Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.\nUpload een afbeelding met de juiste afmetingen en voer de URL hier in.\n\nU kunt <code>$wgStylePath</code> of <code>$wgScriptPath</code> gebruiken als uw logo relatief is aan een van deze paden.\n\nAls u geen logo wilt gebruiken, kunt u dit veld leeg laten.",
        "config-instantcommons": "Instant Commons inschakelen",
-       "config-instantcommons-help": "[https://www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [https://commons.wikimedia.org/ Wikimedia Commons].\nHiervoor heeft MediaWiki toegang nodig tot Internet.\n\nMeer informatie over deze functie en hoe deze in te stellen voor andere wiki's dan Wikimedia Commons is te vinden in de [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos handleiding].",
+       "config-instantcommons-help": "[https://www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [https://commons.wikimedia.org/ Wikimedia Commons].\nHiervoor heeft MediaWiki toegang nodig tot internet.\n\nMeer informatie over deze functie en hoe deze in te stellen voor andere wiki's dan Wikimedia Commons is te vinden in de [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos handleiding].",
        "config-cc-error": "De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.\nVoer de licentie handmatig in.",
        "config-cc-again": "Opnieuw kiezen...",
        "config-cc-not-chosen": "Kies de Creative Commonslicentie die u wilt gebruiken en klik op \"proceed\".",
index 8d54c98..fd8bb17 100644 (file)
@@ -71,7 +71,7 @@
        "config-unicode-using-intl": "Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.",
        "config-unicode-pure-php-warning": "<strong>Uwaga:<strong> [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.\nJeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizacji Unicode].",
        "config-unicode-update-warning": "<strong>Uwaga:</strong> zainstalowana wersja normalizacji Unicode korzysta z nieaktualnej biblioteki [http://site.icu-project.org/ projektu ICU].\nPowinieneś [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations wykonać aktualizację], jeśli chcesz korzystać w pełni z Unicode.",
-       "config-no-db": "Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.\nMożna użyć {{PLURAL:$2|następującego typu bazy|następujących typów baz} danych: $1.\n\nJeśli skompilowałeś PHP samodzielnie, skonfiguruj je ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia <code>./configure --with-mysqli</code>.\nJeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować np. moduł <code>php5-mysql</code>.",
+       "config-no-db": "Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.\nMożna użyć {{PLURAL:$2|następującego typu bazy|następujących typów baz}} danych: $1.\n\nJeśli skompilowałeś PHP samodzielnie, skonfiguruj go ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia <code>./configure --with-mysqli</code>.\nJeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować np. moduł <code>php5-mysql</code>.",
        "config-outdated-sqlite": "'''Ostrzeżenie''': masz SQLite  $1, która jest niższa od minimalnej wymaganej wersji  $2 . SQLite będzie niedostępne.",
        "config-no-fts3": "'''Uwaga''' – SQLite został skompilowany bez [//sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
        "config-pcre-old": "<strong>Błąd krytyczny:</strong> Wymagany jest PCRE w wersji $1 lub nowszej.\nTwój plik wykonywalny PHP jest powiązany z wersją PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Więcej informacji].",
        "config-cc-not-chosen": "Wybierz, którą chcesz licencję Creative Commons i kliknij „proceed”.",
        "config-advanced-settings": "Konfiguracja zaawansowana",
        "config-cache-options": "Ustawienia buforowania obiektów:",
-       "config-cache-help": "Buforowanie obiekto jest używane aby przyspieszyć MediaWiki przez trzymanie w pamięci podręcznej często używanych danych.\nŚrednie oraz duże witryny są wysoce zachęcane by je włączyć, a małe witryny także dostrzegą korzyści.",
+       "config-cache-help": "Buforowanie obiektów jest używane do przyspieszenia MediaWiki przez trzymanie w pamięci podręcznej często używanych danych.\nŚrednie oraz duże witryny są wysoce zachęcane by je włączyć, ale małe witryny również dostrzegą korzyści.",
        "config-cache-none": "Brak buforowania (wszystkie funkcje będą działać, ale mogą wystąpić kłopoty z wydajnością na dużych witrynach wiki)",
        "config-cache-accel": "Buforowania obiektów PHP (APC, XCache lub WinCache)",
        "config-cache-memcached": "Użyj Memcached (wymaga dodatkowej instalacji i konfiguracji)",
        "config-install-begin": "Po naciśnięciu \"{{int:config-continue}}\", rozpocznie się instalacja MediaWiki.\nJeśli nadal chcesz dokonać zmian, naciśnij \"{{int:config-back}}\".",
        "config-install-step-done": "gotowe",
        "config-install-step-failed": "nieudane",
-       "config-install-extensions": "Włącznie z rozszerzeniami",
+       "config-install-extensions": "Dołączanie rozszerzeń",
        "config-install-database": "Konfigurowanie bazy danych",
        "config-install-schema": "Tworzenie schematu",
        "config-install-pg-schema-not-exist": "Schemat PostgreSQL nie istnieje.",
        "config-help": "pomoc",
        "config-help-tooltip": "kliknij, aby rozwinąć",
        "config-nofile": "Nie udało się odnaleźć pliku \"$1\". Czy nie został usunięty?",
-       "config-extension-link": "Czy wiesz, że twoja wiki obsługuje [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions/pl rozszerzenia]?\n\nMożesz przejrzeć [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category rozszerzenia według kategorii] lub [https://www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] aby zobaczyć pełną listę rozszerzeń.",
+       "config-extension-link": "Czy wiesz, że twoja wiki obsługuje [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions rozszerzenia]?\n\nMożesz przejrzeć [https://www.mediawiki.org/wiki/Category:Extensions_by_category rozszerzenia według kategorii] lub [https://www.mediawiki.org/wiki/Extension_Matrix Extension Matrix], aby zobaczyć pełną listę rozszerzeń.",
        "mainpagetext": "<strong>Instalacja MediaWiki powiodła się.</strong>",
-       "mainpagedocfooter": "Zobacz [https://meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika], aby uzyskać informacje o działaniu oprogramowania wiki.\n\n== Na początek ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/pl MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam \nDowiedz się, jak walczyć ze spamem na swojej wiki]"
+       "mainpagedocfooter": "Zobacz [https://meta.wikimedia.org/wiki/Help:Contents/pl przewodnik użytkownika], aby uzyskać informacje o działaniu oprogramowania wiki.\n\n== Na początek ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista ustawień konfiguracyjnych]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki (lista dyskusyjna)]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Dowiedz się, jak walczyć ze spamem na swojej wiki]"
 }
index 833e7d6..d7e86be 100644 (file)
@@ -75,6 +75,7 @@
        "config-memory-bad": "Parameters:\n* $1 is the configured <code>memory_limit</code>.",
        "config-xcache": "Message indicates if this program is available",
        "config-apc": "Message indicates if this program is available",
+       "config-apcu": "Message indicates if this program is available",
        "config-wincache": "Message indicates if this program is available",
        "config-no-cache-apcu": "Status message in the MediaWiki installer environment checks.",
        "config-mod-security": "Status message in the MediaWiki installer environment checks.",
index 53b4c9e..bead2f1 100644 (file)
@@ -79,6 +79,7 @@
        "config-memory-bad": "'''Внимание:''' размер PHP <code>memory_limit</code> составляет $1.\nВероятно, этого слишком мало.\nУстановка может потерпеть неудачу!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] установлен",
        "config-apc": "[http://www.php.net/apc APC] установлен",
+       "config-apcu": "[http://www.php.net/apcu APCu] установлен",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] установлен",
        "config-no-cache-apcu": "'''Внимание:''' Не найдены [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].\nКэширование объектов будет отключено.",
        "config-mod-security": "<strong>Внимание</strong>: На вашем веб-сервере включен [http://modsecurity.org/ mod_security]/mod_security2. Многие его стандартные настройки могут вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный контент.\nОбратитесь к [http://modsecurity.org/documentation/ документации mod_security] или в службу поддержки вашего хостинг-провайдера, если вы сталкиваетесь со случайными ошибками.",
        "config-cache-options": "Параметры кэширования объектов:",
        "config-cache-help": "Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.\nДля средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.",
        "config-cache-none": "Без кэширования (никакой функционал не теряется, но крупные вики-сайты могут работать медленнее)",
-       "config-cache-accel": "PHP кэширование объектов (APC, XCache или WinCache)",
+       "config-cache-accel": "Кэширование PHP-объектов (APC, APCu, XCache или WinCache)",
        "config-cache-memcached": "Использовать Memcached (требует дополнительной настройки)",
        "config-memcached-servers": "Сервера Memcached:",
        "config-memcached-help": "Список IP-адресов, используемых Memcached.\nПеречислите по одному адресу на строку с указанием портов. Например:\n 127.0.0.1:11211\n 192.168.1.25:1234",
index 20ebf9b..45c5a7d 100644 (file)
@@ -67,6 +67,7 @@
        "config-memory-bad": "''' Varning:''' PHP:s <code>memory_limit</code> är $1.\nDetta är förmodligen för lågt.\nInstallationen kan misslyckas!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] är installerat",
        "config-apc": "[http://www.php.net/apc APC] är installerat",
+       "config-apcu": "[http://www.php.net/apcu APCu] är installerat",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] är installerat",
        "config-no-cache-apcu": "'''Varning:''' Kunde inte hitta [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nCachelagring av objekt är inte aktiverat.",
        "config-mod-security": "'''Varning:''' Din webbserver har [http://modsecurity.org/ mod_security] aktiverat. Om felaktigt konfigurerat kan den skapa problem för MediaWiki eller annan programvara som tillåter användaren att posta godtyckligt innehåll.\nTitta på [http://modsecurity.org/documentation/ mod_security-dokumentationen] eller kontakta din värd om du påträffar slumpmässiga fel.",
        "config-cache-options": "Inställningar för cachelagring av objekt:",
        "config-cache-help": "Cachelagring av objekt används för att förbättra hastigheten på MediaWiki genom att cachelagra data som används ofta.\nMedelstora till stora webbplatser är starkt uppmuntrade att aktivera detta, och små webbplatser kommer även att se fördelar.",
        "config-cache-none": "Ingen cachelagring (ingen funktionalitet tas bort, men hastighet kan påverkas på större wiki-webbplatser)",
-       "config-cache-accel": "Cachelagring av PHP-objekt (APC, XCache eller WinCache)",
+       "config-cache-accel": "Cachelagring av PHP-objekt (APC, APCu, XCache eller WinCache)",
        "config-cache-memcached": "Använda Memcached (kräver ytterligare inställningar och konfiguration)",
        "config-memcached-servers": "Memcached-servrar:",
        "config-memcached-help": "Lista över IP-adresser som ska användas för Memcached.\nBör ange en per rad och specificera den port som ska användas. Till exempel:\n 127.0.0.1:11211\n 192.168.1.25:1234",
index d541696..1410ae7 100644 (file)
@@ -80,6 +80,7 @@
        "config-memory-bad": "<strong>警告:</strong>PHP的内存使用上限<code>memory_limit</code>为$1。\n该设定可能过低,并导致安装失败!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache]已安装",
        "config-apc": "[http://www.php.net/apc APC]已安装",
+       "config-apcu": "[http://www.php.net/apcu APCu]已安装",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache]已安装",
        "config-no-cache-apcu": "<strong>警告:</strong>找不到[http://www.php.net/apcu APCu]、[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache]。\n对象缓存未启用。",
        "config-mod-security": "<strong>警告:</strong>您的web服务器已启用[http://modsecurity.org/ mod_security]/mod_security2。它的很多常见配置可能导致MediaWiki及其他软件允许用户发布任意内容的问题。如果可能,这应当被禁用。否则,当您遭遇随机错误时,请参考[http://modsecurity.org/documentation/ mod_security 文档]或联络您的主机支持。",
        "config-cache-options": "对象缓存设置:",
        "config-cache-help": "对象缓存可通过缓存频繁使用的数据来提高MediaWiki的速度。高度推荐中到大型的网站启用该功能,小型网站亦能从其中受益。",
        "config-cache-none": "无缓存(不影响功能,但对较大型的wiki网站会有速度影响)",
-       "config-cache-accel": "PHP对象缓存(APC、XCache或WinCache)",
+       "config-cache-accel": "PHP对象缓存(APC、APCu、XCache或WinCache)",
        "config-cache-memcached": "使用Memcached(需要另外安装并配置)",
        "config-memcached-servers": "Memcached服务器:",
        "config-memcached-help": "用于Memcached的IP地址列表。请保持每行一条,并指定要使用的端口。例如:\n127.0.0.1:11211\n192.168.1.25:1234",
index ab21779..2f2e934 100644 (file)
        "config-cache-options": "物件快取設定:",
        "config-cache-help": "物件快取是用來增進 MediaWiki 速度的一項功能,透過快取經常使用的資料。\n中型到大型的網站我們會建議開啟這個選項,對小型的網站也有一定程度的效果。",
        "config-cache-none": "不快取 (不會影響功能,但在大型 Wiki 網站可能會有處理速度的問題)",
-       "config-cache-accel": "使用 PHP 物件快取 (APC、XCache 或 WinCache)",
+       "config-cache-accel": "使用 PHP 物件快取 (APC、APCu、XCache 或 WinCache)",
        "config-cache-memcached": "使用 Memcached (需要額外安裝與設定)",
        "config-memcached-servers": "Memcached 伺服器:",
        "config-memcached-help": "請列出 Memcached 伺服器的 IP 位址。\n每一行只指定一個位置並且要註明使用的埠號,例如:\n 127.0.0.1:11211\n 192.168.1.25:1234",
diff --git a/includes/libs/CryptRand.php b/includes/libs/CryptRand.php
new file mode 100644 (file)
index 0000000..6d18c81
--- /dev/null
@@ -0,0 +1,389 @@
+<?php
+/**
+ * A cryptographic random generator class used for generating secret keys
+ *
+ * This is based in part on Drupal code as well as what we used in our own code
+ * prior to introduction of this class.
+ *
+ * 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
+ *
+ * @author Daniel Friesen
+ * @file
+ */
+use Psr\Log\LoggerInterface;
+
+class CryptRand {
+       /**
+        * Minimum number of iterations we want to make in our drift calculations.
+        */
+       const MIN_ITERATIONS = 1000;
+
+       /**
+        * Number of milliseconds we want to spend generating each separate byte
+        * of the final generated bytes.
+        * This is used in combination with the hash length to determine the duration
+        * we should spend doing drift calculations.
+        */
+       const MSEC_PER_BYTE = 0.5;
+
+       /**
+        * A boolean indicating whether the previous random generation was done using
+        * cryptographically strong random number generator or not.
+        */
+       protected $strong = null;
+
+       /**
+        * List of functions to call to generate some random state
+        *
+        * @var callable[]
+        */
+       protected $randomFuncs = [];
+
+       /**
+        * List of files to generate some random state from
+        *
+        * @var string[]
+        */
+       protected $randomFiles = [];
+
+       /**
+        * @var LoggerInterface
+        */
+       protected $logger;
+
+       public function __construct( array $randomFuncs, array $randomFiles, LoggerInterface $logger ) {
+               $this->randomFuncs = $randomFuncs;
+               $this->randomFiles = $randomFiles;
+               $this->logger = $logger;
+       }
+
+       /**
+        * Initialize an initial random state based off of whatever we can find
+        * @return string
+        */
+       protected function initialRandomState() {
+               // $_SERVER contains a variety of unstable user and system specific information
+               // It'll vary a little with each page, and vary even more with separate users
+               // It'll also vary slightly across different machines
+               $state = serialize( $_SERVER );
+
+               // Try to gather a little entropy from the different php rand sources
+               $state .= rand() . uniqid( mt_rand(), true );
+
+               // Include some information about the filesystem's current state in the random state
+               $files = $this->randomFiles;
+
+               // We know this file is here so grab some info about ourselves
+               $files[] = __FILE__;
+
+               // We must also have a parent folder, and with the usual file structure, a grandparent
+               $files[] = __DIR__;
+               $files[] = dirname( __DIR__ );
+
+               foreach ( $files as $file ) {
+                       MediaWiki\suppressWarnings();
+                       $stat = stat( $file );
+                       MediaWiki\restoreWarnings();
+                       if ( $stat ) {
+                               // stat() duplicates data into numeric and string keys so kill off all the numeric ones
+                               foreach ( $stat as $k => $v ) {
+                                       if ( is_numeric( $k ) ) {
+                                               unset( $k );
+                                       }
+                               }
+                               // The absolute filename itself will differ from install to install so don't leave it out
+                               $path = realpath( $file );
+                               if ( $path !== false ) {
+                                       $state .= $path;
+                               } else {
+                                       $state .= $file;
+                               }
+                               $state .= implode( '', $stat );
+                       } else {
+                               // The fact that the file isn't there is worth at least a
+                               // minuscule amount of entropy.
+                               $state .= '0';
+                       }
+               }
+
+               // Try and make this a little more unstable by including the varying process
+               // id of the php process we are running inside of if we are able to access it
+               if ( function_exists( 'getmypid' ) ) {
+                       $state .= getmypid();
+               }
+
+               // If available try to increase the instability of the data by throwing in
+               // the precise amount of memory that we happen to be using at the moment.
+               if ( function_exists( 'memory_get_usage' ) ) {
+                       $state .= memory_get_usage( true );
+               }
+
+               foreach ( $this->randomFuncs as $randomFunc ) {
+                       $state .= call_user_func( $randomFunc );
+               }
+
+               return $state;
+       }
+
+       /**
+        * Randomly hash data while mixing in clock drift data for randomness
+        *
+        * @param string $data The data to randomly hash.
+        * @return string The hashed bytes
+        * @author Tim Starling
+        */
+       protected function driftHash( $data ) {
+               // Minimum number of iterations (to avoid slow operations causing the
+               // loop to gather little entropy)
+               $minIterations = self::MIN_ITERATIONS;
+               // Duration of time to spend doing calculations (in seconds)
+               $duration = ( self::MSEC_PER_BYTE / 1000 ) * MWCryptHash::hashLength();
+               // Create a buffer to use to trigger memory operations
+               $bufLength = 10000000;
+               $buffer = str_repeat( ' ', $bufLength );
+               $bufPos = 0;
+
+               // Iterate for $duration seconds or at least $minIterations number of iterations
+               $iterations = 0;
+               $startTime = microtime( true );
+               $currentTime = $startTime;
+               while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
+                       // Trigger some memory writing to trigger some bus activity
+                       // This may create variance in the time between iterations
+                       $bufPos = ( $bufPos + 13 ) % $bufLength;
+                       $buffer[$bufPos] = ' ';
+                       // Add the drift between this iteration and the last in as entropy
+                       $nextTime = microtime( true );
+                       $delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
+                       $data .= $delta;
+                       // Every 100 iterations hash the data and entropy
+                       if ( $iterations % 100 === 0 ) {
+                               $data = sha1( $data );
+                       }
+                       $currentTime = $nextTime;
+                       $iterations++;
+               }
+               $timeTaken = $currentTime - $startTime;
+               $data = MWCryptHash::hash( $data );
+
+               $this->logger->debug( "Clock drift calculation " .
+                       "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
+                       "iterations=$iterations, " .
+                       "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
+
+               return $data;
+       }
+
+       /**
+        * Return a rolling random state initially build using data from unstable sources
+        * @return string A new weak random state
+        */
+       protected function randomState() {
+               static $state = null;
+               if ( is_null( $state ) ) {
+                       // Initialize the state with whatever unstable data we can find
+                       // It's important that this data is hashed right afterwards to prevent
+                       // it from being leaked into the output stream
+                       $state = MWCryptHash::hash( $this->initialRandomState() );
+               }
+               // Generate a new random state based on the initial random state or previous
+               // random state by combining it with clock drift
+               $state = $this->driftHash( $state );
+
+               return $state;
+       }
+
+       /**
+        * Return a boolean indicating whether or not the source used for cryptographic
+        * random bytes generation in the previously run generate* call
+        * was cryptographically strong.
+        *
+        * @return bool Returns true if the source was strong, false if not.
+        */
+       public function wasStrong() {
+               if ( is_null( $this->strong ) ) {
+                       throw new RuntimeException( __METHOD__ . ' called before generation of random data' );
+               }
+
+               return $this->strong;
+       }
+
+       /**
+        * Generate a run of (ideally) cryptographically random data and return
+        * it in raw binary form.
+        * You can use CryptRand::wasStrong() if you wish to know if the source used
+        * was cryptographically strong.
+        *
+        * @param int $bytes The number of bytes of random data to generate
+        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
+        *                          strong sources of entropy even if reading from them may steal
+        *                          more entropy from the system than optimal.
+        * @return string Raw binary random data
+        */
+       public function generate( $bytes, $forceStrong = false ) {
+
+               $this->logger->debug( "Generating cryptographic random bytes for\n" );
+
+               $bytes = floor( $bytes );
+               static $buffer = '';
+               if ( is_null( $this->strong ) ) {
+                       // Set strength to false initially until we know what source data is coming from
+                       $this->strong = true;
+               }
+
+               if ( strlen( $buffer ) < $bytes ) {
+                       // If available make use of mcrypt_create_iv URANDOM source to generate randomness
+                       // On unix-like systems this reads from /dev/urandom but does it without any buffering
+                       // and bypasses openbasedir restrictions, so it's preferable to reading directly
+                       // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
+                       // entropy so this is also preferable to just trying to read urandom because it may work
+                       // on Windows systems as well.
+                       if ( function_exists( 'mcrypt_create_iv' ) ) {
+                               $rem = $bytes - strlen( $buffer );
+                               $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
+                               if ( $iv === false ) {
+                                       $this->logger->debug( "mcrypt_create_iv returned false.\n" );
+                               } else {
+                                       $buffer .= $iv;
+                                       $this->logger->debug( "mcrypt_create_iv generated " . strlen( $iv ) .
+                                               " bytes of randomness.\n" );
+                               }
+                       }
+               }
+
+               if ( strlen( $buffer ) < $bytes ) {
+                       if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
+                               $rem = $bytes - strlen( $buffer );
+                               $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
+                               if ( $openssl_bytes === false ) {
+                                       $this->logger->debug( "openssl_random_pseudo_bytes returned false.\n" );
+                               } else {
+                                       $buffer .= $openssl_bytes;
+                                       $this->logger->debug( "openssl_random_pseudo_bytes generated " .
+                                               strlen( $openssl_bytes ) . " bytes of " .
+                                               ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
+                               }
+                               if ( strlen( $buffer ) >= $bytes ) {
+                                       // openssl tells us if the random source was strong, if some of our data was generated
+                                       // using it use it's say on whether the randomness is strong
+                                       $this->strong = !!$openssl_strong;
+                               }
+                       }
+               }
+
+               // Only read from urandom if we can control the buffer size or were passed forceStrong
+               if ( strlen( $buffer ) < $bytes &&
+                       ( function_exists( 'stream_set_read_buffer' ) || $forceStrong )
+               ) {
+                       $rem = $bytes - strlen( $buffer );
+                       if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
+                               $this->logger->debug( "Was forced to read from /dev/urandom " .
+                                       "without control over the buffer size.\n" );
+                       }
+                       // /dev/urandom is generally considered the best possible commonly
+                       // available random source, and is available on most *nix systems.
+                       MediaWiki\suppressWarnings();
+                       $urandom = fopen( "/dev/urandom", "rb" );
+                       MediaWiki\restoreWarnings();
+
+                       // Attempt to read all our random data from urandom
+                       // php's fread always does buffered reads based on the stream's chunk_size
+                       // so in reality it will usually read more than the amount of data we're
+                       // asked for and not storing that risks depleting the system's random pool.
+                       // If stream_set_read_buffer is available set the chunk_size to the amount
+                       // of data we need. Otherwise read 8k, php's default chunk_size.
+                       if ( $urandom ) {
+                               // php's default chunk_size is 8k
+                               $chunk_size = 1024 * 8;
+                               if ( function_exists( 'stream_set_read_buffer' ) ) {
+                                       // If possible set the chunk_size to the amount of data we need
+                                       stream_set_read_buffer( $urandom, $rem );
+                                       $chunk_size = $rem;
+                               }
+                               $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
+                               $buffer .= $random_bytes;
+                               fclose( $urandom );
+                               $this->logger->debug( "/dev/urandom generated " . strlen( $random_bytes ) .
+                                       " bytes of randomness.\n" );
+
+                               if ( strlen( $buffer ) >= $bytes ) {
+                                       // urandom is always strong, set to true if all our data was generated using it
+                                       $this->strong = true;
+                               }
+                       } else {
+                               $this->logger->debug( "/dev/urandom could not be opened.\n" );
+                       }
+               }
+
+               // If we cannot use or generate enough data from a secure source
+               // use this loop to generate a good set of pseudo random data.
+               // This works by initializing a random state using a pile of unstable data
+               // and continually shoving it through a hash along with a variable salt.
+               // We hash the random state with more salt to avoid the state from leaking
+               // out and being used to predict the /randomness/ that follows.
+               if ( strlen( $buffer ) < $bytes ) {
+                       $this->logger->debug( __METHOD__ .
+                               ": Falling back to using a pseudo random state to generate randomness.\n" );
+               }
+               while ( strlen( $buffer ) < $bytes ) {
+                       $buffer .= MWCryptHash::hmac( $this->randomState(), strval( mt_rand() ) );
+                       // This code is never really cryptographically strong, if we use it
+                       // at all, then set strong to false.
+                       $this->strong = false;
+               }
+
+               // Once the buffer has been filled up with enough random data to fulfill
+               // the request shift off enough data to handle the request and leave the
+               // unused portion left inside the buffer for the next request for random data
+               $generated = substr( $buffer, 0, $bytes );
+               $buffer = substr( $buffer, $bytes );
+
+               $this->logger->debug( strlen( $buffer ) .
+                       " bytes of randomness leftover in the buffer.\n" );
+
+               return $generated;
+       }
+
+       /**
+        * Generate a run of (ideally) cryptographically random data and return
+        * it in hexadecimal string format.
+        * You can use CryptRand::wasStrong() if you wish to know if the source used
+        * was cryptographically strong.
+        *
+        * @param int $chars The number of hex chars of random data to generate
+        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
+        *                          strong sources of entropy even if reading from them may steal
+        *                          more entropy from the system than optimal.
+        * @return string Hexadecimal random data
+        */
+       public function generateHex( $chars, $forceStrong = false ) {
+               // hex strings are 2x the length of raw binary so we divide the length in half
+               // odd numbers will result in a .5 that leads the generate() being 1 character
+               // short, so we use ceil() to ensure that we always have enough bytes
+               $bytes = ceil( $chars / 2 );
+               // Generate the data and then convert it to a hex string
+               $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
+
+               // A bit of paranoia here, the caller asked for a specific length of string
+               // here, and it's possible (eg when given an odd number) that we may actually
+               // have at least 1 char more than they asked for. Just in case they made this
+               // call intending to insert it into a database that does truncation we don't
+               // want to give them too much and end up with their database and their live
+               // code having two different values because part of what we gave them is truncated
+               // hence, we strip out any run of characters longer than what we were asked for.
+               return substr( $hex, 0, $chars );
+       }
+}
diff --git a/includes/libs/MWCryptHash.php b/includes/libs/MWCryptHash.php
new file mode 100644 (file)
index 0000000..f9b7172
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Utility functions for generating hashes
+ *
+ * This is based in part on Drupal code as well as what we used in our own code
+ * prior to introduction of this class, by way of MWCryptRand.
+ *
+ * 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
+ */
+
+class MWCryptHash {
+       /**
+        * The hash algorithm being used
+        */
+       protected static $algo = null;
+
+       /**
+        * The number of bytes outputted by the hash algorithm
+        */
+       protected static $hashLength = [
+               true => null,
+               false => null,
+       ];
+
+       /**
+        * Decide on the best acceptable hash algorithm we have available for hash()
+        * @return string A hash algorithm
+        */
+       public static function hashAlgo() {
+               if ( !is_null( self::$algo ) ) {
+                       return self::$algo;
+               }
+
+               $algos = hash_algos();
+               $preference = [ 'whirlpool', 'sha256', 'sha1', 'md5' ];
+
+               foreach ( $preference as $algorithm ) {
+                       if ( in_array( $algorithm, $algos ) ) {
+                               self::$algo = $algorithm;
+
+                               return self::$algo;
+                       }
+               }
+
+               // We only reach here if no acceptable hash is found in the list, this should
+               // be a technical impossibility since most of php's hash list is fixed and
+               // some of the ones we list are available as their own native functions
+               // But since we already require at least 5.2 and hash() was default in
+               // 5.1.2 we don't bother falling back to methods like sha1 and md5.
+               throw new DomainException( "Could not find an acceptable hashing function in hash_algos()" );
+       }
+
+       /**
+        * Return the byte-length output of the hash algorithm we are
+        * using in self::hash and self::hmac.
+        *
+        * @param bool $raw True to return the length for binary data, false to
+        *   return for hex-encoded
+        * @return int Number of bytes the hash outputs
+        */
+       public static function hashLength( $raw = true ) {
+               $raw = (bool)$raw;
+               if ( is_null( self::$hashLength[$raw] ) ) {
+                       self::$hashLength[$raw] = strlen( self::hash( '', $raw ) );
+               }
+
+               return self::$hashLength[$raw];
+       }
+
+       /**
+        * Generate an acceptably unstable one-way-hash of some text
+        * making use of the best hash algorithm that we have available.
+        *
+        * @param string $data
+        * @param bool $raw True to return binary data, false to return it hex-encoded
+        * @return string A hash of the data
+        */
+       public static function hash( $data, $raw = true ) {
+               return hash( self::hashAlgo(), $data, $raw );
+       }
+
+       /**
+        * Generate an acceptably unstable one-way-hmac of some text
+        * making use of the best hash algorithm that we have available.
+        *
+        * @param string $data
+        * @param string $key
+        * @param bool $raw True to return binary data, false to return it hex-encoded
+        * @return string An hmac hash of the data + key
+        */
+       public static function hmac( $data, $key, $raw = true ) {
+               if ( !is_string( $key ) ) {
+                       // a fatal error in HHVM; an exception will at least give us a stack trace
+                       throw new InvalidArgumentException( 'Invalid key type: ' . gettype( $key ) );
+               }
+               return hash_hmac( self::hashAlgo(), $data, $key, $raw );
+       }
+
+}
index 50e9732..12a5cad 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * APC-backed function memoization
+ * APC-backed and APCu-backed function memoization
  *
  * This class provides memoization for pure functions. A function is pure
  * if its result value depends on nothing other than its input parameters
@@ -8,7 +8,7 @@
  *
  * The first invocation of the memoized callable with a particular set of
  * arguments will be delegated to the underlying callable. Repeat invocations
- * with the same input parameters will be served from APC.
+ * with the same input parameters will be served from APC or APCu.
  *
  * @par Example:
  * @code
@@ -70,7 +70,7 @@ class MemoizedCallable {
        }
 
        /**
-        * Fetch the result of a previous invocation from APC.
+        * Fetch the result of a previous invocation from APC or APCu.
         *
         * @param string $key
         * @param bool &$success
@@ -79,12 +79,14 @@ class MemoizedCallable {
                $success = false;
                if ( function_exists( 'apc_fetch' ) ) {
                        return apc_fetch( $key, $success );
+               } elseif ( function_exists( 'apcu_fetch' ) ) {
+                       return apcu_fetch( $key, $success );
                }
                return false;
        }
 
        /**
-        * Store the result of an invocation in APC.
+        * Store the result of an invocation in APC or APCu.
         *
         * @param string $key
         * @param mixed $result
@@ -92,6 +94,8 @@ class MemoizedCallable {
        protected function storeResult( $key, $result ) {
                if ( function_exists( 'apc_store' ) ) {
                        apc_store( $key, $result, $this->ttl );
+               } elseif ( function_exists( 'apcu_store' ) ) {
+                       apcu_store( $key, $result, $this->ttl );
                }
        }
 
index fdcbf49..a870204 100644 (file)
@@ -184,14 +184,12 @@ class MultiHttpClient {
                unset( $req ); // don't assign over this by accident
 
                $indexes = array_keys( $reqs );
-               if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
-                       if ( isset( $opts['usePipelining'] ) ) {
-                               curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] );
-                       }
-                       if ( isset( $opts['maxConnsPerHost'] ) ) {
-                               // Keep these sockets around as they may be needed later in the request
-                               curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] );
-                       }
+               if ( isset( $opts['usePipelining'] ) ) {
+                       curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] );
+               }
+               if ( isset( $opts['maxConnsPerHost'] ) ) {
+                       // Keep these sockets around as they may be needed later in the request
+                       curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] );
                }
 
                // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS)
@@ -258,10 +256,8 @@ class MultiHttpClient {
                unset( $req ); // don't assign over this by accident
 
                // Restore the default settings
-               if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
-                       curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining );
-                       curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
-               }
+               curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+               curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
 
                return $reqs;
        }
@@ -292,12 +288,7 @@ class MultiHttpClient {
                curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
 
                $url = $req['url'];
-               // PHP_QUERY_RFC3986 is PHP 5.4+ only
-               $query = str_replace(
-                       [ '+', '%7E' ],
-                       [ '%20', '~' ],
-                       http_build_query( $req['query'], '', '&' )
-               );
+               $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 );
                if ( $query != '' ) {
                        $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
                }
@@ -422,10 +413,8 @@ class MultiHttpClient {
        protected function getCurlMulti() {
                if ( !$this->multiHandle ) {
                        $cmh = curl_multi_init();
-                       if ( function_exists( 'curl_multi_setopt' ) ) { // PHP 5.5
-                               curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining );
-                               curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
-                       }
+                       curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+                       curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
                        $this->multiHandle = $cmh;
                }
                return $this->multiHandle;
diff --git a/includes/libs/WaitConditionLoop.php b/includes/libs/WaitConditionLoop.php
deleted file mode 100644 (file)
index 969e86e..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-<?php
-/**
- * Wait loop that reaches a condition or times out.
- *
- * 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 Aaron Schulz
- */
-
-/**
- * Wait loop that reaches a condition or times out
- * @since 1.28
- */
-class WaitConditionLoop {
-       /** @var callable */
-       private $condition;
-       /** @var callable[] */
-       private $busyCallbacks = [];
-       /** @var float Seconds */
-       private $timeout;
-       /** @var float Seconds */
-       private $lastWaitTime;
-       /** @var integer|null */
-       private $rusageMode;
-
-       const CONDITION_REACHED = 1;
-       const CONDITION_CONTINUE = 0; // evaluates as falsey
-       const CONDITION_FAILED = -1;
-       const CONDITION_TIMED_OUT = -2;
-       const CONDITION_ABORTED = -3;
-
-       /**
-        * @param callable $condition Callback that returns a WaitConditionLoop::CONDITION_ constant
-        * @param float $timeout Timeout in seconds
-        * @param array &$busyCallbacks List of callbacks to do useful work (by reference)
-        */
-       public function __construct( callable $condition, $timeout = 5.0, &$busyCallbacks = [] ) {
-               $this->condition = $condition;
-               $this->timeout = $timeout;
-               $this->busyCallbacks =& $busyCallbacks;
-
-               if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
-                       $this->rusageMode = 2; // RUSAGE_THREAD
-               } elseif ( function_exists( 'getrusage' ) ) {
-                       $this->rusageMode = 0; // RUSAGE_SELF
-               }
-       }
-
-       /**
-        * Invoke the loop and continue until either:
-        *   - a) The condition callback returns neither CONDITION_CONTINUE nor false
-        *   - b) The timeout is reached
-        * This a condition callback can return true (stop) or false (continue) for convenience.
-        * In such cases, the halting result of "true" will be converted to CONDITION_REACHED.
-        *
-        * If $timeout is 0, then only the condition callback will be called (no busy callbacks),
-        * and this will immediately return CONDITION_FAILED if the condition was not met.
-        *
-        * Exceptions in callbacks will be caught and the callback will be swapped with
-        * one that simply rethrows that exception back to the caller when invoked.
-        *
-        * @return integer WaitConditionLoop::CONDITION_* constant
-        * @throws Exception Any error from the condition callback
-        */
-       public function invoke() {
-               $elapsed = 0.0; // seconds
-               $sleepUs = 0; // microseconds to sleep each time
-               $lastCheck = false;
-               $finalResult = self::CONDITION_TIMED_OUT;
-               do {
-                       $checkStartTime = $this->getWallTime();
-                       // Check if the condition is met yet
-                       $realStart = $this->getWallTime();
-                       $cpuStart = $this->getCpuTime();
-                       $checkResult = call_user_func( $this->condition );
-                       $cpu = $this->getCpuTime() - $cpuStart;
-                       $real = $this->getWallTime() - $realStart;
-                       // Exit if the condition is reached, and error occurs, or this is non-blocking
-                       if ( $this->timeout <= 0 ) {
-                               $finalResult = $checkResult ? self::CONDITION_REACHED : self::CONDITION_FAILED;
-                               break;
-                       } elseif ( (int)$checkResult !== self::CONDITION_CONTINUE ) {
-                               if ( is_int( $checkResult ) ) {
-                                       $finalResult = $checkResult;
-                               } else {
-                                       $finalResult = self::CONDITION_REACHED;
-                               }
-                               break;
-                       } elseif ( $lastCheck ) {
-                               break; // timeout reached
-                       }
-                       // Detect if condition callback seems to block or if justs burns CPU
-                       $conditionUsesInterrupts = ( $real > 0.100 && $cpu <= $real * .03 );
-                       if ( !$this->popAndRunBusyCallback() && !$conditionUsesInterrupts ) {
-                               // 10 queries = 10(10+100)/2 ms = 550ms, 14 queries = 1050ms
-                               $sleepUs = min( $sleepUs + 10 * 1e3, 1e6 ); // stop incrementing at ~1s
-                               $this->usleep( $sleepUs );
-                       }
-                       $checkEndTime = $this->getWallTime();
-                       // The max() protects against the clock getting set back
-                       $elapsed += max( $checkEndTime - $checkStartTime, 0.010 );
-                       // Do not let slow callbacks timeout without checking the condition one more time
-                       $lastCheck = ( $elapsed >= $this->timeout );
-               } while ( true );
-
-               $this->lastWaitTime = $elapsed;
-
-               return $finalResult;
-       }
-
-       /**
-        * @return float Seconds
-        */
-       public function getLastWaitTime() {
-               return $this->lastWaitTime;
-       }
-
-       /**
-        * @param integer $microseconds
-        */
-       protected function usleep( $microseconds ) {
-               usleep( $microseconds );
-       }
-
-       /**
-        * @return float
-        */
-       protected function getWallTime() {
-               return microtime( true );
-       }
-
-       /**
-        * @return float Returns 0.0 if not supported (Windows on PHP < 7)
-        */
-       protected function getCpuTime() {
-               if ( $this->rusageMode === null ) {
-                       return microtime( true ); // assume worst case (all time is CPU)
-               }
-
-               $ru = getrusage( $this->rusageMode );
-               $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
-               $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
-
-               return $time;
-       }
-
-       /**
-        * Run one of the callbacks that does work ahead of time for another caller
-        *
-        * @return bool Whether a callback was executed
-        */
-       private function popAndRunBusyCallback() {
-               if ( $this->busyCallbacks ) {
-                       reset( $this->busyCallbacks );
-                       $key = key( $this->busyCallbacks );
-                       /** @var callable $workCallback */
-                       $workCallback =& $this->busyCallbacks[$key];
-                       try {
-                               $workCallback();
-                       } catch ( Exception $e ) {
-                               $workCallback = function () use ( $e ) {
-                                       throw $e;
-                               };
-                       }
-                       unset( $this->busyCallbacks[$key] ); // consume
-
-                       return true;
-               }
-
-               return false;
-       }
-}
index e89a9c7..bee34dc 100644 (file)
@@ -4,6 +4,7 @@
  * @ingroup FileBackend
  */
 use Psr\Log\LoggerInterface;
+use Wikimedia\WaitConditionLoop;
 
 /**
  * Resource locking handling.
index 83334fe..aecdf60 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup LockManager
  */
+use Wikimedia\WaitConditionLoop;
 
 /**
  * Manage locks using memcached servers.
index 8f70fc7..9bfcee7 100644 (file)
@@ -75,25 +75,35 @@ class APCBagOStuff extends BagOStuff {
        }
 
        protected function doGet( $key, $flags = 0 ) {
-               $val = apc_fetch( $key . self::KEY_SUFFIX );
+               return $this->getUnserialize(
+                       apc_fetch( $key . self::KEY_SUFFIX )
+               );
+       }
 
-               if ( is_string( $val ) && !$this->nativeSerialize ) {
-                       $val = $this->isInteger( $val )
-                               ? intval( $val )
-                               : unserialize( $val );
+       protected function getUnserialize( $value ) {
+               if ( is_string( $value ) && !$this->nativeSerialize ) {
+                       $value = $this->isInteger( $value )
+                               ? intval( $value )
+                               : unserialize( $value );
                }
-
-               return $val;
+               return $value;
        }
 
        public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               apc_store(
+                       $key . self::KEY_SUFFIX,
+                       $this->setSerialize( $value ),
+                       $exptime
+               );
+
+               return true;
+       }
+
+       protected function setSerialize( $value ) {
                if ( !$this->nativeSerialize && !$this->isInteger( $value ) ) {
                        $value = serialize( $value );
                }
-
-               apc_store( $key . self::KEY_SUFFIX, $value, $exptime );
-
-               return true;
+               return $value;
        }
 
        public function delete( $key ) {
diff --git a/includes/libs/objectcache/APCUBagOStuff.php b/includes/libs/objectcache/APCUBagOStuff.php
new file mode 100644 (file)
index 0000000..02b3c92
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Object caching using PHP's APCU accelerator.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * This is a wrapper for APCU's shared memory functions
+ *
+ * @ingroup Cache
+ */
+class APCUBagOStuff extends APCBagOStuff {
+       /**
+        * Constructor
+        *
+        * Available parameters are:
+        *   - nativeSerialize:     If true, pass objects to apcu_store(), and trust it
+        *                          to serialize them correctly. If false, serialize
+        *                          all values in PHP.
+        *
+        * @param array $params
+        */
+       public function __construct( array $params = [] ) {
+               parent::__construct( $params );
+       }
+
+       protected function doGet( $key, $flags = 0 ) {
+               return $this->getUnserialize(
+                       apcu_fetch( $key . self::KEY_SUFFIX )
+               );
+       }
+
+       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               apcu_store(
+                       $key . self::KEY_SUFFIX,
+                       $this->setSerialize( $value ),
+                       $exptime
+               );
+
+               return true;
+       }
+
+       public function delete( $key ) {
+               apcu_delete( $key . self::KEY_SUFFIX );
+
+               return true;
+       }
+
+       public function incr( $key, $value = 1 ) {
+               /**
+                * @todo When we only support php 7 or higher remove this hack
+                *
+                * https://github.com/krakjoe/apcu/issues/166
+                */
+               if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
+                       return apcu_inc( $key . self::KEY_SUFFIX, $value );
+               } else {
+                       return apcu_set( $key . self::KEY_SUFFIX, $value );
+               }
+       }
+
+       public function decr( $key, $value = 1 ) {
+               /**
+                * @todo When we only support php 7 or higher remove this hack
+                *
+                * https://github.com/krakjoe/apcu/issues/166
+                */
+               if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
+                       return apcu_dec( $key . self::KEY_SUFFIX, $value );
+               } else {
+                       return apcu_set( $key . self::KEY_SUFFIX, -$value );
+               }
+       }
+}
index cd79b67..d3deefb 100644 (file)
@@ -29,6 +29,7 @@
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Psr\Log\NullLogger;
+use Wikimedia\WaitConditionLoop;
 
 /**
  * interface is intended to be more or less compatible with
index 1f9aff1..88af1db 100644 (file)
@@ -22,6 +22,7 @@
  */
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
+use Wikimedia\WaitConditionLoop;
 
 /**
  * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
index 4f1f38e..9f1f228 100644 (file)
@@ -352,7 +352,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                } else {
                        $driver = $dbType;
                }
-               if ( $driver === false ) {
+               if ( $driver === false || $driver === '' ) {
                        throw new InvalidArgumentException( __METHOD__ .
                                " no viable database extension found for type '$dbType'" );
                }
@@ -3037,10 +3037,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
        public function sourceFile(
                $filename,
-               $lineCallback = false,
-               $resultCallback = false,
+               callable $lineCallback = null,
+               callable $resultCallback = null,
                $fname = false,
-               $inputCallback = false
+               callable $inputCallback = null
        ) {
                MediaWiki\suppressWarnings();
                $fp = fopen( $filename, 'r' );
@@ -3073,10 +3073,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
        public function sourceStream(
                $fp,
-               $lineCallback = false,
-               $resultCallback = false,
+               callable $lineCallback = null,
+               callable $resultCallback = null,
                $fname = __METHOD__,
-               $inputCallback = false
+               callable $inputCallback = null
        ) {
                $cmd = '';
 
index f58628e..f82d76d 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Database
  */
+use Wikimedia\WaitConditionLoop;
 
 /**
  * @ingroup Database
index f65e0dd..8395359 100644 (file)
@@ -96,21 +96,21 @@ interface IMaintainableDatabase extends IDatabase {
         * on object's error ignore settings).
         *
         * @param string $filename File name to open
-        * @param bool|callable $lineCallback Optional function called before reading each line
-        * @param bool|callable $resultCallback Optional function called for each MySQL result
+        * @param callable|null $lineCallback Optional function called before reading each line
+        * @param callable|null $resultCallback Optional function called for each MySQL result
         * @param bool|string $fname Calling function name or false if name should be
         *   generated dynamically using $filename
-        * @param bool|callable $inputCallback Optional function called for each
+        * @param callable|null $inputCallback Optional function called for each
         *   complete line sent
         * @return bool|string
         * @throws Exception
         */
        public function sourceFile(
                $filename,
-               $lineCallback = false,
-               $resultCallback = false,
+               callable $lineCallback = null,
+               callable $resultCallback = null,
                $fname = false,
-               $inputCallback = false
+               callable $inputCallback = null
        );
 
        /**
@@ -120,18 +120,18 @@ interface IMaintainableDatabase extends IDatabase {
         * on object's error ignore settings).
         *
         * @param resource $fp File handle
-        * @param bool|callable $lineCallback Optional function called before reading each query
-        * @param bool|callable $resultCallback Optional function called for each MySQL result
+        * @param callable|null $lineCallback Optional function called before reading each query
+        * @param callable|null $resultCallback Optional function called for each MySQL result
         * @param string $fname Calling function name
-        * @param bool|callable $inputCallback Optional function called for each complete query sent
+        * @param callable|null $inputCallback Optional function called for each complete query sent
         * @return bool|string
         */
        public function sourceStream(
                $fp,
-               $lineCallback = false,
-               $resultCallback = false,
+               callable $lineCallback = null,
+               callable $resultCallback = null,
                $fname = __METHOD__,
-               $inputCallback = false
+               callable $inputCallback = null
        );
 
        /**
index 2fb8c4b..83ca650 100644 (file)
@@ -95,7 +95,7 @@ class LBFactoryMulti extends LBFactory {
        private $extLBs = [];
 
        /** @var string */
-       private $loadMonitorClass;
+       private $loadMonitorClass = 'LoadMonitor';
 
        /** @var string */
        private $lastDomain;
@@ -309,7 +309,7 @@ class LBFactoryMulti extends LBFactory {
                        $this->baseLoadBalancerParams(),
                        [
                                'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
-                               'loadMonitor' => $this->loadMonitorClass,
+                               'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
                                'readOnlyReason' => $readOnlyReason
                        ]
                ) );
index 610052f..674bafd 100644 (file)
@@ -67,7 +67,7 @@ class LBFactorySimple extends LBFactory {
                        : [];
                $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
                        ? $conf['loadMonitorClass']
-                       : null;
+                       : 'LoadMonitor';
        }
 
        /**
@@ -124,7 +124,7 @@ class LBFactorySimple extends LBFactory {
                        $this->baseLoadBalancerParams(),
                        [
                                'servers' => $servers,
-                               'loadMonitor' => $this->loadMonitorClass,
+                               'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
                        ]
                ) );
                $this->initLoadBalancer( $lb );
index 37c028b..31c022c 100644 (file)
@@ -32,7 +32,7 @@ class LoadBalancer implements ILoadBalancer {
        private $mServers;
        /** @var array[] Map of (local/foreignUsed/foreignFree => server index => IDatabase array) */
        private $mConns;
-       /** @var array Map of (server index => weight) */
+       /** @var float[] Map of (server index => weight) */
        private $mLoads;
        /** @var array[] Map of (group => server index => weight) */
        private $mGroupLoads;
@@ -40,13 +40,13 @@ class LoadBalancer implements ILoadBalancer {
        private $mAllowLagged;
        /** @var integer Seconds to spend waiting on replica DB lag to resolve */
        private $mWaitTimeout;
-       /** @var string The LoadMonitor subclass name */
-       private $mLoadMonitorClass;
+       /** @var array The LoadMonitor configuration */
+       private $loadMonitorConfig;
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
        private $tableAliases = [];
 
        /** @var ILoadMonitor */
-       private $mLoadMonitor;
+       private $loadMonitor;
        /** @var BagOStuff */
        private $srvCache;
        /** @var BagOStuff */
@@ -150,14 +150,9 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                if ( isset( $params['loadMonitor'] ) ) {
-                       $this->mLoadMonitorClass = $params['loadMonitor'];
+                       $this->loadMonitorConfig = $params['loadMonitor'];
                } else {
-                       $master = reset( $params['servers'] );
-                       if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
-                               $this->mLoadMonitorClass = 'LoadMonitorMySQL';
-                       } else {
-                               $this->mLoadMonitorClass = 'LoadMonitorNull';
-                       }
+                       $this->loadMonitorConfig = [ 'class' => 'LoadMonitorNull' ];
                }
 
                foreach ( $params['servers'] as $i => $server ) {
@@ -217,13 +212,14 @@ class LoadBalancer implements ILoadBalancer {
         * @return ILoadMonitor
         */
        private function getLoadMonitor() {
-               if ( !isset( $this->mLoadMonitor ) ) {
-                       $class = $this->mLoadMonitorClass;
-                       $this->mLoadMonitor = new $class( $this, $this->srvCache, $this->memCache );
-                       $this->mLoadMonitor->setLogger( $this->replLogger );
+               if ( !isset( $this->loadMonitor ) ) {
+                       $class = $this->loadMonitorConfig['class'];
+                       $this->loadMonitor = new $class(
+                               $this, $this->srvCache, $this->memCache, $this->loadMonitorConfig );
+                       $this->loadMonitor->setLogger( $this->replLogger );
                }
 
-               return $this->mLoadMonitor;
+               return $this->loadMonitor;
        }
 
        /**
index e355c03..72a8785 100644 (file)
@@ -34,16 +34,19 @@ interface ILoadMonitor extends LoggerAwareInterface {
         * @param ILoadBalancer $lb LoadBalancer this instance serves
         * @param BagOStuff $sCache Local server memory cache
         * @param BagOStuff $cCache Local cluster memory cache
+        * @param array $options Options map
         */
-       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache );
+       public function __construct(
+               ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache, array $options = []
+       );
 
        /**
         * Perform pre-connection load ratio adjustment.
-        * @param int[] &$loads
+        * @param int[] &$weightByServer Map of (server index => integer weight)
         * @param string|bool $group The selected query group. Default: false
         * @param string|bool $domain Default: false
         */
-       public function scaleLoads( &$loads, $group = false, $domain = false );
+       public function scaleLoads( array &$weightByServer, $group = false, $domain = false );
 
        /**
         * Get an estimate of replication lag (in seconds) for each server
@@ -55,7 +58,7 @@ interface ILoadMonitor extends LoggerAwareInterface {
         *
         * @return array Map of (server index => float|int|bool)
         */
-       public function getLagTimes( $serverIndexes, $domain );
+       public function getLagTimes( array $serverIndexes, $domain );
 
        /**
         * Clear any process and persistent cache of lag times
index 1da8f4e..60400e3 100644 (file)
@@ -37,27 +37,53 @@ class LoadMonitor implements ILoadMonitor {
        /** @var LoggerInterface */
        protected $replLogger;
 
-       public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) {
+       /** @var float Moving average ratio (e.g. 0.1 for 10% weight to new weight) */
+       private $movingAveRatio;
+
+       const VERSION = 1;
+
+       public function __construct(
+               ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache, array $options = []
+       ) {
                $this->parent = $lb;
                $this->srvCache = $srvCache;
                $this->mainCache = $cache;
                $this->replLogger = new \Psr\Log\NullLogger();
+
+               $this->movingAveRatio = isset( $options['movingAveRatio'] )
+                       ? $options['movingAveRatio']
+                       : 0.1;
        }
 
        public function setLogger( LoggerInterface $logger ) {
                $this->replLogger = $logger;
        }
 
-       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+       public function scaleLoads( array &$weightByServer, $group = false, $domain = false ) {
+               $serverIndexes = array_keys( $weightByServer );
+               $states = $this->getServerStates( $serverIndexes, $domain );
+               $coefficientsByServer = $states['weightScales'];
+               foreach ( $weightByServer as $i => $weight ) {
+                       $weightByServer[$i] = $weight * $coefficientsByServer[$i];
+               }
        }
 
-       public function getLagTimes( $serverIndexes, $domain ) {
+       public function getLagTimes( array $serverIndexes, $domain ) {
+               $states = $this->getServerStates( $serverIndexes, $domain );
+
+               return $states['lagTimes'];
+       }
+
+       protected function getServerStates( array $serverIndexes, $domain ) {
                if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
                        # Single server only, just return zero without caching
-                       return [ 0 => 0 ];
+                       return [
+                               'lagTimes' => [ $this->parent->getWriterIndex() => 0 ],
+                               'weightScales' => [ $this->parent->getWriterIndex() => 1 ]
+                       ];
                }
 
-               $key = $this->getLagTimeCacheKey();
+               $key = $this->getCacheKey();
                # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
                $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
                # Keep keys around longer as fallbacks
@@ -67,7 +93,7 @@ class LoadMonitor implements ILoadMonitor {
                $value = $this->srvCache->get( $key );
                if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
                        $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
-                       return $value['lagTimes']; // cache hit
+                       return $value; // cache hit
                }
                $staleValue = $value ?: false;
 
@@ -77,7 +103,7 @@ class LoadMonitor implements ILoadMonitor {
                        $this->srvCache->set( $key, $value, $staleTTL );
                        $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
 
-                       return $value['lagTimes']; // cache hit
+                       return $value; // cache hit
                }
                $staleValue = $value ?: $staleValue;
 
@@ -91,13 +117,16 @@ class LoadMonitor implements ILoadMonitor {
                        } );
                } elseif ( $staleValue ) {
                        # Could not acquire lock but an old cache exists, so use it
-                       return $staleValue['lagTimes'];
+                       return $staleValue;
                }
 
                $lagTimes = [];
+               $weightScales = [];
+               $movAveRatio = $this->movingAveRatio;
                foreach ( $serverIndexes as $i ) {
                        if ( $i == $this->parent->getWriterIndex() ) {
                                $lagTimes[$i] = 0; // master always has no lag
+                               $weightScales[$i] = 1.0; // nominal weight
                                continue;
                        }
 
@@ -109,17 +138,26 @@ class LoadMonitor implements ILoadMonitor {
                                $close = true; // new connection
                        }
 
+                       $lastWeight = isset( $staleValue['weightScales'][$i] )
+                               ? $staleValue['weightScales'][$i]
+                               : 1.0;
+                       $coefficient = $this->getWeightScale( $i, $conn ?: null );
+                       $newWeight = $movAveRatio * $coefficient + ( 1 - $movAveRatio ) * $lastWeight;
+
+                       // Scale from 10% to 100% of nominal weight
+                       $weightScales[$i] = max( $newWeight, .10 );
+
                        if ( !$conn ) {
                                $lagTimes[$i] = false;
                                $host = $this->parent->getServerName( $i );
-                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
+                               $this->replLogger->error( __METHOD__ . ": host $host is unreachable" );
                                continue;
                        }
 
                        $lagTimes[$i] = $conn->getLag();
                        if ( $lagTimes[$i] === false ) {
                                $host = $this->parent->getServerName( $i );
-                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
+                               $this->replLogger->error( __METHOD__ . ": host $host is not replicating?" );
                        }
 
                        if ( $close ) {
@@ -132,26 +170,39 @@ class LoadMonitor implements ILoadMonitor {
                }
 
                # Add a timestamp key so we know when it was cached
-               $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
+               $value = [
+                       'lagTimes' => $lagTimes,
+                       'weightScales' => $weightScales,
+                       'timestamp' => microtime( true )
+               ];
                $this->mainCache->set( $key, $value, $staleTTL );
                $this->srvCache->set( $key, $value, $staleTTL );
                $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
 
-               return $value['lagTimes'];
+               return $value;
+       }
+
+       /**
+        * @param integer $index Server index
+        * @param IDatabase|null $conn Connection handle or null on connection failure
+        * @return float
+        */
+       protected function getWeightScale( $index, IDatabase $conn = null ) {
+               return $conn ? 1.0 : 0.0;
        }
 
        public function clearCaches() {
-               $key = $this->getLagTimeCacheKey();
+               $key = $this->getCacheKey();
                $this->srvCache->delete( $key );
                $this->mainCache->delete( $key );
        }
 
-       private function getLagTimeCacheKey() {
-               $writerIndex = $this->parent->getWriterIndex();
+       private function getCacheKey() {
                // Lag is per-server, not per-DB, so key on the master DB name
                return $this->srvCache->makeGlobalKey(
                        'lag-times',
-                       $this->parent->getServerName( $writerIndex )
+                       self::VERSION,
+                       $this->parent->getServerName( $this->parent->getWriterIndex() )
                );
        }
 }
index 7286417..babd609 100644 (file)
  * @ingroup Database
  */
 class LoadMonitorMySQL extends LoadMonitor {
-       public function scaleLoads( &$loads, $group = false, $domain = false ) {
-               // @TODO: maybe use Threads_running/Threads_created ratio to guess load
-               // and Queries/Uptime to guess if a server is warming up the buffer pool
+       /** @var float What buffer pool use ratio counts as "warm" (e.g. 0.5 for 50% usage) */
+       private $warmCacheRatio;
+
+       public function __construct(
+               ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache, array $options = []
+       ) {
+               parent::__construct( $lb, $srvCache, $cache, $options );
+
+               $this->warmCacheRatio = isset( $options['warmCacheRatio'] )
+                       ? $options['warmCacheRatio']
+                       : 0.0;
+       }
+
+       protected function getWeightScale( $index, IDatabase $conn = null ) {
+               if ( !$conn ) {
+                       return 0.0;
+               }
+
+               $weight = 1.0;
+               if ( $this->warmCacheRatio > 0 ) {
+                       $res = $conn->query( 'SHOW STATUS', false );
+                       $s = $res ? $conn->fetchObject( $res ) : false;
+                       if ( $s === false ) {
+                               $host = $this->parent->getServerName( $index );
+                               $this->replLogger->error( __METHOD__ . ": could not get status for $host" );
+                       } else {
+                               // http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html
+                               if ( $s->Innodb_buffer_pool_pages_total > 0 ) {
+                                       $ratio = $s->Innodb_buffer_pool_pages_data / $s->Innodb_buffer_pool_pages_total;
+                               } elseif ( $s->Qcache_total_blocks > 0 ) {
+                                       $ratio = 1.0 - $s->Qcache_free_blocks / $s->Qcache_total_blocks;
+                               } else {
+                                       $ratio = 1.0;
+                               }
+                               // Stop caring once $ratio >= $this->warmCacheRatio
+                               $weight *= min( $ratio / $this->warmCacheRatio, 1.0 );
+                       }
+               }
+
+               return $weight;
        }
 }
index 8062001..67bac2b 100644 (file)
 use Psr\Log\LoggerInterface;
 
 class LoadMonitorNull implements ILoadMonitor {
-       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ) {
+       public function __construct(
+               ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache, array $options = []
+       ) {
 
        }
 
        public function setLogger( LoggerInterface $logger ) {
        }
 
-       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+       public function scaleLoads( array &$loads, $group = false, $domain = false ) {
 
        }
 
-       public function getLagTimes( $serverIndexes, $domain ) {
+       public function getLagTimes( array $serverIndexes, $domain ) {
                return array_fill_keys( $serverIndexes, 0 );
        }
 
index 3ebda75..11c4d42 100644 (file)
@@ -25,6 +25,7 @@
  * @file
  * @ingroup Media
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Handler for images that need to be transformed
@@ -509,7 +510,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
         * @return string|bool Representing the IM version; false on error
         */
        protected function getMagickVersion() {
-               $cache = ObjectCache::getLocalServerInstance( CACHE_NONE );
+               $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
                return $cache->getWithSetCallback(
                        'imagemagick-version',
                        $cache::TTL_HOUR,
index d81f9e1..df249b2 100644 (file)
@@ -50,7 +50,7 @@ use MediaWiki\MediaWikiServices;
  *
  * - ObjectCache::getLocalServerInstance( $fallbackType )
  *   Purpose: Memory cache for very hot keys.
- *   Stored only on the individual web server (typically APC for web requests,
+ *   Stored only on the individual web server (typically APC or APCu for web requests,
  *   and EmptyBagOStuff in CLI mode).
  *   Not replicated to the other servers.
  *
@@ -265,7 +265,7 @@ class ObjectCache {
        /**
         * Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
         *
-        * This will look for any APC style server-local cache.
+        * This will look for any APC or APCu style server-local cache.
         * A fallback cache can be specified if none is found.
         *
         *     // Direct calls
@@ -280,21 +280,15 @@ class ObjectCache {
         * @since 1.27
         */
        public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
-               if ( function_exists( 'apc_fetch' ) ) {
-                       $id = 'apc';
-               } elseif ( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
-                       $id = 'xcache';
-               } elseif ( function_exists( 'wincache_ucache_get' ) ) {
-                       $id = 'wincache';
-               } else {
+               $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
+               if ( $cache instanceof EmptyBagOStuff ) {
                        if ( is_array( $fallback ) ) {
-                               $id = isset( $fallback['fallback'] ) ? $fallback['fallback'] : CACHE_NONE;
-                       } else {
-                               $id = $fallback;
+                               $fallback = isset( $fallback['fallback'] ) ? $fallback['fallback'] : CACHE_NONE;
                        }
+                       $cache = self::getInstance( $fallback );
                }
 
-               return self::getInstance( $id );
+               return $cache;
        }
 
        /**
@@ -383,11 +377,10 @@ class ObjectCache {
         *
         * @since 1.26
         * @return WANObjectCache
+        * @deprecated Since 1.28 Use MediaWikiServices::getMainWANCache()
         */
        public static function getMainWANInstance() {
-               global $wgMainWANCache;
-
-               return self::getWANInstance( $wgMainWANCache );
+               return MediaWikiServices::getInstance()->getMainWANObjectCache();
        }
 
        /**
@@ -407,11 +400,10 @@ class ObjectCache {
         *
         * @return BagOStuff
         * @since 1.26
+        * @deprecated Since 1.28 Use MediaWikiServices::getMainObjectStash
         */
        public static function getMainStashInstance() {
-               global $wgMainStash;
-
-               return self::getInstance( $wgMainStash );
+               return MediaWikiServices::getInstance()->getMainObjectStash();
        }
 
        /**
index f57df9b..338b1ae 100644 (file)
@@ -2154,18 +2154,6 @@ class Article implements Page {
                return $this->mPage->getLastPurgeTimestamp();
        }
 
-       /**
-        * Call to WikiPage function for backwards compatibility.
-        * @see WikiPage::doQuickEditContent
-        */
-       public function doQuickEditContent(
-               Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
-       ) {
-               return $this->mPage->doQuickEditContent(
-                       $content, $user, $comment, $minor, $serialFormat
-               );
-       }
-
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::doViewUpdates
index 928fc14..98428dd 100644 (file)
@@ -693,7 +693,7 @@ class WikiPage implements Page, IDBAccessObject {
         * @deprecated since 1.21, getContent() should be used instead.
         */
        public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
+               wfDeprecated( __METHOD__, '1.21' );
 
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
@@ -2396,41 +2396,6 @@ class WikiPage implements Page, IDBAccessObject {
                }
        }
 
-       /**
-        * Edit an article without doing all that other stuff
-        * The article must already exist; link tables etc
-        * are not updated, caches are not flushed.
-        *
-        * @param Content $content Content submitted
-        * @param User $user The relevant user
-        * @param string $comment Comment submitted
-        * @param bool $minor Whereas it's a minor modification
-        * @param string $serialFormat Format for storing the content in the database
-        */
-       public function doQuickEditContent(
-               Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
-       ) {
-
-               $serialized = $content->serialize( $serialFormat );
-
-               $dbw = wfGetDB( DB_MASTER );
-               $revision = new Revision( [
-                       'title'      => $this->getTitle(), // for determining the default content model
-                       'page'       => $this->getId(),
-                       'user_text'  => $user->getName(),
-                       'user'       => $user->getId(),
-                       'text'       => $serialized,
-                       'length'     => $content->getSize(),
-                       'comment'    => $comment,
-                       'minor_edit' => $minor ? 1 : 0,
-               ] ); // XXX: set the content object?
-               $revision->insertOn( $dbw );
-               $this->updateRevisionOn( $dbw, $revision );
-
-               Hooks::run( 'NewRevisionFromEditComplete', [ $this, $revision, false, $user ] );
-
-       }
-
        /**
         * Update the article's restriction field, and leave a log entry.
         * This works for protection both existing and non-existing pages.
index 297dcb2..db9c95b 100644 (file)
@@ -162,9 +162,9 @@ abstract class Profiler {
        abstract public function scopedProfileIn( $section );
 
        /**
-        * @param ScopedCallback $section
+        * @param SectionProfileCallback $section
         */
-       public function scopedProfileOut( ScopedCallback &$section = null ) {
+       public function scopedProfileOut( SectionProfileCallback &$section = null ) {
                $section = null;
        }
 
index bbe139b..7e3c398 100644 (file)
@@ -58,7 +58,7 @@ class SectionProfiler {
 
        /**
         * @param string $section
-        * @return ScopedCallback
+        * @return SectionProfileCallback
         */
        public function scopedProfileIn( $section ) {
                $this->profileInInternal( $section );
index 3bec457..35044e1 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * ExtensionRegistry class
  *
@@ -86,7 +88,7 @@ class ExtensionRegistry {
                // we don't want to fail here if $wgObjectCaches is not configured
                // properly for APC setup
                try {
-                       $this->cache = ObjectCache::getLocalServerInstance();
+                       $this->cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
                } catch ( MWException $e ) {
                        $this->cache = new EmptyBagOStuff();
                }
index ed7c6df..3efbd3b 100644 (file)
@@ -180,6 +180,7 @@ class SkinTemplate extends Skin {
                                        'text' => $ilLangName,
                                        'title' => $ilTitle,
                                        'class' => $class,
+                                       'link-class' => 'interlanguage-link-target',
                                        'lang' => $ilInterwikiCodeBCP47,
                                        'hreflang' => $ilInterwikiCodeBCP47,
                                ];
index ed3cd5e..1dd78d7 100644 (file)
@@ -224,7 +224,7 @@ class SpecialBotPasswords extends FormSpecialPage {
                                        'name' => 'op',
                                        'value' => 'create',
                                        'label-message' => 'botpasswords-label-create',
-                                       'flags' => [ 'primary', 'constructive' ],
+                                       'flags' => [ 'primary', 'progressive' ],
                                ] );
                        }
 
index fe1dd98..c58af60 100644 (file)
@@ -594,9 +594,13 @@ class ImportReporter extends ContextSource {
                $this->mPageCount++;
 
                if ( $successCount > 0 ) {
+                       // <bdi> prevents jumbling of the versions count
+                       // in RTL wikis in case the page title is LTR
                        $this->getOutput()->addHTML(
                                "<li>" . Linker::linkKnown( $title ) . " " .
+                                       "<bdi>" .
                                        $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() .
+                                       "</bdi>" .
                                        "</li>\n"
                        );
 
index 3a12cf3..7b7661d 100644 (file)
@@ -462,7 +462,7 @@ class MovePageForm extends UnlistedSpecialPage {
                                'name' => 'wpMove',
                                'value' => $this->msg( 'movepagebtn' )->text(),
                                'label' => $this->msg( 'movepagebtn' )->text(),
-                               'flags' => [ 'constructive', 'primary' ],
+                               'flags' => [ 'primary', 'progressive' ],
                                'type' => 'submit',
                        ] ),
                        [
old mode 100644 (file)
new mode 100755 (executable)
index 718a6dc..d719e53
@@ -376,7 +376,11 @@ class SpecialNewpages extends IncludableSpecialPage {
 
                if ( !$title->equals( $oldTitle ) ) {
                        $oldTitleText = $oldTitle->getPrefixedText();
-                       $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped();
+                       $oldTitleText = Html::rawElement(
+                               'span',
+                               [ 'class' => 'mw-newpages-oldtitle' ],
+                               $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
+                       );
                }
 
                return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} "
index 6083db9..00fc9be 100644 (file)
@@ -1802,12 +1802,16 @@ class User implements IDBAccessObject {
                        return false;
                }
 
+               $limits = array_merge(
+                       [ '&can-bypass' => true ],
+                       $wgRateLimits[$action]
+               );
+
                // Some groups shouldn't trigger the ping limiter, ever
-               if ( !$this->isPingLimitable() ) {
+               if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
                        return false;
                }
 
-               $limits = $wgRateLimits[$action];
                $keys = [];
                $id = $this->getId();
                $userLimit = false;
index 1376fa7..2756861 100644 (file)
@@ -29,6 +29,7 @@
  * @author Chris Steipp
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 class MWCryptHKDF {
 
@@ -160,7 +161,7 @@ class MWCryptHKDF {
         * @throws MWException
         */
        protected static function singleton() {
-               global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey, $wgMainCacheType;
+               global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey;
 
                $secret = $wgHKDFSecret ?: $wgSecretKey;
                if ( !$secret ) {
@@ -174,8 +175,12 @@ class MWCryptHKDF {
                $context[] = getmypid();
                $context[] = gethostname();
 
-               // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup
-               $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
+               // Setup salt cache
+               $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
+               if ( $cache instanceof EmptyBagOStuff ) {
+                       // Use APC, or fallback to the main cache if it isn't setup
+                       $cache = ObjectCache::getLocalClusterInstance();
+               }
 
                if ( is_null( self::$singleton ) ) {
                        self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context );
diff --git a/includes/utils/MWCryptHash.php b/includes/utils/MWCryptHash.php
deleted file mode 100644 (file)
index 1117357..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-/**
- * Utility functions for generating hashes
- *
- * This is based in part on Drupal code as well as what we used in our own code
- * prior to introduction of this class, by way of MWCryptRand.
- *
- * 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
- */
-
-class MWCryptHash {
-       /**
-        * The hash algorithm being used
-        */
-       protected static $algo = null;
-
-       /**
-        * The number of bytes outputted by the hash algorithm
-        */
-       protected static $hashLength = [
-               true => null,
-               false => null,
-       ];
-
-       /**
-        * Decide on the best acceptable hash algorithm we have available for hash()
-        * @return string A hash algorithm
-        */
-       public static function hashAlgo() {
-               if ( !is_null( self::$algo ) ) {
-                       return self::$algo;
-               }
-
-               $algos = hash_algos();
-               $preference = [ 'whirlpool', 'sha256', 'sha1', 'md5' ];
-
-               foreach ( $preference as $algorithm ) {
-                       if ( in_array( $algorithm, $algos ) ) {
-                               self::$algo = $algorithm;
-                               wfDebug( __METHOD__ . ': Using the ' . self::$algo . " hash algorithm.\n" );
-
-                               return self::$algo;
-                       }
-               }
-
-               // We only reach here if no acceptable hash is found in the list, this should
-               // be a technical impossibility since most of php's hash list is fixed and
-               // some of the ones we list are available as their own native functions
-               // But since we already require at least 5.2 and hash() was default in
-               // 5.1.2 we don't bother falling back to methods like sha1 and md5.
-               throw new DomainException( "Could not find an acceptable hashing function in hash_algos()" );
-       }
-
-       /**
-        * Return the byte-length output of the hash algorithm we are
-        * using in self::hash and self::hmac.
-        *
-        * @param bool $raw True to return the length for binary data, false to
-        *   return for hex-encoded
-        * @return int Number of bytes the hash outputs
-        */
-       public static function hashLength( $raw = true ) {
-               $raw = (bool)$raw;
-               if ( is_null( self::$hashLength[$raw] ) ) {
-                       self::$hashLength[$raw] = strlen( self::hash( '', $raw ) );
-               }
-
-               return self::$hashLength[$raw];
-       }
-
-       /**
-        * Generate an acceptably unstable one-way-hash of some text
-        * making use of the best hash algorithm that we have available.
-        *
-        * @param string $data
-        * @param bool $raw True to return binary data, false to return it hex-encoded
-        * @return string A hash of the data
-        */
-       public static function hash( $data, $raw = true ) {
-               return hash( self::hashAlgo(), $data, $raw );
-       }
-
-       /**
-        * Generate an acceptably unstable one-way-hmac of some text
-        * making use of the best hash algorithm that we have available.
-        *
-        * @param string $data
-        * @param string $key
-        * @param bool $raw True to return binary data, false to return it hex-encoded
-        * @return string An hmac hash of the data + key
-        */
-       public static function hmac( $data, $key, $raw = true ) {
-               if ( !is_string( $key ) ) {
-                       // a fatal error in HHVM; an exception will at least give us a stack trace
-                       throw new InvalidArgumentException( 'Invalid key type: ' . gettype( $key ) );
-               }
-               return hash_hmac( self::hashAlgo(), $data, $key, $raw );
-       }
-
-}
index dd3ea1b..5818958 100644 (file)
  * @file
  */
 
-class MWCryptRand {
-       /**
-        * Minimum number of iterations we want to make in our drift calculations.
-        */
-       const MIN_ITERATIONS = 1000;
-
-       /**
-        * Number of milliseconds we want to spend generating each separate byte
-        * of the final generated bytes.
-        * This is used in combination with the hash length to determine the duration
-        * we should spend doing drift calculations.
-        */
-       const MSEC_PER_BYTE = 0.5;
-
-       /**
-        * Singleton instance for public use
-        */
-       protected static $singleton = null;
-
-       /**
-        * A boolean indicating whether the previous random generation was done using
-        * cryptographically strong random number generator or not.
-        */
-       protected $strong = null;
-
-       /**
-        * Initialize an initial random state based off of whatever we can find
-        * @return string
-        */
-       protected function initialRandomState() {
-               // $_SERVER contains a variety of unstable user and system specific information
-               // It'll vary a little with each page, and vary even more with separate users
-               // It'll also vary slightly across different machines
-               $state = serialize( $_SERVER );
-
-               // To try vary the system information of the state a bit more
-               // by including the system's hostname into the state
-               $state .= wfHostname();
-
-               // Try to gather a little entropy from the different php rand sources
-               $state .= rand() . uniqid( mt_rand(), true );
-
-               // Include some information about the filesystem's current state in the random state
-               $files = [];
-
-               // We know this file is here so grab some info about ourselves
-               $files[] = __FILE__;
-
-               // We must also have a parent folder, and with the usual file structure, a grandparent
-               $files[] = __DIR__;
-               $files[] = dirname( __DIR__ );
-
-               // The config file is likely the most often edited file we know should
-               // be around so include its stat info into the state.
-               // The constant with its location will almost always be defined, as
-               // WebStart.php defines MW_CONFIG_FILE to $IP/LocalSettings.php unless
-               // being configured with MW_CONFIG_CALLBACK (e.g. the installer).
-               if ( defined( 'MW_CONFIG_FILE' ) ) {
-                       $files[] = MW_CONFIG_FILE;
-               }
-
-               foreach ( $files as $file ) {
-                       MediaWiki\suppressWarnings();
-                       $stat = stat( $file );
-                       MediaWiki\restoreWarnings();
-                       if ( $stat ) {
-                               // stat() duplicates data into numeric and string keys so kill off all the numeric ones
-                               foreach ( $stat as $k => $v ) {
-                                       if ( is_numeric( $k ) ) {
-                                               unset( $k );
-                                       }
-                               }
-                               // The absolute filename itself will differ from install to install so don't leave it out
-                               $path = realpath( $file );
-                               if ( $path !== false ) {
-                                       $state .= $path;
-                               } else {
-                                       $state .= $file;
-                               }
-                               $state .= implode( '', $stat );
-                       } else {
-                               // The fact that the file isn't there is worth at least a
-                               // minuscule amount of entropy.
-                               $state .= '0';
-                       }
-               }
-
-               // Try and make this a little more unstable by including the varying process
-               // id of the php process we are running inside of if we are able to access it
-               if ( function_exists( 'getmypid' ) ) {
-                       $state .= getmypid();
-               }
-
-               // If available try to increase the instability of the data by throwing in
-               // the precise amount of memory that we happen to be using at the moment.
-               if ( function_exists( 'memory_get_usage' ) ) {
-                       $state .= memory_get_usage( true );
-               }
-
-               // It's mostly worthless but throw the wiki's id into the data for a little more variance
-               $state .= wfWikiID();
-
-               // If we have a secret key set then throw it into the state as well
-               global $wgSecretKey;
-               if ( $wgSecretKey ) {
-                       $state .= $wgSecretKey;
-               }
-
-               return $state;
-       }
-
-       /**
-        * Randomly hash data while mixing in clock drift data for randomness
-        *
-        * @param string $data The data to randomly hash.
-        * @return string The hashed bytes
-        * @author Tim Starling
-        */
-       protected function driftHash( $data ) {
-               // Minimum number of iterations (to avoid slow operations causing the
-               // loop to gather little entropy)
-               $minIterations = self::MIN_ITERATIONS;
-               // Duration of time to spend doing calculations (in seconds)
-               $duration = ( self::MSEC_PER_BYTE / 1000 ) * MWCryptHash::hashLength();
-               // Create a buffer to use to trigger memory operations
-               $bufLength = 10000000;
-               $buffer = str_repeat( ' ', $bufLength );
-               $bufPos = 0;
-
-               // Iterate for $duration seconds or at least $minIterations number of iterations
-               $iterations = 0;
-               $startTime = microtime( true );
-               $currentTime = $startTime;
-               while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
-                       // Trigger some memory writing to trigger some bus activity
-                       // This may create variance in the time between iterations
-                       $bufPos = ( $bufPos + 13 ) % $bufLength;
-                       $buffer[$bufPos] = ' ';
-                       // Add the drift between this iteration and the last in as entropy
-                       $nextTime = microtime( true );
-                       $delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
-                       $data .= $delta;
-                       // Every 100 iterations hash the data and entropy
-                       if ( $iterations % 100 === 0 ) {
-                               $data = sha1( $data );
-                       }
-                       $currentTime = $nextTime;
-                       $iterations++;
-               }
-               $timeTaken = $currentTime - $startTime;
-               $data = MWCryptHash::hash( $data );
-
-               wfDebug( __METHOD__ . ": Clock drift calculation " .
-                       "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
-                       "iterations=$iterations, " .
-                       "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
-
-               return $data;
-       }
-
-       /**
-        * Return a rolling random state initially build using data from unstable sources
-        * @return string A new weak random state
-        */
-       protected function randomState() {
-               static $state = null;
-               if ( is_null( $state ) ) {
-                       // Initialize the state with whatever unstable data we can find
-                       // It's important that this data is hashed right afterwards to prevent
-                       // it from being leaked into the output stream
-                       $state = MWCryptHash::hash( $this->initialRandomState() );
-               }
-               // Generate a new random state based on the initial random state or previous
-               // random state by combining it with clock drift
-               $state = $this->driftHash( $state );
-
-               return $state;
-       }
-
-       /**
-        * @see self::wasStrong()
-        */
-       public function realWasStrong() {
-               if ( is_null( $this->strong ) ) {
-                       throw new MWException( __METHOD__ . ' called before generation of random data' );
-               }
-
-               return $this->strong;
-       }
-
-       /**
-        * @see self::generate()
-        */
-       public function realGenerate( $bytes, $forceStrong = false ) {
-
-               wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " .
-                       wfGetAllCallers( 5 ) . "\n" );
-
-               $bytes = floor( $bytes );
-               static $buffer = '';
-               if ( is_null( $this->strong ) ) {
-                       // Set strength to false initially until we know what source data is coming from
-                       $this->strong = true;
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       // If available make use of mcrypt_create_iv URANDOM source to generate randomness
-                       // On unix-like systems this reads from /dev/urandom but does it without any buffering
-                       // and bypasses openbasedir restrictions, so it's preferable to reading directly
-                       // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
-                       // entropy so this is also preferable to just trying to read urandom because it may work
-                       // on Windows systems as well.
-                       if ( function_exists( 'mcrypt_create_iv' ) ) {
-                               $rem = $bytes - strlen( $buffer );
-                               $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
-                               if ( $iv === false ) {
-                                       wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
-                               } else {
-                                       $buffer .= $iv;
-                                       wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) .
-                                               " bytes of randomness.\n" );
-                               }
-                       }
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
-                               $rem = $bytes - strlen( $buffer );
-                               $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
-                               if ( $openssl_bytes === false ) {
-                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" );
-                               } else {
-                                       $buffer .= $openssl_bytes;
-                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " .
-                                               strlen( $openssl_bytes ) . " bytes of " .
-                                               ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
-                               }
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // openssl tells us if the random source was strong, if some of our data was generated
-                                       // using it use it's say on whether the randomness is strong
-                                       $this->strong = !!$openssl_strong;
-                               }
-                       }
-               }
-
-               // Only read from urandom if we can control the buffer size or were passed forceStrong
-               if ( strlen( $buffer ) < $bytes &&
-                       ( function_exists( 'stream_set_read_buffer' ) || $forceStrong )
-               ) {
-                       $rem = $bytes - strlen( $buffer );
-                       if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
-                               wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom " .
-                                       "without control over the buffer size.\n" );
-                       }
-                       // /dev/urandom is generally considered the best possible commonly
-                       // available random source, and is available on most *nix systems.
-                       MediaWiki\suppressWarnings();
-                       $urandom = fopen( "/dev/urandom", "rb" );
-                       MediaWiki\restoreWarnings();
-
-                       // Attempt to read all our random data from urandom
-                       // php's fread always does buffered reads based on the stream's chunk_size
-                       // so in reality it will usually read more than the amount of data we're
-                       // asked for and not storing that risks depleting the system's random pool.
-                       // If stream_set_read_buffer is available set the chunk_size to the amount
-                       // of data we need. Otherwise read 8k, php's default chunk_size.
-                       if ( $urandom ) {
-                               // php's default chunk_size is 8k
-                               $chunk_size = 1024 * 8;
-                               if ( function_exists( 'stream_set_read_buffer' ) ) {
-                                       // If possible set the chunk_size to the amount of data we need
-                                       stream_set_read_buffer( $urandom, $rem );
-                                       $chunk_size = $rem;
-                               }
-                               $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
-                               $buffer .= $random_bytes;
-                               fclose( $urandom );
-                               wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) .
-                                       " bytes of randomness.\n" );
-
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // urandom is always strong, set to true if all our data was generated using it
-                                       $this->strong = true;
-                               }
-                       } else {
-                               wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" );
-                       }
-               }
-
-               // If we cannot use or generate enough data from a secure source
-               // use this loop to generate a good set of pseudo random data.
-               // This works by initializing a random state using a pile of unstable data
-               // and continually shoving it through a hash along with a variable salt.
-               // We hash the random state with more salt to avoid the state from leaking
-               // out and being used to predict the /randomness/ that follows.
-               if ( strlen( $buffer ) < $bytes ) {
-                       wfDebug( __METHOD__ .
-                               ": Falling back to using a pseudo random state to generate randomness.\n" );
-               }
-               while ( strlen( $buffer ) < $bytes ) {
-                       $buffer .= MWCryptHash::hmac( $this->randomState(), strval( mt_rand() ) );
-                       // This code is never really cryptographically strong, if we use it
-                       // at all, then set strong to false.
-                       $this->strong = false;
-               }
-
-               // Once the buffer has been filled up with enough random data to fulfill
-               // the request shift off enough data to handle the request and leave the
-               // unused portion left inside the buffer for the next request for random data
-               $generated = substr( $buffer, 0, $bytes );
-               $buffer = substr( $buffer, $bytes );
-
-               wfDebug( __METHOD__ . ": " . strlen( $buffer ) .
-                       " bytes of randomness leftover in the buffer.\n" );
-
-               return $generated;
-       }
-
-       /**
-        * @see self::generateHex()
-        */
-       public function realGenerateHex( $chars, $forceStrong = false ) {
-               // hex strings are 2x the length of raw binary so we divide the length in half
-               // odd numbers will result in a .5 that leads the generate() being 1 character
-               // short, so we use ceil() to ensure that we always have enough bytes
-               $bytes = ceil( $chars / 2 );
-               // Generate the data and then convert it to a hex string
-               $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
-
-               // A bit of paranoia here, the caller asked for a specific length of string
-               // here, and it's possible (eg when given an odd number) that we may actually
-               // have at least 1 char more than they asked for. Just in case they made this
-               // call intending to insert it into a database that does truncation we don't
-               // want to give them too much and end up with their database and their live
-               // code having two different values because part of what we gave them is truncated
-               // hence, we strip out any run of characters longer than what we were asked for.
-               return substr( $hex, 0, $chars );
-       }
-
-       /** Publicly exposed static methods **/
+use MediaWiki\MediaWikiServices;
 
+class MWCryptRand {
        /**
-        * Return a singleton instance of MWCryptRand
-        * @return MWCryptRand
+        * @return CryptRand
         */
        protected static function singleton() {
-               if ( is_null( self::$singleton ) ) {
-                       self::$singleton = new self;
-               }
-
-               return self::$singleton;
+               return MediaWikiServices::getInstance()->getCryptRand();
        }
 
        /**
@@ -385,7 +42,7 @@ class MWCryptRand {
         * @return bool Returns true if the source was strong, false if not.
         */
        public static function wasStrong() {
-               return self::singleton()->realWasStrong();
+               return self::singleton()->wasStrong();
        }
 
        /**
@@ -401,7 +58,7 @@ class MWCryptRand {
         * @return string Raw binary random data
         */
        public static function generate( $bytes, $forceStrong = false ) {
-               return self::singleton()->realGenerate( $bytes, $forceStrong );
+               return self::singleton()->generate( $bytes, $forceStrong );
        }
 
        /**
@@ -417,6 +74,6 @@ class MWCryptRand {
         * @return string Hexadecimal random data
         */
        public static function generateHex( $chars, $forceStrong = false ) {
-               return self::singleton()->realGenerateHex( $chars, $forceStrong );
+               return self::singleton()->generateHex( $chars, $forceStrong );
        }
 }
index abba5a1..95b4463 100644 (file)
@@ -21,6 +21,7 @@
  * @author Aaron Schulz
  */
 use Wikimedia\Assert\Assert;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class for getting statistically unique IDs
@@ -368,7 +369,7 @@ class UIDGenerator {
                // Counter values would not survive accross script instances in CLI mode.
                $cache = null;
                if ( ( $flags & self::QUICK_VOLATILE ) && PHP_SAPI !== 'cli' ) {
-                       $cache = ObjectCache::getLocalServerInstance();
+                       $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
                }
                if ( $cache ) {
                        $counter = $cache->incrWithInit( $bucket, $cache::TTL_INDEFINITE, $count, $count );
index 88eb4bf..5ad5bd3 100644 (file)
        "preview": "Eu dilèë",
        "showpreview": "Peuleumah hasé",
        "showdiff": "Peuleumah neuubah",
-       "anoneditwarning": "Droëneuh   hana teudapeuta tamong. Alamat IP Droëneuh   teucatat lam tarèh (riwayat away) ôn nyoë.",
+       "anoneditwarning": "<strong>Peuneugah:</strong> Droëneuh hana lom neutamong. Alamat IP-neuh jeuët deuh bak ureuëng la'én meunyö neumeuandam. Meunyö Droëneuh <strong>[$1 neutamong]</strong> atawa <strong>[$2 neudapeuta]</strong>, neuandamneuh jeuët teutuléh ateuëh nan Droëneuh ngön na lom meunapha'at nyang la'én.",
        "missingcommenttext": "Neupasoë beunalah di yup.",
        "summary-preview": "Eu dilèë neuringkaih:",
        "blockedtitle": "Ureueng ngui geutheun",
        "tooltip-t-recentchangeslinked": "Neuubah barô lam laman nyang meupawôt nibak laman nyoë",
        "tooltip-feed-rss": "Umpeuën RSS keu laman nyoë",
        "tooltip-feed-atom": "Umpeuën Atom keu miëng nyoë",
-       "tooltip-t-contributions": "Dapeuta beuneuri ureuëng ngui nyoë",
+       "tooltip-t-contributions": "Dapeuta beuneuri {{GENDER:$1|ureuëng ngui nyoë}}",
        "tooltip-t-emailuser": "Peu'ét surat-e keu ureuëng ngui nyoë",
        "tooltip-t-upload": "Peutamong beureukaih",
        "tooltip-t-specialpages": "Dapeuta ban dum miëng kusuih",
        "exif-xresolution": "Resolusi linteuëng",
        "exif-yresolution": "Rèsolusi buju",
        "exif-datetime": "Uroë buleuën ngön watèë neuubah beureukaih",
+       "exif-make": "Pabrék kamèra",
+       "exif-model": "Moden kamèra",
        "exif-software": "Software geungui",
        "exif-exifversion": "Versi Exif",
        "exif-colorspace": "Ruweuëng wareuna",
        "tag-filter": "Saréng [[Special:Tags|tag]]:",
        "tag-filter-submit": "Saréng",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
+       "logentry-delete-delete": "$1 {{GENDER:$2|geusampôh}} miëng $3",
        "logentry-newusers-create": "$1 {{GENDER:$2|geupeugöt}} akun ureuëng ngui",
        "searchsuggest-search": "Mita",
        "duration-seconds": "{{PLURAL:$1|deutik}}",
index de6f5ab..7b4a68e 100644 (file)
@@ -67,7 +67,8 @@
                        "Hhaboh162002",
                        "بدارين",
                        "باسم",
-                       "Moud hosny"
+                       "Moud hosny",
+                       "ديفيد"
                ]
        },
        "tog-underline": "سطر تحت الوصلات:",
        "faq": "الأسئلة المتكررة",
        "faqpage": "Project:أسئلة متكررة",
        "actions": "أفعال",
-       "namespaces": "Ù\81ضاءات Ø§Ù\84تسÙ\85Ù\8aØ©",
+       "namespaces": "Ù\86طاÙ\82ات",
        "variants": "المتغيرات",
        "navigation-heading": "قائمة التصفح",
        "errorpagetitle": "خطأ",
        "history": "تاريخ الصفحة",
        "history_short": "تاريخ",
        "updatedmarker": "عدلت منذ زيارتي الأخيرة",
-       "printableversion": "بتنسق للطباعة",
+       "printableversion": "نسخة للطباعة",
        "permalink": "رابط دائم",
        "print": "اطبع",
        "view": "مطالعة",
        "searchprofile-advanced-tooltip": "ابحث في النطاقات المخصصة",
        "search-result-size": "$1 ({{PLURAL:$2|لا كلمات|كلمة واحدة|كلمتان|$2 كلمات|$2 كلمة}})",
        "search-result-category-size": "{{PLURAL:$1|لا أعضاء|عضو واحد|عضوان|$1 أعضاء|$1 عضوًا|$1 عضو}} ({{PLURAL:$2|لا تصانيف فرعية|تصنيف فرعي واحد|تصنيفان فرعيان|$2 تصنيفات فرعية|$2 تصنيفًا فرعيًا|$2 تصنيف فرعي}} و{{PLURAL:$3|لا ملفات|ملف واحد|ملفان|$3 ملفات|$3 ملفًا|$3 ملف}})",
-       "search-redirect": "(تحويلة $1)",
+       "search-redirect": "(تحويلة من $1)",
        "search-section": "(قسم $1)",
        "search-category": "(التصنيف $1)",
        "search-file-match": "(يطابق محتوى الملف)",
        "allmessagesname": "الاسم",
        "allmessagesdefault": "النص الافتراضي",
        "allmessagescurrent": "النص الحالي",
-       "allmessagestext": "Ù\87Ø°Ù\87 Ù\82ائÙ\85Ø© Ø¨Ø±Ø³Ø§Ø¦Ù\84 Ø§Ù\84Ù\86ظاÙ\85 Ø§Ù\84Ù\85تÙ\88Ù\81رة Ù\81Ù\8a Ù\86طاÙ\82 Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a.\nÙ\8aرجÙ\89 Ø²Ù\8aارة :\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation MediaWiki Localisation] Ù\88 [https://translatewiki.net translatewiki.net]\nإازا Ù\83Ù\86ت ØªØ±ØºØ¨ Ù\81Ù\8a Ø§Ù\84Ù\85ساÙ\87Ù\85Ø© Ø¨ØªØ¹Ø±Ù\8aب Ù\85Ù\8aدÙ\8aا Ù\88Ù\8aÙ\83Ù\8a",
+       "allmessagestext": "Ù\87Ø°Ù\87 Ù\82ائÙ\85Ø© Ø¨Ø±Ø³Ø§Ø¦Ù\84 Ø§Ù\84Ù\86ظاÙ\85 Ø§Ù\84Ù\85تÙ\88Ù\81رة Ù\81Ù\8a Ù\86طاÙ\82 Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a.\nÙ\85Ù\86 Ù\81ضÙ\84Ù\83 Ø²Ø± [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation ØªØ±Ø¬Ù\85Ø© Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a] Ù\88 [https://translatewiki.net ØªØ±Ø§Ù\86سÙ\84Ù\8aت Ù\88Ù\8aÙ\83Ù\8a Ø¯Ù\88ت Ù\86ت] Ù\84Ù\88 Ù\83Ù\86ت ØªØ±ØºØ¨ Ù\81Ù\8a Ø§Ù\84Ù\85ساÙ\87Ù\85Ø© Ù\81Ù\8a ØªØ±Ø¬Ù\85Ø© Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a Ø§Ù\84أساسÙ\8aØ©.",
        "allmessagesnotsupportedDB": "هذه الصفحة لا يمكن استخدامها لأن '''$wgUseDatabaseMessages''' تم تعطيله.",
        "allmessages-filter-legend": "المرشح",
        "allmessages-filter": "رشح حسب حالة التخصيص:",
        "feedback-external-bug-report-button": "أرسل تقرير علة تقنية",
        "feedback-dialog-title": "أرسل تغذية راجعة",
        "feedback-dialog-intro": "أنت يمكنك استخدام الاستمارة السهلة بالأسفل لإرسال تعليقك. تعليقك ستتم إضافته للصفحة \"$1\"، مع اسم المستخدم الخاص بك.",
-       "feedback-error-title": "خطأ",
        "feedback-error1": "خطأ: لا يمكن التعرف عليها من API",
        "feedback-error2": "خطأ: فشل في تحرير",
        "feedback-error3": "خطأ : لا توجد استجابة من API",
index 54fe2cb..4173d45 100644 (file)
        "feedback-external-bug-report-button": "Rexistrar una xera técnica",
        "feedback-dialog-title": "Unviar opinión",
        "feedback-dialog-intro": "Puedes usar el formulariu fácil de más abaxo pa unviar comentarios. Estos amestaránse a la páxina «$1», xunto col to nome d'usuariu.",
-       "feedback-error-title": "Error",
        "feedback-error1": "Fallu: Resultáu de la API non reconocíu",
        "feedback-error2": "Fallu: Falló la edición",
        "feedback-error3": "Fallu: Ensin respuesta de la API",
index c4d8ba5..428dafc 100644 (file)
        "feedback-bugnew": "Mən yoxladım. Yeni xəta barədə xəbər ver",
        "feedback-cancel": "İmtina",
        "feedback-close": "Oldu",
-       "feedback-error-title": "Xəta",
        "feedback-error2": "Xəta: Redaktə qeydə alınmadı",
        "feedback-message": "Mesaj:",
        "feedback-subject": "Mövzu:",
index 7a4ccf1..c8e697d 100644 (file)
        "feedback-external-bug-report-button": "Техник эш еберергә",
        "feedback-dialog-title": "Баһалама ебәрергә",
        "feedback-dialog-intro": "Баһалама ебәреү өсөн түбәндәге форманы файҙаланығыҙ. Һеҙҙең исем менән комментарий «$1» битендә буласаҡ.",
-       "feedback-error-title": "Хата",
        "feedback-error1": "Хата: API-нан беленмәгән хата",
        "feedback-error2": "Хата: Мөхәррирләү хатаһы",
        "feedback-error3": "Хата: API-нан яуап юҡ",
index f2413c7..cff2680 100644 (file)
        "previewerrortext": "Адбылася памылка пры спробе папярэдняга прагляду вашых зьменаў.",
        "blockedtitle": "Удзельнік заблякаваны",
        "blockedtext": "<strong>Ваш рахунак удзельніка ці IP-адрас быў заблякаваны.</strong>\n\nБлякаваньне выканаў $1.\nПрычына гэтага: <em>$2</em>.\n\n* Пачатак блякаваньня: $8\n* Сканчэньне блякаваньня: $6\n* Быў заблякаваны: $7\n\nВы можаце скантактавацца з $1 ці адным зь іншых [[{{MediaWiki:Grouppage-sysop}}|адміністратараў]], каб абмеркаваць блякаваньне. Заўважце, што Вы ня зможаце ўжыць магчымасьць «даслаць ліст па электроннай пошце», пакуль не пазначыце сапраўдны адрас электроннай пошты ў Вашых [[Special:Preferences|наладах]], і калі гэта Вам не было забаронена.\nВаш IP-адрас — $3, ідэнтыфікатар блякаваньня — #$5.\nКалі ласка, улучайце ўсю вышэйпададзеную інфармацыю ва ўсе запыты, што Вы будзеце рабіць.",
-       "autoblockedtext": "Ваш IP-адрас быў аўтаматычна заблякаваны, таму што ён ужываўся іншым удзельнікам, які быў заблякаваны $1.\nПрычына гэтага:\n\n:''$2''\n\n* Блякаваньне пачалося: $8\n* Блякаваньне скончыцца: $6\n* Быў заблякаваны: $7\n\nВы можаце скантактавацца з $1 ці з адным зь іншых [[{{MediaWiki:Grouppage-sysop}}|адміністратараў]], каб абмеркаваць блякаваньне.\n\nЗаўважце, што Вы ня зможаце ужываць магчымасьць «даслаць ліст праз электронную пошту», пакуль ня будзе пазначаны дзейны адрас электроннай пошты ў Вашых [[Special:Preferences|наладах удзельніка]], і калі гэта Вам не было забаронена.\n\nВаш цяперашні IP-адрас — $3, ідэнтыфікатар блякаваньня — #$5.\nКалі ласка, улучайце ўсю вышэйпададзеную інфармацыю ва ўсе запыты, што Вы будзеце рабіць.",
+       "autoblockedtext": "Ваш IP-адрас быў аўтаматычна заблякаваны, таму што ён ужываўся іншым удзельнікам, які быў заблякаваны $1.\nПрычына гэтага:\n\n:<em>$2</em>\n\n* Блякаваньне пачалося: $8\n* Блякаваньне скончыцца: $6\n* Быў заблякаваны: $7\n\nВы можаце скантактавацца з $1 ці з адным зь іншых [[{{MediaWiki:Grouppage-sysop}}|адміністратараў]], каб абмеркаваць блякаваньне.\n\nЗаўважце, што Вы ня зможаце ўжываць магчымасьць «даслаць ліст праз электронную пошту», пакуль ня будзе пазначаны дзейны адрас электроннай пошты ў Вашых [[Special:Preferences|наладах удзельніка]], і калі гэта Вам не было забаронена.\n\nВаш цяперашні IP-адрас — $3, ідэнтыфікатар блякаваньня — #$5.\nКалі ласка, улучайце ўсю вышэйпададзеную інфармацыю ва ўсе запыты, што Вы будзеце рабіць.",
        "blockednoreason": "прычына не пазначана",
        "whitelistedittext": "Вам трэба $1, каб рэдагаваць старонкі.",
        "confirmedittext": "Вы мусіце пацьвердзіць Ваш адрас электроннай пошты перад рэдагаваньнем старонак. Калі ласка, пазначце і пацьвердзіце адрас электроннай пошты праз Вашы [[Special:Preferences|налады]].",
        "htmlform-datetime-placeholder": "ГГГГ-ММ-ДД ГГ:ХХ:СС",
        "htmlform-date-invalid": "Уведзенае вамі значэньне не зьяўляецца датай. Паспрабуйце ўжыць фармат ГГГГ-ММ-ДД.",
        "htmlform-time-invalid": "Уведзенае вамі значэньне не зьяўляецца часам. Паспрабуйце ўжыць фармат ГГ:ХХ:СС.",
+       "htmlform-datetime-invalid": "Уведзенае вамі значэньне не зьяўляецца датай і часам. Паспрабуйце ўжыць фармат ГГГГ-ММ-ДД ГГ:ХХ:СС.",
+       "htmlform-date-toolow": "Уведзенае вамі значэньне меней за самую раньнюю дазволеную дату $1.",
+       "htmlform-date-toohigh": "Значэньне, якое вы выбралі, болей за самую позьнюю дазволеную дату $1.",
+       "htmlform-time-toolow": "Значэньне, якое вы выбралі, меней за самы раньні дазволены час $1.",
+       "htmlform-time-toohigh": "Значэньне, якое вы выбралі, болей за самы позьні дазволены час $1.",
+       "htmlform-datetime-toolow": "Значэньне, якое вы выбралі, меней за самую раньнюю дазволеную дату і час $1.",
+       "htmlform-datetime-toohigh": "Значэньне, якое вы выбралі, болей за найпазьнейшую дазволеную дату і час $1.",
        "htmlform-title-badnamespace": "[[:$1]] знаходзіцца не ў прасторы назваў «{{ns:$2}}».",
        "htmlform-title-not-creatable": "«$1» — немагчымы загаловак для старонкі",
        "htmlform-title-not-exists": "$1 не існуе.",
        "feedback-external-bug-report-button": "Аформіць тэхнічную задачу",
        "feedback-dialog-title": "Адаслаць водгук",
        "feedback-dialog-intro": "Свой водгук Вы можаце адаслаць праз простую форму зьнізу. Ваш камэнтар будзе дададзены на старонку «$1» разам з Вашым іменем.",
-       "feedback-error-title": "Памылка",
        "feedback-error1": "Памылка: невядомы вынік з API",
        "feedback-error2": "Памылка рэдагаваньня",
        "feedback-error3": "Памылка: няма адказу ад API",
index 7ce602c..a3019e0 100644 (file)
        "views": "Віды",
        "toolbox": "Прылады",
        "tool-link-userrights": "Змяніць групы {{GENDER:$1|ўдзельніка|ўдзельніцы}}",
+       "tool-link-emailuser": "Напісаць ліст {{GENDER:$1|удзельніку|удзельніцы}}",
        "userpage": "Паказаць старонку ўдзельніка",
        "projectpage": "Паказаць старонку праекта",
        "imagepage": "Гл. старонку файла",
        "upload-foreign-cant-upload": "Гэта вікі не настроена для ўкладання файлаў у запытанае старонняе сховішча файлаў.",
        "upload-dialog-title": "Укласці файл",
        "upload-dialog-button-cancel": "Нічога",
+       "upload-dialog-button-back": "Назад",
        "upload-dialog-button-done": "Гатова",
        "upload-dialog-button-save": "Запісаць",
        "upload-dialog-button-upload": "Укласці",
        "export-download": "Прапанаваць запісаць у файл",
        "export-templates": "Разам з шаблонамі",
        "export-pagelinks": "Разам са старонкамі, на якія ёсць спасылкі (макс. кольк. крокаў):",
+       "export-manual": "Дадаць старонкі ўручную:",
        "allmessages": "Сістэмныя паведамленні",
        "allmessagesname": "Назва",
        "allmessagesdefault": "Прадвызначаны тэкст",
        "thumbnail-temp-create": "Не ўдаецца стварыць часовы файл эскіза",
        "thumbnail-dest-create": "Не ўдаецца захаваць эскіз па месцы прызначэння",
        "thumbnail_invalid_params": "Няправільныя параметры драбніцы",
+       "thumbnail_toobigimagearea": "Файл з памерамі большымі, чым $1",
        "thumbnail_dest_directory": "Немагчыма стварыць мэтавую тэчку",
        "thumbnail_image-type": "Дадзены тып выявы не падтрымліваецца",
        "thumbnail_gd-library": "Няпоўная канфігурацыя бібліятэкі GD, адсутнічае функцыя $1",
        "tag-filter": "Фільтр [[Special:Tags|бірак]]:",
        "tag-filter-submit": "Фільтр",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тэг|Тэгі}}]]: $2)",
+       "tag-mw-contentmodelchange": "змена мадэлі змесціва",
        "tags-title": "Біркі",
        "tags-intro": "Тут пералічаныя біркі, якімі праграмы могуць пазначыць праўку, а таксама іх значэнні.",
        "tags-tag": "Назва біркі",
        "feedback-cancel": "Адмена",
        "feedback-close": "Зроблена.",
        "feedback-dialog-title": "Даслаць водгук",
-       "feedback-error-title": "Памылка",
        "feedback-error1": "Памылка. Невядомы вынік з API",
        "feedback-error2": "Памылка. Збой праўкі",
        "feedback-error3": "Памылка. Няма адказу ад API",
index b357dea..889061b 100644 (file)
        "feedback-bugornote": "Ако сте готови подробно да опишете технически проблем, моля [$1 докладвайте го тук].\nВ противен случай, можете да използвате лесния формуляр по-долу. Коментарът ви ще бъде добавен към страницата \"[$3 $2]\", наред с вашето потребителско име.",
        "feedback-cancel": "Отказване",
        "feedback-close": "Готово",
-       "feedback-error-title": "Грешка",
        "feedback-error1": "Грешка: Неразпознат резултат от API",
        "feedback-error2": "Грешка: Неуспешна редакция",
        "feedback-error3": "Грешка: Няма отговор от API",
index 50e9cb4..1a60e1a 100644 (file)
        "passwordreset-emailelement": "ব্যবহারকারী নাম: \n$1\n\nঅস্থায়ী পাসওয়ার্ড: \n$2",
        "passwordreset-emailsentemail": "যদি এই ই-মেইল ঠিকানা আপনার অ্যাকাউন্টের সাথে সংযুক্ত করা থাকে, তাহলে একটি পাসওয়ার্ড বদলের ইমেইল পাঠানো হবে।",
        "passwordreset-emailsentusername": "যদি এই ব্যবহারকারী নামের সাথে ই-মেইল ঠিকানা সংযুক্ত করা থাকে, তাহলে একটি পাসওয়ার্ড বদলের ইমেইল পাঠানো হবে।",
-       "passwordreset-emailsent-capture2": "পাসওয়ার্ড পুনঃধার্যকরণের {{PLURAL:$1|email has|ইমেইল পাঠানো}} হয়েছে। {{PLURAL:$1|username and password|ব্যবহারকারীনাম ও পাসওয়ার্ড তালিকা}} এখানে দেখা যাবে।",
-       "passwordreset-emailerror-capture2": "{{GENDER:$2|ব্যবহারকারীকে}} ইমেইল পাঠানো যায়নি: $1 {{PLURAL:$3|username and password|ব্যবহারকারীনাম ও পাসওয়ার্ড তালিকা}} এখানে দেখা যাবে।",
+       "passwordreset-emailsent-capture2": "পাসওয়ার্ড পুনঃধার্যকরণের {{PLURAL:$1|ইমেইল পাঠানো}} হয়েছে। {{PLURAL:$1|ব্যবহারকারী নাম ও পাসওয়ার্ড|ব্যবহারকারী নাম ও পাসওয়ার্ডের তালিকা}} এখানে দেখা যাবে।",
+       "passwordreset-emailerror-capture2": "{{GENDER:$2|ব্যবহারকারীকে}} ইমেইল পাঠানো যায়নি: $1 {{PLURAL:$3|ব্যবহারকারী নাম ও পাসওয়ার্ড|ব্যবহারকারী নাম ও পাসওয়ার্ডের তালিকা}} এখানে দেখা যাবে।",
        "passwordreset-nocaller": "একটি আহ্বানকারী প্রদান করা আবশ্যক",
        "passwordreset-nosuchcaller": "আহ্বানকারীর অস্তিত্ব নেই: $1",
        "passwordreset-ignored": "পাসওয়ার্ড পুনঃধার্যকরণ করা যায়নি। হয়তো কোন প্রদানকারী কনফিগার করা হয়েনি?",
        "searchprofile-advanced-tooltip": "স্বনির্ধারিত নামস্থানে অনুসন্ধান করো",
        "search-result-size": "$1 ({{PLURAL:$2|১টি শব্দ|$2টি শব্দ}})",
        "search-result-category-size": "{{PLURAL:$1 |১টি সদস্য |$1টি সদস্য}} ({{PLURAL:$2 |১টি উপবিষয়শ্রেণী|$2টি উপবিষয়শ্রেণী}}, {{PLURAL:$3 |১টি ফাইল |$3টি ফাইল}})",
-       "search-redirect": "(পুনর্নিদেশনা $1)",
+       "search-redirect": "($1 থেকে পুনর্নির্দেশিত)",
        "search-section": "(অনুচ্ছেদ $1)",
        "search-category": "(বিষয়শ্রেণী $1)",
        "search-file-match": "(নথির বিষয়বস্তু মিলে যায়)",
        "feedback-external-bug-report-button": "প্রযুক্তিগত কাজ ফাইল করুন",
        "feedback-dialog-title": "প্রতিক্রিয়া জমা দিন",
        "feedback-dialog-intro": "আপনি আপনার প্রতিক্রিয়া জানাতে নীচের সহজ ফরম ব্যবহার করতে পারেন। আপনার মন্তব্য আপনার ব্যবহারকারী নামসহ, \"$1\" পাতায় যোগ করা হবে।",
-       "feedback-error-title": "ত্রুটি",
        "feedback-error1": "ত্রুটি: এপিআই হতে অজানা ফলাফল এসেছে",
        "feedback-error2": "ত্রুটি: সম্পাদনা ব্যর্থ",
        "feedback-error3": "ত্রুটি: এপিআই হতে কোন সাড়া নেই",
index c2b1d88..bd7643b 100644 (file)
@@ -51,7 +51,7 @@
        "tog-enotifminoredits": "Također mi pošalji e-poštu za male izmjene na stranicama i datotekama",
        "tog-enotifrevealaddr": "Otkrij adresu moje e-pošte u porukama obavještenja",
        "tog-shownumberswatching": "Prikaži broj korisnika koji prate",
-       "tog-oldsig": "Postojeći potpis:",
+       "tog-oldsig": "Vaš postojeći potpis:",
        "tog-fancysig": "Smatraj potpis kao wikitekst (bez automatskog linka)",
        "tog-uselivepreview": "Koristi pregled uživo",
        "tog-forceeditsummary": "Opomeni me pri unosu praznog sažetka",
@@ -68,7 +68,7 @@
        "tog-showhiddencats": "Prikaži skrivene kategorije",
        "tog-norollbackdiff": "Ne prikazuj razliku nakon izvršenog vraćanja",
        "tog-useeditwarning": "Upozori me kad napuštam stranicu za izmjene bez sačuvanih promjena",
-       "tog-prefershttps": "Uvijek koristi sigurnu konekciju kada sam prijavljen.",
+       "tog-prefershttps": "Uvijek koristi sigurnu vezu dok sam prijavljen",
        "underline-always": "Uvijek",
        "underline-never": "Nikad",
        "underline-default": "Prema predodređenim postavkama teme ili preglednika",
        "october-date": "$1. oktobar",
        "november-date": "$1. novembar",
        "december-date": "$1. decembar",
+       "period-am": "AM",
+       "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Kategorija|Kategorije}}",
        "category_header": "Članci u kategoriji \"$1\"",
        "subcategories": "Podkategorije",
        "newwindow": "(otvara se u novom prozoru)",
        "cancel": "Odustani",
        "moredotdotdot": "Više...",
-       "morenotlisted": "Ovaj spisak nije potpun.",
+       "morenotlisted": "Ovaj spisak možda nije potpun.",
        "mypage": "Stranica",
        "mytalk": "Razgovor",
        "anontalk": "Razgovor",
        "talk": "Razgovor",
        "views": "Pregledi",
        "toolbox": "Alati",
+       "tool-link-userrights": "Promijeni {{GENDER:$1|korisničke}} grupe",
+       "tool-link-emailuser": "Pošalji e-poruku {{GENDER:$1|korisniku|korisnici}}",
        "userpage": "Pogledaj korisničku stranicu",
        "projectpage": "Pogledaj stranicu projekta",
        "imagepage": "Pogledajte stranicu datoteke",
        "confirmable-confirm": "Da li {{GENDER:$1|ste}} sigurni?",
        "confirmable-yes": "Da",
        "confirmable-no": "Ne",
-       "thisisdeleted": "Pogledati ili vratiti $1?",
+       "thisisdeleted": "Pogledaj ili vrati $1?",
        "viewdeleted": "Pogledaj $1?",
-       "restorelink": "{{PLURAL:$1|$1 izbrisana izmjena|$1 izbrisanih izmjena}}",
+       "restorelink": "{{PLURAL:$1|jednu obrisanu izmjenu|$1 obrisane izmjene|$1 obrisanih izmjena}}",
        "feedlinks": "Fid:",
        "feed-invalid": "Nedozvoljen tip potpisa",
        "feed-unavailable": "RSS izvori nisu dostupni",
        "laggedslavemode": "'''Upozorenje''': Stranica, možda, nije ažurirana.",
        "readonly": "Baza je zaključana",
        "enterlockreason": "Unesite razlog za zaključavanje, uključujući procjenu vremena otključavanja",
-       "readonlytext": "Baza je trenutno zaključana za nove unose i ostale izmjene, vjerovatno zbog rutinskog održavanja, posle čega će biti vraćena u uobičajeno stanje.\n\nAdministrator koji ju je zaključao je ponudio ovo objašnjenje: $1",
+       "readonlytext": "Baza podataka trenutno je zaključana za nove unose i druge izmjene, vjerovatno zbog rutinskog održavanja, nakon čega će biti vraćena u uobičajeno stanje.\n\nSistemski administrator koji ju je zaključao naveo je sljedeće objašnjenje: $1",
        "missing-article": "U bazi podataka nije pronađen tekst stranice tražen pod nazivom \"$1\" $2.\n\nDo ovoga dolazi kad se prati pomjeranje ili historija linka za stranicu koja je pobrisana.\n\n\nU slučaju da se ne radi o gore navedenom moguće je da ste pronašli grešku u programu.\nMolimo Vas da ovo prijavite [[Special:ListUsers/sysop|administratoru]] s navođenjem tačne adrese stranice.",
        "missingarticle-rev": "(revizija#: $1)",
        "missingarticle-diff": "(Razlika: $1, $2)",
        "userlogin-resetpassword-link": "Zaboravili ste lozinku?",
        "userlogin-helplink2": "Pomoć pri prijavljivanju",
        "userlogin-loggedin": "Već ste prijavljeni kao {{GENDER:$1|$1}}.\nKoristite donji obrazac da biste se prijavili kao drugi korisnik.",
+       "userlogin-reauth": "Morate se ponovo prijaviti da bismo potvrdili da ste zaista {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Napravi još jedan račun",
        "createacct-emailrequired": "Adresa e-pošte",
        "createacct-emailoptional": "Adresa e-pošte (opcionalno)",
        "noname": "Niste izabrali ispravno korisničko ime.",
        "loginsuccesstitle": "Prijavljen",
        "loginsuccess": "'''Sad ste prijavljeni na {{SITENAME}} kao \"$1\".'''",
-       "nosuchuser": "Ne postoji korisnik s imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite Vaš unos ili [[Special:CreateAccount|napravite novi korisnički račun]].",
+       "nosuchuser": "Ne postoji korisnik s imenom \"$1\".\nKorisnička imena razlikuju velika i mala slova.\nProvjerite jeste li ga tačno upisali ili [[Special:CreateAccount|otvorite novi račun]].",
        "nosuchusershort": "Ne postoji korisnik s imenom \"$1\".\nProvjerite jeste li dobro ukucali.",
        "nouserspecified": "Morate izabrati korisničko ime.",
        "login-userblocked": "Ovaj korisnik je blokiran. Prijava nije dopuštena.",
        "wrongpasswordempty": "Lozinka koju ste unijeli je bila prazna.\nMolimo Vas da pokušate ponovno.",
        "passwordtooshort": "Lozinka mora imati najmanje {{PLURAL:$1|1 znak|$1 znaka|$1 znakova}}.",
        "passwordtoolong": "Lozinke ne mogu biti duže od {{PLURAL:$1|jednog znaka|$1 znaka|$1 znakova}}.",
+       "passwordtoopopular": "Ovo je često korištena lozinka i ne može se koristiti. Molimo Vas da izaberete jaču lozinku.",
        "password-name-match": "Vaša lozinka mora biti različita od Vašeg korisničkog imena.",
        "password-login-forbidden": "Korištenje ovih korisničkih imena i šifara je zabranjeo.",
        "mailmypassword": "Poništi lozinku",
        "noemail": "Ne postoji adresa e-pošte za korisnika \"$1\".",
        "noemailcreate": "Morate da navedete validnu e-mail adresu",
        "passwordsent": "Nova lozinka poslana je na adresu e-pošte korisnika \"$1\".\nMolimo Vas da se prijavite nakon što je primite.",
-       "blocked-mailpassword": "Da bi se spriječila nedozvoljena akcija, Vašoj IP adresi je onemogućeno uređivanje stranica kao i mogućnost zahtijevanje nove lozinke.",
+       "blocked-mailpassword": "Vašoj IP-adresi onemogućeno je uređivanje. Da bi se spriječila zloupotreba, s nje nije moguće zahtijevati novu lozinku.",
        "eauthentsent": "Na navedenu adresu e-pošte poslana je poruka s potvrdom.\nPrije nego što pošaljemo daljnje poruke, pratite uputstva s e-pošte da biste potvrdili da je račun zaista Vaš.",
        "throttled-mailpassword": "Već Vam je poslana e-poruka za promjenu lozinke u {{PLURAL:$1|posljednjih sat vremena|posljednja $1 sata|posljednjih $1 sati}}.\nDa bi se spriječila zloupotreba, može se poslati samo jedna e-poruka za promjenu lozinke {{PLURAL:$1|svakih sat vremena|svaka $1 sata|svakih $1 sati}}.",
        "mailerror": "Greška pri slanju e-pošte: $1",
        "passwordreset-emailtext-ip": "Neko (vjerovatno Vi, s IP adrese $1) je zatražio podsjetnik Vaših detalja računa za {{SITENAME}} ($4). Sljedeći {{PLURAL:$3|račun korisnika je|računi korisnika su}} povezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena šifra|Ove privremene šifre}} će isteći za {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu šifru. Ako je neko drugi napravio ovaj zahtjev, ili ako ste se sjetili Vaše početne šifre, a ne želite je promijeniti, možete zanemariti ovu poruku i nastaviti koristiti staru šifru.",
        "passwordreset-emailtext-user": "Korisnik $1 na {{SITENAME}} je zatražio podsjetnik o detaljima Vašeg računa za {{SITENAME}} ($4). Sljedeći {{PLURAL:$3|korisnički račun je|korisnički računi su}} povezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena šifra|Ove privremene šifre}} će isteći za {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu šifru. Ako je neko drugi napravio ovaj zahtjev, ili ako ste se sjetili Vaše originalne šifre, a ne želite je više promijeniti, možete zanemariti ovu poruku i nastaviti koristiti staru šifru.",
        "passwordreset-emailelement": "Korisničko ime: \n$1\n\nPrivremena šifra: \n$2",
-       "passwordreset-emailsentemail": "Ako je ovo adresa e-pošte s kojom ste registrirali ovaj račun, podsjetnik šifre će vam biti poslan na vašu adresu e-pošte.",
+       "passwordreset-emailsentemail": "Ako je ova adresa e-pošte povezana s Vašim računom, podsjetnik o lozinci bit će Vam poslan na adresu e-pošte.",
        "changeemail": "Promjena ili uklanjanje e-adrese",
        "changeemail-header": "Ispunite sljedeći formular da biste promijenili adresu e-pošte. Ako želite ukloniti postojeću adresu e-pošte s vašeg korisničkog računa, pri ispunjavanju formulara, polje nove adrese e-pošte ostavite prazno.",
        "changeemail-no-info": "Morate biti prijavljeni za direktan pristup ovoj stranici.",
        "accmailtext": "Nasumično odabrana šifra za [[User talk:$1|$1]] je poslata na adresu $2.\n\nŠifra/lozinka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|izmjene šifre]]'' nakon prijave.",
        "newarticle": "(Novi)",
        "newarticletext": "Došli ste na stranicu koja još nema sadržaja.\n*Ako želite unijeti sadržaj, počnite tipkati u prozor ispod ovog teksta.\n*Ako Vam treba pomoć, idite na [$1 stranicu za pomoć].\n*Ako ste ovamo dospjeli slučajno, kliknite na dugme \"Nazad\" (''Back'') u Vašem internetskom pregledniku.",
-       "anontalkpagetext": "----''Ovo je stranica za razgovor za anonimnog korisnika koji još nije napravio nalog ili ga ne koristi.\nZbog toga moramo da koristimo brojčanu IP adresu kako bismo identifikovali njega ili nju.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i mislite da su vam upućene nebitne primjedbe, molimo Vas da [[Special:CreateAccount|napravite nalog]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu sa ostalim anonimnim korisnicima.''",
+       "anontalkpagetext": "----\n<em>Ovo je stranica za razgovor s anonimnim korisnikom koji još nije napravio račun ili ga ne koristi.</em>\nZbog toga moramo koristiti brojčanu IP-adresu kako bismo ga prepoznali.\nTakvu adresu može dijeliti više korisnika.\nAko ste anonimni korisnik i smatrate da su Vam upućene nebitne primjedbe, molimo Vas da [[Special:CreateAccount|napravite račun]] ili se [[Special:UserLogin|prijavite]] da biste izbjegli buduću zabunu s ostalim anonimnim korisnicima.",
        "noarticletext": "Na ovoj stranici trenutno nema teksta.\nMožete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] na drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tražiti u povezanim zapisnicima] ili [{{fullurl:{{FULLPAGENAME}}|action=edit}} napraviti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Trenutno nema teksta na ovoj stranici.\nMožete [[Special:Search/{{PAGENAME}}|tražiti ovaj naslov stranice]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane zapisnike]</span>, ali nemate dozvolu da napravite ovu stranicu.",
        "missing-revision": "Uređivanje broj $1 na stranici \"{{FULLPAGENAME}}\" ne postoji.\n\nOvo se obično dešava kad pratite zastarjelu vezu na stranicu koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protokolu brisanja].",
        "previewnote": "<strong>Ne zaboravite da je ovo samo pregled.</strong>\nVaše izmjene još nisu sačuvane!",
        "continue-editing": "Idi na područje uređivanja",
        "previewconflict": "Ovaj pregled prikazuje kako će tekst u gornjem polju\nizgledati ako kliknete \"Sačuvaj članak\".",
-       "session_fail_preview": "<strong>Izvinjavamo se! Nismo mogli obraditi vašu izmjenu zbog gubitka podataka o prijavi.\nMolimo pokušajte ponovno.\nAko i dalje ne bude radilo, pokušajte se [[Special:UserLogout|odjaviti]] i ponovno prijaviti.</strong>",
+       "session_fail_preview": "Izvinjavamo se! Nismo mogli obraditi Vašu izmjenu zbog gubitka podataka o prijavi.\n\nMožda ste odjavljeni. <strong>Provjerite jeste li prijavljeni i pokušajte ponovo</strong>.\nAko i dalje ne radi, pokušajte se [[Special:UserLogout|odjaviti]] i ponovo prijaviti i provjerite dozvoljava li Vaš preglednik kolačiće s ovog sajta.",
        "session_fail_preview_html": "'''Žao nam je! Nismo mogli da obradimo vašu izmjenu zbog gubitka podataka.'''\n\n''Zbog toga što {{SITENAME}} ima omogućen izvorni HTML, predpregled je sakriven kao predostrožnost protiv JavaScript napada.''\n\n'''Ako ste pokušali da napravite pravu izmjenu, molimo pokušajte ponovo. Ako i dalje ne radi, pokušajte da se [[Special:UserLogout|odjavite]] i ponovo prijavite.'''",
        "token_suffix_mismatch": "'''Vaša izmjena nije prihvaćena jer je Vaš web preglednik ubacio znakove interpunkcije u token uređivanja.\nIzmjena je odbačena da bi se spriječilo uništavanje teksta stranice.\nTo se događa ponekad kad korisite problematični anonimni proxy koji je baziran na web-u.'''",
        "edit_form_incomplete": "'''Neki dijelovi uređivačkog obrasca nisu došli do servera; dvaput provjerite da su vaše izmjene nepromjenjene i pokušajte ponovno.'''",
        "nohistory": "Ne postoji historija izmjena za ovu stranicu.",
        "currentrev": "Trenutna verzija",
        "currentrev-asof": "Trenutna verzija na dan $2 u $3",
-       "revisionasof": "Verzija od $1",
+       "revisionasof": "Verzija na dan $2 u $3",
        "revision-info": "Izmjena od $1 od {{GENDER:$6|$2}}$7",
        "previousrevision": "← Starija izmjena",
        "nextrevision": "Novija izmjena →",
        "revdelete-submit": "Primijeni na odabrane {{PLURAL:$1|reviziju|revizije}}",
        "revdelete-success": "'''Vidljivost izmjene je ažurirana.'''",
        "revdelete-failure": "'''Vidljivost revizije nije mogla biti ažurirana:'''\n$1",
-       "logdelete-success": "'''Vidljivost evidencije uspješno postavljena.'''",
+       "logdelete-success": "Postavljena je vidljivost unosa u zapisniku.",
        "logdelete-failure": "'''Zapisnik vidljivosti nije mogao biti postavljen:'''\n$1",
        "revdel-restore": "Promijeni dostupnost",
        "pagehist": "Historija stranice",
        "difference-multipage": "(Razlika između stranica)",
        "lineno": "Red $1:",
        "compareselectedversions": "Uporedi označene verzije",
-       "showhideselectedversions": "Pokaži/sakrij odabrane verzije",
+       "showhideselectedversions": "Prikaži/sakrij izabrane izmjene",
        "editundo": "poništi",
        "diff-empty": "(Nema razlike)",
        "diff-multi-sameuser": "({{PLURAL:$1|Nije prikazana jedna međurevizija|Nisu prikazane $1 međurevizije}} istog korisnika)",
        "editusergroup": "Uredi {{GENDER:$1|korisničke}} grupe",
        "editinguser": "Mijenjate korisnička prava korisnika <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Uredi korisničke grupe",
-       "saveusergroups": "Sačuvaj korisničke grupe",
+       "saveusergroups": "Sačuvaj {{GENDER:$1|korisničke}} grupe",
        "userrights-groupsmember": "Član:",
        "userrights-groupsmember-auto": "Uključeni član od:",
        "userrights-groups-help": "Možete promijeniti grupe kojima ovaj korisnik pripada:\n* Označeni kvadratić znači da je korisnik u toj grupi.\n* Neoznačen kvadratić znači da korisnik nije u toj grupi.\n* Oznaka * (zvjezdica) označava da Vi ne možete izbrisati ovu grupu ako je dodate i obrnutno.",
        "right-override-export-depth": "Izvoz stranica uključujući povezane stranice do dubine od 5 linkova",
        "right-sendemail": "Slanje e-maila drugim korisnicima",
        "right-passwordreset": "Pogledaj e-mailove za obnavljanje šifre",
-       "right-managechangetags": "Napravi i briši [[Special:Tags|oznake]] iz baze podataka",
+       "right-managechangetags": "Napravi i (de)aktiviraj [[Special:Tags|oznake]]",
        "right-applychangetags": "Primijeni [[Special:Tags|oznake]] na nečije izmjene",
        "right-changetags": "Dodavanje ili uklanjanje raznih [[Special:Tags|oznaka]] na pojedinačnim verzijama i unosima zapisnika",
        "grant-group-page-interaction": "Upravljanje stranicama",
        "uploadstash-clear": "Očisti sakrivene datoteke",
        "uploadstash-nofiles": "Nemate sakrivenih datoteka.",
        "uploadstash-badtoken": "Izvršavanje ove akcije je bilo neuspješno, možda zato što su vaša uređivačka odobrenja istekla. Pokušajte ponovo.",
-       "uploadstash-errclear": "Brisanje sakrivenih datoteka je bilo neuspješno.",
+       "uploadstash-errclear": "Brisanje datoteka nije uspjelo.",
        "uploadstash-refresh": "Osvježi spisak datoteka",
        "invalid-chunk-offset": "Neispravna polazna tačka",
        "img-auth-accessdenied": "Pristup onemogućen",
        "log-title-wildcard": "Traži naslove koji počinju ovim tekstom",
        "showhideselectedlogentries": "Pokaži/sakrij izabrane zapise u evidenciji",
        "log-edit-tags": "Uredi oznake izabranih zapisničkih unosa",
+       "checkbox-select": "Izaberi: $1",
+       "checkbox-all": "Sve",
+       "checkbox-none": "Ništa",
+       "checkbox-invert": "Obrni",
        "allpages": "Sve stranice",
        "nextpage": "Sljedeća stranica ($1)",
        "prevpage": "Prethodna stranica ($1)",
        "enotif_body": "Poštovani $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nSažetak urednika: $PAGESUMMARY $PAGEMINOREDIT\n\nKontaktirajte urednika:\ne-pošta: $PAGEEDITOR_EMAIL\nwiki: $PAGEEDITOR_WIKI\n\nNeće biti drugih obavještenja u slučaju daljnjih izmjena osim ako prijavljeni ponovno posjetite stranicu. Također možete poništiti oznake obavijesti za sve praćene stranice koje imate na vašem spisku praćenja.\n\nVaš prijateljski {{SITENAME}} sistem obavještavanja\n\n--\nZa promjenu vaših postavki email obavijesti, posjetite\n{{canonicalurl:{{#special:Preferences}}}}\n\nZa promjenu postavki vašeg praćenja, posjetite\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nDa obrišete stranicu sa vašeg spiska praćenja, posjetite\n$UNWATCHURL\n\nPovratne informacije i daljnja pomoć:\n$HELPPAGE",
        "created": "napravljena",
        "changed": "promijenjena",
-       "deletepage": "Obrišite stranicu",
+       "deletepage": "Obriši stranicu",
        "confirm": "Potvrdite",
        "excontent": "sadržaj je bio: \"$1\"",
        "excontentauthor": "sadržaj je bio: \"$1\", a jedini urednik \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|razgovor]])",
        "exbeforeblank": "sadržaj prije brisanja je bio: \"$1\"",
        "delete-confirm": "Brisanje \"$1\"",
        "delete-legend": "Obriši",
-       "historywarning": "<strong>Upozorenje</strong>: Stranica koju želite da obrišete ima historiju sa otprilike $1 {{PLURAL:$1|revizijom|revizije|revizija}}:",
+       "historywarning": "<strong>Upozorenje:</strong> Stranica koju želite obrisati ima historiju sa $1 {{PLURAL:$1|izmjenom|izmjene|izmjena}}:",
        "historyaction-submit": "Prikaži",
-       "confirmdeletetext": "Brisanjem ćete obrisati stranicu ili sliku zajedno sa historijom iz baze podataka, ali će se iste moći vratiti kasnije.\nMolim potvrdite svoju namjeru, da razumijete posljedice i da ovo radite u skladu sa [[{{MediaWiki:Policy-url}}|pravilima]].",
+       "confirmdeletetext": "Obrisat ćete stranicu i njenu historiju.\nPotvrdite svoju namjeru, da razumijete posljedice i da ovo radite u skladu s [[{{MediaWiki:Policy-url}}|pravilima]].",
        "actioncomplete": "Radnja je izvršena",
        "actionfailed": "Akcija nije uspjela",
        "deletedtext": "Stranica \"$1\" je obrisana.\nPogledajte $2 za zapisnik nedavnih brisanja.",
        "restriction-level-all": "svi nivoi",
        "undelete": "Pregled obrisanih stranica",
        "undeletepage": "Pregled i vraćanje obrisanih stranica",
-       "undeletepagetitle": "'''Sljedeći sadržaj prikazuje obrisane revizije od [[:$1|$1]]'''.",
+       "undeletepagetitle": "<strong>Sljedeći sadržaj prikazuje obrisane izmjene stranice [[:$1|$1]]<strong>.",
        "viewdeletedpage": "Pregled obrisanih stranica",
        "undeletepagetext": "{{PLURAL:$1|Slijedeća $1 stranica je obrisana|Slijedeće $1 stranice su obrisane|Slijedećih $1 je obrisano}} ali su još uvijek u arhivi i mogu biti vraćene.\nArhiva moše biti periodično čišćena.",
-       "undelete-fieldset-title": "Vraćanje revizija",
-       "undeleteextrahelp": "Da biste vratili cijelu historiju stranice, ostavite sve kućice neoznačene i kliknite na <strong><em>{{int:undeletebtn}}</em></strong>.\nDa biste vratili određene stranice, izaberite verzije koje želite vratiti i kliknite na <strong><em>{{int:undeletebtn}}</em></strong>.",
+       "undelete-fieldset-title": "Vraćanje izmjena",
+       "undeleteextrahelp": "Da biste vratili cijelu historiju stranice, ostavite sve kućice neoznačene i kliknite na <strong><em>{{int:undeletebtn}}</em></strong>.\nDa biste vratili određene izmjene, označite ih i kliknite na <strong><em>{{int:undeletebtn}}</em></strong>.",
        "undeleterevisions": "$1 {{PLURAL:$1|izmjena je obrisana|izmjena je obrisano}}",
-       "undeletehistory": "Ako vratite stranicu, sve će verzije biti vraćene u njenu historiju.\nAko je u međuvremenu napravljena nova verzija s istim nazivom, vraćene verzije će se pojaviti njenoj ranijoj historiji.",
-       "undeleterevdel": "Vraćanje obrisanog se neće izvršiti ako bi rezultiralo da zaglavlje stranice ili revizija datoteke bude djelimično obrisano.\nU takvim slučajevima, morate ukloniti označene ili otkriti sakrivene najskorije obrisane revizije.",
+       "undeletehistory": "Ako vratite stranicu, sve će izmjene biti vraćene u njenu historiju.\nAko je u međuvremenu napravljena nova izmjena s istim nazivom, vraćene izmjene pojavit će se u njenoj ranijoj historiji.",
+       "undeleterevdel": "Vraćanje neće biti izvršeno ako je rezultat toga djelomično brisanje posljednje izmjene.\nU takvim slučajevima morate isključiti ili otkriti najnoviju obrisanu izmjenu.",
        "undeletehistorynoadmin": "Ova stranica je obrisana.\nRazlog za brisanje se nalazi ispod, zajedno s detaljima o korisniku koji je mijenjao stranicu prije brisanja.\nTekst obrisanih verzija dostupan je samo administratorima.",
        "undelete-revision": "Obrisana izmjena stranice $1 (dana $4, u $5) koju je {{GENDER:$3|napravio|napravila}} $3:",
        "undeleterevision-missing": "Nepoznata ili nedostajuća revizija.\nMožda ste unijeli pogrešan link, ili je revizija vraćena ili uklonjena iz arhive.",
        "undeletebtn": "Vrati",
        "undeletelink": "pogledaj/vrati",
        "undeleteviewlink": "pogledaj",
-       "undeleteinvert": "Izmijeni odabir",
+       "undeleteinvert": "Obrni izbor",
        "undeletecomment": "Razlog:",
        "undeletedrevisions": "{{PLURAL:$1|vraćena $1 verzija|vraćene $1 verzije|vraćeno $1 verzija}}",
        "undeletedrevisions-files": "{{PLURAL:$1|1 verzija|$1 verzije|$1 verzija}} i {{PLURAL:$2|1 datoteka|$2 datoteke|$2 datoteka}} vraćeno",
        "undeletedfiles": "{{PLURAL:$1|1 datoteka vraćena|$1 datoteke vraćene|$1 datoteka vraćeno}}",
-       "cannotundelete": "Vraćanje nije uspjelo:\n$1",
+       "cannotundelete": "Vraćanje jedne ili svih stavki nije uspjelo:\n$1",
        "undeletedpage": "'''$1 je vraćena'''\n\nProvjerite [[Special:Log/delete|zapis brisanja]] za zapise najskorijih brisanja i vraćanja.",
        "undelete-header": "Pogledajte [[Special:Log/delete|zapisnik brisanja]] za nedavno obrisane stranice.",
        "undelete-search-title": "Pretraga obrisanih stranica",
        "sp-contributions-newbies-sub": "Za nove korisnike",
        "sp-contributions-newbies-title": "Doprinosi novih korisnika",
        "sp-contributions-blocklog": "zapisnik blokiranja",
-       "sp-contributions-suppresslog": "obrisani doprinosi korisnika",
-       "sp-contributions-deleted": "obrisani doprinosi korisnika",
+       "sp-contributions-suppresslog": "obrisani {{GENDER:$1|korisnički}} doprinosi",
+       "sp-contributions-deleted": "obrisani {{GENDER:$1|korisnički}} doprinosi",
        "sp-contributions-uploads": "postavljanja",
        "sp-contributions-logs": "zapisnici",
        "sp-contributions-talk": "razgovor",
        "whatlinkshere-hideredirs": "$1 preusmjerenja",
        "whatlinkshere-hidetrans": "$1 uključenja",
        "whatlinkshere-hidelinks": "$1 linkove",
-       "whatlinkshere-hideimages": "Veze do datoteke $1",
+       "whatlinkshere-hideimages": "$1 linkova do datoteke",
        "whatlinkshere-filters": "Filteri",
        "autoblockid": "Automatska blokada #$1",
        "block": "Blokiraj korisnika",
        "move-page": "Premjesti $1",
        "move-page-legend": "Premjesti stranicu",
        "movepagetext": "Korištenjem ovog formulara možete preimenovati stranicu, premještajući cijelu historiju na novo ime.\nČlanak pod starim imenom postat će stranica koja preusmjerava na članak pod novim imenom. \nMožete automatski izmijeniti preusmjerenje do izvornog naslova.\nAko se ne odlučite na to, provjerite [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|neispravna preusmjeravanja]].\nDužni ste provjeriti da svi linkovi i dalje nastave voditi na prave stranice.\n\nImajte na umu da članak '''neće''' biti premješten ako već postoji članak pod imenom na koje ga namjeravate preusmjeriti osim u slučaju stranice za preusmjeravanje koja nema nikakvih starih izmjena.\nTo znači da možete vratiti stranicu na prethodno mjesto ako pogriješite, ali ne možete zamijeniti postojeću stranicu.\n\n'''Pažnja!'''\nOvo može biti drastična i neočekivana promjena kad su u pitanju popularne stranice.\nMolimo da dobro razmislite prije no što premjestite stranicu.",
-       "movepagetext-noredirectfixer": "Koristeći donji obrazac, preimenovat ćete stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv postat će preusmjerenje na novi naziv.\nMolimo da provjerite postoje li [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti jesu li linkovi ispravni i vode li tamo kamo bi trebali voditi.\n\nImajte na umu da stranica '''neće''' biti premještena ako već postoji stranica s tim imenom, osim ako je prazna ili je preusmjerenje ili nema ranije historije.\nOvo znači da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili, ali ne možete ponovo preimenovati postojeću stranicu.\n\n'''Pažnja!'''\nImajte na umu da premještanje popularnog članka može biti\ndrastična i neočekivana promjena za korisnike; molimo da budete sigurni da ste shvatili posljedice prije no što nastavite.",
+       "movepagetext-noredirectfixer": "Koristeći donji obrazac, preimenovat ćete stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv postat će preusmjerenje na novi naziv.\nMolimo da provjerite postoje li [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti jesu li linkovi ispravni i vode li tamo kamo bi trebali voditi.\n\nImajte na umu da stranica '''neće''' biti premještena ako već postoji stranica s tim imenom, osim ako je prazna ili je preusmjerenje ili nema ranije historije.\nOvo znači da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili, ali ne možete ponovo preimenovati postojeću stranicu.\n\n<strong>Napomena:</strong>\nImajte na umu da premještanje popularnog članka može biti\ndrastična i neočekivana promjena za korisnike; molimo da budete sigurni da ste shvatili posljedice prije no što nastavite.",
        "movepagetalktext": "Ako označite ovu kutijicu, odgovarajuća stranica za razgovor, ako postoji, automatski će biti premještena na novi naziv, osim ako već postoji sadržaj na odredišnoj stranici za razgovor.\n\nU tom slučaju, morat ćete ručno premjestiti ili prekopirati stranicu ako to želite.",
-       "moveuserpage-warning": "'''Upozorenje:''' Premještate korisničku stranicu. Molimo da zapamtite da će se samo stranica premjestiti a korisnik se ''neće'' preimenovati.",
+       "moveuserpage-warning": "<strong>Upozorenje:</strong> Premještate korisničku stranicu. Imajte u vidu da će samo stranica biti premještena, a sam korisnik <em>neće</em> biti preimenovan.",
        "movecategorypage-warning": "<strong>Upozorenje:</strong> Premještate stranicu kategorije. Imajte na umu da će samo stranica biti premještena i da sve stranice u staroj kategoriji <em>neće</em> biti ponovo kategorirane u novu kategoriju.",
        "movenologintext": "Morate biti registrovani korisnik i [[Special:UserLogin|prijavljeni]] da biste premjestili stranicu.",
        "movenotallowed": "Nemate dopuštenje za premještanje stranica.",
        "cant-move-category-page": "Nemate dopuštene da premještate stranice kategorija.",
        "cant-move-to-category-page": "Nemate dopuštenje da premjestite stranicu na stranicu kategorije.",
        "newtitle": "Novi naslov:",
-       "move-watch": "Prati ovu stranicu",
+       "move-watch": "Prati izvornu i odredišnu stranicu",
        "movepagebtn": "Premjesti stranicu",
        "pagemovedsub": "Premještanje uspjelo",
        "movepage-moved": "'''\"$1\" je premještena na \"$2\"'''",
        "movepage-moved-noredirect": "Pravljenje preusmjerenja je onemogućeno.",
        "articleexists": "Stranica pod tim imenom već postoji ili je ime koje ste izabrali neispravno. Molimo Vas da izaberete drugo ime.",
        "cantmove-titleprotected": "Ne možete premjestiti stranicu na ovu lokaciju jer je novi naslov zaštićen od pravljenja.",
-       "movetalk": "Premjestite i stranicu za razgovor ako je moguće.",
+       "movetalk": "Premjesti i stranicu za razgovor",
        "move-subpages": "Premjesti sve podstranice (do $1)",
        "move-talk-subpages": "Premjesti podstranice stranica za razgovor (do $1)",
        "movepage-page-exists": "Stranica $1 već postoji i ne može biti automatski zamijenjena.",
        "revertmove": "vrati",
        "delete_and_move_text": "==Potebno brisanje==\nOdredišna stranica \"[[:$1]]\" već postoji.\nDa li je želite obrisati kako bi ste mogli izvršiti premještanje?",
        "delete_and_move_confirm": "Da, obriši stranicu",
-       "delete_and_move_reason": "Obrisano da bi se napravio prostor za premještanje iz \"[[$1]]\"",
+       "delete_and_move_reason": "Obrisano da se oslobodi mjesto za premještanje iz \"[[$1]]\"",
        "selfmove": "Izvorni i ciljani naziv su isti; strana ne može da se premjesti preko same sebe.",
        "immobile-source-namespace": "Ne mogu premjestiti stranice u imenski prostor \"$1\"",
        "immobile-target-namespace": "Ne mogu se premjestiti stranice u imenski prostor \"$1\"",
        "tags-actions-header": "Radnje",
        "tags-active-yes": "Da",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Definirano preko proširenja",
+       "tags-source-extension": "Definirano softverom",
        "tags-source-manual": "Ručno postavili korisnici ili botovi",
        "tags-source-none": "Više se ne koristi",
        "tags-edit": "uređivanje",
        "feedback-external-bug-report-button": "Podnesi tehnički zadatak",
        "feedback-dialog-title": "Pošalji povratne informacije",
        "feedback-dialog-intro": "Možete koristiti jednostavni formular ispod kako biste poslali povratne informacije. Vaš komentar će biti dodan stranici \"$1\" zajedno s vašim korisničkim imenom.",
-       "feedback-error-title": "Greška",
        "feedback-error1": "Greška: Neprepoznati rezultat od API",
        "feedback-error2": "Greška: Uređivanje nije uspjelo",
        "feedback-error3": "Greška: Nema odgovora od API",
index 15b39de..fafca4a 100644 (file)
        "tooltip-pt-preferences": "汝其設定",
        "tooltip-pt-watchlist": "汝監視其頁面有改過其單單",
        "tooltip-pt-mycontris": "汝其貢獻其單單",
-       "tooltip-pt-login": "Hĭ-uông nṳ̄ sĕng láuk-diē; bók-guó nàng-gă mò̤ ăng nṳ̄ cūng-kuāng có̤.",
+       "tooltip-pt-login": "Gióng-ngiê nṳ̄ sĕng láuk-diē; bók-guó nàng-gă mò̤ ăng nṳ̄ cūng-kuāng có̤.",
        "tooltip-pt-logout": "躒出",
+       "tooltip-pt-createaccount": "Gióng-ngiê nṳ̄ sĕng kŭi dióng-hô gái láuk-diē; bók-guó nàng-gă mò̤ ăng nṳ̄ cūng-kuāng có̤.",
        "tooltip-ca-talk": "Nô̤i-ṳ̀ng gì tō̤-lâung",
-       "tooltip-ca-edit": "汝會使修改茲蜀頁。起動敆保存以前使預覽按鈕",
+       "tooltip-ca-edit": "Siŭ-gāi cī hiĕk",
        "tooltip-ca-addsection": "Gă sĭng dâung",
        "tooltip-ca-viewsource": "茲蜀頁乞保護起去。\n汝會使看伊其源代碼。",
        "tooltip-ca-history": "Ché̤ṳ cī hiĕk gó̤-dā̤ gì bēng-buōng",
        "tooltip-ca-watch": "Ciŏng cī siŏh hiĕk gă diē nṳ̄ gì gáng-sê-dăng",
        "tooltip-ca-unwatch": "共茲頁趁監視單𡅏移開去",
        "tooltip-search": "Sìng-tō̤ {{SITENAME}} [alt-f]",
+       "tooltip-search-go": "Nâ ô dè̤ng-miàng gì hiĕk còng-câi, cô kó̤ hiā hiĕk",
        "tooltip-search-fulltext": "Sìng-tō̤ sāi-ê̤ṳng ciā ùng-cê gì hiĕk-miêng",
        "tooltip-p-logo": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
        "tooltip-n-mainpage": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
        "tooltip-n-mainpage-description": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-portal": "Guăng-ṳ̀ ciā gĕ̤ng-tiàng, nṳ̄ â̤ có̤ gì, kó̤ diē-nē̤ tō̤ nó̤h",
        "tooltip-n-recentchanges": "Cī-bŏng diŏh wiki ô gāi-biéng gì dăng-dăng",
        "tooltip-n-randompage": "Sùi-biêng muōng ché̤ṳ",
-       "tooltip-t-whatlinkshere": "鏈遘嚽塊其所有維基頁面其單單\nCuòng-buô lièng-gáu cŭ-uái gì wiki hiĕk-miêng dăng-dăng",
+       "tooltip-n-help": "Sìng-tō̤ bŏng-cô gì sū-câi",
+       "tooltip-t-whatlinkshere": "Cuòng-buô lièng-gáu cŭ-uái gì wiki hiĕk-miêng dăng-dăng",
        "tooltip-t-recentchangeslinked": "鏈遘茲頁其頁面其最近修改\nCī hiĕk lièng gáu bĕk hiĕk gì cī-bŏng gì gāi-biéng",
        "tooltip-t-contributions": "茲蜀用戶其貢獻單單",
        "tooltip-t-emailuser": "向茲蜀隻用戶寄電批",
        "tooltip-watch": "共茲蜀頁加遘汝其監視單[alt-w]",
        "anonymous": "{{SITENAME}}其無名{{PLURAL:$1|用戶}}",
        "lastmodifiedatby": "茲頁最後是$3著$1$2改變其。",
+       "pageinfo-toolboxlink": "Hiĕk-miêng séng-sék",
        "deletedrevision": "刪掉舊其版本$1",
        "previousdiff": "← 舊其修改",
        "nextdiff": "新其修改 →",
        "file-nohires": "無更高決斷",
+       "show-big-image-size": "$1 × $2 chiông-só",
        "ilsubmit": "尋討",
        "bydate": "按日期",
        "metadata": "Nguòng-só-gé̤ṳ",
index d4671b6..decdb5b 100644 (file)
        "feedback-bugornote": "Хьайн техникин халонах лаьцна яздан хӀума делахь, дехар до, [$1 хаам бе тхоьга].\nДацахь хьан йиш ю хӀокху атта кепаца «[$3 $2]» агӀонг къамел тӀетоха хьан декъашхочун цӀарца, кхин лелош йолу браузер билгал еш.",
        "feedback-cancel": "Цаоьшу",
        "feedback-close": "Кийчча ю",
-       "feedback-error-title": "ГӀалат",
        "feedback-message": "Хаам:",
        "feedback-subject": "Къамел:",
        "feedback-submit": "Дахьийта",
index b897410..2f78340 100644 (file)
        "feedback-external-bug-report-button": "Založit technický úkol",
        "feedback-dialog-title": "Odeslat názor",
        "feedback-dialog-intro": "Pomocí níže zobrazeného jednoduchého formuláře můžete odeslat svůj názor. Váš komentář se spolu s vaším uživatelským jménem přidá na stránku „$1“.",
-       "feedback-error-title": "Chyba",
        "feedback-error1": "Chyba: Nerozpoznaný výsledek z API",
        "feedback-error2": "Chyba: Editace se nezdařila",
        "feedback-error3": "Chyba: API nevrátilo žádnou odpověď",
index b764fc0..80ac64d 100644 (file)
        "feedback-external-bug-report-button": "Eine technische Aufgabe einreichen",
        "feedback-dialog-title": "Rückmeldung senden",
        "feedback-dialog-intro": "Du kannst das einfache Formular unten verwenden, um deine Rückmeldung einzureichen. Dein Kommentar wird zusammen mit deinem Benutzernamen zur Seite „$1“ hinzugefügt.",
-       "feedback-error-title": "Fehler",
        "feedback-error1": "Fehler: Unbekanntes Ergebnis der API",
        "feedback-error2": "Fehler: Bearbeitung gescheitert",
        "feedback-error3": "Fehler: Keine Antwort von der API",
index 3f1ea09..9277bf5 100644 (file)
        "categoryviewer-pagedlinks": "($1) ($2)",
        "about": "Heqa cı de",
        "article": "Pela zerreki",
-       "newwindow": "(teqayaa newid abeno)",
+       "newwindow": "(teqaya newi de abena)",
        "cancel": "Bıtexelne",
        "moredotdotdot": "Vêşi...",
        "morenotlisted": "Na lista qay kemi ya.",
        "nstab-template": "Şablon",
        "nstab-help": "Pela peşti",
        "nstab-category": "Kategoriye",
-       "mainpage-nstab": "Pela seri",
+       "mainpage-nstab": "Perra esas",
        "nosuchaction": "Fealiyeto wınasi çıniyo",
        "nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta eşkera bıkero.",
        "nosuchspecialpage": "Pela xasa wınasiye çıniya",
index dfe09b4..1d12d07 100644 (file)
        "searchprofile-advanced-tooltip": "Sērca int i spâsi di nòm fât só mzûra.",
        "search-result-size": "$1 ({{PLURAL:$2|'na parôla|$2 parôli}})",
        "search-result-category-size": "{{PLURAL:$1|1 utèint|$1 utèint}} ({{PLURAL:$2|1 sotcategoréia|$2 sotcategoréi}},{{PLURAL:$3|1 file|$3 files}})",
-       "search-redirect": "(redirect $1)",
+       "search-redirect": "(redirect from $1)",
        "search-section": "(sesiòun $1)",
        "search-category": "(categoréia $1)",
        "search-file-match": "(relasiòun dèinter al file)",
index cbe755d..fc8ba1f 100644 (file)
        "searchprofile-advanced-tooltip": "Search in custom namespaces",
        "search-result-size": "$1 ({{PLURAL:$2|1 word|$2 words}})",
        "search-result-category-size": "{{PLURAL:$1|1 member|$1 members}} ({{PLURAL:$2|1 subcategory|$2 subcategories}}, {{PLURAL:$3|1 file|$3 files}})",
-       "search-redirect": "(redirect $1)",
+       "search-redirect": "(redirect from $1)",
        "search-section": "(section $1)",
        "search-category": "(category $1)",
        "search-file-match": "(matches file content)",
        "feedback-external-bug-report-button": "File a technical task",
        "feedback-dialog-title": "Submit feedback",
        "feedback-dialog-intro": "You can use the easy form below to submit your feedback. Your comment will be added to the page \"$1\", along with your username.",
-       "feedback-error-title": "Error",
        "feedback-error1": "Error: Unrecognized result from API",
        "feedback-error2": "Error: Edit failed",
        "feedback-error3": "Error: No response from API",
index d1546a1..af8fb98 100644 (file)
        "upload-dialog-disabled": "Alŝutoj de dosiero per ĉi tiun dialogon estas malfunkciigita sur ĉi tiu vikio.",
        "upload-dialog-title": "Alŝuti dosieron",
        "upload-dialog-button-cancel": "Nuligi",
+       "upload-dialog-button-back": "Reen",
        "upload-dialog-button-done": "Farite",
        "upload-dialog-button-save": "Konservi",
        "upload-dialog-button-upload": "Alŝuti",
        "htmlform-cloner-create": "Aldoni plian",
        "htmlform-cloner-delete": "Forigi",
        "htmlform-cloner-required": "Almenaŭ unu valoro estas nepra.",
+       "htmlform-date-placeholder": "JJJJ-MM-TT",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "JJJJ-MM-TT HH:MM:SS",
        "htmlform-title-badnamespace": "[[:$1]] ne  estas en \"{{ns:$2}}\" nomspaco.",
        "htmlform-title-not-creatable": "\"$1\" estas nekreebla titolo por paĝo",
        "htmlform-title-not-exists": "$1 ne ekzistas.",
        "feedback-external-bug-report-button": "Krei teĥnikan taskon",
        "feedback-dialog-title": "Sendi prijuĝajn rimarkojn",
        "feedback-dialog-intro": "Vi povas uzi suban simplan formularon por sendi viajn prijuĝajn rimarkojn. Via komento estos aldonita al la paĝo \"$1\" kun via uzanto-nomo.",
-       "feedback-error-title": "Eraro",
        "feedback-error1": "Eraro: Nerekonita rezulto de API",
        "feedback-error2": "Eraro: La redakto malsukcesis",
        "feedback-error3": "Eraro: Neniu respondo de API",
index 04d8760..700f4e0 100644 (file)
        "feedback-external-bug-report-button": "Enviar una tarea técnica",
        "feedback-dialog-title": "Enviar comentarios",
        "feedback-dialog-intro": "Puedes usar el formulario sencillo debajo para enviar tus comentarios. Ellos se agregarán a la página \"$1\", junto con tu nombre de usuario.",
-       "feedback-error-title": "Error",
        "feedback-error1": "Error: No se reconoce resultado de API",
        "feedback-error2": "Error: Falló la edición",
        "feedback-error3": "Error: No hay respuesta de la API",
index 6ff7c3b..a3b56d8 100644 (file)
        "talk": "Eztabaida",
        "views": "Ikustaldiak",
        "toolbox": "Tresnak",
+       "tool-link-userrights": "Erabiltzaile {{GENDER:$1|taldea}} aldatu",
+       "tool-link-emailuser": "{{GENDER:$1|Erabiltzale}} honi e-posta bidali",
        "userpage": "Lankide orrialdea ikusi",
        "projectpage": "Proiektuaren orrialdea ikusi",
        "imagepage": "Ikusi fitxategiaren orria",
        "eauthentsent": "Egiaztapen mezu bat bidali da zehaztutako e-posta helbidera.\nHelbide horretara beste edozein mezu bidali aurretik, bertan azaltzen diren argibideak jarraitu behar dituzu, kontua zurea dela egiaztatzeko.",
        "throttled-mailpassword": "Pasahitz gogorarazle bat bidali da jada azken {{PLURAL:$1|orduan|$1 orduetan}}.\nBandalismoa sahiesteko pasahitz eskaera bat baino ezin da egin {{PLURAL:$1|orduan|$1 orduan}} behin.",
        "mailerror": "Errorea mezua bidaltzerakoan: $1",
-       "acct_creation_throttle_hit": "Sentitzen dugu, {{PLURAL:$1|erabiltzaile kontu bat sortu duzu|$1 erabiltzaile kontu sortu dituzu}} dagoeneko.\nOndorioz, ezin duzu kontu gehiago sortu.",
+       "acct_creation_throttle_hit": "Sentitzen dugu, {{PLURAL:$1|erabiltzaile kontu bat sortu duzu|$1 erabiltzaile kontu sortu dituzu}} dagoeneko azken $2(e)tan.\nOndorioz, ezin duzu kontu gehiago sortu.",
        "emailauthenticated": "Zure e-posta helbidea autentifikatu da $2an $3(e)tan.",
        "emailnotauthenticated": "Zure posta helbidea egiaztatu gabe dago. \nEz da mezurik bidaliko hurrengo ezaugarrientzako.",
        "noemailprefs": "Zehaztu e-posta helbide bat ezaugarri hauek erabili ahal izateko.",
        "feedback-cancel": "Utzi",
        "feedback-close": "Egina",
        "feedback-dialog-title": "Feedbacka bidali",
-       "feedback-error-title": "Errorea",
        "feedback-error1": "Akatsa: APIaren emaitza ez ezagunak",
        "feedback-error2": "Akatsa: Aldaketa ez da egin",
        "feedback-error3": "Akatsa: APIaren erantzunik gabe",
index 93f0896..d74dd1e 100644 (file)
        "feedback-external-bug-report-button": "Lähetä tekninen tehtävä",
        "feedback-dialog-title": "Lähetä palautetta",
        "feedback-dialog-intro": "Voit käyttää tätä helppoa lomaketta palautteesi lähettämiseen. Kommenttisi lisätään sivulle \"$1\" käyttäjätunnuksesi kera.",
-       "feedback-error-title": "Virhe",
        "feedback-error1": "Virhe: Ohjelmointirajapinnan vastausta ei tunnistettu",
        "feedback-error2": "Virhe: Muokkaus epäonnistui",
        "feedback-error3": "Virhe: Ohjelmointirajapinta ei vastaa",
index 79538b2..91b98f4 100644 (file)
        "namespacesall": "Tous",
        "monthsall": "tous",
        "confirmemail": "Confirmer l’adresse de courriel",
-       "confirmemail_noemail": "Vous n’avez pas défini une adresse de courriel valide dans vos [[Special:Preferences|préférences]].",
+       "confirmemail_noemail": "Vous n’avez pas défini une adresse de courriel valide dans vos [[Special:Preferences|préférences utilisateur]].",
        "confirmemail_text": "Ce wiki nécessite la vérification de votre adresse de courriel avant de pouvoir utiliser toute fonction de messagerie.\nUtilisez le bouton ci-dessous pour envoyer un courriel de confirmation à votre adresse.\nLe courriel inclura un lien comportant un code à usage unique et limité dans le temps ;\nchargez ce lien dans votre navigateur pour confirmer que votre adresse de courriel est valide.",
        "confirmemail_pending": "Un code de confirmation vous a déjà été envoyé par courriel ;\nsi vous venez de créer votre compte, veuillez attendre quelques minutes que le courriel arrive avant de demander un nouveau code.",
        "confirmemail_send": "Envoyer un code de confirmation",
        "confirmemail_success": "Votre adresse de courriel a été confirmée.\nVous pouvez maintenant vous [[Special:UserLogin|{{MediaWiki:Loginreqlink}}]] et profiter du wiki.",
        "confirmemail_loggedin": "Votre adresse de courriel est maintenant confirmée.",
        "confirmemail_subject": "Confirmation d’adresse de courriel pour {{SITENAME}}",
-       "confirmemail_body": "Quelqu’un, probablement vous, à partir de l’adresse IP $1,\na enregistré un compte « $2 » avec cette adresse de courriel\nsur le site {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient vraiment et afin\nd’activer les fonctions de messagerie sur {{SITENAME}},\nveuillez suivre ce lien dans votre navigateur :\n\n$3\n\nSi vous n’avez *pas* enregistré ce compte, n’ouvrez pas ce lien ;\nvous pouvez suivre l’autre lien ci-dessous pour annuler la\nconfirmation de votre adresse courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
+       "confirmemail_body": "Quelqu’un, probablement vous, à partir de l’adresse IP $1,\na créé un compte « $2 » avec cette adresse de courriel sur le site {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient vraiment et afin\nd’activer les fonctions de messagerie sur {{SITENAME}},\nveuillez suivre ce lien dans votre navigateur :\n\n$3\n\nSi vous n’avez *pas* créé ce compte, suivez le lien ci-dessous \npour annuler la confirmation de votre adresse courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
        "confirmemail_body_changed": "Quelqu’un, probablement vous, à partir de l’adresse IP $1,\na modifié l’adresse de courriel associée au compte « $2 » de {{SITENAME}}\nen cette adresse.\n\nPour confirmer que ce compte vous appartient vraiment et afin\nde réactiver les fonctions de messagerie sur {{SITENAME}},\nveuillez suivre ce lien dans votre navigateur :\n\n$3\n\nSi ce compte ne vous appartient *pas*, n’ouvrez pas ce lien ;\nvous pouvez suivre l’autre lien ci-dessous pour annuler la\nconfirmation de votre adresse courriel :\n\n$5\n\nCe code de confirmation expirera le $4.",
        "confirmemail_body_set": "Quelqu’un, probablement vous, depuis l’adresse IP $1, a modifié l’adresse de courriel du compte « $2 » en celle-ci sur {{SITENAME}}.\n\nPour confirmer que ce compte vous appartient et réactiver les fonctions de courriel sur {{SITENAME}}, ouvrez ce lien dans votre navigateur Web :\n\n$3\n\nCe code de confirmation expirera le $4.\n\nSi le compte ne vous appartient *pas*, suivez plutôt ce lien pour annuler la confirmation de l’adresse de courriel :\n\n$5",
        "confirmemail_invalidated": "Confirmation de l’adresse courriel annulée",
        "hebrew-calendar-m12-gen": "eloul",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|discussion]])",
        "timezone-local": "Local",
-       "duplicate-defaultsort": "Attention : la clé de tri par défaut « $2 » écrase la précédente clé « $1 ».",
+       "duplicate-defaultsort": "<strong>Attention :</strong> la clé de tri par défaut « $2 » écrase la précédente clé « $1 ».",
        "duplicate-displaytitle": "<strong>Attention :</strong> Le titre d'affichage « $2 » remplace l'ancien titre d'affichage « $1 ».",
        "restricted-displaytitle": "<strong>Avertissement :</strong> le titre d’affichage \"$1\" a été ignoré car il n'est pas équivalent au titre effectif de la page.",
        "invalid-indicator-name": "<strong>Erreur :</strong> L’attribut <code>name</code> des indicateurs d’état de la page ne doit pas être vide.",
        "version-license-not-found": "Aucune information détaillée de la licence n'a été trouvée pour cette extension.",
        "version-credits-title": "Remerciements pour $1",
        "version-credits-not-found": "Aucune information détaillée des remerciements n'a été trouvée pour cette extension.",
-       "version-poweredby-credits": "Ce wiki fonctionne grâce à '''[https://www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
+       "version-poweredby-credits": "Ce wiki fonctionne grâce à <strong>[https://www.mediawiki.org/ MediaWiki]</strong>, copyright © 2001-$1 $2.",
        "version-poweredby-others": "autres",
        "version-poweredby-translators": "traducteurs de translatewiki.net",
        "version-credits-summary": "Nous tenons à remercier les personnes suivantes pour leur contribution à  [[Special:Version|MediaWiki]].",
-       "version-license-info": "MediaWiki est un logiciel libre, vous pouvez le redistribuer ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation ; soit la version 2 de la Licence, ou (à votre choix) toute version ultérieure.\n\nMediaWiki est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE, sans même la garantie implicite de COMMERCIALISATION ou D'ADAPTATION À UN USAGE PARTICULIER. Voir la Licence Publique Générale GNU pour plus de détails.\n\nVous devriez avoir reçu [{{SERVER}}{{SCRIPTPATH}}/COPYING une copie de la Licence Publique Générale GNU] avec ce programme, sinon, écrivez à la Free Software Foundation, Inc., 51, rue Franklin, cinquième étage, Boston, MA 02110-1301, États-Unis ou [//www.gnu.org/licenses/old-licenses/gpl-2.0.html lisez-la en ligne].",
+       "version-license-info": "MediaWiki est un logiciel libre, vous pouvez le redistribuer ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation ; soit la version 2 de la Licence, ou (à votre choix) toute version ultérieure.\n\nMediaWiki est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE, sans même la garantie implicite de COMMERCIALISATION ou D'ADAPTATION À UN USAGE PARTICULIER. Voir la Licence Publique Générale GNU pour plus de détails.\n\nVous devriez avoir reçu [{{SERVER}}{{SCRIPTPATH}}/COPYING une copie de la Licence Publique Générale GNU] avec ce programme, sinon, écrivez à la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, États-Unis ou [//www.gnu.org/licenses/old-licenses/gpl-2.0.html lisez-la en ligne].",
        "version-software": "Logiciels installés",
        "version-software-product": "Produit",
        "version-software-version": "Version",
        "feedback-external-bug-report-button": "Signaler un bogue technique",
        "feedback-dialog-title": "Soumettre un commentaire",
        "feedback-dialog-intro": "Vous pouvez utiliser le simple formulaire ci-dessous pour faire parvenir vos commentaires. Votre commentaire sera ajouté à la page « $1 », ainsi que votre nom d’utilisateur.",
-       "feedback-error-title": "Erreur",
        "feedback-error1": "Erreur : Résultat de l'IPA non reconnu",
        "feedback-error2": "Erreur : la modification a échoué",
        "feedback-error3": "Erreur : aucune réponse de l'API",
index c6eab5b..4de10f0 100644 (file)
        "feedback-external-bug-report-button": "Enviar unha tarefa técnica",
        "feedback-dialog-title": "Enviar comentarios",
        "feedback-dialog-intro": "Pode usar o formulario simple de abaixo para enviar os seus comentarios sobre o editor visual. O seu comentario será engadido á páxina \"$1\", xunto co seu nome de usuario.",
-       "feedback-error-title": "Erro",
        "feedback-error1": "Erro: Resultado da API non recoñecido",
        "feedback-error2": "Erro: Fallo de edición",
        "feedback-error3": "Erro: Non hai resposta da API",
index 432338d..2aa61b6 100644 (file)
        "print": "Cetaki",
        "view": "Bilohi",
        "view-foreign": "Bilohi to $1",
-       "edit": "Momoli'o",
+       "edit": "Boli'o",
        "edit-local": "Ubawa deskripsi lokal",
        "create": "Mohutu",
        "create-local": "Duhengi deskripsi lokal",
        "lastmodifiedat": "Halaman botiye biloli'o pulitiyo $1, $2.",
        "viewcount": "Halaman botiye ma hilu'o {{PLURAL:$1|$1 kali}}.<br />",
        "protectedpage": "Halaman udaha-daha",
-       "jumpto": "Lumanti'a ode",
-       "jumptonavigation": "Navigasi",
+       "jumpto": "Lumanti'a ode:",
+       "jumptonavigation": "navigasi",
        "jumptosearch": "lolohe",
        "view-pool-error": "Ma'apu, server onggo sibuk sa'ati boti.\nNgohuntuwa pengguna mocoba momilehe halaman boti.\nWulatipo ngope'e to'u yi'o dipo mocoba momilehe halaman boti pooli.\n\n\n$1",
        "generic-pool-error": "Ma'apu, server onggo sibuk sa'ati boti.\nNgohuntuwa pengguna mocoba momilohe halaman boti.\nWulatipo ngope'e to'u  yi'u dipo mocoba momilehe halaman boti pooli.",
        "newmessageslinkplural": "{{PLURAL:$1|tuwawu tahuli bohu|999=tahuli bohu}}",
        "newmessagesdifflinkplural": "{{PLURAL:$1|iluba|999=u iluba}} pulitiyo",
        "youhavenewmessagesmulti": "Yio lootapu tahuli bohu to $1",
-       "editsection": "boli'a",
+       "editsection": "boli'o",
        "editold": "boli'a",
        "viewsourceold": "Bilohi bungoliyo",
        "editlink": "boli'a",
        "nstab-template": "Templat",
        "nstab-help": "Halaman tulungi",
        "nstab-category": "Kategori",
-       "mainpage-nstab": "Halamani bungaliyo",
+       "mainpage-nstab": "Halaman Bungaliyo",
        "nosuchaction": "Diya'a huhutu boyito",
        "nosuchactiontext": "Huhutu u hepohile lo URL ja valid.\nYi'o lotalawa lopomaso lo URL, meyalo lodudu'a pranala u ja banari.\nUtiye olo kira-kira tuwotiyo woluwo bug to pilaakasi u hepomake {{SITENAME}}",
        "nosuchspecialpage": "Diya'a halaman istimewa boyito",
        "yourpasswordagain": "Ulangiya tahe u'unti",
        "createacct-yourpasswordagain": "Konfirmasi tahe u'unti",
        "createacct-yourpasswordagain-ph": "Tuwota pooli tahe u'unti",
-       "remembermypassword": "Eelayi tahe u'unti'u to komputer botiye (to delomo $1 {{PLURAL:$1|huyi}})",
        "userlogin-remembermypassword": "Hulima'o wa'u tuwo-tuwoto",
        "userlogin-signwithsecure": "Popohunawa server aamani",
        "cannotloginnow-title": "Ja mowali tumuwoto log sa'ati botiya",
        "tooltip-invert": "Centang kotak botiye u mopowanto'o halaman yiloboli'a to delomo huwali lo tanggulo tilulawoto (wawu huwali lo tanggulo a'ayita wanu dicentang)",
        "namespace_association": "Huwali lo tanggulo a'aayita",
        "tooltip-namespace_association": "Centang halaman botiye u mopowayito huwali lo tanggulo lo'iyawa meyalo subjek u a'ayita wolo huwali lo tanggulo u tilulawoto.",
-       "blanknamespace": "Bungaliyo",
+       "blanknamespace": "(Bungaliyo)",
        "contributions": "Kontribusi {{GENDER:$1|Ta ohu'uwo}}",
        "mycontris": "Kontribusi",
        "anoncontribs": "Kontribusi",
index 0f3e624..6220a07 100644 (file)
        "disclaimers": "𐌲𐌰𐍂𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐍂𐌰𐌹𐌷𐍄𐌰𐌹𐍃",
        "disclaimerpage": "Project:𐌲𐌰𐌼𐌰𐌹𐌽𐌰 𐌲𐌰𐍂𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐍂𐌰𐌹𐌷𐍄𐌰𐌹𐍃",
        "edithelp": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌹𐌷𐌹𐌻𐍀𐌰",
+       "helppage-top-gethelp": "𐌷𐌹𐌻𐍀𐌰",
        "mainpage": "𐌰𐌽𐌰𐍃𐍄𐍉𐌳𐌴𐌹𐌽𐌹𐌻𐌰𐌿𐍆𐍃",
        "mainpage-description": "𐌰𐌽𐌰𐍃𐍄𐍉𐌳𐌴𐌹𐌽𐌹𐌻𐌰𐌿𐍆𐍃",
-       "portal": "ð\90\8c±ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c²ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹",
-       "portal-url": "Project:ð\90\8c±ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c²ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹",
+       "portal": "ð\90\8c²ð\90\8c°ð\90\8cµð\90\8c¿ð\90\8c¼ð\90\8c¸ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c½ð\90\8c³ð\90\8c¿ð\90\8c¸ð\90\8c°ð\90\8c¹ð\90\8d\83",
+       "portal-url": "Project:ð\90\8c²ð\90\8c°ð\90\8cµð\90\8c¿ð\90\8c¼ð\90\8c¸ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c½ð\90\8c³ð\90\8c¿ð\90\8c¸ð\90\8c°ð\90\8c¹ð\90\8d\83",
        "privacy": "𐌲𐌰𐍂𐌴𐌳𐌴𐌹𐌽𐍉𐍃 𐍃𐌿𐌽𐌳𐍂𐍉𐍅𐌹𐍃𐌰𐌽𐌰",
        "privacypage": "Project:𐌲𐌰𐍂𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐍃𐌿𐌽𐌳𐍂𐍉𐍅𐌹𐍃𐌰𐌽𐌰",
        "retrievedfrom": "𐌲𐌰𐌽𐌿𐌼𐌰𐌽 𐍆𐍂𐌰𐌼 \"$1\"",
        "error": "𐌰𐌹𐍂𐌶𐌴𐌹",
        "databaseerror-error": "𐌰𐌹𐍂𐌶𐌴𐌹: $1",
        "missing-article": "𐌳𐌰𐍄𐌰𐌱𐌴𐍃 𐌽𐌹 𐌱𐌹𐌲𐌰𐍄 𐌱𐍉𐌺𐍉𐍃 𐌻𐌰𐌿𐌱𐌹𐍃 𐌸𐌹𐌶𐌴𐌹 𐍃𐌺𐌿𐌻𐌳𐌴𐌳𐌹 𐌱𐌹𐌲𐌹𐍄𐌰𐌽, 𐌷𐌰𐌹𐍄𐌰𐌽𐍃 \"$1\" $2. \n\n𐌸𐌰𐍄𐌰 𐌿𐍆𐍄𐌰 𐍅𐌰𐌹𐍂𐌸𐌹𐌸 𐌾𐌰𐌱𐌰𐌹 𐌻𐌰𐌹𐍃𐍄𐌾𐌰𐌳𐌰 𐍆𐌰𐌹𐍂𐌽𐌾𐌰 𐌳𐌹𐍆𐍆 𐌸𐌰𐌿 𐍃𐍀𐌹𐌻𐌻𐌰𐌲𐌰𐍅𐌹𐍃𐍃 𐍃𐌴𐌹 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌹𐌳𐌰 𐌹𐍃𐍄. 𐌽𐌹𐌱𐌰𐌹 𐌹𐍃𐍄, 𐌼𐌰𐌷𐍄𐍃 𐌹𐍃𐍄 𐌴𐌹 𐌱𐌹𐌲𐌴𐍄𐌴𐌹𐍃 𐌰𐌹𐍂𐌶𐌴𐌹𐌽 𐌹𐌽 𐍃𐌰𐌿𐍆𐍄𐍅𐌰𐌹𐍂𐌰. \n\n𐌱𐌹𐌳𐌾𐌰𐌼 𐌸𐌿𐌺, 𐌼𐌴𐍂𐌴𐌹 𐌸𐌰𐍄𐌰 𐌳𐌿 [[Special:ListUsers/sysop\n|𐍂𐌴𐌹𐌺]] 𐌲𐌹𐍆𐌿𐌷 𐌲𐌰𐍅𐌹𐍃𐍃.",
+       "cannotdelete-title": "𐌽𐌹 𐌼𐌰𐌲 𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌾𐌰𐌽 𐌻𐌰𐌿𐌱𐌰 \"$1\"",
        "badtitle": "𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐍄𐌰 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹",
        "badtitletext": "𐍆𐍂𐌰𐌹𐌷𐌰𐌽𐍃 𐌻𐌰𐌿𐍆𐍃 𐍅𐌰𐍃 𐌿𐌽𐌲𐌰𐌼𐌰𐌲𐌰𐌽𐌳𐍃, 𐌻𐌰𐌿𐍃, 𐌰𐌹𐌸𐌸𐌰𐌿 𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐌱𐌰 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐍃 𐌼𐌹𐌸𐍂𐌰𐌶𐌳𐌰 𐌸𐌰𐌿 𐌼𐌹𐌸-𐍅𐌹𐌺𐌹 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹. 𐌼𐌰𐌲𐌹 𐌷𐌰𐌱𐌰𐌽 𐌰𐌹𐌽𐌰 𐌸𐌰𐌿 𐌼𐌰𐌽𐌰𐌲𐌹𐌶𐍉𐍃 𐌱𐍉𐌺𐍉𐍃 𐌱𐍂𐌿𐌺𐌹𐌳𐍉𐍃 𐌹𐌽 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌾𐌰𐌼.",
        "viewsource": "𐍃𐌰𐌹𐍈 𐌱𐍂𐌿𐌽𐌽𐌰𐌽",
+       "mycustomjsprotected": "𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃 𐌰𐌽𐌳𐌻𐌴𐍄 𐌳𐌿 𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 JavaScript 𐌻𐌰𐌿𐍆.",
        "yourname": "𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰𐌼𐍉:",
        "userlogin-yourname": "𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰𐌼𐍉",
        "userlogin-yourname-ph": "𐌼𐌴𐌻𐌴𐌹 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰𐌼𐍉 𐌸𐌴𐌹𐌽",
        "createacct-yourpasswordagain": "𐌲𐌰𐍃𐌹𐌲𐌻𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
        "createacct-yourpasswordagain-ph": "𐌼𐌴𐌻𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳 𐌰𐍆𐍄𐍂𐌰",
        "userlogin-remembermypassword": "𐌲𐌰𐍆𐌰𐍃𐍄 𐌼𐌹𐌺 {{GENDER:𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰𐌽𐌰|𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽𐌰}}",
+       "cannotloginnow-title": "𐌽𐌿 𐌽𐌹 𐌼𐌰𐌲𐍄 𐌰𐍄𐌲𐌰𐌲𐌲𐌰𐌽",
        "login": "𐌰𐍄𐌲𐌰𐌲𐌲",
        "nav-login-createaccount": "𐌰𐍄𐌲𐌰𐌲𐌲 / 𐍃𐌺𐌰𐍀𐌴𐌹 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽",
        "userlogin": "𐌰𐍄𐌲𐌰𐌲𐌲 / 𐍃𐌺𐌰𐍀𐌴𐌹 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽",
        "pt-login": "𐌰𐍄𐌲𐌰𐌲𐌲",
        "pt-login-button": "𐌰𐍄𐌲𐌰𐌲𐌲",
        "pt-createaccount": "𐍃𐌺𐌰𐍀𐌴𐌹 𐌺𐌰𐍅𐍄𐍃𐌾𐍉𐌽",
+       "pt-userlogout": "𐌰𐍆𐌻𐌴𐌹𐌸",
        "passwordreset": "𐌰𐍆𐍄𐍂𐌰 𐍃𐌰𐍄𐌴𐌹 𐌲𐌰𐌼𐍉𐍄𐌰𐍅𐌰𐌿𐍂𐌳",
        "bold_sample": "𐍃𐍅𐌹𐌽𐌸𐍉𐍃 𐌱𐍉𐌺𐍉𐍃",
        "bold_tip": "𐍃𐍅𐌹𐌽𐌸𐍉𐍃 𐌱𐍉𐌺𐍉𐍃",
        "showpreview": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐍆𐌰𐌿𐍂𐌰𐍃𐌹𐌿𐌽",
        "showdiff": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌹𐌽𐌼𐌰𐌹𐌳𐌹𐌽𐌹𐌽𐍃",
        "loginreqlink": "𐌰𐍄𐌲𐌰𐌲𐌲",
-       "newarticle": "(ð\90\8c½ð\90\8c¹ð\90\8c¿ð\90\8c¾ð\90\8c°ð\90\8d\84ð\90\8c°)",
+       "newarticle": "(ð\90\8c½ð\90\8c¹ð\90\8d\85ð\90\8c¹)",
        "newarticletext": "𐌻𐌰𐌹𐍃𐍄𐌹𐌳𐌴𐍃 𐌲𐌰𐍅𐌹𐍃 𐌳𐌿 𐌻𐌰𐌿𐌱𐌰 𐍃𐌰𐌴𐌹 𐌽𐌹𐍃𐍄. 𐌳𐌿 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆, 𐌰𐌽 𐌰𐍃𐍄𐍉𐌳𐌴𐌹 𐌼𐌴𐌻𐌾𐌰𐌽 𐌹𐌽 𐌰𐍂𐌺𐌰𐌹 𐌿𐍆 (𐍃𐌰𐌹𐍈 [$1 𐌷𐌹𐌻𐍀𐌰𐌻𐌰𐌿𐍆] 𐌼𐌰𐌽𐌰𐌲𐌹𐌶𐌹𐌽 𐌺𐌿𐌽𐌸𐌾𐌰). 𐌾𐌰𐌱𐌰𐌹 𐌹𐍃 𐌷𐌴𐍂 𐌹𐌽 𐌰𐌹𐍂𐌶𐌴𐌹𐌽𐍃, 𐌲𐌰𐌲𐌲 𐌳𐌿 <𐍃𐍄𐍂𐍉𐌽𐌲>𐌹𐌱𐌿𐌺𐌰𐌷𐌰𐌿𐌱𐌹𐌳𐌹𐌻𐍉𐌽.",
        "noarticletext": "𐌽𐌿 𐌽𐌹 𐍃𐌹𐌽𐌳 𐌱𐍉𐌺𐍉𐍃 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰.\n𐌼𐌰𐌲𐍄 [[Special:Search/{{PAGENAME}}|𐍃𐍉𐌺𐌾𐌰𐌽 𐌸𐌰𐍄𐌰 𐌻𐌰𐌿𐌱𐌰-𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹]] 𐌹𐌽 𐌰𐌽𐌸𐌰𐍂𐌰𐌹𐌼 𐌻𐌰𐌿𐌱𐌰𐌼,  <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 𐍃𐍉𐌺𐌾𐌰𐌽 𐌲𐌰𐌷𐌰𐌷𐌾𐍉 𐌲𐌰𐍆𐌰𐍃𐍄𐍉𐍃], 𐌰𐌹𐌸𐌸𐌰𐌿 [{{fullurl:{{FULLPAGENAME}}|action=edit}} 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆.]</ span>",
        "noarticletext-nopermission": "𐌽𐌿 𐌽𐌹 𐍃𐌹𐌽𐌳 𐌱𐍉𐌺𐍉𐍃 𐌹𐌽 𐌸𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰.\n𐌼𐌰𐌲𐍄 [[Special:Search/{{PAGENAME}}|𐍃𐍉𐌺𐌾𐌰𐌽 𐌸𐌰𐍄𐌰 𐌻𐌰𐌿𐌱𐌰-𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹]] 𐌹𐌽 𐌰𐌽𐌸𐌰𐍂𐌰𐌹𐌼 𐌻𐌰𐌿𐌱𐌰𐌼, 𐌸𐌰𐌿 <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 𐍃𐍉𐌺𐌾𐌰𐌽 𐌲𐌰𐌷𐌰𐌷𐌾𐍉 𐌲𐌰𐍆𐌰𐍃𐍄𐍉𐍃]</span>, 𐌹𐌸 𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃 𐌰𐌽𐌳𐌻𐌴𐍄 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆.",
        "updated": "(𐌰𐌽𐌰𐌽𐌹𐍅𐌹𐌸)",
        "previewnote": "<strong>𐌲𐌰𐌼𐌹𐌽𐌸𐌴𐌹 𐌸𐌰𐍄𐌴𐌹 𐌸𐌰𐍄𐌰 𐌹𐍃𐍄 𐌸𐌰𐍄𐌰𐌹𐌽𐌴𐌹 𐍆𐌰𐌿𐍂𐌰𐍃𐌹𐌿𐌽𐍃.</strong>\n𐌸𐌴𐌹𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐌽𐌰𐌿𐌷 𐌽𐌹 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽𐍉𐍃 𐍃𐌹𐌽𐌳!",
-       "editing": "{{GENDER:𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐍃|𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐌴𐌹}} $1",
-       "creating": "{{GENDER:𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐍃|𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐌴𐌹}} $1",
+       "editing": "{{GENDER:𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐍃|𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐌴𐌹|𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐍃}} $1",
+       "creating": "{{GENDER:𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐍃|𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐌴𐌹|𐍃𐌺𐌰𐍀𐌾𐌰𐌽𐌳𐍃\n}} $1",
        "editingsection": "{{GENDER:𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐍃|𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐌴𐌹}} $1 (𐌳𐌰𐌹𐌻)",
        "editingcomment": "{{GENDER:𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐍃|𐌹𐌽𐌼𐌰𐌹𐌳𐌾𐌰𐌽𐌳𐌴𐌹}} $1 (𐌽𐌹𐌿𐌾𐌰 𐌳𐌰𐌹𐌻)",
        "yourdiff": "𐌲𐌰𐍃𐌺𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
        "hiddencategories": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌹𐍃𐍄 𐌲𐌰𐌳𐌰𐌹𐌻𐌰 {{PLURAL:$1|1 𐌰𐌽𐌰𐌻𐌰𐌿𐌲𐌽𐌹𐍃 𐌺𐌿𐌽𐌾𐌹𐍃|$1 𐌰𐌽𐌰𐌻𐌰𐌿𐌲𐌽𐌰𐌹𐌶𐌴 𐌺𐌿𐌽𐌾𐌴}}:‎",
        "permissionserrorstext-withaction": "𐌽𐌹 𐌷𐌰𐌱𐌰𐌹𐍃 𐌰𐌽𐌳𐌻𐌴𐍄 𐌳𐌿 $2, 𐌹𐌽 {{PLURAL:$1|𐌹𐍆𐍄𐌿𐌼𐌰𐌹𐌶𐍉𐍃 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃|𐌹𐍆𐍄𐌿𐌼𐌰𐌹𐌶𐍉 𐍅𐌰𐌹𐌷𐍄𐌴}}:",
        "moveddeleted-notice": "𐍃𐌰 𐌻𐌰𐌿𐍆𐍃 𐌿𐍃𐌽𐌿𐌼𐌰𐌽𐍃 𐌹𐍃𐍄. 𐌿𐍃𐌽𐌿𐌼𐍄𐍃 𐌾𐌰𐌷 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌹𐌽𐍃 𐌼𐌹𐌸𐍃𐌰𐍄𐌴𐌹𐌽𐌰𐌹𐍃 𐌿𐍆 𐍃𐌹𐌽𐌳 𐌿𐍃𐍄𐌰𐌹𐌺𐌽𐌴𐌹𐌽𐌰𐌹.",
+       "postedit-confirmation-created": "𐌻𐌰𐌿𐍆𐍃 𐌲𐌰𐍃𐌺𐌰𐍀𐌰𐌽𐍃 𐌹𐍃𐍄.",
+       "edit-already-exists": "𐌽𐌹 𐍅𐌰𐍃 𐌼𐌰𐌷𐍄𐍃 𐍃𐌺𐌰𐍀𐌾𐌰𐌽 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆. \n𐌾𐌿 𐌹𐍃𐍄.",
        "post-expand-template-inclusion-warning": "'''𐌷𐍅𐍉𐍄𐌾𐌰𐌽𐌳𐍃:''' 𐍆𐌰𐌿𐍂𐌰𐌼𐌴𐌻𐌴𐌹𐌽𐍃 𐍃𐌹𐌽𐌳 𐌿𐍆𐌰𐍂𐌼𐌹𐌺𐌹𐌻𐍃. 𐍃𐌿𐌼𐍃 𐍆𐌰𐌿𐍂𐌴𐌼𐌴𐌻𐌴𐌹𐌽𐍉𐍃 𐌽𐌹 𐌼𐌰𐌲 𐍅𐌹𐍃𐌰𐌽 𐌸𐌰𐍂",
        "post-expand-template-inclusion-category": "𐍃𐌴𐌹𐌳𐍉𐌽𐍃 𐌸𐌰𐍂 𐍆𐌰𐌿𐍂𐌰𐌼𐌴𐌻𐌴𐌹𐌽𐍃 𐍃𐌹𐌽𐌳 𐌿𐍆𐌰𐍂𐌼𐌹𐌺𐌹𐌻𐍃",
        "viewpagelogs": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌹𐌽𐌹𐌽𐍃 𐌸𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰",
        "history-feed-item-nocomment": "$1 𐌰𐍄 $2",
        "rev-delundel": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹 𐌰𐌽𐌰𐍃𐌹𐌿𐌽",
        "revdel-restore": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹 𐌰𐌽𐌰𐍃𐌹𐌿𐌽",
-       "revertmerge": "ð\90\8c¿ð\90\8c½ð\90\8c²ð\90\8c°ð\90\8d\84ð\90\8c¹ð\90\8c»ð\90\8d\89ð\90\8d\83",
+       "revertmerge": "ð\90\8c¿ð\90\8c½ð\90\8c²ð\90\8c°ð\90\8c±ð\90\8c»ð\90\8c°ð\90\8c½ð\90\8c³",
        "history-title": "𐌰𐍆𐍄𐍂𐌰𐍃𐌹𐌿𐌽𐌹𐍃𐍀𐌹𐌻𐌻 𐌻𐌰𐌿𐌱𐌹𐍃 \"$1\"",
        "difference-title": "𐌲𐌰𐍃𐌺𐌰𐌹𐌳𐌴𐌹𐌽𐍃 𐌼𐌹𐌸 𐌰𐍆𐍄𐍂𐌰𐍃𐌹𐌿𐌽𐍉𐌼 𐌻𐌰𐌿𐌱𐌹𐍃 \"$1\"",
+       "difference-multipage": "(𐌰𐌽𐌸𐌰𐍂𐌻𐌴𐌹𐌺𐌾𐌰 𐌼𐌹𐌸 𐌻𐌰𐌿𐌱𐌰𐌼)",
        "lineno": "𐍃𐍄𐍂𐌹𐌺𐍃 $1:",
        "editundo": "𐍃𐌺𐌰𐍀𐌴𐌹 𐌰𐍆𐍄𐍂𐌰",
        "diff-multi-sameuser": "({{PLURAL:$1|𐌰𐌹𐌽𐌰 𐌼𐌹𐌳𐌿𐌼𐌰𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍃|$1 𐌼𐌹𐌳𐌿𐌼𐌰 𐌲𐌰𐌱𐍉𐍄𐌴𐌹𐌽𐍉𐍃}} 𐍆𐍂𐌰𐌼 𐍃𐌰𐌼𐌹𐌽 𐌱𐍂𐌿𐌺𐌾𐌹𐌽 𐌽𐌹 𐌰𐍄𐌰𐌿𐌲𐌹𐌳𐌰/𐌰𐍄𐌰𐌿𐌲𐌹𐌳𐍉𐍃)",
        "search-showingresults": "{{ZPLURAL:$4|𐍄𐌰𐌿𐌹 <strong>$1 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃 <strong>$3|𐍄𐍉𐌾𐌰 <strong>$1 - $2 𐍅𐌰𐌹𐌷𐍄𐌰𐌹𐍃 <strong>$3}}",
        "search-nonefound": "𐌽𐌹 𐍄𐌰𐌿𐌹 𐍅𐌰𐍃 𐍃𐌰𐌼𐌰𐌽𐌰 𐍃𐍅𐌰 𐍃𐍉𐌺𐌴𐌹𐌽.",
        "powersearch-legend": "𐍃𐍉𐌺𐌴𐌹",
-       "preferences": "ð\90\8c¼ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c±ð\90\8d\82ð\90\8c¿ð\90\8cºð\90\8c¾ð\90\8c°ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8c´ð\90\8c¹𐍃",
-       "mypreferences": "ð\90\8c²ð\90\8c°ð\90\8c»ð\90\8c´ð\90\8c¹ð\90\8cºð\90\8c°ð\90\8c½ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½𐍃 𐍅𐌰𐌹𐌷𐍄𐍃",
+       "preferences": "ð\90\8c¼ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8d\86ð\90\8d\82ð\90\8c¹ð\90\8c¾ð\90\8d\89ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8c·ð\90\8d\84𐍃",
+       "mypreferences": "ð\90\8c¼ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8d\86ð\90\8d\82ð\90\8c¹ð\90\8c¾ð\90\8d\89ð\90\8c½ð\90\8d\89𐍃 𐍅𐌰𐌹𐌷𐍄𐍃",
        "prefs-skin": "𐍆𐌹𐌻𐌻",
        "skin-preview": "𐍆𐌰𐌿𐍂𐌰𐍃𐌰𐌹𐍈",
        "saveprefs": "𐌲𐌰𐍆𐌰𐍃𐍄",
        "minoreditletter": "l",
        "newpageletter": "N",
        "boteditletter": "b",
-       "recentchangeslinked": "ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹𐌳𐌰𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
-       "recentchangeslinked-feed": "Máideinlieks",
-       "recentchangeslinked-toolbox": "ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹𐌳𐌰𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
-       "recentchangeslinked-title": "ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹𐌳𐌰𐌽𐍉𐍃 𐌼𐌹𐌸 \"$1\"",
-       "recentchangeslinked-summary": "𐍃𐍉 𐌹𐍃𐍄 𐌻𐌴𐌹𐍃𐍄𐌰 𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐌴 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍃 𐍃𐌺𐍉𐍀 𐌰𐌽𐌰 𐍃𐌴𐌹𐌳𐍉𐌽𐍃 𐌻𐌴𐌹𐌽𐌺𐍉𐌽𐌳 𐌿𐍃 𐌿𐍃𐍃𐌹𐌽𐌳𐌰𐌹 𐍃𐌴𐌹𐌳𐍉𐌽 (𐌰𐌹𐌸𐌸𐌰𐌿 𐌻𐌹𐌸𐌰𐌿𐍃 𐌿𐍃𐍃𐌹𐌽𐌳𐌰𐌹𐌶𐍉𐍃 𐌷𐌰𐌽𐍃𐍉𐍃). 𐍃𐌴𐌹𐌳𐍉𐌽𐍃 [[Special:Watchlist|𐍅𐌹𐍄𐌰𐌽𐌳𐌻𐌴𐌹𐍃𐍄𐍉𐍃 𐌸𐌴𐌹𐌽𐍉𐍃]] 𐍃𐌹𐌽𐌳 '''𐌳𐌹𐌲𐍂𐍃𐍄𐌰𐍆𐍃'''.",
+       "recentchangeslinked": "ð\90\8c²ð\90\8c°ð\90\8c±ð\90\8c¿ð\90\8c½𐌳𐌰𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
+       "recentchangeslinked-feed": "𐌲𐌰𐌱𐌿𐌽𐌳𐌰𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
+       "recentchangeslinked-toolbox": "ð\90\8c²ð\90\8c°ð\90\8c±ð\90\8c¿ð\90\8c½𐌳𐌰𐌽𐍉𐍃 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃",
+       "recentchangeslinked-title": "ð\90\8c¹ð\90\8c½ð\90\8c¼ð\90\8c°ð\90\8c¹ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8c½ð\90\8d\89ð\90\8d\83 ð\90\8c²ð\90\8c°ð\90\8c±ð\90\8c¿ð\90\8c½𐌳𐌰𐌽𐍉𐍃 𐌼𐌹𐌸 \"$1\"",
+       "recentchangeslinked-summary": "A𐌸𐌰𐍄𐌰 𐌹𐍃𐍄 𐍅𐌹𐌺𐍉 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉 𐌽𐌹𐌿𐌾𐌰𐌱𐌰 𐌲𐌰𐍄𐌰𐍅𐌹𐌳𐍉𐍃 𐌻𐌰𐌿𐌱𐌰𐌼 𐌲𐌰𐌱𐌿𐌽𐌳𐌰𐌹 𐍃𐌿𐌼𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰 (𐌸𐌰𐌿 𐌲𐌰𐌳𐌰𐌹𐌻𐌰𐌼 𐍃𐌿𐌼𐌹𐍃 𐌺𐌿𐌽𐌾𐌹𐍃). <strong>𐌻𐌰𐌿𐌱𐍉𐍃 𐌰𐌽𐌰 [[Special:Watchist|your]] 𐍃𐌹𐌽𐌳 </strong>𐍃𐍅𐌹𐌽𐌸𐌰𐌹.",
        "recentchangeslinked-page": "𐌻𐌰𐌿𐌱𐌰𐌽𐌰𐌼𐍉:",
        "recentchangeslinked-to": "𐌰𐍄𐌰𐌿𐌲𐌴𐌹 𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹𐌽𐍉𐍃 𐌻𐌰𐌿𐌱𐌴 𐌸𐌰𐌹𐌴𐌹 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐌰𐌹 𐌳𐌿 𐌲𐌹𐌱𐌰𐌽𐌰𐌼𐌼𐌰 𐌻𐌰𐌿𐌱𐌰.",
        "upload": "𐌿𐍃𐌷𐌻𐌰𐌸𐌰𐌽 𐍆𐌴𐌹𐌻𐌰𐌽𐍃",
        "brokenredirects-edit": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹",
        "brokenredirects-delete": "𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹",
        "nbytes": "$1 {{PLURAL:$1|𐌱𐌹𐍄|𐌱𐌰𐍄𐌰}}",
-       "ncategories": "$1 {{PLURAL:$1|ð\90\8cºð\90\8c¿ð\90\8c½ð\90\8c¾ð\90\8c°|ð\90\8cºð\90\8c¿ð\90\8c½ð\90\8c¾ð\90\8d\89ð\90\8d\83}}",
+       "ncategories": "$1 {{PLURAL:$1|ð\90\8cºð\90\8c¿ð\90\8c½ð\90\8c¹|ð\90\8cºð\90\8c¿ð\90\8c½ð\90\8c¾ð\90\8c°}}",
        "nlinks": "$1 {{PLURAL:$1|𐌲𐌰𐍅𐌹𐍃𐍃|𐌲𐌰𐍅𐌹𐍃𐍃𐌴𐌹𐍃}}",
        "nmembers": "$1 {{PLURAL:$1|𐌲𐌰𐌳𐌰𐌹𐌻𐌰|𐌲𐌰𐌳𐌰𐌹𐌻𐌰𐌽𐍃}}",
        "wantedpages": "𐌲𐌰𐌹𐍂𐌽𐌹𐌳𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
        "longpages": "𐌻𐌰𐌲𐌲𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
        "listusers": "𐍂𐌴𐌲𐌹𐍃𐍄𐍂𐌴𐍂𐌰𐌳𐌴 𐌱𐍂𐌿𐌺𐌾𐌰𐌽𐌳𐍃",
        "newpages": "𐌽𐌹𐌿𐌾𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
-       "move": "ð\90\8c½ð\90\8c°ð\90\8c¼ð\90\8c¾ð\90\8c°ð\90\8c½ ð\90\8c°ð\90\8d\86ð\90\8d\84ð\90\8d\82ð\90\8c°",
+       "move": "ð\90\8c¼ð\90\8c¹ð\90\8c¸ð\90\8d\83ð\90\8c°ð\90\8d\84ð\90\8c´ð\90\8c¹",
        "movethispage": "𐌼𐌹𐌸𐍃𐌰𐍄𐌴𐌹 𐌸𐌰𐌽𐌰 𐌻𐌰𐌿𐍆",
        "booksources": "𐌱𐍉𐌺𐌰𐌱𐍂𐌿𐌽𐌽𐌰𐌽𐍃",
        "booksources-search-legend": "𐍃𐍉𐌺𐌴𐌹 𐌱𐍉𐌺𐌰𐌱𐍂𐌿𐌽𐌽𐌰𐌽𐍃",
        "log": "𐌻𐍉𐌲𐌱𐍉𐌺𐍉𐍃",
        "all-logs-page": "𐌰𐌻𐌻𐌰 𐌻𐍉𐌲𐍉𐍃",
        "allpages": "𐌰𐌻𐌻𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
-       "nextpage": "ð\90\8c¹ð\90\8d\86ð\90\8d\84ð\90\8c¿ð\90\8c¼ð\90\8c° ð\90\8d\83ð\90\8c´ð\90\8c¹ð\90\8c³ð\90\8d\89 ($1)",
+       "nextpage": "ð\90\8c¹ð\90\8d\86ð\90\8d\84ð\90\8c¿ð\90\8c¼ð\90\8c° ð\90\8c»ð\90\8c°ð\90\8c¿ð\90\8d\86ð\90\8d\83 ($1)",
        "prevpage": "𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐍃 𐌻𐌰𐌿𐍆𐍃 ($1)",
        "allarticles": "𐌰𐌻𐌻𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
        "allpagessubmit": "𐌲𐌰𐌲𐌲",
        "unwatch": "𐌽𐌹𐍅𐌰𐍂𐌰𐌽",
        "watchlist-details": "{{PLURAL:$1|$1 𐌻𐌰𐌿𐍆𐍃|$1 𐌻𐌰𐌿𐌱𐍉𐍃}} 𐌰𐌽𐌰 𐌸𐌴𐌹𐌽𐌰𐌹 𐍅𐌹𐍄𐌰𐍅𐌹𐌺𐍉𐌽, 𐌽𐌹 𐍃𐌿𐌽𐌳𐍂𐍉 𐍂𐌰𐌷𐌽𐌾𐌰𐌽𐌳𐌰 𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌾𐌰𐌻𐌰𐌿𐌱𐍉𐍃.",
        "watching": "Wita...",
-       "unwatching": "Niwita...",
+       "unwatching": "𐌿𐌽𐍅𐌹𐍄𐌰𐌽𐌳𐍉...",
        "created": "𐌲𐌰𐍃𐌺𐌰𐍀𐌾𐌰𐌽",
        "deletepage": "𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹 𐌻𐌰𐌿𐌱𐌰",
        "delete-legend": "𐍆𐍂𐌰𐌵𐌹𐍃𐍄𐌴𐌹",
        "prot_1movedto2": "[[$1]] 𐌼𐌹𐌸𐍃𐌰𐍄𐌹𐌸 𐌳𐌿 [[$2]]",
        "protect-level-sysop": "𐌰𐌽𐌳𐌻𐌴𐍄𐌹𐌸 𐌸𐌰𐍄𐌰𐌹𐌽𐌴𐌹 𐍂𐌴𐌹𐌺𐍃",
        "protect-expiring": "𐌿𐍃𐍄𐌹𐌿𐌷𐌹𐌸 $1 (UTC)",
-       "restriction-type": "Freihals:",
+       "restriction-type": "𐌰𐌽𐌳𐌻𐌴𐍄",
        "restriction-edit": "𐌹𐌽𐌼𐌰𐌹𐌳𐌴𐌹",
-       "restriction-move": "ð\90\8d\83ð\90\8cºð\90\8c¹ð\90\8c¿ð\90\8c±ð\90\8c°ð\90\8c½",
+       "restriction-move": "ð\90\8c¼ð\90\8c¹ð\90\8c¸ð\90\8d\83ð\90\8c°ð\90\8d\84ð\90\8c´ð\90\8c¹",
        "undeletebtn": "𐌰𐍆𐍄𐍂𐌰 𐌲𐌰𐌱𐍉𐍄𐌾𐌰𐌽",
        "undeletelink": "𐍃𐌰𐌹𐍈𐌰𐌽/𐌰𐍆𐍄𐍂𐌰𐌲𐌰𐍃𐌰𐍄𐌾𐌰𐌽",
        "undeleteviewlink": "𐍃𐌰𐌹𐍈𐌹𐍃",
        "blocklogentry": "𐌰𐍆𐌳𐍂𐌰𐌿𐍃𐌹𐌸 [[$1]] 𐍆𐌰𐌿𐍂 $2 $3",
        "newtitle": "𐌽𐌹𐌿𐌾𐌹 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹:",
        "move-watch": "𐌰𐍄𐍅𐌹𐍄 𐌱𐍂𐌿𐌽𐌽𐌰𐌻𐌰𐌿𐌱𐌰 𐌾𐌰𐌷 𐌼𐌿𐌽𐌳𐍂𐌴𐌹𐌻𐌰𐌿𐌱𐌰",
-       "movepagebtn": "ð\90\8d\83ð\90\8cºð\90\8c¹ð\90\8c¿ð\90\8c±ð\90\8c° ð\90\8d\83ð\90\8c´ð\90\8c¹ð\90\8c³ð\90\8d\89",
+       "movepagebtn": "ð\90\8c¼ð\90\8c¹ð\90\8c¸ð\90\8d\83ð\90\8c°ð\90\8d\84ð\90\8c´ð\90\8c¹ ð\90\8c»ð\90\8c°ð\90\8c¿ð\90\8d\86",
        "movelogpage": "𐌼𐌹𐌸𐍃𐌰𐍄𐌴𐌹 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌹𐌽",
        "movereason": "𐍆𐌰𐌹𐍂𐌹𐌽𐌰:",
        "revertmove": "𐍂𐌰𐌹𐌳𐌾𐌰𐌽",
        "export": "𐌿𐍄𐌱𐌰𐌹𐍂 𐌻𐌰𐌿𐌱𐌰𐌽𐍃",
+       "allmessages-filter-translate": "𐌲𐌰𐍃𐌺𐌴𐌹𐍂𐌴𐌹",
        "thumbnail-more": "\n𐌼𐌹𐌺𐌹𐌻𐌴𐌹",
        "tooltip-pt-userpage": "{{GENDER:|Your user}} 𐌻𐌰𐌿𐍆𐍃",
        "tooltip-pt-mytalk": "{{GENDER:|𐌸𐌴𐌹𐌽𐍃}} 𐌻𐌰𐌿𐍆𐍃 𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌾𐌹𐍃",
        "table_pager_limit_submit": "Affgaggan",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|𐌲𐌰𐍅𐌰𐌿𐍂𐌳𐌾𐌰]])",
        "version-other": "Anþar",
+       "version-poweredby-translators": "translatewiki.net 𐌲𐌰𐍃𐌺𐌴𐌹𐍂𐌾𐌰𐌽𐍃",
        "specialpages": "𐌿𐍃𐍃𐌹𐌽𐌳𐌰𐌹 𐌻𐌰𐌿𐌱𐍉𐍃",
        "tag-filter": "[[Special:Tags|𐍄𐌰𐌹𐌺𐌽𐍉𐍃]] 𐍆𐌹𐌻𐌷𐌰",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|𐍃𐍉𐌺𐌴𐌹𐌽𐌹𐍅𐌰𐌿𐍂𐌳|𐍃𐍉𐌺𐌴𐌹𐌽𐌹𐍅𐌰𐌿𐍂𐌳𐌰}}]]: $2)",
        "tags-source-header": "𐌱𐍂𐌿𐌽𐌽𐌰",
-       "tags-actions-header": "ð\90\8c³ð\90\8c´ð\90\8c³ð\90\8c´ð\90\8c¹ð\90\8d\83",
+       "tags-actions-header": "ð\90\8d\84ð\90\8d\89ð\90\8c¾ð\90\8c°",
        "tags-source-none": "𐌽𐌹 𐌾𐌿 𐌱𐍂𐌿𐌺𐌾𐌰𐌳𐌰",
        "tags-delete": "𐌿𐍃𐌽𐌹𐌼",
        "tags-activate": "𐌲𐌰𐌵𐌹𐌿𐌴𐌹",
index 180325b..297a954 100644 (file)
        "searchprofile-advanced-tooltip": "חיפוש במרחבי שם מותאמים אישית",
        "search-result-size": "$1 ({{PLURAL:$2|מילה אחת|$2 מילים}})",
        "search-result-category-size": "{{PLURAL:$1|פריט אחד|$1 פריטים}} ({{PLURAL:$2|קטגוריית משנה אחת|$2 קטגוריות משנה}}, {{PLURAL:$3|קובץ אחד|$3 קבצים}})",
-       "search-redirect": "(הפניה $1)",
+       "search-redirect": "(הפניה מהדף $1)",
        "search-section": "(פסקה $1)",
        "search-category": "(קטגוריה $1)",
        "search-file-match": "(התאמה בתוכן הקובץ)",
        "feedback-external-bug-report-button": "דיווח על משימה טכנית",
        "feedback-dialog-title": "שליחת המשוב",
        "feedback-dialog-intro": "באפשרותך להשתמש בטופס הפשוט שלהלן כדי לשלוח משוב. ההערה שלך תתווסף לדף \"$1\", יחד עם שם המשתמש שלך.",
-       "feedback-error-title": "שגיאה",
        "feedback-error1": "שגיאה: תוצאה לא מזוהה מה־API",
        "feedback-error2": "שגיאה: העריכה נכשלה",
        "feedback-error3": "שגיאה: אין תשובה מה־API",
index 716b426..3335a30 100644 (file)
        "export-download": "Ponudi opciju snimanja u datoteku",
        "export-templates": "Uključi predloške",
        "export-pagelinks": "Uključi povezane stranice do dubine od:",
-       "allmessages": "Sve sistemske poruke",
+       "allmessages": "Sve poruke sustava",
        "allmessagesname": "Ime",
        "allmessagesdefault": "Prvotni tekst",
        "allmessagescurrent": "Trenutačni tekst",
        "feedback-external-bug-report-button": "Arhiviraj tehnički zadatak",
        "feedback-dialog-title": "Slanje povratnih informacija",
        "feedback-dialog-intro": "Da biste poslali povratnu informaciju, rabite jednostavan obrazac. Vaš će komentar biti dodan na stranici \"$1\" s Vašim suradničkim imenom.",
-       "feedback-error-title": "Pogrješka",
        "feedback-error1": "Pogreška: neprepoznati rezultat API funkcije",
        "feedback-error2": "Pogreška: uređivanje nije uspjelo",
        "feedback-error3": "Pogreška: nema odgovora API funkcije",
index 3fc5dfe..f9f9e50 100644 (file)
@@ -43,7 +43,7 @@
        "tog-enotifminoredits": "Notificar me etiam de modificationes minor de paginas e files",
        "tog-enotifrevealaddr": "Revelar mi adresse de e-mail in messages de notification",
        "tog-shownumberswatching": "Monstrar le numero de usatores que observa le pagina",
-       "tog-oldsig": "Signatura existente:",
+       "tog-oldsig": "Tu signatura existente:",
        "tog-fancysig": "Tractar signatura como wikitexto (sin ligamine automatic)",
        "tog-uselivepreview": "Usar previsualisation dynamic",
        "tog-forceeditsummary": "Avisar me si io non entra un summario de modification",
@@ -60,7 +60,7 @@
        "tog-showhiddencats": "Monstrar categorias celate",
        "tog-norollbackdiff": "Non monstrar differentias post exequer un revocation",
        "tog-useeditwarning": "Advertir me quando io quita un pagina de modification sin publicar le cambiamentos",
-       "tog-prefershttps": "Sempre usar un connexion secur in session aperte",
+       "tog-prefershttps": "Sempre usar un connexion secur durante session aperte",
        "underline-always": "Sempre",
        "underline-never": "Nunquam",
        "underline-default": "Como definite per tu navigator o apparentia",
        "newwindow": "(se aperi in un nove fenestra)",
        "cancel": "Cancellar",
        "moredotdotdot": "Plus...",
-       "morenotlisted": "Iste lista non es complete.",
+       "morenotlisted": "Iste lista pote esser incomplete.",
        "mypage": "Pagina",
        "mytalk": "Discussion",
        "anontalk": "Discussion",
        "eauthentsent": "Un message de confirmation ha essite inviate al adresse de e-mail specificate.\nPro permitter que le systema invia altere messages a iste adresse, tu debe sequer le instructiones in iste message pro confirmar que le adresse es realmente tue.",
        "throttled-mailpassword": "Un message pro le reinitialisation del contrasigno ha jam essite inviate intra le ultime {{PLURAL:$1|hora|$1 horas}}.\nPro prevenir le abuso, solmente un message pro le reinitialisation del contrasigno essera inviate per {{PLURAL:$1|hora|$1 horas}}.",
        "mailerror": "Error de inviar e-mail: $1",
-       "acct_creation_throttle_hit": "Le visitatores de iste wiki usante tu adresse IP ha create {{PLURAL:$1|1 conto|$1 contos}} durante le ultime die, e isto es le maximo permittite in iste periodo de tempore.\nA causa de isto, le visitatores usante iste adresse IP non pote crear nove contos al momento.",
+       "acct_creation_throttle_hit": "Le visitatores de iste wiki usante tu adresse IP ha create {{PLURAL:$1|1 conto|$1 contos}} durante le ultime $2, e isto es le maximo permittite in iste periodo de tempore.\nA causa de isto, le visitatores usante iste adresse IP non pote crear nove contos al momento.",
        "emailauthenticated": "Tu adresse de e-mail ha essite confirmate le $2 a $3.",
        "emailnotauthenticated": "Tu non ha ancora confirmate tu adresse de e-mail.\nNulle e-mail essera inviate pro le sequente functiones.",
        "noemailprefs": "Es necessari specificar un adresse de e-mail in tu preferentias pro poter executar iste functiones.",
        "botpasswords-label-resetpassword": "Reinitialisar le contrasigno",
        "botpasswords-label-grants": "Concessiones applicabile:",
        "botpasswords-help-grants": "Cata concession da accesso al derectos de usator listate que un conto de usator jam ha. Vide le [[Special:ListGrants|tabula de concessiones]] pro plus information.",
-       "botpasswords-label-restrictions": "Restrictiones de uso:",
        "botpasswords-label-grants-column": "Concedite",
        "botpasswords-bad-appid": "Le nomine del robot \"$1\" non es valide.",
        "botpasswords-insert-failed": "Le addition del nomine de robot \"$1\" ha fallite. Esque illo ha jam essite addite?",
        "passwordreset-emailelement": "Nomine de usator: \n$1\n\nContrasigno temporari: \n$2",
        "passwordreset-emailsentemail": "Si iste adresse es associate a tu conto, alora un e-mail pro reinitialisar le contrasigno essera inviate.",
        "passwordreset-emailsentusername": "Si il ha un adresse de e-mail associate a iste conto, alora un e-mail pro reinitialisar le contrasigno essera inviate.",
-       "passwordreset-emailsent-capture2": "Le {{PLURAL:$1|message|messages}} de e-mail pro reinitialisation de contrasigno ha essite inviate. Le {{PLURAL:$1|nomine de usator e contrasigno|lista de nomines de usator e contrasignos}} appare hic infra.",
-       "passwordreset-emailerror-capture2": "Le invio de e-mail al {{GENDER:$2|usator}} ha fallite: $1 Le {{PLURAL:$3|nomine de usator e contrasigno|lista de nomines de usator e contrasignos}} appare hic infra.",
+       "passwordreset-emailsent-capture2": "Le {{PLURAL:$1|message|messages}} de e-mail pro reinitialisation de contrasigno ha essite inviate. Le {{PLURAL:$1|nomine de usator e contrasigno|lista de nomines de usator e contrasignos}} es monstrate hic.",
+       "passwordreset-emailerror-capture2": "Le invio de e-mail al {{GENDER:$2|usator}} ha fallite: $1 Le {{PLURAL:$3|nomine de usator e contrasigno|lista de nomines de usator e contrasignos}} es monstrate hic.",
        "passwordreset-nocaller": "Un appellator debe esser fornite",
        "passwordreset-nosuchcaller": "Appellator non existe: $1",
        "passwordreset-ignored": "Le reinitialisation del contrasigno non ha essite realisate. Es possibile que nulle fornitor ha essite configurate?",
        "upload-dialog-disabled": "Le incargamento de files con iste dialogo es disactivate in iste wiki.",
        "upload-dialog-title": "Incargar file",
        "upload-dialog-button-cancel": "Cancellar",
+       "upload-dialog-button-back": "Retornar",
        "upload-dialog-button-done": "Facite",
        "upload-dialog-button-save": "Salveguardar",
        "upload-dialog-button-upload": "Incargar",
        "htmlform-cloner-create": "Adder plus",
        "htmlform-cloner-delete": "Remover",
        "htmlform-cloner-required": "Al minus un valor es requirite.",
+       "htmlform-date-placeholder": "AAAA-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "AAAA-MM-DD HH:MM:SS",
+       "htmlform-date-invalid": "Le valor specificate non es recognoscite como data. Tenta usar le formato AAAA-MM-DD.",
+       "htmlform-time-invalid": "Le valor specificate non es recognoscite como hora. Tenta usar le formato HH:MM:SS.",
+       "htmlform-datetime-invalid": "Le valor specificate non es recognoscite como data e hora. Tenta usar le formato AAAA-MM-DD HH:MM:SS.",
        "htmlform-title-badnamespace": "[[:$1]] non es in le spatio de nomines \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "\"$1\" non es un titulo de pagina creabile",
        "htmlform-title-not-exists": "$1 non existe.",
        "feedback-external-bug-report-button": "Signalar un problema technic",
        "feedback-dialog-title": "Submitter commentario",
        "feedback-dialog-intro": "Usa le formulario sequente pro submitter tu commentario, le qual apparera in le pagina \"$1\" insimul a tu nomine de usator.",
-       "feedback-error-title": "Error",
        "feedback-error1": "Error: Resultato del API non recognoscite",
        "feedback-error2": "Error: Modification fallite",
        "feedback-error3": "Error: Nulle responsa del API",
index fd1d4c4..0e3003a 100644 (file)
        "listgrouprights-namespaceprotection-restrictedto": "Réttindi sem leyfa notanda að breyta",
        "listgrants-rights": "Réttindi",
        "trackingcategories-name": "Heiti skilaboða",
+       "restricted-displaytitle-ignored": "Síður með hunsaða sýnda titla",
        "trackingcategories-nodesc": "Enginn lýsing tiltæk.",
        "trackingcategories-disabled": "Flokkurinn er óvirkur",
        "mailnologin": "Ekkert netfang til að senda á",
        "htmlform-title-not-exists": "$1 er ekki til",
        "htmlform-user-not-exists": "<strong>$1</strong> er ekki til.",
        "htmlform-user-not-valid": "<strong>$1</strong> er ekki gilt notandanafn.",
-       "sqlite-has-fts": "$1 með fullum texta leitar stuðningi",
-       "sqlite-no-fts": "$1 án fullum texta leitar stuðningi",
        "logentry-delete-delete": "$1 {{GENDER:$2|eyddi}} síðunni $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|endurvakti}} $3",
        "logentry-delete-event": "$1 {{GENDER:$2|breytti}} sýnileika {{PLURAL:$5|færslu|$5 færslna}} á $3: $4",
        "feedback-external-bug-report-button": "Senda inn tæknilegar lýsingar/verkefni",
        "feedback-dialog-title": "Senda umsögn",
        "feedback-dialog-intro": "Þú getur þú notað einfalt eyðublað hér fyrir neðan til að senda inn umsögn. Athugasemdinni þinni verður bætt við síðuna \"$1\" ásamt notandanafni þínu.",
-       "feedback-error-title": "Villa",
        "feedback-error1": "Villa: Óþekkt útkoma frá API",
        "feedback-error2": "Villa: Breytingin mistókst",
        "feedback-error3": "Villa: Ekkert svar frá API",
index 328d5c7..eb3066d 100644 (file)
                        "Einreiher",
                        "Anto",
                        "Saracrovetto",
-                       "Tosky"
+                       "Tosky",
+                       "Selven"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "talk": "Discussione",
        "views": "Visite",
        "toolbox": "Strumenti",
+       "tool-link-userrights": "Modifica gruppi {{GENDER:$1|utente}}",
        "tool-link-emailuser": "Invia una email a questo {{GENDER:$1|utente}}",
        "userpage": "Visualizza la pagina utente",
        "projectpage": "Visualizza la pagina di servizio",
        "createacct-yourpasswordagain-ph": "Inserisci nuovamente la password",
        "userlogin-remembermypassword": "Mantienimi collegato",
        "userlogin-signwithsecure": "Usa una connessione sicura",
+       "cannotlogin-title": "Non è possibile effettuare l'accesso",
        "cannotlogin-text": "L'accesso non è possibile.",
        "cannotloginnow-title": "Impossibile accedere ora",
        "cannotloginnow-text": "L'accesso non è possibile quando si sta usando $1.",
        "searchprofile-advanced-tooltip": "Cerca nei namespace personalizzati",
        "search-result-size": "$1 ({{PLURAL:$2|una parola|$2 parole}})",
        "search-result-category-size": "{{PLURAL:$1|1 utente|$1 utenti}} ({{PLURAL:$2|1 sottocategoria|$2 sottocategorie}}, {{PLURAL:$3|1 file|$3 files}})",
-       "search-redirect": "(redirect $1)",
+       "search-redirect": "(reindirizzamento da $1)",
        "search-section": "(sezione $1)",
        "search-category": "(categoria $1)",
        "search-file-match": "(corrispondenza nel contenuto del file)",
        "upload-http-error": "Si è verificato un errore HTTP: $1",
        "upload-copy-upload-invalid-domain": "Non è consentito il caricamento di copie da questo dominio.",
        "upload-foreign-cant-upload": "Questo wiki non è configurato per caricare i file nel repository di file esterno richiesto.",
+       "upload-foreign-cant-load-config": "Impossibile caricare il file di configurazione per caricarlo nel repositorio esterno.",
        "upload-dialog-disabled": "Il caricamento di file tramite questa finestra di dialogo è disabilitato in questo wiki.",
        "upload-dialog-title": "Carica file",
        "upload-dialog-button-cancel": "Annulla",
        "uploadstash-errclear": "La pulizia dei file non è riuscita.",
        "uploadstash-refresh": "Aggiorna l'elenco dei file",
        "uploadstash-thumbnail": "vedi miniatura",
+       "uploadstash-exception": "Impossibile memorizzare il caricamento in stash ($1): \"$2\".",
        "invalid-chunk-offset": "Offset della parte non valido.",
        "img-auth-accessdenied": "Accesso negato",
        "img-auth-nopathinfo": "PATH_INFO mancante.\nIl server non è impostato per passare questa informazione.\nPotrebbe essere basato su CGI e non può supportare img_auth.\nVedi https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
        "feedback-external-bug-report-button": "Documenta un problema tecnico",
        "feedback-dialog-title": "Invia un feedback",
        "feedback-dialog-intro": "Usa il modulo sottostante per inviare il tuo feedback. Il tuo commento apparirà nella pagina \"$1\", assieme al tuo nome utente.",
-       "feedback-error-title": "Errore",
        "feedback-error1": "Errore: Dalla API è arrivato un risultato non riconosciuto",
        "feedback-error2": "Errore: Non è stato possibile eseguire la modifica",
        "feedback-error3": "Errore: Nessuna risposta dalla API",
        "linkaccounts-submit": "Collega utenze",
        "unlinkaccounts": "Scollega utenze",
        "unlinkaccounts-success": "L'utenza è stata scollegata.",
+       "authenticationdatachange-ignored": "Il cambiamento dei dati di autenticazione non è stato gestito. Forse non è stato configurato nessun provider?",
+       "userjsispublic": "Ricorda: le sottopagine JavaScript non devono contenere dati riservati poichè sono visualizzabili da altri utenti.",
+       "usercssispublic": "Ricorda: le sottopagine CSS non devono contenere dati riservati poichè sono visualizzabili da altri utenti.",
        "restrictionsfield-badip": "Indirizzo IP o intervallo non valido: $1",
        "restrictionsfield-label": "Intervalli IP consentiti:",
        "restrictionsfield-help": "Un indirizzo IP o intervallo CIDR per linea. Per consentire tutto, utilizza<br><code>0.0.0.0/0</code><br><code>::/0</code>"
index f8b033b..24f70e6 100644 (file)
@@ -90,7 +90,7 @@
        "tog-enotifminoredits": "문서나 파일의 사소한 편집도 이메일로 알림",
        "tog-enotifrevealaddr": "알림 메일에 내 이메일 주소를 밝히기",
        "tog-shownumberswatching": "주시하는 사용자 수 보이기",
-       "tog-oldsig": "현재 서명:",
+       "tog-oldsig": "당신의 기존 서명:",
        "tog-fancysig": "서명을 위키텍스트로 취급 (자동으로 링크를 걸지 않음)",
        "tog-uselivepreview": "실시간 미리 보기 사용하기",
        "tog-forceeditsummary": "편집 요약을 쓰지 않았을 때 내게 물어보기",
        "file-thumbnail-no": "파일 이름이 <strong>$1</strong>으로 시작합니다.\n이 파일은 그림의 크기를 줄인 (섬네일) 파일인 것 같습니다.\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]])이 이전에 삭제된 적이 있습니다. 파일을 다시 올리기 전에 문서의 삭제 기록을 확인해 주시기 바랍니다.",
        "file-deleted-duplicate-notitle": "이 파일과 같은 파일이 이전에 삭제된 적이 있으며, 제목은 숨겨져 있습니다.\n다시 올리기 전에 상확은 검토하기 위해 숨겨진 파일 데이터를 볼 수 있는 누군가에게 물어봐야 합니다.",
        "feedback-external-bug-report-button": "기술적 보고 제기",
        "feedback-dialog-title": "피드백 제출",
        "feedback-dialog-intro": "당신의 피드백을 제출하기 위해 아래 쉬운 양식을 사용할 수 있습니다. 당신의 의견은 당신의 사용자 이름과 함께, \"$1\" 문서에 추가됩니다.",
-       "feedback-error-title": "오류",
        "feedback-error1": "오류: API 실행 결과를 인식할 수 없음",
        "feedback-error2": "오류: 편집 실패",
        "feedback-error3": "오류: API가 응답하지 않음",
        "userjsispublic": "주목해 주십시오: 자바스크립트의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다.",
        "usercssispublic": "주목해 주십시오: CSS의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다.",
        "restrictionsfield-badip": "유효하지 않은 IP 주소나 대역: $1",
-       "restrictionsfield-label": "허용된 IP 대역:"
+       "restrictionsfield-label": "허용된 IP 대역:",
+       "restrictionsfield-help": "줄 단위의 하나의 IP 주소 또는 CIDR 대역입니다. 모든 곳에 적용하려면, 다음을 사용하세요<br><code>0.0.0.0/0</code><br><code>::/0</code>"
 }
index 767938c..70addda 100644 (file)
        "searchprofile-advanced-tooltip": "Sich an den Nummraim déi an de perséinlichen Astellungen festgeluecht sinn",
        "search-result-size": "$1 ({{PLURAL:$2|1 Wuert|$2 Wierder}})",
        "search-result-category-size": "{{PLURAL:$1|1 Säit|$1 Säiten}} ({{PLURAL:$2|1 Ënnerkategorie|$2 Ënnerkategorien}}, {{PLURAL:$3|1 Fichier|$3 Fichieren}})",
-       "search-redirect": "(Viruleedung $1)",
+       "search-redirect": "(Viruleedung vu(n) $1)",
        "search-section": "(Abschnitt $1)",
        "search-category": "(Kategorie $1)",
        "search-file-match": "(Inhalt vum Fichier passt)",
        "upload-options": "Optioune vum Eroplueden",
        "watchthisupload": "Dëse Fichier iwwerwaachen",
        "filewasdeleted": "E Fichier mat dësem Numm gouf schonn eemol eropgelueden an duerno nees geläscht. Kuckt w.e.g op $1 no, ier Dir dee Fichier nach eng Kéier eropluet.",
+       "filename-thumb-name": "Dësen Numm gesäit aus wéi den Numm vun engem Miniaturbild. Luet w.e.g. keng Miniatur-Biller zréck op déi selwecht Wiki. Sollt et sech ëm een anert Bild handelen da sicht w.e.g. e Fichiersnumm dee méi verständlech ass an den net sou ufänkt wéi e Miniaturbild.",
        "filename-bad-prefix": "Den Numm vum Fichier fänkt mat '''„$1“''' un. Dësen Numm krut en automatesch vun der Kamera a seet näischt iwwer dat aus, wat drop ass. Gitt dem Fichier w.e.g. en Numm, deen den Inhalt besser beschreift, an deen net verwiesselt ka ginn.",
        "upload-proto-error": "Falsche Protokoll",
        "upload-proto-error-text": "D'URL muss mat <code>http://</code> oder <code>ftp://</code> ufänken.",
        "htmlform-date-toohigh": "De wäert deen Dir aginn hutt ass nom leschten erlaabten Datum vum $1.",
        "htmlform-time-toolow": "De Wäert deen Dir aginn hutt ass virun der éischter erlaabter Zäit vu(n) $1.",
        "htmlform-time-toohigh": "De Wäert deen Dir aginn hutt ass no der leschter erlaabter Zäit vu(n) $1.",
+       "htmlform-datetime-toohigh": "De Wäert deen Dir uginn hutt ass nom leschten erlaabten Datum an der Zäit vu(n) $1.",
        "htmlform-title-badnamespace": "[[:$1]] ass net am Nummraum \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "\"$1\" ass kee Säitentitel deen ugeluecht ka ginn",
        "htmlform-title-not-exists": "$1 gëtt et net.",
        "feedback-close": "Fäerdeg",
        "feedback-external-bug-report-button": "Eng technesch Aufgab notifizéieren",
        "feedback-dialog-title": "Feedback schécken",
-       "feedback-error-title": "Feeler",
        "feedback-error1": "Feeler: Resultat vum API gouf net erkannt",
        "feedback-error2": "Feeler: D'Ännerung gouf net gespäichert",
        "feedback-error3": "Feeler: Keng Äntwert vum API",
index 779c36c..8cc7e7d 100644 (file)
@@ -45,7 +45,7 @@
        "tog-enotifminoredits": "Versjik  mich 'ne e-mail bie klein bewirkinge op pagina's en bestenj op mien volglies",
        "tog-enotifrevealaddr": "Mien e-mailadres tuine in e-mailberichte",
        "tog-shownumberswatching": "'t Aantal gebroekers tuine die dees pagina volg",
-       "tog-oldsig": "Bestaonde ongerteikening:",
+       "tog-oldsig": "Dien bestaonde ongerteikening:",
        "tog-fancysig": "Es wikiteks behanjele (zonder autematische verwiezing)",
        "tog-uselivepreview": "\"live veurbesjouwing\" gebroeke",
        "tog-forceeditsummary": "'n Melding gaeve bie 'n laeg samevatting",
        "category-file-count-limited": "Dees categorie bevat {{PLURAL:$1|'t volgende bestandj|de volgende $1 bestenj}}.",
        "listingcontinuesabbrev": "wiejer",
        "index-category": "Geïndexeerde paazjes",
-       "noindex-category": "Óngeïndexeerde paazjes",
+       "noindex-category": "Neet-geïndexeerde pazjena's",
        "broken-file-category": "Pazjena's mit ónjuuste bestandjsverwiezinge",
        "about": "Informatie",
        "article": "Pagina",
        "newwindow": "(in nuuj venster)",
        "cancel": "Aafbraeke",
        "moredotdotdot": "Miè...",
-       "morenotlisted": "Deze lies is neet compleet.",
+       "morenotlisted": "Deze lies is mäögelik neet compleet.",
        "mypage": "Mien gebroekerspagina",
        "mytalk": "Euverlèk",
        "anontalk": "Euverlèk veur dit IP adres",
        "yourpassword": "Die wachwaord",
        "userlogin-yourpassword": "Wachwaord",
        "yourpasswordagain": "Wachwaord opnuuj intype",
-       "remembermypassword": "Mien wachwaord onthouwe veur later sessies (hoegstens $1 {{PLURAL:$1|daag|daag}})",
        "yourdomainname": "Die domein",
        "externaldberror": "d'r Is 'n fout opgetraoje biej 't aanmelje biej de database of doe höbs gén toesjtömming diene externe gebroeker biej te wèrke.",
        "login": "Aanmèlde",
        "htmlform-submit": "Slaon óp",
        "htmlform-reset": "Maak verangeringe óngedaon",
        "htmlform-selectorother-other": "Anges",
-       "sqlite-has-fts": "Zeuk versie $1 mit óngersteuning veur \"full-text\"",
-       "sqlite-no-fts": "Zeuk versie $1 zónger óngersteuning veur \"fulltext\"",
        "logentry-delete-delete": "$1 {{GENDER:$1|haet}} de pagina $3 gewösj",
        "logentry-delete-restore": "$1 haet de pagina $3 trögkgezat",
        "logentry-delete-event": "$1 haet de zichbaarheid van {{PLURAL:$5|'ne logbookregel|$5 logbookregels}} van $3 gewiezig: $4",
index ee6a8bc..c37713f 100644 (file)
        "feedback-external-bug-report-button": "Užpildyti techninę užduotį",
        "feedback-dialog-title": "Pateikti atsiliepimą",
        "feedback-dialog-intro": "Galite naudoti lengvą formą esančia žemiau, kad pateiktumėte savo atsiliepimus. Jūsų komentaras bus pridėtas prie puslapio \"$1\", kartu su jūsų vartotojo vardu.",
-       "feedback-error-title": "Klaida",
        "feedback-error1": "Klaida: Neatpažįstamas rezultatas iš API",
        "feedback-error2": "Klaida: Redagavimas nepavyko",
        "feedback-error3": "Klaida: Jokio atsakymo iš API",
index 82bd5ab..8baed2c 100644 (file)
        "upload-http-error": "HTTP kļūda: $1",
        "upload-dialog-title": "Augšupielādēt failu",
        "upload-dialog-button-cancel": "Atcelt",
+       "upload-dialog-button-back": "Atpakaļ",
        "upload-dialog-button-done": "Gatavs",
        "upload-dialog-button-save": "Saglabāt",
        "upload-dialog-button-upload": "Augšupielādēt",
        "nolicense": "Neviena licence nav izvēlēta",
        "license-nopreview": "(Priekšskatījums nav pieejams)",
        "upload_source_url": "(derīgs, publiski pieejams URL)",
-       "upload_source_file": "(fails datorā)",
+       "upload_source_file": "(tavs izvēlētais fails tavā datorā)",
        "listfiles-delete": "dzēst",
        "listfiles-summary": "Šajā īpašajā lapā ir redzami visi augšupielādētie faili.",
        "listfiles_search_for": "Meklēt failu pēc vārda:",
        "year": "No gada (un senāki):",
        "sp-contributions-newbies": "Rādīt jauno lietotāju devumu",
        "sp-contributions-newbies-sub": "Jaunie lietotāji",
-       "sp-contributions-blocklog": "Bloķēšanas reģistrs",
+       "sp-contributions-blocklog": "bloķēšanas reģistrs",
        "sp-contributions-deleted": "dzēstais {{GENDER:$1|dalībnieka|dalībnieces}} devums",
        "sp-contributions-uploads": "augšupielādes",
        "sp-contributions-logs": "reģistri",
        "sp-contributions-talk": "diskusija",
-       "sp-contributions-userrights": "Lietotāju tiesību pārvaldība",
+       "sp-contributions-userrights": "dalībnieka tiesību pārvaldība",
        "sp-contributions-blocked-notice": "Šis lietotājs pašlaik ir nobloķēts.\nPēdējais bloķēšanas reģistra ieraksts ir apskatāms zemāk:",
        "sp-contributions-blocked-notice-anon": "Šī IP adrese pašlaik ir nobloķēta.\nPēdējais bloķēšanas reģistra ieraksts ir apskatāms zemāk:",
        "sp-contributions-search": "Meklēt lietotāju veiktās izmaiņas",
        "ipb-unblock-addr": "Atbloķēt $1",
        "ipb-unblock": "Atbloķēt lietotāju vai IP adresi",
        "ipb-blocklist": "Apskatīt esošos blokus",
-       "ipb-blocklist-contribs": "$1 devums",
+       "ipb-blocklist-contribs": "{{GENDER:$1|$1}} devums",
        "unblockip": "Atbloķēt lietotāju",
        "unblockiptext": "Šeit var atbloķēt iepriekš nobloķētu IP adresi vai lietotāja vārdu (atjaunot viņiem rakstīšanas piekļuvi).",
        "ipusubmit": "Noņemt šo bloku",
        "newimages-legend": "Filtrs",
        "newimages-label": "Faila nosaukums (vai tā daļa):",
        "newimages-showbots": "Parādīt botu augšupielādētos failus",
+       "newimages-hidepatrolled": "Paslēpt pārbaudītās augšupielādes",
        "noimages": "Nav nekā ko redzēt.",
        "ilsubmit": "Meklēt",
        "bydate": "<b>pēc datuma</b>",
        "tags-actions-header": "Darbības",
        "tags-active-yes": "Jā",
        "tags-active-no": "Nē",
-       "tags-source-extension": "Nosaka paplašinājums",
+       "tags-source-extension": "Nosaka programmatūra",
        "tags-source-none": "Vairs netiek izmantots",
        "tags-edit": "labot",
        "tags-delete": "dzēst",
        "feedback-cancel": "Atcelt",
        "feedback-close": "Gatavs",
        "feedback-dialog-title": "Iesniegt atsauksmes",
-       "feedback-error-title": "Kļūda",
        "feedback-error1": "Kļūda: API neatpazīts rezultāts",
        "feedback-error2": "Kļūda: Labojums neizdevās",
        "feedback-error3": "Kļūda: Nav atbildes no API",
index cd0a50d..7834379 100644 (file)
@@ -9,7 +9,8 @@
                        "Ibero-kolxi",
                        "Reedy",
                        "The Evil IP address",
-                       "아라"
+                       "아라",
+                       "Velg"
                ]
        },
        "tog-underline": "Link'iş tude kogu3’uxaçki:",
        "nstab-template": "Şabloni",
        "nstab-help": "Meşvelaşi but’k’a",
        "nstab-category": "Kʼatʼegori",
+       "mainpage-nstab": "Dudi But'k'a",
        "error": "Çilata",
        "missing-article": "Datʼabeizik, na igoren \"$1\" $2 coxoni butʼkʼaşi tekstʼi var az*iru.\n\nMuşeni? Çunki am butʼkʼa, jileri na ren a butʼkʼaşi golaxteri versiyoni ren.\n\nEger sebebi aya na va renna, pʼrogramis ar çilata z*irit.\nMu iqʼven! Aya, a [[Special:ListUsers/sysop|adminis]], URL-ti çʼareli şekʼilite rapʼortʼi doçʼarit.",
        "missingarticle-rev": "(revizyoni#: $1)",
        "userlogin-yourname": "Skani maxmare-coxo",
        "yourpassword": "Pʼarola-skani:",
        "userlogin-yourpassword": "Pʼarola-skani",
-       "remembermypassword": "Parola-skani goişini (for a maximum of $1 {{PLURAL:$1|day|days}})",
        "yourdomainname": "Skani domaini:",
        "login": "Sitʼeşa amaxti",
        "nav-login-createaccount": "Sitʼeşa amaxti / hesabi dokʼidi",
        "createacct-reason": "Muşen",
        "mailmypassword": "Ağne pʼarola-çkimi moncğoni",
        "loginlanguagelabel": "Nena: $1",
+       "pt-login": "Sitʼeşa amaxti",
+       "pt-createaccount": "Hesabi dokʼidi",
        "oldpassword": "Mcveşi p'arola:",
        "newpassword": "Ağani P'arola:",
        "passwordreset-username": "Skani maxmare-coxo:",
        "rc-enhanced-expand": "Detayepe ko3ʼiri (JavaScript-i unon)",
        "rc-enhanced-hide": "Detayepe doşinaxi",
        "recentchangeslinked": "Alakʼali na renan oktirobape",
+       "recentchangeslinked-toolbox": "Alakʼali na renan oktirobape",
        "recentchangeslinked-title": "\"$1\" kʼala alakʼali na renan oktirobape",
        "recentchangeslinked-summary": "Tude na çʼars listʼe, kʼiti na noğiru butʼkʼaşa (varna kʼiti na noğiru kʼatʼegorişi makʼaturepeşa) kʼontʼaktʼi na ikips butʼkʼapes na ixvenu çodinaşi oktirobapeşi listʼe ren.\n[[Special:Watchlist|Gotxozu na ginon butʼkʼapeşi listʼes]] na renan butʼkʼape '''mçxu''' nçʼaraten niçʼaru.",
        "recentchangeslinked-page": "Butʼkʼaşi coxo:",
        "tooltip-pt-login": "Ginon na sitʼeşa amaxti, mecburi va re",
        "tooltip-pt-logout": "Siteşen Kogamaxti",
        "tooltip-ca-talk": "Butʼkʼaş doloxe na içʼaren çʼarape şeni mutxanepe mi3ʼvit",
-       "tooltip-ca-edit": "Am butʼkʼa kodogaktiren. Mu iqʼven! ipti \"Evvelişen i3ʼkʼedi\" tʼuşi ixmari do na çʼari çʼara ikʼontʼroli, ukʼule ikʼayitʼi.",
+       "tooltip-ca-edit": "Butʼkʼa doktiri",
        "tooltip-ca-addsection": "Ağani burme dokʼidi.",
        "tooltip-ca-viewsource": "Am butʼkʼa içven. Xvala odudeş kʼodi gaz*iren. Doloxe muşi va gaktirinen.",
        "tooltip-ca-history": "Am butʼkʼaşi golaxteri versiyonepe",
        "watchlisttools-raw": "Kʼobo gotxozu listʼe doktiri",
        "specialpages": "Doxmeli butʼkʼape",
        "rightsnone": "(Va ren)",
+       "searchsuggest-search": "Mgori",
        "special-characters-group-latin": "Lat'ini",
        "special-characters-group-greek": "Xorumi",
        "special-characters-group-arabic": "Arabuli"
index cd09ec9..ed6dde8 100644 (file)
        "feedback-bugnew": "हम जाँच केलौ। एक नव बग रिपोर्ट करी",
        "feedback-cancel": "रद्द करी",
        "feedback-close": "भ गेल",
-       "feedback-error-title": "त्रुटि",
        "feedback-error1": "त्रुटि: नै पहचानल गेल परिणाम एपीआईसँ",
        "feedback-error2": "त्रुटि: संपादन विफल भेल",
        "feedback-error3": "त्रुटि:एपीआईसँग कोनो प्रतिक्रिया नै",
index bce08c3..c65a403 100644 (file)
        "feedback-external-bug-report-button": "Поднеси техничка задача",
        "feedback-dialog-title": "Поднеси мислење",
        "feedback-dialog-intro": "Послужете се со едноставниот образец подолу за да го поднесете вашето мислење. Коментарот ќе ви биде додаден на страницата „$1“, заедно со вашето корисничко име.",
-       "feedback-error-title": "Грешка",
        "feedback-error1": "Грешка: Непрепознаен резултат од извршникот",
        "feedback-error2": "Грешка: Уредувањето не успеа",
        "feedback-error3": "Грешка: Извршникот не одговара",
index 11c42a1..0d18c0d 100644 (file)
        "searchprofile-advanced-tooltip": "पारंपरित(कस्टम) नामविश्वांमध्ये शोधा",
        "search-result-size": "$1 ({{PLURAL:$2|१ शब्द|$2 शब्द}})",
        "search-result-category-size": "{{PLURAL:$1|१ सदस्य|$1 सदस्य}} ({{PLURAL:$2|१ उपवर्ग|$2 उपवर्ग}}, {{PLURAL:$3|1 संचिका|$3 संचिका}})",
-       "search-redirect": "(पुनर्निर्देशन $1)",
+       "search-redirect": "($1 पासून पुनर्निर्देशन)",
        "search-section": "(विभाग $1)",
        "search-category": "(वर्ग $1)",
        "search-file-match": "(संचिका आशयाशी अनुरुपते)",
        "tag-filter": "[[Special:Tags|खूणपताका]] गाळक:",
        "tag-filter-submit": "गाळक",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|खूणपताका}}]]: $2)",
-       "tags-title": "खुणा",
-       "tags-intro": "प्रणालीतून विशिष्ट संपादनांच्या अर्थासहित  खुणांची  यादी नमूद करणारे पान",
+       "tags-title": "à¤\96à¥\81णपताà¤\95ा",
+       "tags-intro": "प्रणालीतून, विशिष्ट संपादनांच्या अर्थासहित  खुणपताकांची  यादी नमूद करणारे पान",
        "tags-tag": "खूण नाव",
        "tags-display-header": "बदल सुचीवर कसे दिसेल",
        "tags-description-header": "अर्थाची पूर्ण माहिती",
        "tags-hitcount": "$1 {{PLURAL:$1|बदल|बदल}}",
        "tags-manage-blocked": "आपण प्रतिबंधित असतांना बदल खूणपताकांचे व्यवस्थापन करु शकत नाही.",
        "tags-create-heading": "नवीन बिल्ला तयार करा",
+       "tags-create-explanation": "अविचलरित्या, नविन तयार केलेल्या खुणपताका सदस्यांना व सांगकाम्यांना वापरासाठी उपलब्ध होतील.",
        "tags-create-tag-name": "खूणपताकेचे नाव:",
        "tags-create-reason": "कारण:",
        "tags-create-submit": "निर्मित करा",
        "feedback-cancel": "रद्द करा",
        "feedback-close": "झाले",
        "feedback-dialog-title": "प्रतिक्रिया सादर करा",
-       "feedback-error-title": "चूक",
        "feedback-error1": "चूक: API कडून अनोळखी परिणाम",
        "feedback-error2": "त्रुटी: संपादन रद्द",
        "feedback-error3": "त्रुटी:एपीआय तर्फे काहीच प्रत्युत्तर नाही",
index 151f629..4b303df 100644 (file)
        "upload-dialog-disabled": "Filopplastinger med denne dialogen er slått av for denne wikien.",
        "upload-dialog-title": "Last opp fil",
        "upload-dialog-button-cancel": "Avbryt",
+       "upload-dialog-button-back": "Tilbake",
        "upload-dialog-button-done": "Utført",
        "upload-dialog-button-save": "Lagre",
        "upload-dialog-button-upload": "Last opp",
        "htmlform-cloner-create": "Legg til mer",
        "htmlform-cloner-delete": "Fjern",
        "htmlform-cloner-required": "Minst én verdi kreves.",
+       "htmlform-date-placeholder": "ÅÅÅÅ-MM-DD",
+       "htmlform-time-placeholder": "TT:MM:SS",
+       "htmlform-datetime-placeholder": "ÅÅÅÅ-MM-DD TT:MM:SS",
+       "htmlform-date-invalid": "Verdien du anga gjenkjennes ikke som en dato. Prøv formatet ÅÅÅÅ-MM-DD.",
+       "htmlform-time-invalid": "Verdien du anga gjenkjennes ikke som et tidspunkt. Prøv formatet TT:MM:SS.",
+       "htmlform-datetime-invalid": "Verdien du anga gjenkjennes ikke som en dato og et tidspunkt. Prøv formatet ÅÅÅÅ-MM-DD TT:MM:SS.",
+       "htmlform-date-toolow": "Verdien du anga er før den tidligste tillatte datoen $1.",
+       "htmlform-date-toohigh": "Verdien du anga er etter den siste tillatte datoen $1.",
+       "htmlform-time-toolow": "Verdien du anga er før det tidligste tillatte tidspunktet $1.",
+       "htmlform-time-toohigh": "Verdien du anga er etter det siste tillatte tidspunktet $1.",
+       "htmlform-datetime-toolow": "Verdien du anga er før den tidligste tillatte datoen og tidspunktet $1.",
+       "htmlform-datetime-toohigh": "Verdien du anga er etter den siste tillatte datoen og tidspunktet $1.",
        "htmlform-title-badnamespace": "[[:$1]] er ikke i «{{ns:$2}}»-navnerommet",
        "htmlform-title-not-creatable": "«$1» er ikke en opprettbar sidetittel",
        "htmlform-title-not-exists": "$1 forefinnes ikke.",
        "feedback-external-bug-report-button": "Registrer en teknisk sak",
        "feedback-dialog-title": "Send tilbakemelding",
        "feedback-dialog-intro": "Bruk det enkle skjemaet under om du vil gi tilbakemelding. Kommentaren din vil bli lagt ut på siden «$1» sammen med brukernavnet ditt.",
-       "feedback-error-title": "Feil",
        "feedback-error1": "Feil: Ukjent resultat fra API",
        "feedback-error2": "Feil: Redigering feilet",
        "feedback-error3": "Feil: Ingen respons fra API",
index 9839755..34419b4 100644 (file)
        "talk": "Overleg",
        "views": "Weergaven",
        "toolbox": "Hulpmiddelen",
+       "tool-link-userrights": "{{GENDER:$1|Gebruikersgroepen}} wijzigen",
+       "tool-link-emailuser": "Deze {{GENDER:$1|gebruiker}} e-mailen",
        "userpage": "Gebruikerspagina bekijken",
        "projectpage": "Projectpagina bekijken",
        "imagepage": "Bestandspagina bekijken",
        "passwordreset-emailelement": "Gebruikersnaam: \n$1\n\nTijdelijk wachtwoord: \n$2",
        "passwordreset-emailsentemail": "Als dit e-mailadres aan uw account gekoppeld is, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
        "passwordreset-emailsentusername": "Als er een e-mailadres geregistreerd is voor die gebruikersnaam, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
+       "passwordreset-emailerror-capture2": "Het e-mailen naar de {{GENDER:$2|gebruiker}} is mislukt: $1 {{PLURAL:$3|De gebruikersnaam en het wachtwoord|De lijst met gebruikersnamen en wachtwoorden}} wordt hieronder weergegeven.",
        "passwordreset-invalideamil": "Ongeldig e-mailadres",
        "passwordreset-nodata": "Er is geen gebruikersnaam of e-mailadres opgegeven",
        "changeemail": "E-mailadres wijzigen of verwijderen",
        "searchprofile-advanced-tooltip": "Zoeken in opgegeven naamruimten",
        "search-result-size": "$1 ({{PLURAL:$2|1 woord|$2 woorden}})",
        "search-result-category-size": "{{PLURAL:$1|1 categorielid|$1 categorieleden}} ({{PLURAL:$2|1 ondercategorie|$2 ondercategorieën}}, {{PLURAL:$3|1 bestand|$3 bestanden}})",
-       "search-redirect": "(doorverwijzing $1)",
+       "search-redirect": "(doorverwijzing vanaf $1)",
        "search-section": "(subkop $1)",
        "search-category": "(categorie $1)",
        "search-file-match": "(komt overeen met de inhoud van het bestand)",
        "upload-foreign-cant-upload": "Deze wiki is niet geconfigureerd om bestanden te uploaden naar de bestandsrepository op een andere site.",
        "upload-dialog-title": "Bestand uploaden",
        "upload-dialog-button-cancel": "Annuleren",
+       "upload-dialog-button-back": "Terug",
        "upload-dialog-button-done": "Afgerond",
        "upload-dialog-button-save": "Opslaan",
        "upload-dialog-button-upload": "Uploaden",
        "sp-contributions-newbies-title": "Bijdragen van nieuwe gebruikers",
        "sp-contributions-blocklog": "blokkeerlogboek",
        "sp-contributions-suppresslog": "onderdrukte gebruikersbijdragen",
-       "sp-contributions-deleted": "verwijderde bijdragen",
+       "sp-contributions-deleted": "verwijderde {{GENDER:$1|bijdragen}}",
        "sp-contributions-uploads": "uploads",
        "sp-contributions-logs": "logboeken",
        "sp-contributions-talk": "overleg",
        "watchlistedit-raw-done": "Uw volglijst is bijgewerkt.",
        "watchlistedit-raw-added": "Er {{PLURAL:$1|is 1 pagina|zijn $1 pagina's}} toegevoegd:",
        "watchlistedit-raw-removed": "Er {{PLURAL:$1|is 1 pagina|zijn $1 pagina's}} verwijderd:",
-       "watchlistedit-clear-title": "Volglijst gewist",
+       "watchlistedit-clear-title": "Volglijst wissen",
        "watchlistedit-clear-legend": "Volglijst wissen",
        "watchlistedit-clear-explain": "Alle pagina's worden van uw volglijst verwijderd",
        "watchlistedit-clear-titles": "Pagina's:",
        "feedback-external-bug-report-button": "Een technische taak indienen",
        "feedback-dialog-title": "Terugkoppeling verzenden",
        "feedback-dialog-intro": "U kunt het eenvoudige formulier gebruiken om uw terugkoppeling in te sturen. Uw reactie wordt toegevoegd aan de pagina \"$1\" samen met uw gebruikersnaam.",
-       "feedback-error-title": "Fout",
        "feedback-error1": "Fout: onbekend resultaat uit de API",
        "feedback-error2": "Fout: de bewerking is mislukt",
        "feedback-error3": "Fout: geen reactie van de API",
        "searchsuggest-containing": "bevat...",
        "api-error-badaccess-groups": "U mag geen bestanden uploaden in deze wiki.",
        "api-error-badtoken": "Interne fout: het token klopt niet.",
+       "api-error-blocked": "U bent geblokkeerd en kunt niet bewerken.",
        "api-error-copyuploaddisabled": "Uploaden via URL is uitgeschakeld op deze server.",
        "api-error-duplicate": "Er {{PLURAL:$1|staat al een bestand|staan al bestanden}} met dezelfde inhoud in de wiki.",
        "api-error-duplicate-archive": "Er {{PLURAL:$1|was al een ander bestand|waren al $1 andere bestanden}}  op de site met dezelfde inhoud, maar {{PLURAL:$1|dat is|die zijn}} verwijderd.",
        "api-error-nomodule": "Interne fout: er is geen uploadmodule ingesteld.",
        "api-error-ok-but-empty": "Interne fout: de server heeft geen gegevens teruggeleverd.",
        "api-error-overwrite": "Het overschrijven van een bestand bestand is niet toegestaan.",
+       "api-error-ratelimited": "U probeert meer bestanden te uploaden in een korte periode dan deze wiki toelaat.\nProbeer het over een aantal minuten opnieuw.",
        "api-error-stashfailed": "Interne fout: de server kon het tijdelijke bestand niet opslaan.",
        "api-error-publishfailed": "Interne fout: de server kon het tijdelijke bestand niet publiceren.",
        "api-error-stasherror": "Er is een fout opgetreden tijdens het uploaden van het bestand naar de tijdelijke opslagruimte.",
index ad9848b..f4847c2 100644 (file)
        "searchprofile-advanced-tooltip": "Szukanie w wybranych przestrzeniach nazw",
        "search-result-size": "$1 ({{PLURAL:$2|1 słowo|$2 słowa|$2 słów}})",
        "search-result-category-size": "{{PLURAL:$1|1 element|$1 elementy|$1 elementów}} ({{PLURAL:$2|1 kategoria|$2 kategorie|$2 kategorii}}, {{PLURAL:$3|1 plik|$3 pliki|$3 plików}})",
-       "search-redirect": "(przekierowanie $1)",
+       "search-redirect": "(przekierowanie $1)",
        "search-section": "(sekcja $1)",
        "search-category": "(kategoria $1)",
        "search-file-match": "(odpowiada zawartości pliku)",
        "blankpage": "Pusta strona",
        "intentionallyblankpage": "Tę stronę umyślnie pozostawiono pustą.",
        "external_image_whitelist": " #Pozostaw tę linię dokładnie tak, jak jest.<pre>\n#Wstaw poniżej fragmenty wyrażeń regularnych (tylko to, co znajduje się między //).\n#Wyrażenia te zostaną dopasowane do adresów URL zewnętrznych (bezpośrednio linkowanych) grafik.\n#Dopasowane adresy URL zostaną wyświetlone jako grafiki, w przeciwnym wypadku będzie pokazany jedynie link do grafiki.\n#Linie zaczynające się od # są traktowane jako komentarze.\n#We wpisach ma znaczenie wielkość znaków.\n\n#Wstaw wszystkie deklaracje wyrażeniami regularnymi poniżej tej linii. Pozostaw tę linię dokładnie tak, jak jest.</pre>",
-       "tags": "Sprawdź zmiany w oparciu o wzorce tekstu",
+       "tags": "Znaczniki zmian",
        "tag-filter": "Filtr [[Special:Tags|znaczników]]:",
        "tag-filter-submit": "Filtr",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Znacznik|Znaczniki}}]]: $2)",
+       "tag-mw-contentmodelchange": "zmiana modelu zawartości",
        "tags-title": "Znaczniki",
        "tags-intro": "Na tej stronie znajduje się lista znaczników, którymi oprogramowanie może oznaczyć edycje, oraz ich opisy.",
        "tags-tag": "Nazwa znacznika",
        "htmlform-cloner-delete": "Usuń",
        "htmlform-cloner-required": "Wymagana jest co najmniej jedna wartość.",
        "htmlform-date-placeholder": "RRRR-MM-DD",
+       "htmlform-time-placeholder": "GG:MM:SS",
+       "htmlform-datetime-placeholder": "RRRR-MM-DD GG:MM:SS",
        "htmlform-title-badnamespace": "[[:$1]] nie znajduje się w przestrzeni nazw „{{ns:$2}}”.",
        "htmlform-title-not-creatable": "Nie można użyć „$1” do utworzenia tytułu strony",
        "htmlform-title-not-exists": "$1 nie istnieje.",
        "feedback-external-bug-report-button": "Zgłoś problem techniczny",
        "feedback-dialog-title": "Prześlij opinię",
        "feedback-dialog-intro": "Możesz użyć tego prostego formularza w celu zgłoszenia swojej opinii. Twój komentarz, wraz z Twoją nazwą użytkownika (albo numerem IP) pojawi się na stronie $1.",
-       "feedback-error-title": "Błąd",
        "feedback-error1": "Błąd – nierozpoznana odpowiedź API",
        "feedback-error2": "Błąd – edycja nieudana",
        "feedback-error3": "Błąd – brak odpowiedzi API",
        "linkaccounts-success-text": "Konto zostało połączone.",
        "linkaccounts-submit": "Połącz konta",
        "unlinkaccounts": "Odłącz konta",
-       "unlinkaccounts-success": "Konta zostały odłączone."
+       "unlinkaccounts-success": "Konta zostały odłączone.",
+       "restrictionsfield-badip": "Nieprawidłowy adres IP lub zakres adresów: $1",
+       "restrictionsfield-label": "Dozwolone zakresy adresów IP:"
 }
index bdb05bf..b13dd55 100644 (file)
@@ -90,7 +90,7 @@
        "tog-watchdefault": "Adicionar as páginas e ficheiros que eu editar às minhas páginas vigiadas",
        "tog-watchmoves": "Adicionar as páginas e ficheiros que eu mover às minhas páginas vigiadas",
        "tog-watchdeletion": "Adicionar as páginas e ficheiros que eu eliminar às minhas páginas vigiadas",
-       "tog-watchuploads": "Adicionar novos ficheiros carregados por mim à minha lista de artigos vigiados",
+       "tog-watchuploads": "Adicionar novos ficheiros carregados por mim à minha lista de páginas vigiadas",
        "tog-watchrollback": "Adicionar páginas onde fiz uma reversão às minhas páginas vigiadas",
        "tog-minordefault": "Por omissão, marcar todas as edições como menores",
        "tog-previewontop": "Mostrar a antevisão antes da caixa de edição",
        "category_header": "Páginas na categoria \"$1\"",
        "subcategories": "Subcategorias",
        "category-media-header": "Multimédia na categoria \"$1\"",
-       "category-empty": "''Esta categoria não contém atualmente nenhuma página ou ficheiro multimédia.''",
+       "category-empty": "<em>Esta categoria não contém atualmente nenhuma página ou ficheiro multimédia.</em>",
        "hidden-categories": "{{PLURAL:$1|Categoria oculta|Categorias ocultas}}",
        "hidden-category-category": "Categorias ocultas",
        "category-subcat-count": "{{PLURAL:$2|Esta categoria só contém a seguinte subcategoria.|Esta categoria contém {{PLURAL:$1|a seguinte subcategoria|as seguintes $1 subcategorias}} (de um total de $2).}}",
        "talk": "Discussão",
        "views": "Vistas",
        "toolbox": "Ferramentas",
+       "tool-link-userrights": "Alterar grupos {{GENDER:$1|do utilizador|da utilizadora}}",
+       "tool-link-emailuser": "Enviar correio eletrónico a {{GENDER:$1|este utilizador|esta utilizadora|este(a) utilizador(a)}}",
        "userpage": "Ver página de utilizador",
        "projectpage": "Ver página de projeto",
        "imagepage": "Ver página de ficheiro",
        "missingarticle-rev": "(revisão#: $1)",
        "missingarticle-diff": "(Dif.: $1, $2)",
        "readonly_lag": "A base de dados foi automaticamente bloqueada enquanto os servidores secundários se sincronizam com o primário",
+       "nonwrite-api-promise-error": "O cabeçalho HTTP 'Promise-Non-Write-API-Action' foi enviado, mas o pedido está a ser feito a um módulo de escrita da API.",
        "internalerror": "Erro interno",
        "internalerror_info": "Erro interno: $1",
        "internalerror-fatal-exception": "Exceção fatal do tipo \"$1\"",
        "cannotloginnow-title": "Não é possível iniciar sessão agora",
        "cannotloginnow-text": "Não pode iniciar a sessão quando utilizar $1.",
        "cannotcreateaccount-title": "Não é possível criar contas",
+       "cannotcreateaccount-text": "A criação direta de contas não está activada nesta wiki.",
        "yourdomainname": "O seu domínio:",
        "password-change-forbidden": "Não pode alterar palavras-passe nesta wiki.",
        "externaldberror": "Ocorreu um erro externo à base de dados durante a autenticação ou não lhe é permitido atualizar a sua conta externa.",
        "changepassword-success": "A sua palavra-passe foi alterada!",
        "changepassword-throttled": "Realizou demasiadas tentativas de início de sessão com esta conta.\nAguarde $1 antes de tentar novamente, por favor.",
        "botpasswords": "Palavras-passe de robô",
-       "botpasswords-summary": "As <em>palavras-passe de robô</em> permitem o acesso a uma conta de utilizador através da API sem utilizar as principais credenciais de login da conta. Os direitos de um utilizador, ao iniciar sessão com uma palavra-passe de robô, podem estar limitados.\n\nSe não sabe o que o leva a fazer isso, provavelmente não deveria fazê-lo. Ninguém deve solicitar que gere uma destas palavras-passe e a entregue.",
+       "botpasswords-summary": "As <em>palavras-passe de robô</em> permitem o acesso a uma conta de utilizador através da API, sem utilizar as credenciais principais de autenticação dessa conta. Os direitos de um utilizador, ao iniciar uma sessão com a palavra-passe de robô, podem estar limitados.\n\nSe não sabe para que necessita desta palavra-passe provavelmente não deveria criá-la. Nunca lhe deve ser solicitado que gere e entregue uma destas palavras-passe.",
        "botpasswords-disabled": "As palavras-passe de robô estão desactivadas.",
        "botpasswords-no-central-id": "Para utilizar palavras-passe de robô, deve iniciar sessão com uma conta centralizada.",
        "botpasswords-existing": "Palavras-passe de robô existentes",
        "botpasswords-updated-body": "O robô palavra-passe para o nome do robô \"$1\" do utilizador \"$2\" foi atualizado.",
        "botpasswords-deleted-title": "Palavra-passe de robô eliminada",
        "botpasswords-deleted-body": "O robô palavra-passe para o nome do robô \"$1\"do utilizador \"$2\" foi eliminado.",
-       "botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. Por favor, recorde-se dela para futura referência.</em>",
+       "botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. <em>Anote-a para referência futura, por favor.</em> <br> (Para robôs antigos cujo nome de acesso tenha de ser igual ao eventual nome de utilizador, também pode usar o nome de utilizador <strong>$3</strong> e a palavra-passe <strong>$4</strong>.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
        "botpasswords-restriction-failed": "Restrições de palavra-passe de robô evitam esta autenticação.",
        "botpasswords-invalid-name": "O nome de utilizador especificado não contém o separador de palavra-passe de robô (\"$1\").",
        "passwordreset-emailelement": "{{GENDER:$1|Utilizador|Utilizadora}}: \n$1\n\nPalavra-passe temporária: \n$2",
        "passwordreset-emailsentemail": "Se este é o endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma palavra-passe de reposição.",
        "passwordreset-emailsentusername": "Se houver um endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma mensagem para redefinir a sua palavra-passe.",
-       "passwordreset-emailsent-capture2": "A redefinição da palavra-passe {{PLURAL:$1|do e-mail|dos e-mails}} foi enviada. {{PLURAL:$1|O nome de utilizador e palavra-passe|A lista de nomes de utilizador e palavras-passe}} encontram-se a seguir.",
-       "passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao utilizador|à utilizadora|a(o) utilizador(a)}} falhou: $1 {{PLURAL:$3|O nome de utilizador e palavra-passe são mostradas abaixo|A lista de nomes de utilizadores e palavras-passe é mostrada abaixo}}.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|A mensagem|As mensagens}} de redefinição da palavra-passe {{PLURAL:$1|foi enviada|foram enviadas}} para o seu correio eletrónico. {{PLURAL:$1|O nome de utilizador e palavra-passe são mostrados aqui|A lista de nomes de utilizador e palavras-passe é mostrada aqui}}.",
+       "passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao utilizador|à utilizadora|a(o) utilizador(a)}} falhou: $1 {{PLURAL:$3|O nome de utilizador e palavra-passe são mostrados aqui|A lista de nomes de utilizador e palavras-passe é mostrada aqui}}.",
        "passwordreset-nocaller": "Um interlocutor deve ser fornecido",
        "passwordreset-nosuchcaller": "A pessoa que chama não existe: $1",
        "passwordreset-ignored": "A reposição de palavra-passe não foi realizada. Talvez não tenha sido configurado o provedor?",
        "content-json-empty-object": "Objeto vazio",
        "content-json-empty-array": "Matriz vazia",
        "deprecated-self-close-category": "Páginas com etiquetas HTML de autofechamento não válidas",
+       "deprecated-self-close-category-desc": "Esta página contém tags HTML auto-fechadas, que são inválidas, tais como <code>&lt;b/></code> ou <code>&lt;span/></code>.  O comportamento destas tags será alterado em breve, para ser consistente com a especificação HTML5, pelo que o seu uso na notação wiki foi descontinuado.",
        "duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] chama [[:$2]] com mais de um valor para o parâmetro \"$3\". Somente o último valor fornecido será utilizado.",
        "duplicate-args-category": "Páginas com argumentos de predefinições duplicados",
        "duplicate-args-category-desc": "A página contém campos de predefinições que utilizam duplicatas de argumentos, tais como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ou <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "revdelete-text-text": "Revisões eliminadas ainda aparecerão no histórico da página, mas parte do seu conteúdo estará inacessível para o público.",
        "revdelete-text-file": "Versões eliminadas do ficheiro ainda aparecerão no histórico da página, mas parte do seu conteúdo estará inacessível para o público.",
        "logdelete-text": "Os eventos eliminados ainda aparecerão no histórico da página, mas pare de seu conteúdo será inacessível ao público.",
-       "revdelete-text-others": "Outros administradores serão ainda capazes de aceder ao conteúdo oculto e torná-lo visível novamente, a menos que sejam definidas restrições adicionais.",
+       "revdelete-text-others": "Os outros administradores ainda podem aceder ao conteúdo oculto e torná-lo visível novamente, a menos que sejam definidas restrições adicionais.",
        "revdelete-confirm": "Por favor, confirme que pretende executar esta operação, que compreende as suas consequências e que o faz em concordância com as [[{{MediaWiki:Policy-url}}|políticas e recomendações]].",
        "revdelete-suppress-text": "A supressão '''só''' deverá ser usada nos seguintes casos:\n* Informação potencialmente caluniosa, difamatória ou injuriosa\n* Informação pessoal imprópria\n*: ''endereços de domicílio e números de telefone, números de identificação nacional, etc''",
        "revdelete-legend": "Definir restrições de visibilidade",
        "userrights-changeable-col": "Grupos que pode alterar",
        "userrights-unchangeable-col": "Grupos que não pode alterar",
        "userrights-conflict": "Conflito entre alterações de privilégios de utilizador! Por favor, reveja e confirme as suas mudanças.",
-       "userrights-removed-self": "Removeu os seus privilégios. Como resultado disto, já não consegue aceder a esta página.",
+       "userrights-removed-self": "Removeu os seus privilégios. Em resultado, já não pode aceder a esta página.",
        "group": "Grupo:",
        "group-user": "Utilizadores",
        "group-autoconfirmed": "Utilizadores autoconfirmados",
        "grant-highvolume": "Alta quantidade de edições",
        "grant-oversight": "Ocultar utilizadores e edições suprimidas",
        "grant-patrol": "Patrulhar alterações a páginas",
+       "grant-privateinfo": "Aceder a informação privada",
        "grant-protect": "Proteger e desproteger páginas",
        "grant-rollback": "Reverter alterações a páginas",
        "grant-sendemail": "Enviar correio electrónico a outros utilizadores",
        "uploaded-script-svg": "Encontrou um elemento scriptable no ficheiro \"$1\" SVG carregado.",
        "uploaded-hostile-svg": "Encontrou-se um código CSS não seguro no elemento de estilo do arquivo SVG carregado.",
        "uploaded-event-handler-on-svg": "Não está permitido configurar atributos controladores de eventos <code>$1=\"$2\"</code> nos arquivos SVG.",
+       "uploaded-setting-href-svg": "O uso da tag \"set\" para adicionar o atributo \"href\" ao elemento mãe está bloqueado.",
+       "uploaded-wrong-setting-svg": "O uso da tag \"set\" para adicionar um destino remoto/de dados/<i>script</i> a qualquer atributo está bloqueado. No ficheiro SVG enviado foi encontrado <code>&lt;set to=\"$1\"&gt;</code>.",
        "uploaded-image-filter-svg": "Foi encontrado um filtro de imagem com a URL: <code>&lt;$1 $2=\"$3\"&gt;</code> no ficheiro SVG carregado.",
        "uploadscriptednamespace": "Este ficheiro SVG contém um domínio que não é permitido \"$1\".",
        "uploadinvalidxml": "Erro detectado na análise do XML do ficheiro carregado.",
        "upload-foreign-cant-upload": "Esta wiki não está configurada para carregar ficheiros para o repositório externo solicitado.",
        "upload-dialog-title": "Carregar ficheiro",
        "upload-dialog-button-cancel": "Cancelar",
+       "upload-dialog-button-back": "Voltar",
        "upload-dialog-button-done": "Feito",
        "upload-dialog-button-save": "Gravar",
        "upload-dialog-button-upload": "Carregar",
        "upload-form-label-infoform-title": "Detalhes",
        "upload-form-label-infoform-name": "Nome",
+       "upload-form-label-infoform-name-tooltip": "Um título descritivo e único para ser usado como nome do ficheiro. Pode usar linguagem normal e espaços. Não inclua a extensão do ficheiro.",
        "upload-form-label-infoform-description": "Descrição",
        "upload-form-label-usage-title": "Uso",
        "upload-form-label-usage-filename": "Nome do ficheiro",
        "listgrants-grant": "Atribuição",
        "listgrants-rights": "Direitos",
        "trackingcategories": "Categorias de monitorização",
-       "trackingcategories-summary": "Esta página lista as categorias monitoradas que foram geradas automaticamente pelo software MediaWiki. Os seus nomes podem ser alterados ao editar sua mensagem correspondente no domínio {{ns:8}}.",
+       "trackingcategories-summary": "Esta página lista as categorias de monitorização geradas automaticamente pelo software MediaWiki. Os nomes das categorias podem ser alterados modificando as mensagens de sistema relevantes no domínio {{ns:8}}.",
        "trackingcategories-msg": "Categoria monitorada",
        "trackingcategories-name": "Nome da mensagem",
        "trackingcategories-desc": "Critérios de inclusão",
        "restricted-displaytitle-ignored": "Páginas com títulos de exibição ignorados",
        "restricted-displaytitle-ignored-desc": "Esta página tem um <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ignorado porque não é equivalente ao título verdadeiro da página.",
-       "noindex-category-desc": "A página não é indexada por robôs porque contém a palavra mágica <code><nowiki>__NOINDEX__</nowiki></code> e está num domínio onde o estatuto é permitido.",
+       "noindex-category-desc": "A página não é indexada por robôs porque contém a palavra mágica <code><nowiki>__NOINDEX__</nowiki></code> e está num domínio onde esta palavra mágica é permitida.",
        "index-category-desc": "A página contém a palavra mágica <code><nowiki>__INDEX__</nowiki></code> (e está num domínio em que essa marca é permitida) e, portanto, será indexada pelos robôs mesmo quando normalmente não o seria.",
        "post-expand-template-inclusion-category-desc": "O tamanho da página é superior a <code>$wgMaxArticleSize</code>, após a expansão de todas as predefinições, pelo que algumas predefinições não foram expandidas.",
        "post-expand-template-argument-category-desc": "O tamanho da página é superior a <code>$wgMaxArticleSize</code>, após a expansão de um argumento de predefinição (algo em chavetas triplas, como <code>{{{Foo}}}</code>).",
        "htmlform-cloner-create": "Adicionar mais",
        "htmlform-cloner-delete": "Remover",
        "htmlform-cloner-required": "Pelo menos um valor é necessário.",
+       "htmlform-date-placeholder": "AAAA-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "AAAA-MM-DD HH:MM:SS",
        "htmlform-title-badnamespace": "[[:$1]] não se encontra no domínio \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "\"$1\" não é um título que possa ser atribuído a uma página",
        "htmlform-title-not-exists": "$1 não existe.",
        "feedback-external-bug-report-button": "Assinalar erro técnico",
        "feedback-dialog-title": "Enviar opinião",
        "feedback-dialog-intro": "Pode usar o fácil formulário abaixo para enviar os seus comentários. A sua opinião será adicionada à página \"$1\", juntamente com o seu nome de utilizador(a).",
-       "feedback-error-title": "Erro",
        "feedback-error1": "Erro: O resultado da API não foi reconhecido",
        "feedback-error2": "Erro: A edição falhou",
        "feedback-error3": "Erro: A API não responde",
index 915f629..5d72887 100644 (file)
        "group-bot": "{{doc-group|bot}}\n{{Identical|Bot}}",
        "group-sysop": "{{doc-group|sysop}}\n{{Identical|Administrator}}",
        "group-bureaucrat": "{{doc-group|bureaucrat}}",
-       "group-suppress": "{{doc-group|suppress}}\nThis is an optional (disabled by default) user group, meant for the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}",
+       "group-suppress": "{{doc-group|suppress}}\nThis is an optional (disabled by default) user group, meant for the suppression feature in [[mw:Flow|Flow]]. It is not to be confused with the Oversighters group, which also has access to the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}",
        "group-all": "The name of the user group that contains all users, including anonymous users\n\n{{Identical|All}}",
        "group-user-member": "{{doc-group|user|member}}\n{{Identical|User}}",
        "group-autoconfirmed-member": "{{doc-group|autoconfirmed|member}}",
        "feedback-external-bug-report-button": "A button for submitting an external technical bug report.",
        "feedback-dialog-title": "Title of the feedback dialog",
        "feedback-dialog-intro": "An introduction at the top of the feedback dialog. $1 - Feedback page link",
-       "feedback-error-title": "{{Identical|Error}}",
        "feedback-error1": "Error message, appears when an unknown error occurs submitting feedback",
        "feedback-error2": "Error message, appears when we could not add feedback",
        "feedback-error3": "Error message, appears when we lose our connection to the wiki",
index dadd322..98f7e64 100644 (file)
        "feedback-external-bug-report-button": "Semnalare problemă tehnică",
        "feedback-dialog-title": "Trimitere păreri",
        "feedback-dialog-intro": "Puteți folosi formularul simplificat de mai jos pentru a vă trimite părerile. Comentariul dumneavoastră va fi adăugat în pagina „$1”, alături de numele dumneavoastră de utilizator.",
-       "feedback-error-title": "Eroare",
        "feedback-error1": "Eroare: Rezultat necunoscut de la API",
        "feedback-error2": "Eroare: editarea nu a reușit",
        "feedback-error3": "Eroare: Niciun răspuns de la API",
index 1fc53df..d4e4a5d 100644 (file)
        "feedback-external-bug-report-button": "Отправить техническое задание",
        "feedback-dialog-title": "Отправить отзыв",
        "feedback-dialog-intro": "Вы можете воспользоваться простой формой ниже, чтобы оставить свой отзыв. Комментарий с вашим именем участника будет добавлен на страницу «$1».",
-       "feedback-error-title": "Ошибка",
        "feedback-error1": "Ошибка. Неизвестный результат из API",
        "feedback-error2": "Ошибка. Сбой редактирования",
        "feedback-error3": "Ошибка. Нет ответа от API",
index 3b9e82a..bf9746d 100644 (file)
        "feedback-back": "پوئتي",
        "feedback-cancel": "رد",
        "feedback-close": "ٿي ويو",
-       "feedback-error-title": "چُڪَ",
        "feedback-message": "نياپو:",
        "feedback-subject": "موضوع:",
        "feedback-submit": "جمع ڪرايو",
index 142323f..acdd4eb 100644 (file)
        "upload-form-label-own-work": "ဢၼ်ၼႆႉပဵၼ် ၼႃႈၵၢၼ်တူဝ်ၶႃႈ။",
        "upload-form-label-infoform-categories": "လိူင်ႈ",
        "upload-form-label-infoform-date": "ဝၼ်းထီႉ",
+       "backend-fail-stream": "ဢမ်ႇၸၢင်ႈပိုတ်ႇလႆၾၢႆႇ \"$1\" ဢၼ်ၼႆႉ။",
+       "backend-fail-backup": "ဢမ်ႇၸၢင်ႈႁဵတ်း ၵႅမ်လင်ၾၢႆႇ \"$1\" ၼႆႉ။",
+       "backend-fail-notexists": "ၾၢႆႇ $1 ၼႆႉ မၼ်းဢမ်ႇလႆႈမီးဝႆႉ။",
+       "backend-fail-hashes": "ဢမ်ႇၸၢင်ႈဢဝ် ၾၢႆႇဢၼ်ယွႆႈ တွၼ်ႈတႃႇတေမႃး ၶဵင်ႇထတ်းၵၼ်။",
+       "backend-fail-notsame": "ၾၢႆႇဢၼ်ဢမ်ႇပႆႇလႆႈ ထတ်းသၢင်ႈၼၼ်ႉ မီးဝႆႉလူင်ႈၼႃႈ ၵႃႈတီႈ \"$1\" ။",
+       "backend-fail-invalidpath": "\"$1\" ၼႆႉ သၢႆတၢင်းတႃႇတေသိမ်းမၼ်း ၽိတ်းဝႆႉ။",
+       "backend-fail-delete": "ဢမ်ႇၸၢင်ႈမွတ်ႇပႅတ်ႈ ၾၢႆႇ \"$1\".",
+       "backend-fail-describe": "ဢမ်ႇၸၢင်ႈလႅၵ်ႈလၢႆႈ ၶေႃႈမုၼ်းၽၢႆႇလင် တွၼ်ႈတႃႇၾၢႆႇ \"$1\" ။",
+       "backend-fail-alreadyexists": "ၾၢႆႇ \"$1\" မီးဝႆႉထႃႈယဝ်ႉ။",
+       "backend-fail-store": "ဢမ်ႇၸၢင်ႈသိမ်းၾၢႆႇ \"$1\" ၵႃႈတီႈ \"$2\" ။",
+       "backend-fail-copy": "ဢမ်ႇၸၢင်ႈထုတ်ႇဢဝ် ၾၢႆႇ \"$1\" ၸူး \"$2\".",
+       "backend-fail-move": "ဢမ်ႇၸၢင်ႈၶၢႆႉၾၢႆႇ \"$1\" ၸူး \"$2\" ။",
+       "backend-fail-opentemp": "ဢမ်ႇၸၢင်ႈပိုတ်ႇၾၢႆႇတိုဝ်းၸူဝ်ႈၵႅပ်ႉ။",
+       "backend-fail-writetemp": "ဢမ်ႇၸၢင်ႈတႅမ်ႈသႂ်ႇၸူး ၾၢႆႇတိုဝ်းၸူဝ်ႈၵႅပ်ႉ။",
+       "backend-fail-closetemp": "ဢမ်ႇၸၢင်ႈဢိုတ်း ၾၢႆႇတိုဝ်းၸူဝ်ႈၵႅပ်ႉ။",
+       "backend-fail-read": "ဢမ်ႇၸၢင်ႈလူၾၢႆႇ \"$1\" ။",
+       "backend-fail-create": "ဢမ်ႇၸၢင်ႈတႅမ်ႈၾၢႆႇ \"$1\" ။",
+       "backend-fail-maxsize": "ဢမ်ႇၸၢင်ႈတႅမ်ႈၾၢႆႇ \"$1\" ယွၼ်ႉပိူဝ်ႈဝႃႈ မၼ်းၼႆႉ ယႂ်ႇလိူဝ်သေ {{PLURAL:$2|ၼိုင်ႈပၢႆႉ|$2 ပၢႆႉ}}",
+       "backend-fail-readonly": "တီႈသိမ်းသုတ်းလင် \"$1\" ၼႆႉ ယၢမ်းလဵဝ် မၼ်းတေၸၢင်ႈလူလႆႈၵူၺ်း။ လွင်ႈတၢင်း ဢၼ်လႆႈပၼ်ဝႆႉတႄႉ ပဵၼ် <em>$2</em>",
+       "backend-fail-synced": "ၾၢႆႇ \"$1\" ၼႆႉ မၼ်းပဵၼ်သၢႆႇငၢႆ ဢၼ်ဢမ်ႇမႃးငမ်ႇမႅၼ်ႈၵၼ် တီႈၼႂ်း ဢွင်ႈသိမ်းသုတ်းလင် ၽၢႆႇၼႂ်း။",
+       "backend-fail-connect": "ဢမ်ႇၸၢင်ႈၵွင်ႉသိုပ်ႇၸူး ဢွင်ႈသိမ်းသုတ်းလင် \"$1\" ။",
+       "backend-fail-internal": "လွင်ႈၽိတ်းပိူင်ႈ ဢၼ်ဢမ်ႇႁူႉလွင်ႈမၼ်း လႆႈပဵၼ်ဝႆႉ ၵႃႈတီႈၼႂ်း ဢွင်ႈသိမ်းသုတ်းလင် \"$1\" ။",
+       "backend-fail-contenttype": "ဢမ်ႇၸၢင်ႈတႅပ်းတတ်းလိူင်ႈၾၢႆႇ ဢၼ်တႃႇတေသိမ်းၵႃႈတီႈ \"$1\" ။",
+       "backend-fail-batchsize": "ဢွင်ႈတီႈသိမ်းသုတ်းလင် ထုၵ်ႇပၼ်ဝႆႉ ၸုမ်းၾၢႆႇ $1 ၾၢႆႇ\n{{PLURAL:$1|ၵၢၼ်ႁဵတ်း|ၵၢၼ်ႁဵတ်း}}; တီႈမၵ်းၶၢၼ်းမၼ်းပဵၼ် $2\n{{PLURAL:$2|ၵၢၼ်ႁဵတ်း|ၵၢၼ်ႁဵတ်း}}.",
+       "backend-fail-usable": "ဢမ်ႇၸၢင်ႈလူ ဢမ်ႇၼၼ် ဢမ်ႇၸၢင်ႈတႅမ်ႈ ၾၢႆႇ \"$1\" ယွၼ်ႉပိူဝ်ႈဝႃႈ ၵၢၼ်လူတ်းပွႆႇ ဢမ်ႇတဵမ်ထူၼ်ႈ ဢမ်ႇၼၼ် ႁၢမ်းဝႆႉ ဢွင်ႈတီႈသိမ်း။",
+       "filejournal-fail-dbconnect": "ဢမ်ႇၸၢင်ႈၵွင်ႉသိုပ်ႇၸူး ယေးၶေႃႈမုၼ်း ၵျႃႇၼႄႇ တွၼ်ႈတႃႇ ဢွင်ႈတီႈသိမ်းသုတ်းလင် \"$1\" ။",
+       "filejournal-fail-dbquery": "ဢမ်ႇၸၢင်ႈဢၢပ်ႉတိတ်ႉ ယေးၶေႃႈမုၼ်း ၵျႃႇၼႄႇ တွၼ်ႈတႃႇ ဢွင်ႈတီႈသိမ်းသုတ်းလင် \"$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-bucket": "ဢမ်ႇၸၢင်ႈၵပ်းသိုပ်ႇလႆႈ ယေးၶေႃႈမုၼ်းဢၼ်ၶတ်းဝႆႉ ၵႃႈတီႈၼႂ်း ပုင်း $1 ၼၼ်ႉ။",
        "lockmanager-fail-db-release": "ဢမ်ႇၸၢင်ႈပွႆႇပၼ်ၵၢၼ်ၶတ်း ၵႃႈတီႈ ယွင်ၶေႃႈမုၼ်း $1 ။",
        "lockmanager-fail-svr-acquire": "ဢမ်ႇၸၢင်ႈဢဝ်လႆႈ ၵၢၼ်ၶတ်း ၵႃႈတီႈၼိူဝ် သႃႇပိူဝ်ႇ $1 ။",
        "lockmanager-fail-svr-release": "ဢမ်ႇၸၢင်ႈပွႆႇပၼ် ၵၢၼ်ၶတ်း ၵႃႈတီႈၼိူဝ် သႃႇပိူဝ်ႇ $1 ။",
+       "zip-file-open-error": "ၽွင်းမိူဝ်ႈပိုတ်ႇၽၢႆႇ တွၼ်ႈတႃႇ ၵူတ်ႇထတ်း ZIP ၼၼ်ႉ လႆႈထူပ်းၺႃး လွင်ႈၽိတ်းပိူင်ႈ။",
        "zip-wrong-format": "ၾၢႆႇဢၼ်မၵ်းမၼ်ႈဝႆႉပၼ်ၼၼ်ႉ မၼ်းဢမ်ႇၸႂ်ႈ ၾၢႆႇ ZIP ။",
        "zip-bad": "ၾၢႆႇၼႆႉ မၼ်းၵွႆဝႆႉ ဢမ်ႇၼၼ် မၼ်းပဵၼ် ၾၢႆႇ ZIP ဢၼ်ဢမ်ႇလူႇလႆႈ။\nမၼ်းဢမ်ႇၸၢင်ႈ ၵူတ်ႇတူၺ်း တွၼ်ႈတႃႇ ပၢႆးႁူမ်ႇလူမ်ႈ လႆႈလီလီ။",
        "zip-unsupported": "ၾၢႆႇၼႆႉပဵၼ် ZIP ၾၢႆႇ ဢၼ်ၸႂ်ႉဝႆႉ ၽၢင်ႁၢင်ႈၵၢၼ် ZIP ဢၼ် သိူဝ်ႇၶၢဝ်ႇဝီႇၶီႇ ဢမ်ႇၵမ်ႉထႅမ်ဝႆႉ။  မၼ်းဢမ်ႇၸၢင်ႈ ၵူတ်ႇတူၺ်း တွၼ်ႈတႃႇ ပၢႆးႁူမ်ႇလူမ်ႈ လႆႈလီလီ။",
        "uploadstash": "လူတ်ႇၶိုၼ်ႈ ၵၢၼ်သိူင်ႇ",
+       "uploadstash-summary": "ၼႃႈလိၵ်ႈၼႆႉ ၵမ်ႉထႅမ်ပၼ် တႃႇၶဝ်ႈၸႂ်ႉ ၾၢႆႇဢၼ်လူတ်ႇၶိုၼ်ႈဝႆႉ ဢမ်ႇၼၼ် ဢၼ်တိုၵ်ႉမီးၼႂ်းၵၢၼ်လူတ်ႇၶိုၼ်ႈၼၼ်ႉ၊ ၵူၺ်းၵႃႈ တေပဵၼ်ၸိူဝ်းဢၼ်ဢမ်ႇပႆႇလႆႈ ​ပိုတ်ႇၽႄ ၸူးၵႃႈတီႈ ဝီႇၶီႇ။ ၾၢႆႇၸိူဝ်းၼႆႉၼႆႉ ၵူၼ်းတၢင်ႇၸိူဝ်းတေဢမ်ႇလႆႈႁၼ်၊ ၵူၺ်းၵႃႈဝႃႈ ၵူၼ်းၸိူဝ်းလူတ်ႇၶိုၼ်ႈၶဝ်ႈတႄႉ တေၸၢင်ႈႁၼ်လႆႈယူႇ။",
        "uploadstash-clear": "ၽဵဝ်ႈလၢင်ႈ ၾၢႆႇၸိူဝ်းသိူင်ႇသိမ်းဝႆႉ",
        "uploadstash-nofiles": "တီႈၸဝ်ႈၵဝ်ႇ ဢမ်ႇမီးၾၢႆႇသိူင်ႇသိမ်းသင်။",
+       "uploadstash-badtoken": "ႁဵတ်းသၢင်ႈ ၸိူဝ်းလွင်ႈႁဵတ်းဢၼ်လႆႈ တူၵ်းသုမ်းၵႂႃႇၼၼ်ႉ၊ မၢင်ၽဝ်ႇတေပဵၼ်ယွၼ်ႉလူၺ်ႈ ၶေႃႈၵမ်ႉတူဝ် ၵၢၼ်မႄးထတ်းၸဝ်ႈၵဝ်ႇၼၼ်ႉ ဢႃႇယုသုတ်းၵႂႃႇလႄႈႁိုဝ်။ ၶိုၼ်းမႄး ၶတ်းၸႂ်တူၺ်းထႅင်ႈၵမ်းၼိုင်ႈလႄႈ။",
        "uploadstash-errclear": "လွင်ႈၽဵဝ်ႈလၢင်ႉၾၢႆႇဢမ်ႇၶႅမ်ႉလႅပ်ႈ။",
        "uploadstash-refresh": "သၢႆႇၶိုၼ်း သဵၼ်ႈမၢႆၾၢႆႇ",
        "uploadstash-thumbnail": "တူၺ်းၼင်ႇ ႁၢင်ႈလဵၵ်ႉ",
+       "uploadstash-exception": "ဢမ်ႇၸၢင်ႈသိမ်း ၵၢၼ်လူတ်ႇၶိုၼ်ႈ ၵႃႈတီႈၼႂ်း တီႈသိူင်ႇ($1): \"$2\" ။",
+       "invalid-chunk-offset": "ပွတ်းတူဝ်ၵၢမ်း ၽိတ်းပိူင်ႈ",
        "img-auth-accessdenied": "ၵၢၼ်ၸႂ်ႉတိုဝ်း ထုၵ်ႇထဵင်ၶိုၼ်း။",
+       "img-auth-notindir": " သၢႆတၢင်းဢၼ်တုၵ်းယွၼ်းမႃးၼၼ်ႉ မၼ်းဢမ်ႇမီးၵႃႈတီႈၼႂ်း သၢႆတၢင်းလူတ်ႇၶိုၼ်ႈ ဢၼ်မႄးၵုမ်းဝႆႉ။",
        "img-auth-nologinnWL": "ၸဝ်ႈၵဝ်ႇ ဢမ်ႇလႆႈၶဝ်ႈ လွၵ်ႉဢိၼ်ဝႆႉသေ \"$1\" ၼႆႉ မၼ်းဢမ်ႇမီးဝႆႉ တီႈၼႂ်း သဵၼ်ႈမၢႆၶၢဝ်။",
        "img-auth-nofile": "ၾၢႆႇ \"$1\" ၼႆႉ မၼ်းဢမ်ႇမီးဝႆႉ။",
        "img-auth-isdir": "ၸဝ်ႈၵဝ်ႇ တိုၵ်ႉၶတ်းၸႂ်ႉ ၶဝ်ႈၸႂ်ႉ ၾူဝ်ႇတိူဝ်ႇ \"$1\" ယူႇ။\nၶႂၢင်းဝႆႉပၼ် ၵၢၼ်ၸႂ်ႉတိုဝ်းၾၢႆႇၵူၺ်း။",
index a2e0dac..863a822 100644 (file)
        "feedback-external-bug-report-button": "Založiť technickú úlohu",
        "feedback-dialog-title": "Odoslať názor",
        "feedback-dialog-intro": "Pomocou formulára nižšie môžete odoslať svoj názor. Váš komentár sa spolu s vašim použivateľským menom pridá na stránku „$1“.",
-       "feedback-error-title": "Chyba",
        "feedback-error1": "Chyba: Nerozpoznaný výsledok z API",
        "feedback-error2": "Chyba: Úprava sa nepodarila",
        "feedback-error3": "Chyba: Žiadna odpoveď z API",
index bc4f06a..49aa2b5 100644 (file)
        "searchprofile-advanced-tooltip": "Iskanje v imenskih prostorih po meri",
        "search-result-size": "$1 ({{PLURAL:$2|1 beseda|2 besedi|$2 besede|$2 besed|$2 besed}})",
        "search-result-category-size": "$1 {{PLURAL:$1|član|člana|člani|članov}} ($1 {{PLURAL:$2|podkategorija|podkategoriji|podkategorije|podkategorij}}, $1 {{PLURAL:$3|datoteka|datoteki|datoteke|datotek}})",
-       "search-redirect": "(preusmeritev $1)",
+       "search-redirect": "(preusmeritev s strani $1)",
        "search-section": "(razdelek $1)",
        "search-category": "(kategorija $1)",
        "search-file-match": "(ujema se z vsebino datoteke)",
        "feedback-external-bug-report-button": "Vloži tehnično opravilo",
        "feedback-dialog-title": "Pošljite povratne informacije",
        "feedback-dialog-intro": "Spodnji enostavni obrazec lahko uporabite za pošiljanje povratnih informacij. Vašo pripombo bomo dodali na stran »$1« skupaj z vašim uporabniškim imenom.",
-       "feedback-error-title": "Napaka",
        "feedback-error1": "Napaka: Neznan rezultat iz API",
        "feedback-error2": "Napaka: Urejanje je spodletelo",
        "feedback-error3": "Napaka: Ni odgovora od API",
index fce5916..2485d1c 100644 (file)
        "yourpasswordagain": "Markale qor ereysirka:",
        "createacct-yourpasswordagain": "Hubi erey sirta",
        "createacct-yourpasswordagain-ph": "Mar kale gali erey sirta",
-       "remembermypassword": "Kumbuyuutarkaan ku xasuusnaaw magaceyga gudagalka (ilaa  $1 {{PLURAL:$1|maalin|maalmood}})",
        "userlogin-remembermypassword": "Ka dhig mid diyaar ah mar waliba",
        "userlogin-signwithsecure": "Adeegso khad la hubo",
        "yourdomainname": "Magacaga shabakada",
        "gender-female": "Dhedig",
        "prefs-help-gender": "Ahaysiinta xulashaan waa mid dooqasha ah.\nBarnaamijkaan adeegsigiisa waxay qiima u yeelaysaa wadahadalkaada iyo kuwa kale iyadoo bedelaysa habka qofka sida lab ama dheddig.\nMacluumadkaan waa mid la wada arkayo.",
        "email": "E-mail",
-       "prefs-help-realname": "Optional: if you choose to provide it this will be used for giving you attribution for your work.",
+       "prefs-help-realname": "Magaca runta ah waa dooqasho.\nHaddii aad doorato in aan keento, waxaa loo adeegsan karaa mid tilmaama howshaada.",
        "prefs-help-email": "E-mail waa wax aad xor u leedahay. laakiin waa loo baahanyahay hadii aad eraysirka badaleesid, hadii aad ilaawdo eraygaaga sirta ah",
        "prefs-help-email-others": "Waxaa kale oo aad u isticmaali kartaa in ee dadka kale kugula soo xiriiraan e-mail ayaga oo isticmaalaayo linki isticmaalahaada ama bogga wadahadalka.\nE-mailkaada mala sheegaayo markii ee dadka kale kula soo xiriirayaan.",
        "prefs-help-email-required": "Waxaa loo baahanyahay e-mail.",
index 540c6c0..c7f0086 100644 (file)
        "eauthentsent": "Ett e-postmeddelande för bekräftelse har skickats till den angivna e-postadressen.\nInnan någon annan e-post kan skickas till kontot, måste du följa instruktionerna i e-postmeddelandet för att bekräfta att kontot verkligen är ditt.",
        "throttled-mailpassword": "En lösenordsåterställning har redan skickats för mindre än {{PLURAL:$1|en timme|$1 timmar}} sedan.\nFör att förhindra missbruk skickas bara en lösenordsåterställning per {{PLURAL:$1|timme|$1-timmarsperiod}}.",
        "mailerror": "Fel vid skickande av e-post: $1",
-       "acct_creation_throttle_hit": "Besökare till den här wikin som har använt din IP-adress har skapat {{PLURAL:$1|ett användarkonto|$1 användarkonton}} under det senaste dygnet, vilket är det maximalt tillåtna inom den tidsperioden.\nSom ett resultat kan besökare som använder den här IP-adressen inte skapa några fler användarkonton just nu.",
+       "acct_creation_throttle_hit": "Besökare till den här wikin som har använt din IP-adress har skapat {{PLURAL:$1|ett användarkonto|$1 användarkonton}} under det senaste $2, vilket är det maximalt tillåtna inom den tidsperioden.\nSom ett resultat kan besökare som använder den här IP-adressen inte skapa några fler användarkonton just nu.",
        "emailauthenticated": "Din e-postadress bekräftades den $2 kl. $3.",
        "emailnotauthenticated": "Din e-postadress är ännu inte bekräftad. Ingen e-post kommer att skickas vad gäller det följande funktionerna.",
        "noemailprefs": "Uppge en e-postadress i dina inställningar för att få dessa funktioner att fungera.",
        "searchprofile-advanced-tooltip": "Sök i vissa namnrymder",
        "search-result-size": "$1 ({{PLURAL:$2|1 ord|$2 ord}})",
        "search-result-category-size": "{{PLURAL:$1|1 medlem|$1 medlemmar}} ({{PLURAL:$2|1 underkategori|$2 underkategorier}}, {{PLURAL:$3|1 fil|$3 filer}})",
-       "search-redirect": "(omdirigering $1)",
+       "search-redirect": "(omdirigering från $1)",
        "search-section": "(avsnitt $1)",
        "search-category": "(kategorin $1)",
        "search-file-match": "(överensstämmer filens innehåll)",
        "upload-dialog-disabled": "Filuppladdningar med denna dialogruta har inaktiverats på denna wiki.",
        "upload-dialog-title": "Ladda upp fil",
        "upload-dialog-button-cancel": "Avbryt",
+       "upload-dialog-button-back": "Tillbaka",
        "upload-dialog-button-done": "Klar",
        "upload-dialog-button-save": "Spara",
        "upload-dialog-button-upload": "Ladda upp",
        "htmlform-date-placeholder": "ÅÅÅÅ-MM-DD",
        "htmlform-time-placeholder": "TT:MM:SS",
        "htmlform-datetime-placeholder": "ÅÅÅÅ-MM-DD TT:MM:SS",
+       "htmlform-date-invalid": "Värdet du angav är inte ett igenkänt datum. Prova att använda formatet ÅÅÅÅ-MM-DD.",
+       "htmlform-time-invalid": "Värdet du angav är inte en igenkänd tid. Prova att använda formatet HH:MM:SS.",
+       "htmlform-datetime-invalid": "Värdet du angav är inte ett igenkänt datum och tid. Prova att använda formatet ÅÅÅÅ-MM-DD HH:MM:SS.",
+       "htmlform-date-toolow": "Värdet du angav är innan det tidigaste tillåtna datumet för $1.",
+       "htmlform-date-toohigh": "Värdet du angav är efter det senaste tillåtna datumet $1.",
+       "htmlform-time-toolow": "Värdet du angav är innan den tidigaste tillåtna tiden för $1.",
+       "htmlform-time-toohigh": "Värdet du angav är efter den senaste tillåtna tiden $1.",
+       "htmlform-datetime-toolow": "Värdet du angav är innan det tidigaste tillåtna datumet och tiden för $1.",
+       "htmlform-datetime-toohigh": "Värdet du angav är efter det senaste tillåtna datumet och tiden $1.",
        "htmlform-title-badnamespace": "[[:$1]] är inte i \"{{ns:$2}}\"-namnrymden.",
        "htmlform-title-not-creatable": "\"$1\" är inte en sidtitel som kan skapas",
        "htmlform-title-not-exists": "$1 finns inte.",
        "feedback-external-bug-report-button": "Registrera en teknisk uppgift",
        "feedback-dialog-title": "Skicka återkoppling",
        "feedback-dialog-intro": "Du kan använda det enkla formuläret nedan för att skicka in din återkoppling. Din kommentar kommer att läggas till på sidan \"$1\" tillsammans med ditt användarnamn.",
-       "feedback-error-title": "Fel",
        "feedback-error1": "Fel: Okänt resultat från API",
        "feedback-error2": "Fel: Redigeringen misslyckades",
        "feedback-error3": "Fel: Inget svar från API",
        "unlinkaccounts-success": "Kontot avlänkades.",
        "authenticationdatachange-ignored": "Ändringen av autentiseringsdata hanterades inte. Kanske ingen tillhandahållare har konfigurerats?",
        "userjsispublic": "Observera: JavaScript-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare.",
-       "usercssispublic": "Observera: CSS-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare."
+       "usercssispublic": "Observera: CSS-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare.",
+       "restrictionsfield-badip": "Ogiltig IP-adress eller intervall: $1",
+       "restrictionsfield-label": "Tillåtna IP-intervall:",
+       "restrictionsfield-help": "En IP-adress eller CIDR-intervall per rad. För att aktivera allting, använd<br><code>0.0.0.0/0</code><br><code>::/0</code>"
 }
index 5009a53..b7cadee 100644 (file)
        "password-change-forbidden": "Сез бу викидә серсүзне үзгәртә алмыйсыз.",
        "externaldberror": "Тышкы мәгълүмат базасы ярдәмендә аутентификация үткәндә хата чыкты, яисә тышкы хисап язмагызга үзгәрешләр кертү хокукыгыз юк.",
        "login": "Керү",
+       "login-security": "Шәхесегезне раслагыз",
        "nav-login-createaccount": "Керү / теркәлү",
        "userlogin": "Керү / теркәлү",
        "userloginnocreate": "Керү",
        "minoredit": "Бу кече үзгәртү",
        "watchthis": "Бу битне күзәтү",
        "savearticle": "Битне саклау",
+       "savechanges": "Үзгәртүләрне саклау",
+       "publishpage": "Бит ясау",
+       "publishchanges": "Битне бастыру",
        "preview": "Алдан карау",
        "showpreview": "Алдан карау",
        "showdiff": "Кертелгән үзгәртүләр",
        "exif-copyrighted": "Автор хокуклары халәте:",
        "exif-copyrightowner": "Автор хокуклары иясе",
        "exif-usageterms": "Куллану шартлары",
-       "exif-orientation-1": "Ð\9dоÑ\80малÑ\8c",
+       "exif-orientation-1": "Ð\93адÓ\99Ñ\82и",
        "exif-orientation-3": "180° ка борылган",
        "exif-componentsconfiguration-0": "юк",
        "exif-exposureprogram-0": "Билгесез",
        "feedback-bugornote": "Әгәр дә сез техник проблеманы җентекләп тасвирларга әзер икәнсез, зинһар өчен, [$1 хата турында хәбәр итегез].\nБашка очракта сез түбәндәге гади форманы куллана аласыз. Сезнең шәрехләмә \"[$3 $2]\" сәхифәсенә сезнең кулланучы исеме һәм сез кулланган браузер исеме белән бергә өстәләчәк.",
        "feedback-cancel": "Баш тарту",
        "feedback-close": "Әзер",
-       "feedback-error-title": "Хата",
        "feedback-error1": "Хата. APIдан билгесез нәтиҗә",
        "feedback-error2": "Хата: төзәтү уңышсыз килеп чыкты",
        "feedback-error3": "Хата: APIдан җавап юк.",
index abf9f33..a5e2198 100644 (file)
        "resetpass-expired": "آپ کے پاس ورد کی مدت ختم ہو چکی ہے۔ داخل ہونے کے لیے براہ کرم نیا پاس ورڈ بنائیں۔",
        "resetpass-expired-soft": "آپ کے پاس ورڈ کی مدت ختم ہو چکی ہے، لہذا اسے دوبارہ بنانے کی ضرورت ہے۔\nبراہ کرم نیا پاس ورڈ بنائیں، تاہم اگر مستقبل میں اس کی ترتیب نو مقصود ہو تو «{{int:authprovider-resetpass-skip-label}}» پر کلک کریں۔",
        "resetpass-validity-soft": "آپ کا پاس ورڈ درست نہیں: $1\n\nبراہ کرم نیا پاس ورڈ بنائیں، تاہم اگر مستقبل میں اس کی ترتیب نو مقصود ہو تو «{{int:authprovider-resetpass-skip-label}}» پر کلک کریں۔",
-       "passwordreset": "پارÙ\84Ù\81ظ Ú©Û\8c Ø¨Ø§Ø²ØªØ¹Û\8cÙ\86Û\8c",
+       "passwordreset": "پاس Ù\88رÚ\88 Ú©Û\8c ØªØ±ØªÛ\8cب Ù\86Ù\88",
        "passwordreset-text-one": "برقی خط کے ذریعہ عارضی پاس ورڈ حاصل کرنے کے لیے اس فارم کو پُر کریں۔",
        "passwordreset-text-many": "{{PLURAL:$1|برقی خط کے ذریعہ عارضی پاس ورڈ حاصل کرنے کے لیے کسی ایک خانے کو پُر کریں۔}}",
        "passwordreset-disabled": "اس ویکی پر پاس ورڈ کی ترتیب نو کی سہولت فعال نہیں ہے۔",
        "passwordreset-ignored": "پاس ورڈ کی ترتیب نو مکمل نہیں ہو سکی۔ شاید کوئی پرووائڈر فراہم نہیں کیا گیا؟",
        "passwordreset-invalideamil": "نادرست برقی ڈاک پتا",
        "passwordreset-nodata": "کوئی صارف نام اور نہ کوئی برقی ڈاک پتا فراہم کیا گیا",
-       "changeemail": "برقی ڈاک پتا تبدیل یا حذف کریں",
+       "changeemail": "برقی ڈاک پتے میں تبدیلی یا حذف شدگی",
        "changeemail-header": "اپنے برقی ڈاک پتے کو تبدیل کرنے کے لیے اس فارم کو پُر کریں۔ اگر آپ اپنے کھاتے سے منسلک کسی برقی ڈاک پتے کو ختم کرنا چاہتے ہیں تو فارم پُر کرنے کے دوران میں نئے برقی ڈاک پتے کا خانہ خالی چھوڑ دیں۔",
        "changeemail-no-info": "اِس صفحہ تک براہِ راست رسائی کیلئے آپ کو داخل ہونا پڑے گا۔",
        "changeemail-oldemail": "حالیہ برقی ڈاک پتہ:",
        "changeemail-submit": "برقی ڈاک تبدیل کریں",
        "changeemail-throttled": "آپ نے متعدد مرتبہ داخل ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار فرمائیں۔",
        "changeemail-nochange": "براہ کرم کوئی دوسرا برقی ڈاک پتہ درج کریں۔",
-       "resettokens": "ٹوکنوں کو دوبارہ ترتیب دیں",
+       "resettokens": "ٹوکنوں کی ترتیب نو",
        "resettokens-no-tokens": "ترتیب نو کے لیے کوئی ٹوکن موجود نہیں۔",
        "resettokens-tokens": "ٹوکن:",
        "resettokens-token-label": "$1 (موجودہ قدر: $2)",
        "filestatus": "کاپی رائٹ کی صورت حال:",
        "filesource": "ذرائع",
        "ignorewarning": "انتباہ نظر انداز کرتے ہوئے فائل کو بہرصورت محفوظ کر لیا جائے",
-       "ignorewarnings": "ہر انتباہ نظرانداز کردیا جاۓ۔",
+       "ignorewarnings": "تمام انتباہات کو نظر انداز کریں",
        "minlength1": "فائل کے ناموں میں کم از کم ایک حرف ہونا ضروری ہے۔",
        "illegalfilename": "اس فائل کے نام \"$1\" میں ایسے حروف موجود ہیں جو صفحہ کے عنوانات میں ممنوع ہیں۔\nبراہ کرم فائل کا نام تبدیل کرکے دوبارہ اپلوڈ کرنے کی کوشش کریں۔",
        "filename-toolong": "فائل کے نام 240 بائٹ سے زیادہ طویل نہ ہوں۔",
        "upload-maxfilesize": "فائل کا زیادہ سے زیادہ حجم: $1",
        "upload-description": "فائل کی وضاحت",
        "upload-options": "اپلوڈ کے اختیارات",
-       "watchthisupload": "یہ صفحہ زیر نظر کریں",
+       "watchthisupload": "اس فائل کو زیر نظر کریں",
        "upload-proto-error": "غلط پروٹوکول",
        "upload-file-error": "داخلی نقص",
        "upload-misc-error": "اپلوڈ کے دوران میں نامعلوم نقص",
        "upload-curl-error6-text": "فراہم کردہ یوآرایل قابل رسائی نہیں ہے۔\nبراہ کرم اس یوآرایل کو دوبارہ جانچ لیں کہ آیا وہ درست ہے اور متعلقہ سائٹ فعال ہے یا نہیں۔",
        "upload-curl-error28": "اپلوڈ کی مہلت ختم",
        "upload-curl-error28-text": "یہ سائٹ جواب دینے میں بہت زیادہ وقت لے رہی ہے۔\nبراہ کرم اس سائٹ کو جانچ لیں کہ آیا وہ فعال ہے یا نہیں، اور کچھ دیر انتظار کرنے کے بعد دوبارہ کوشش کریں۔\nشاید آپ اسے کم مصروف وقت میں آزمانا چاہیں۔",
-       "license": "اجازہ:",
+       "license": "اجازت نامہ:",
        "license-header": "اجازہ کاری",
        "nolicense": "غیر منتخب",
        "licenses-edit": "اجازت نامہ کے اختیارات میں ترمیم کریں",
        "listduplicatedfiles": "مکررات کے ساتھ فائلوں کی فہرست",
        "listduplicatedfiles-summary": "اس صفحہ میں ان فائلوں کی فہرست موجود ہے جن کا حالیہ نسخہ کسی دوسری فائل کے تازہ ترین نسخہ کی نقل ہے۔ اس فہرست میں محض مقامی فائلیں درج کی گئی ہیں۔",
        "listduplicatedfiles-entry": "[[:File:$1|$1]] [[$3|{{PLURAL:$2|کی ایک نقل ہے|$2 نقلیں ہیں}}]]۔",
-       "unusedtemplates": "غیر استعمال شدہ سانچے",
+       "unusedtemplates": "غیر مستعمل سانچے",
        "unusedtemplatestext": "اس صفحہ میں {{ns:template}} نام فضا کے ان تمام صفحات کی فہرست درج ہے جو کسی دوسرے صفحہ میں شامل نہیں ہیں۔\nانہیں حذف کرنے سے قبل سانچوں سے مربوط دیگر صفحات کو جانچنا نہ بھولیں۔",
        "unusedtemplateswlh": "دیگر روابط",
        "randompage": "جستہ جستہ مطالعہ",
        "withoutinterwiki-summary": "درج ذیل صفحات دوسری زبان کے صفحات سے مربوط نہیں ہیں۔",
        "withoutinterwiki-legend": "سابقہ",
        "withoutinterwiki-submit": "دکھائیں",
-       "fewestrevisions": "کم نظرِ ثانی شدہ مضامین",
+       "fewestrevisions": "کم ترامیم کے حامل صفحات",
        "nbytes": "$1 {{PLURAL:$1|بائٹ}}",
        "ncategories": "{{PLURAL:$1|زمرہ|زمرہ جات}} $1",
        "ninterwikis": "$1 {{PLURAL:$1|بین الویکی ربط|بین الویکی روابط}}",
        "uncategorizedcategories": "بے زمرہ زمرہ جات",
        "uncategorizedimages": "بے زمرہ تصاویر",
        "uncategorizedtemplates": "غیر زمرہ بند سانچہ جات",
-       "unusedcategories": "غیر استعمال شدہ زمرہ جات",
-       "unusedimages": "غیر استعمال شدہ فائلیں",
+       "unusedcategories": "غیر مستعمل زمرہ جات",
+       "unusedimages": "غیر مستعمل فائلیں",
        "wantedcategories": "مطلوبہ زمرہ جات",
-       "wantedpages": "درخواست شدہ مضامین",
+       "wantedpages": "مطلوبہ مضامین",
        "wantedpages-summary": "ذیل میں ان غیر موجود صفحات کی فہرست ہے جن سے بہت سارے روابط مربوط ہیں، البتہ ان میں وہ صفحات شامل نہیں جن میں محض ان سے مربوط رجوع مکررات موجود ہیں۔ ان صفحوں کو دیکھنے کے لیے [[{{#special:BrokenRedirects}}|شکستہ روابط کی فہرست]] ملاحظہ فرمائیں۔",
        "wantedpages-badtitle": "نتائج میں نادرست عنوان: $1",
        "wantedfiles": "مطلوب تصاویر",
        "listusers-noresult": "یہ صارف نہیں ملا",
        "listusers-blocked": "(مسدود)",
        "activeusers": "متحرک صارفین کی فہرست",
-       "activeusers-intro": "ذیل میں ان صارفین کی فہرست ہے جو گزشتہ $1 {{PLURAL:$1|دن|دنوں}} میں کسی وقت فعال رہے ہوں۔",
+       "activeusers-intro": "ذیل میں ان صارفین کی فہرست ہے جو گزشتہ $1 {{PLURAL:$1|دن|دنوں}} میں کسی بھی قسم کی سرگرمی میں شریک رہے ہوں۔",
        "activeusers-count": "گزشتہ {{PLURAL:$3|دن|$3 دنوں}} میں $1 {{PLURAL:$1|اقدام|اقدامات}}",
        "activeusers-from": "اس حرف سے شروع ہونے والے صارفین کے نام دکھائیں:",
-       "activeusers-hidebots": "پوشیدہ خود کار صارف",
-       "activeusers-hidesysops": "Ù¾Ù\88Ø´Û\8cدÛ\81 Ù\85Ù\86تظÙ\85Û\8cÙ\86",
+       "activeusers-hidebots": "خودکار صارفین کو چھپائیں",
+       "activeusers-hidesysops": "Ù\85Ù\86تظÙ\85Û\8cÙ\86 Ú©Ù\88 Ú\86ھپائÛ\8cÚº",
        "activeusers-noresult": "یہ صارف نہیں مل سکا",
        "activeusers-submit": "فعال صارفین دکھائیں",
        "listgrouprights": "صارف گروہوں کے اختیارات",
        "blankpage": "خالی صفحہ",
        "intentionallyblankpage": "اس صفحہ کو دانستہ خالی چھوڑا گیا ہے۔",
        "external_image_whitelist": "#اس سطر کو ہو بہو ایسا ہی رہنے دیں<pre>\n#ذیل میں ریجیکس کی عبارتیں درج کریں (محض // کے درمیان)\n#ان عبارتوں کی بیرونی تصویروں کے روابط سے مطابقت کی جائے گی\n#جو مطابق ہو جائیں وہ تصویر کے طور پر نظر آئیں گے ورنہ محض تصویر کا ربط ظاہر ہوگا\n# علامت # سے شرع ہونے والی سطروں کو تبصرہ سمجھا جائے گا\n#چھوٹے بڑے حروف کو نظر انداز کیا جائے گا\n\nریجیکس کی تمام عبارتوں کو اس سطر کے اوپر رکھیں۔ اس سطر کو ہو بہو ایسا ہی رہنے دیں</pre>",
-       "tags": "تبدÛ\8cÙ\84Û\8c Ú©Û\92 Ø¯Ø±Ø³Øª ٹیگ",
+       "tags": "درست ØªØ¨Ø¯Û\8cÙ\84Û\8c Ú©Û\92 ٹیگ",
        "tag-filter": "مقطار [[Special:Tags|ٹیگ]]:",
        "tag-filter-submit": "مقطار",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ}}]]: $2)",
        "feedback-external-bug-report-button": "تکنیکی خامی کی اطلاع دیں",
        "feedback-dialog-title": "تبصرہ روانہ کریں",
        "feedback-dialog-intro": "اپنا تبصرہ شائع کرنے کے لیے ذیل میں موجود فارم کو استعمال کر سکتے ہیں۔ آپ کا تبصرہ آپ کے صارف نام کے ساتھ صفحہ «$1» میں شامل کر دیا جائے گا۔",
-       "feedback-error-title": "نقص",
        "feedback-error1": "نقص: اے پی آئی کی جانب سے غیر معروف نتیجہ",
        "feedback-error2": "نقص: ترمیم ناکام ہو گئی",
        "feedback-error3": "نقص: اے پی آئی سے کوئی جواب نہیں",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|بائٹ}}",
        "limitreport-expansiondepth": "توسیع کی بلند ترین گہرائی",
        "limitreport-expensivefunctioncount": "کثیر الاستعمال پارسر فنکشنوں کی تعداد",
-       "expandtemplates": "سانچے کو وسیع کریں",
+       "expandtemplates": "سانچوں کی توسیع",
        "expand_templates_intro": "اس خصوصی صفحہ میں ویکی کی عبارتوں کو اخذ کرکے ان میں موجود تمام مستعمل سانچوں کو کھولا جاتا ہے۔\nنیز اس صفحہ میں <code><nowiki>{{</nowiki>#language:…}}</code> جیسے پارسر فنکشنوں اور <code><nowiki>{{</nowiki>CURRENTDAY}}</code> جیسے متغیرات کی معاونت بھی رکھی گئی ہے۔\nدرحقیقت یہاں ہر چیز کو دوہرے محرابی قوسین میں کھول دیا جاتا ہے۔",
        "expand_templates_title": "اس عبارت کا عنوان، مثلاً {{FULLPAGENAME}} وغیرہ کے لیے:",
        "expand_templates_input": "ان پٹ متن:",
index 71b389f..25b3d3d 100644 (file)
                        "Arystanbek",
                        "6ahodir",
                        "Таржимон",
-                       "Ximik1991"
+                       "Ximik1991",
+                       "Bmansurov"
                ]
        },
-       "tog-underline": "Havolalarning tagiga chizish:",
+       "tog-underline": "Havolaning tagiga chizish:",
        "tog-hideminor": "Yangi oʻzgarishlar roʻyxatida kichik tahrirlarni yashirish",
        "tog-hidepatrolled": "Yangi oʻzgarishlar roʻyxatida tekshirilgan tahrirlarni yashirish",
        "tog-newpageshidepatrolled": "Yangi sahifalar roʻyxatidan tekshirilgan sahifalarni yashirish",
        "tog-diffonly": "Versiyalar taqqoslanayotganda, pastda sahifa matni koʻrsatilmasin",
        "tog-showhiddencats": "Yashirin turkumlarni koʻrsatish",
        "tog-norollbackdiff": "Tahrir qaytarilganda, versiyalar taqqosi koʻrsatilmasin",
-       "tog-useeditwarning": "Oʻzgarishlarni saqlamay sahifadan chiqib ketayotganim haqida ogohlantirish",
+       "tog-useeditwarning": "Oʻzgarishlarni saqlamay sahifadan chiqib ketayotganim haqida ogohlantir",
        "tog-prefershttps": "Doim himoyalangan holda kirish",
        "underline-always": "Har doim",
        "underline-never": "Hech qachon",
-       "underline-default": "Brauzer moslamari boʻyicha",
+       "underline-default": "Bezak mavzusi yoki brauzer andozasi boʻyicha",
        "editfont-style": "Tahrirlash maydonidagi shrift turi:",
-       "editfont-default": "Brauzer moslamari boʻyicha",
+       "editfont-default": "Brauzer andozasi boʻyicha",
        "editfont-monospace": "Teng enli shrift (Monospaced)",
        "editfont-sansserif": "Kertiksiz shrift (Sans-serif)",
        "editfont-serif": "Kertikli shrift (Serif)",
        "oct": "Okt",
        "nov": "Noy",
        "dec": "Dek",
-       "january-date": "Yanvar $1",
-       "february-date": "Fevral $1",
-       "march-date": "Mart $1",
-       "april-date": "Aprel $1",
-       "may-date": "$1-may",
-       "june-date": "Iyun $1",
-       "july-date": "Iyul $1",
+       "january-date": "$1 yanvar",
+       "february-date": "$1 fevral",
+       "march-date": "$1 mart",
+       "april-date": "$1 aprel",
+       "may-date": "$1 may",
+       "june-date": "$1 iyun",
+       "july-date": "$1 iyul",
        "august-date": "Avgust $1",
        "september-date": "Sentabr $1",
        "october-date": "Oktabr $1",
        "yourpasswordagain": "Maxfiy so‘zni qayta kiriting:",
        "createacct-yourpasswordagain": "Maxfiy soʻzni tasdiqlang",
        "createacct-yourpasswordagain-ph": "Maxfiy soʻzni yana bir bor kiriting",
-       "remembermypassword": "Hisob ma’lumotlarim ushbu brauzerda eslab qolinsin (ko‘pi bilan $1 {{PLURAL:$1|kunga|kunga}})",
        "userlogin-remembermypassword": "Kirgan deb esda saqla",
        "userlogin-signwithsecure": "Himoyalangan holda kirish",
        "yourdomainname": "Sizning domeningiz:",
index 3cf6a4d..1a15bf2 100644 (file)
        "group-bot": "Bot",
        "group-sysop": "Bảo quản viên",
        "group-bureaucrat": "Hành chính viên",
-       "group-suppress": "Giám sát viên",
+       "group-suppress": "Người xóa hẳn Flow",
        "group-all": "(tất cả)",
        "group-user-member": "{{GENDER:$1}}thành viên",
        "group-autoconfirmed-member": "{{GENDER:$1}}thành viên tự động xác nhận",
        "group-bot-member": "{{GENDER:$1}}bot",
        "group-sysop-member": "{{GENDER:$1}}bảo quản viên",
        "group-bureaucrat-member": "{{GENDER:$1}}hành chính viên",
-       "group-suppress-member": "{{GENDER:$1}}giám sát viên",
+       "group-suppress-member": "{{GENDER:$1}}người xóa hẳn Flow",
        "grouppage-user": "{{ns:project}}:Thành viên",
        "grouppage-autoconfirmed": "{{ns:project}}:Thành viên tự xác nhận",
        "grouppage-bot": "{{ns:project}}:Bot",
        "grouppage-sysop": "{{ns:project}}:Bảo quản viên",
        "grouppage-bureaucrat": "{{ns:project}}:Hành chính viên",
-       "grouppage-suppress": "{{ns:project}}:Đàn áp viên",
+       "grouppage-suppress": "{{ns:project}}:Người xóa hẳn Flow",
        "right-read": "Đọc trang",
        "right-edit": "Sửa trang",
        "right-createpage": "Tạo trang (không phải trang thảo luận)",
        "feedback-external-bug-report-button": "Tạo một công việc kỹ thuật",
        "feedback-dialog-title": "Gửi phản hồi",
        "feedback-dialog-intro": "Bạn có thể gửi phản hồi dễ dàng qua biểu mẫu bên dưới. Thông tin phản hồi của bạn sẽ được bổ sung vào trang “$1” cùng với tên người dùng của bạn.",
-       "feedback-error-title": "Lỗi",
        "feedback-error1": "Hủy bỏ",
        "feedback-error2": "Lỗi: Sửa đổi thất bại",
        "feedback-error3": "Lỗi: API không có phản ứng",
index 1ee2677..70940ec 100644 (file)
        "userlogin-createanother": "创建另一个账户",
        "createacct-emailrequired": "电子邮件地址",
        "createacct-emailoptional": "电子邮件地址(可选)",
-       "createacct-email-ph": "请输入的电子邮件地址",
+       "createacct-email-ph": "请输入的电子邮件地址",
        "createacct-another-email-ph": "输入电子邮件地址",
        "createaccountmail": "使用一个临时的随机密码并将其发送到指定的电子邮件地址中",
        "createaccountmail-help": "可被用于为另一个人创建账户而不需要得知密码。",
        "resetpass-temp-emailed": "您通过一个暂时电子邮件发送的代码登录。要完成登录,您必须在此设置一个新密码:",
        "resetpass-temp-password": "临时密码:",
        "resetpass-abort-generic": "密码更改已经被扩展程序中止。",
-       "resetpass-expired": "的密码已经到期。请设置新登录密码。",
+       "resetpass-expired": "的密码已经到期。请设置新登录密码。",
        "resetpass-expired-soft": "您的密码已经到期,需要重置。请现在更换新密码,或单击“{{int:authprovider-resetpass-skip-label}}”以稍后重置。",
        "resetpass-validity-soft": "您的密码无效:$1\n\n请选择一个新密码,或单击“{{int:authprovider-resetpass-skip-label}}”以稍后重置。",
        "passwordreset": "重置密码",
-       "passwordreset-text-one": "请输入你要重置的用户名。",
+       "passwordreset-text-one": "请完成此表单来通过电子邮件接收临时密码。",
        "passwordreset-text-many": "{{PLURAL:$1|在此键入您希望接收临时密码的邮件地址。}}",
        "passwordreset-disabled": "此Wiki已经禁用密码重置。",
        "passwordreset-emaildisabled": "此Wiki上无法使用邮件功能。",
        "searchprofile-advanced-tooltip": "在自定义名字空间中搜索",
        "search-result-size": "$1($2个字)",
        "search-result-category-size": "$1个成员($2个子分类,$3个文件)",
-       "search-redirect": "(重定向自“$1”)",
+       "search-redirect": "(重定向自$1)",
        "search-section": "(“$1”章节)",
        "search-category": "(分类$1)",
        "search-file-match": "(匹配文件内容)",
        "uploadstash-summary": "这个页面提供已经上传(或者上传中)但未发布到wiki之文件存取。这些文件除了上传的用户之外不会被其他人可见。",
        "uploadstash-clear": "清除贮藏文件",
        "uploadstash-nofiles": "您没有被隐藏的文件。",
-       "uploadstash-badtoken": "执行对应操作失败。可能是因为您的编辑凭证已过期。请重试。",
+       "uploadstash-badtoken": "执行对应操作失败,可能是因为您的编辑凭据已过期。请重试。",
        "uploadstash-errclear": "清除文件失败。",
        "uploadstash-refresh": "更新文件列表",
        "uploadstash-thumbnail": "显示缩略图",
        "htmlform-cloner-create": "添加更多",
        "htmlform-cloner-delete": "移除",
        "htmlform-cloner-required": "至少一个值是必需的。",
+       "htmlform-date-placeholder": "YYYY-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "YYYY-MM-DD HH:MM:SS",
+       "htmlform-date-invalid": "您指定的值不是可识别的日期。尝试使用YYYY-MM-DD格式。",
+       "htmlform-time-invalid": "您指定的值不是可识别的时间。尝试使用HH:MM:SS格式。",
+       "htmlform-datetime-invalid": "您指定的值不是可识别的日期和时间。尝试使用YYYY-MM-DD HH:MM:SS格式。",
+       "htmlform-date-toolow": "您指定的值在$1的最早允许日期之前。",
+       "htmlform-date-toohigh": "您指定的值在$1的最晚允许日期之后。",
+       "htmlform-time-toolow": "您指定的值在$1的最早允许时间之前。",
+       "htmlform-time-toohigh": "您指定的值在$1的最晚允许时间之后。",
+       "htmlform-datetime-toolow": "您指定的值在$1的最早允许日期和时间之前。",
+       "htmlform-datetime-toohigh": "您指定的值在$1的最晚允许日期和时间之后。",
        "htmlform-title-badnamespace": "[[:$1]]不在“{{ns:$2}}”名字空间中。",
        "htmlform-title-not-creatable": "“$1”不是一个可创建的页面标题",
        "htmlform-title-not-exists": "$1不存在",
        "feedback-external-bug-report-button": "提交技术报告",
        "feedback-dialog-title": "发送反馈",
        "feedback-dialog-intro": "您可以使用下面的简便表格提交您的反馈。您的评论将连同您的用户名一起加入至页面“$1”。",
-       "feedback-error-title": "错误",
        "feedback-error1": "错误:从API返回无法识别的结果",
        "feedback-error2": "错误:编辑失败",
        "feedback-error3": "错误:API没有响应",
        "authmanager-authn-not-in-progress": "身份验证尚未进行,或会话数据丢失。请从头重新开始。",
        "authmanager-authn-no-primary": "提供的凭据不能被认证。",
        "authmanager-authn-no-local-user": "提供的证书没有与该wiki上的任何用户相关联。",
-       "authmanager-authn-no-local-user-link": "提供的证书有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的证书链接到对应账户的选项。",
+       "authmanager-authn-no-local-user-link": "提供的凭据有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的凭据链接到对应账户的选项。",
        "authmanager-authn-autocreate-failed": "所有账户的自动创建失败:$1",
-       "authmanager-change-not-supported": "提供的证书不能被更改,因为没有东西会使用它们。",
+       "authmanager-change-not-supported": "提供的凭据不能被更改,因为没有东西会使用它们。",
        "authmanager-create-disabled": "账户创建已停用。",
        "authmanager-create-from-login": "要创建您的账户,请填写下方的字段。",
        "authmanager-create-not-in-progress": "账户创建尚未进行,或会话数据丢失。请从头重新开始。",
-       "authmanager-create-no-primary": "提供的证书不能用于账户创建。",
+       "authmanager-create-no-primary": "提供的凭据不能用于账户创建。",
        "authmanager-link-no-primary": "提供的证书不能用于账户链接。",
        "authmanager-link-not-in-progress": "账户链接尚未进行,或会话数据丢失。请从头重新开始。",
        "authmanager-authplugin-setpass-failed-title": "密码更改失败",
        "authpage-cannot-link-continue": "无法继续账户链接。您的会话大概已超时。",
        "cannotauth-not-allowed-title": "权限被拒绝",
        "cannotauth-not-allowed": "您不被允许使用此页面",
-       "changecredentials": "更改证书",
-       "changecredentials-submit": "更改证书",
-       "changecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "changecredentials": "更改凭据",
+       "changecredentials-submit": "更改凭据",
+       "changecredentials-invalidsubpage": "$1不是有效的凭据类型。",
        "changecredentials-success": "您的证书已被更改。",
-       "removecredentials": "移除证书",
+       "removecredentials": "移除凭据",
        "removecredentials-submit": "移除证书",
-       "removecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "removecredentials-invalidsubpage": "$1不是有效的凭据类型。",
        "removecredentials-success": "您的证书已被移除。",
-       "credentialsform-provider": "证书类型:",
+       "credentialsform-provider": "凭据类型:",
        "credentialsform-account": "帐户名称:",
        "cannotlink-no-provider-title": "没有可链接账户",
        "cannotlink-no-provider": "没有可链接账户。",
index ba3d364..14f5b6f 100644 (file)
 
 $fallback = 'ru';
 
+$namespaceNames = [
+       NS_MEDIA            => 'Medii',
+       NS_SPECIAL          => 'Erikoine',
+       NS_TALK             => 'Pagin',
+       NS_USER             => 'Käyttäi',
+       NS_USER_TALK        => 'Käyttäi_pagin',
+       NS_PROJECT_TALK     => '$1_pagin',
+       NS_FILE             => 'Failu',
+       NS_FILE_TALK        => 'Failu_pagin',
+       NS_MEDIAWIKI        => 'MediiWiki',
+       NS_MEDIAWIKI_TALK   => 'MediiWiki_pagin',
+       NS_TEMPLATE         => 'Šablonu',
+       NS_TEMPLATE_TALK    => 'Šablonu_pagin',
+       NS_HELP             => 'Abu',
+       NS_HELP_TALK        => 'Abu_pagin',
+       NS_CATEGORY         => 'Kategourii',
+       NS_CATEGORY_TALK    => 'Kategourii_pagin',
+];
+
 $linkTrail = '/^([a-zčČšŠžŽäÄöÖ]+)(.*)$/sDu';
 
diff --git a/maintenance/archives/patch-change_tag-ct_id.sql b/maintenance/archives/patch-change_tag-ct_id.sql
new file mode 100644 (file)
index 0000000..7b986d6
--- /dev/null
@@ -0,0 +1,5 @@
+-- Primary key in change_tag table
+
+ALTER TABLE /*$wgDBprefix*/change_tag
+       ADD COLUMN ct_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
+       ADD PRIMARY KEY (ct_id);
diff --git a/maintenance/archives/patch-tag_summary-ts_id.sql b/maintenance/archives/patch-tag_summary-ts_id.sql
new file mode 100644 (file)
index 0000000..66fa72e
--- /dev/null
@@ -0,0 +1,5 @@
+-- Primary key in tag_summary table
+
+ALTER TABLE /*$wgDBprefix*/tag_summary
+       ADD COLUMN ts_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
+       ADD PRIMARY KEY (ts_id);
index b1e0fa4..3cc009e 100644 (file)
@@ -7,7 +7,6 @@ c2find|http://c2.com/cgi/wiki?FindPage&value=$1|0|
 cache|http://www.google.com/search?q=cache:$1|0|
 commons|https://commons.wikimedia.org/wiki/$1|0|https://commons.wikimedia.org/w/api.php
 dictionary|http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1|0|
-docbook|http://wiki.docbook.org/$1|0|
 doi|http://dx.doi.org/$1|0|
 drumcorpswiki|http://www.drumcorpswiki.com/$1|0|http://drumcorpswiki.com/api.php
 dwjwiki|http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1|0|
@@ -16,22 +15,18 @@ emacswiki|http://www.emacswiki.org/cgi-bin/wiki.pl?$1|0|
 foldoc|http://foldoc.org/?$1|0|
 foxwiki|http://fox.wikis.com/wc.dll?Wiki~$1|0|
 freebsdman|http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1|0|
-gej|http://www.esperanto.de/dej.malnova/aktivikio.pl?$1|0|
 gentoo-wiki|http://gentoo-wiki.com/$1|0|
 google|http://www.google.com/search?q=$1|0|
 googlegroups|http://groups.google.com/groups?q=$1|0|
 hammondwiki|http://www.dairiki.org/HammondWiki/$1|0|
 hrwiki|http://www.hrwiki.org/wiki/$1|0|http://www.hrwiki.org/w/api.php
 imdb|http://www.imdb.com/find?q=$1&tt=on|0|
-jargonfile|http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1|0|
 kmwiki|http://kmwiki.wikispaces.com/$1|0|
 linuxwiki|http://linuxwiki.de/$1|0|
 lojban|http://mw.lojban.org/papri/$1|0|
 lqwiki|http://wiki.linuxquestions.org/wiki/$1|0|
-lugkr|http://www.lug-kr.de/wiki/$1|0|
 meatball|http://www.usemod.com/cgi-bin/mb.pl?$1|0|
 mediawikiwiki|https://www.mediawiki.org/wiki/$1|0|https://www.mediawiki.org/w/api.php
-mediazilla|https://bugzilla.wikimedia.org/$1|0|
 memoryalpha|http://en.memory-alpha.org/wiki/$1|0|http://en.memory-alpha.org/api.php
 metawiki|http://sunir.org/apps/meta.pl?$1|0|
 metawikimedia|https://meta.wikimedia.org/wiki/$1|0|https://meta.wikimedia.org/w/api.php
@@ -39,25 +34,20 @@ mozillawiki|http://wiki.mozilla.org/$1|0|https://wiki.mozilla.org/api.php
 mw|https://www.mediawiki.org/wiki/$1|0|https://www.mediawiki.org/w/api.php
 oeis|http://oeis.org/$1|0|
 openwiki|http://openwiki.com/ow.asp?$1|0|
-ppr|http://c2.com/cgi/wiki?$1|0|
 pythoninfo|http://wiki.python.org/moin/$1|0|
 rfc|http://www.rfc-editor.org/rfc/rfc$1.txt|0|
 s23wiki|http://s23.org/wiki/$1|0|http://s23.org/w/api.php
 seattlewireless|http://seattlewireless.net/$1|0|
 senseislibrary|http://senseis.xmp.net/?$1|0|
 shoutwiki|http://www.shoutwiki.com/wiki/$1|0|http://www.shoutwiki.com/w/api.php
-sourcewatch|http://www.sourcewatch.org/index.php?title=$1|0|http://www.sourcewatch.org/api.php
 squeak|http://wiki.squeak.org/squeak/$1|0|
-tejo|http://www.tejo.org/vikio/$1|0|
 tmbw|http://www.tmbw.net/wiki/$1|0|http://tmbw.net/wiki/api.php
 tmnet|http://www.technomanifestos.net/?$1|0|
 theopedia|http://www.theopedia.com/$1|0|
 twiki|http://twiki.org/cgi-bin/view/$1|0|
-uea|http://uea.org/vikio/index.php/$1|0|http://uea.org/vikio/api.php
 uncyclopedia|http://en.uncyclopedia.co/wiki/$1|0|http://en.uncyclopedia.co/w/api.php
 unreal|http://wiki.beyondunreal.com/$1|0|http://wiki.beyondunreal.com/w/api.php
 usemod|http://www.usemod.com/cgi-bin/wiki.pl?$1|0|
-webseitzwiki|http://webseitz.fluxent.com/wiki/$1|0|
 wiki|http://c2.com/cgi/wiki?$1|0|
 wikia|http://www.wikia.com/wiki/$1|0|
 wikibooks|https://en.wikibooks.org/wiki/$1|0|https://en.wikibooks.org/w/api.php
index b7d1a84..0e0bb5c 100644 (file)
@@ -9,7 +9,6 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local,iw_api) VALUES
 ('cache','http://www.google.com/search?q=cache:$1',0,''),
 ('commons','https://commons.wikimedia.org/wiki/$1',0,'https://commons.wikimedia.org/w/api.php'),
 ('dictionary','http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1',0,''),
-('docbook','http://wiki.docbook.org/$1',0,''),
 ('doi','http://dx.doi.org/$1',0,''),
 ('drumcorpswiki','http://www.drumcorpswiki.com/$1',0,'http://drumcorpswiki.com/api.php'),
 ('dwjwiki','http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1',0,''),
@@ -18,22 +17,18 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local,iw_api) VALUES
 ('foldoc','http://foldoc.org/?$1',0,''),
 ('foxwiki','http://fox.wikis.com/wc.dll?Wiki~$1',0,''),
 ('freebsdman','http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1',0,''),
-('gej','http://www.esperanto.de/dej.malnova/aktivikio.pl?$1',0,''),
 ('gentoo-wiki','http://gentoo-wiki.com/$1',0,''),
 ('google','http://www.google.com/search?q=$1',0,''),
 ('googlegroups','http://groups.google.com/groups?q=$1',0,''),
 ('hammondwiki','http://www.dairiki.org/HammondWiki/$1',0,''),
 ('hrwiki','http://www.hrwiki.org/wiki/$1',0,'http://www.hrwiki.org/w/api.php'),
 ('imdb','http://www.imdb.com/find?q=$1&tt=on',0,''),
-('jargonfile','http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1',0,''),
 ('kmwiki','http://kmwiki.wikispaces.com/$1',0,''),
 ('linuxwiki','http://linuxwiki.de/$1',0,''),
 ('lojban','http://www.lojban.org/tiki/tiki-index.php?page=$1',0,''),
 ('lqwiki','http://wiki.linuxquestions.org/wiki/$1',0,''),
-('lugkr','http://www.lug-kr.de/wiki/$1',0,''),
 ('meatball','http://www.usemod.com/cgi-bin/mb.pl?$1',0,''),
 ('mediawikiwiki','https://www.mediawiki.org/wiki/$1',0,'https://www.mediawiki.org/w/api.php'),
-('mediazilla','https://bugzilla.wikimedia.org/$1',0,''),
 ('memoryalpha','http://en.memory-alpha.org/wiki/$1',0,'http://en.memory-alpha.org/api.php'),
 ('metawiki','http://sunir.org/apps/meta.pl?$1',0,''),
 ('metawikimedia','https://meta.wikimedia.org/wiki/$1',0,'https://meta.wikimedia.org/w/api.php'),
@@ -41,25 +36,20 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local,iw_api) VALUES
 ('mw','https://www.mediawiki.org/wiki/$1',0,'https://www.mediawiki.org/w/api.php'),
 ('oeis','http://oeis.org/$1',0,''),
 ('openwiki','http://openwiki.com/ow.asp?$1',0,''),
-('ppr','http://c2.com/cgi/wiki?$1',0,''),
 ('pythoninfo','http://wiki.python.org/moin/$1',0,''),
 ('rfc','http://www.rfc-editor.org/rfc/rfc$1.txt',0,''),
 ('s23wiki','http://s23.org/wiki/$1',0,'http://s23.org/w/api.php'),
 ('seattlewireless','http://seattlewireless.net/$1',0,''),
 ('senseislibrary','http://senseis.xmp.net/?$1',0,''),
 ('shoutwiki','http://www.shoutwiki.com/wiki/$1',0,'http://www.shoutwiki.com/w/api.php'),
-('sourcewatch','http://www.sourcewatch.org/index.php?title=$1',0,'http://www.sourcewatch.org/api.php'),
 ('squeak','http://wiki.squeak.org/squeak/$1',0,''),
-('tejo','http://www.tejo.org/vikio/$1',0,''),
 ('tmbw','http://www.tmbw.net/wiki/$1',0,'http://tmbw.net/wiki/api.php'),
 ('tmnet','http://www.technomanifestos.net/?$1',0,''),
 ('theopedia','http://www.theopedia.com/$1',0,''),
 ('twiki','http://twiki.org/cgi-bin/view/$1',0,''),
-('uea','http://uea.org/vikio/index.php/$1',0,'http://uea.org/vikio/api.php'),
 ('uncyclopedia','http://en.uncyclopedia.co/wiki/$1',0,'http://en.uncyclopedia.co/w/api.php'),
 ('unreal','http://wiki.beyondunreal.com/$1',0,'http://wiki.beyondunreal.com/w/api.php'),
 ('usemod','http://www.usemod.com/cgi-bin/wiki.pl?$1',0,''),
-('webseitzwiki','http://webseitz.fluxent.com/wiki/$1',0,''),
 ('wiki','http://c2.com/cgi/wiki?$1',0,''),
 ('wikia','http://www.wikia.com/wiki/$1',0,''),
 ('wikibooks','https://en.wikibooks.org/wiki/$1',0,'https://en.wikibooks.org/w/api.php'),
diff --git a/maintenance/mssql/archives/patch-change_tag-ct_id.sql b/maintenance/mssql/archives/patch-change_tag-ct_id.sql
new file mode 100644 (file)
index 0000000..94cb9d1
--- /dev/null
@@ -0,0 +1,4 @@
+-- Primary key in change_tag table
+
+ALTER TABLE /*_*/change_tag ADD ct_id INT IDENTITY;
+ALTER TABLE /*_*/change_tag ADD CONSTRAINT pk_change_tag PRIMARY KEY(ct_id)
diff --git a/maintenance/mssql/archives/patch-tag_summary-ts_id.sql b/maintenance/mssql/archives/patch-tag_summary-ts_id.sql
new file mode 100644 (file)
index 0000000..d62bd35
--- /dev/null
@@ -0,0 +1,4 @@
+-- Primary key in tag_summary table
+
+ALTER TABLE /*_*/tag_summary ADD ts_id INT IDENTITY;
+ALTER TABLE /*_*/tag_summary ADD CONSTRAINT pk_tag_summary PRIMARY KEY(ts_id)
index ea087a6..beb9727 100644 (file)
@@ -1193,6 +1193,7 @@ CREATE TABLE /*_*/updatelog (
 
 -- A table to track tags for revisions, logs and recent changes.
 CREATE TABLE /*_*/change_tag (
+  ct_id int NOT NULL PRIMARY KEY IDENTITY,
   -- RCID for the change
   ct_rc_id int NULL REFERENCES /*_*/recentchanges(rc_id),
   -- LOGID for the change
@@ -1215,6 +1216,7 @@ CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_i
 -- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT
 -- that only works on MySQL 4.1+
 CREATE TABLE /*_*/tag_summary (
+  ts_id int NOT NULL PRIMARY KEY IDENTITY,
   -- RCID for the change
   ts_rc_id int NULL REFERENCES /*_*/recentchanges(rc_id),
   -- LOGID for the change
diff --git a/maintenance/oracle/archives/patch-change_tag-ct_id.sql b/maintenance/oracle/archives/patch-change_tag-ct_id.sql
new file mode 100644 (file)
index 0000000..6672872
--- /dev/null
@@ -0,0 +1,6 @@
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.change_tag ADD (
+ct_id NUMBER NOT NULL,
+);
+ALTER TABLE &mw_prefix.change_tag ADD CONSTRAINT &mw_prefix.change_tag_pk PRIMARY KEY (ct_id);
diff --git a/maintenance/oracle/archives/patch-tag_summary-ts_id.sql b/maintenance/oracle/archives/patch-tag_summary-ts_id.sql
new file mode 100644 (file)
index 0000000..91c3338
--- /dev/null
@@ -0,0 +1,6 @@
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.tag_summary ADD (
+ts_id NUMBER NOT NULL,
+);
+ALTER TABLE &mw_prefix.tag_summary ADD CONSTRAINT &mw_prefix.tag_summary_pk PRIMARY KEY (ts_id);
index d9369c9..616b401 100644 (file)
@@ -616,23 +616,27 @@ CREATE TABLE &mw_prefix.updatelog (
 ALTER TABLE &mw_prefix.updatelog ADD CONSTRAINT &mw_prefix.updatelog_pk PRIMARY KEY (ul_key);
 
 CREATE TABLE &mw_prefix.change_tag (
+  ct_id NUMBER NOT NULL,
   ct_rc_id NUMBER NULL,
   ct_log_id NUMBER NULL,
   ct_rev_id NUMBER NULL,
   ct_tag VARCHAR2(255) NOT NULL,
   ct_params BLOB NULL
 );
+ALTER TABLE &mw_prefix.change_tag ADD CONSTRAINT &mw_prefix.change_tag_pk PRIMARY KEY (ct_id);
 CREATE UNIQUE INDEX &mw_prefix.change_tag_u01 ON &mw_prefix.change_tag (ct_rc_id,ct_tag);
 CREATE UNIQUE INDEX &mw_prefix.change_tag_u02 ON &mw_prefix.change_tag (ct_log_id,ct_tag);
 CREATE UNIQUE INDEX &mw_prefix.change_tag_u03 ON &mw_prefix.change_tag (ct_rev_id,ct_tag);
 CREATE INDEX &mw_prefix.change_tag_i01 ON &mw_prefix.change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
 
 CREATE TABLE &mw_prefix.tag_summary (
+  ts_id NUMBER NOT NULL,
   ts_rc_id NUMBER NULL,
   ts_log_id NUMBER NULL,
   ts_rev_id NUMBER NULL,
   ts_tags BLOB NOT NULL
 );
+ALTER TABLE &mw_prefix.tag_summary ADD CONSTRAINT &mw_prefix.tag_summary_pk PRIMARY KEY (ts_id);
 CREATE UNIQUE INDEX &mw_prefix.tag_summary_u01 ON &mw_prefix.tag_summary (ts_rc_id);
 CREATE UNIQUE INDEX &mw_prefix.tag_summary_u02 ON &mw_prefix.tag_summary (ts_log_id);
 CREATE UNIQUE INDEX &mw_prefix.tag_summary_u03 ON &mw_prefix.tag_summary (ts_rev_id);
index 95c87c0..2273761 100644 (file)
@@ -25,6 +25,8 @@ DROP SEQUENCE IF EXISTS category_cat_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS archive_ar_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS externallinks_el_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS sites_site_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS change_tag_ct_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS tag_summary_ts_id_seq CASCADE;
 DROP FUNCTION IF EXISTS page_deleted() CASCADE;
 DROP FUNCTION IF EXISTS ts2_page_title() CASCADE;
 DROP FUNCTION IF EXISTS ts2_page_text() CASCADE;
@@ -653,7 +655,9 @@ CREATE TABLE category (
 CREATE UNIQUE INDEX category_title ON category(cat_title);
 CREATE INDEX category_pages ON category(cat_pages);
 
+CREATE SEQUENCE change_tag_ct_id_seq;
 CREATE TABLE change_tag (
+  ct_id      INTEGER  NOT NULL  PRIMARY KEY DEFAULT nextval('change_tag_ct_id_seq'),
   ct_rc_id   INTEGER      NULL,
   ct_log_id  INTEGER      NULL,
   ct_rev_id  INTEGER      NULL,
@@ -665,11 +669,13 @@ CREATE UNIQUE INDEX change_tag_log_tag ON change_tag(ct_log_id,ct_tag);
 CREATE UNIQUE INDEX change_tag_rev_tag ON change_tag(ct_rev_id,ct_tag);
 CREATE INDEX change_tag_tag_id ON change_tag(ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
 
+CREATE SEQUENCE tag_summary_ts_id_seq;
 CREATE TABLE tag_summary (
-  ts_rc_id   INTEGER     NULL,
-  ts_log_id  INTEGER     NULL,
-  ts_rev_id  INTEGER     NULL,
-  ts_tags    TEXT    NOT NULL
+  ts_id      INTEGER  NOT NULL  PRIMARY KEY DEFAULT nextval('tag_summary_ts_id_seq'),
+  ts_rc_id   INTEGER      NULL,
+  ts_log_id  INTEGER      NULL,
+  ts_rev_id  INTEGER      NULL,
+  ts_tags    TEXT     NOT NULL
 );
 CREATE UNIQUE INDEX tag_summary_rc_id ON tag_summary(ts_rc_id);
 CREATE UNIQUE INDEX tag_summary_log_id ON tag_summary(ts_log_id);
index a9a982c..cc976ed 100644 (file)
@@ -91,7 +91,7 @@ class MwSql extends Maintenance {
                                $this->error( "Unable to open input file", true );
                        }
 
-                       $error = $db->sourceStream( $file, false, [ $this, 'sqlPrintResult' ] );
+                       $error = $db->sourceStream( $file, null, [ $this, 'sqlPrintResult' ] );
                        if ( $error !== true ) {
                                $this->error( $error, true );
                        } else {
diff --git a/maintenance/sqlite/archives/patch-change_tag-ct_id.sql b/maintenance/sqlite/archives/patch-change_tag-ct_id.sql
new file mode 100644 (file)
index 0000000..1c01094
--- /dev/null
@@ -0,0 +1,25 @@
+DROP TABLE IF EXISTS /*_*/change_tag_tmp;
+
+CREATE TABLE /*$wgDBprefix*/change_tag_tmp (
+  ct_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ct_rc_id int NULL,
+  ct_log_id int NULL,
+  ct_rev_id int NULL,
+  ct_tag varchar(255) NOT NULL,
+  ct_params blob NULL
+);
+
+INSERT OR IGNORE INTO /*_*/change_tag_tmp (
+    ct_rc_id, ct_log_id, ct_rev_id, ct_tag, ct_params )
+    SELECT
+    ct_rc_id, ct_log_id, ct_rev_id, ct_tag, ct_params
+    FROM /*_*/change_tag;
+
+DROP TABLE /*_*/change_tag;
+
+ALTER TABLE /*_*/change_tag_tmp RENAME TO /*_*/change_tag;
+
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
diff --git a/maintenance/sqlite/archives/patch-tag_summary-ts_id.sql b/maintenance/sqlite/archives/patch-tag_summary-ts_id.sql
new file mode 100644 (file)
index 0000000..b6a1202
--- /dev/null
@@ -0,0 +1,23 @@
+DROP TABLE IF EXISTS /*_*/tag_summary_tmp;
+
+CREATE TABLE /*$wgDBprefix*/tag_summary_tmp (
+  ts_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ts_rc_id int NULL,
+  ts_log_id int NULL,
+  ts_rev_id int NULL,
+  ts_tags blob NOT NULL
+);
+
+INSERT OR IGNORE INTO /*_*/tag_summary_tmp (
+    ts_rc_id, ts_log_id, ts_rev_id, ts_tags )
+    SELECT
+    ts_rc_id, ts_log_id, ts_rev_id, ts_tags
+    FROM /*_*/tag_summary;
+
+DROP TABLE /*_*/tag_summary;
+
+ALTER TABLE /*_*/tag_summary_tmp RENAME TO /*_*/tag_summary;
+
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
index b5c14e3..03ce508 100644 (file)
@@ -1472,6 +1472,7 @@ CREATE TABLE /*_*/updatelog (
 
 -- A table to track tags for revisions, logs and recent changes.
 CREATE TABLE /*_*/change_tag (
+  ct_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
   -- RCID for the change
   ct_rc_id int NULL,
   -- LOGID for the change
@@ -1494,6 +1495,7 @@ CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_i
 -- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT
 -- that only works on MySQL 4.1+
 CREATE TABLE /*_*/tag_summary (
+  ts_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
   -- RCID for the change
   ts_rc_id int NULL,
   -- LOGID for the change
index b81fbde..32a754f 100644 (file)
@@ -1020,7 +1020,6 @@ return [
                        'feedback-cancel',
                        'feedback-close',
                        'feedback-dialog-title',
-                       'feedback-error-title',
                        'feedback-error1',
                        'feedback-error2',
                        'feedback-error3',
index 594cea2..d72957d 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
index 6437ca8..2f811da 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -406,6 +406,10 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
        max-width: 100%;
        padding: 0;
        white-space: normal;
+       float: left;
+}
+.oo-ui-fieldsetLayout-group {
+       clear: both;
 }
 .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
        float: right;
index 08d91b4..9a3d7eb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -529,6 +529,10 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
        max-width: 100%;
        padding: 0;
        white-space: normal;
+       float: left;
+}
+.oo-ui-fieldsetLayout-group {
+       clear: both;
 }
 .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
        float: right;
index c982010..109645b 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
@@ -10121,6 +10121,7 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
        }
 
        // Initialization
+       this.$group.addClass( 'oo-ui-fieldsetLayout-group' );
        this.$element
                .addClass( 'oo-ui-fieldsetLayout' )
                .prepend( this.$label, this.$help, this.$icon, this.$group );
index 343508c..616f78e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
index 9c9954e..d6ed767 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index a413005..411c6bb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-tool.oo-ui-widget-enabled {
        -webkit-transition: background-color 100ms;
index ba959cf..822b2d9 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
index bd8034d..2d2b200 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
index 126b591..d1b4225 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
index 62195df..636e3f5 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
index 3cff8f7..1cceac5 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
index 2c115f9..38e40b9 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:06Z
+ * Date: 2016-10-03T18:59:06Z
  */
 .oo-ui-window {
        background: transparent;
index 8ef5ea5..0a29b8b 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.9
+ * OOjs UI v0.17.10
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-09-13T18:30:02Z
+ * Date: 2016-10-03T18:59:01Z
  */
 ( function ( OO ) {
 
index 5c3715d..e58a6cf 100644 (file)
                                        fullscreenButton.$element,
                                        new OO.ui.ButtonWidget( {
                                                label: mw.message( 'apisandbox-submit' ).text(),
-                                               flags: [ 'primary', 'constructive' ]
+                                               flags: [ 'primary', 'progressive' ]
                                        } ).on( 'click', ApiSandbox.sendRequest ).$element,
                                        new OO.ui.ButtonWidget( {
                                                label: mw.message( 'apisandbox-reset' ).text(),
                                                        dynamicParamNameWidget,
                                                        new OO.ui.ButtonWidget( {
                                                                icon: 'add',
-                                                               flags: 'constructive'
+                                                               flags: 'progressive'
                                                        } ).on( 'click', addDynamicParamWidget ),
                                                        {
                                                                label: mw.message( 'apisandbox-dynamic-parameters-add-label' ).text(),
index 18fdb2f..a281e67 100644 (file)
@@ -14,7 +14,7 @@
 
 // Neutral button styling
 //
-// These are the main actions on the page/workflow. The page should have only one of progressive, constructive and desctructive buttons, the rest being quiet.
+// These are the main actions on the page/workflow. The page should have only one of progressive and destructive buttons, the rest being quiet.
 //
 // Markup:
 // <div>
@@ -93,9 +93,6 @@
        //   <button class="mw-ui-button mw-ui-progressive mw-ui-big">.mw-ui-progressive</button>
        // </div>
        // <div>
-       //   <button class="mw-ui-button mw-ui-constructive mw-ui-big">.mw-ui-constructive</button>
-       // </div>
-       // <div>
        //   <button class="mw-ui-button mw-ui-destructive mw-ui-big">.mw-ui-destructive</button>
        // </div>
        //
        //   <button class="mw-ui-button mw-ui-progressive mw-ui-block">.mw-ui-progressive</button>
        // </div>
        // <div>
-       //   <button class="mw-ui-button mw-ui-constructive mw-ui-block">.mw-ui-constructive</button>
-       // </div>
-       // <div>
        //   <button class="mw-ui-button mw-ui-destructive mw-ui-block">.mw-ui-destructive</button>
        // </div>
        //
 
        // Quiet buttons
        //
-       // Use quiet buttons when they are less important and alongside other constructive, progressive or destructive buttons. It should be used for an action that exits the user from the current view/workflow.
+       // Use quiet buttons when they are less important and alongside other progressive or destructive buttons. It should be used for an action that exits the user from the current view/workflow.
        // Its use is  not recommended on mobile/tablet due to lack of hover state.
        //
        // Markup:
        //   <button class="mw-ui-button mw-ui-quiet">.mw-ui-button</button>
        // </div>
        // <div>
-       //   <button class="mw-ui-button mw-ui-constructive mw-ui-quiet">.mw-ui-constructive</button>
-       // </div>
-       // <div>
-       //   <button class="mw-ui-button mw-ui-constructive mw-ui-quiet" disabled>.mw-ui-constructive</button>
-       // </div>
-       // <div>
        //   <button class="mw-ui-button mw-ui-destructive mw-ui-quiet">.mw-ui-destructive</button>
        // </div>
        // <div>
index aedec5b..2327efc 100644 (file)
@@ -30,7 +30,7 @@
 //     <input class="mw-ui-input" value="input">
 //   </div>
 //   <div class="mw-ui-vform-field">
-//     <button class="mw-ui-button mw-ui-constructive">Button in vform</button>
+//     <button class="mw-ui-button mw-ui-progressive">Button in vform</button>
 //   </div>
 // </form>
 //
index 76fee23..90e769e 100644 (file)
@@ -99,7 +99,7 @@ textarea.mw-ui-input {
 //
 // Markup:
 // <input class="mw-ui-input mw-ui-input-inline">
-// <button class="mw-ui-button mw-ui-constructive">Submit</button>
+// <button class="mw-ui-button mw-ui-progressive">Submit</button>
 //
 // Styleguide 1.2.
 input[type="number"],
index cc27e9e..5551745 100644 (file)
@@ -16,10 +16,10 @@ Text
 Context classes may be used on elements with only plain-text content with the mw-ui-text base. When the context classes
 are used on interactive and block-level elements, the appropriate alternative base type classes should also be used. For
 example, mw-ui-anchor with A, or mw-ui-button with buttons.
+'Constructive' is deprecated and merged with 'Progressive'.
 
 Markup:
 <span class="mw-ui-text mw-ui-progressive">Progressive</span>
-<span class="mw-ui-text mw-ui-constructive">Constructive</span>
 <span class="mw-ui-text mw-ui-destructive">Destructive</span>
 
 Styleguide 6.1.
@@ -28,11 +28,9 @@ Styleguide 6.1.
 .mw-ui-text {
        // The selector order is like this on purpose; IE 6 ignores the second selector,
        // so we don't want to accidentally apply this color on all mw-ui-CONTEXT classes
-       .mw-ui-progressive& {
-               color: @colorProgressive;
-       }
+       .mw-ui-progressive&,
        .mw-ui-constructive& {
-               color: @colorConstructive;
+               color: @colorProgressive;
        }
        .mw-ui-destructive& {
                color: @colorDestructive;
index 0f61d97..844d74c 100644 (file)
                } );
        };
 
+       /**
+        * @param {mw.Title} filename
+        * @return {jQuery.Promise} Resolves (on success) or rejects with OO.ui.Error
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.validateFilename = function ( filename ) {
+               return ( new mw.Api() ).get( {
+                       action: 'query',
+                       prop: 'info',
+                       titles: filename.getPrefixedDb(),
+                       formatversion: 2
+               } ).then(
+                       function ( result ) {
+                               // if the file already exists, reject right away, before
+                               // ever firing finishStashUpload()
+                               if ( !result.query.pages[ 0 ].missing ) {
+                                       return $.Deferred().reject( new OO.ui.Error(
+                                               $( '<p>' ).msg( 'fileexists', filename.getPrefixedDb() ),
+                                               { recoverable: false }
+                                       ) );
+                               }
+                       },
+                       function () {
+                               // API call failed - this could be a connection hiccup...
+                               // Let's just ignore this validation step and turn this
+                               // failure into a successful resolve ;)
+                               return $.Deferred().resolve();
+                       }
+               );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.saveFile = function () {
+               var title = mw.Title.newFromText(
+                               this.getFilename(),
+                               mw.config.get( 'wgNamespaceIds' ).file
+                       );
+
+               return this.uploadPromise
+                       .then( this.validateFilename.bind( this, title ) )
+                       .then( mw.ForeignStructuredUpload.BookletLayout.parent.prototype.saveFile.bind( this ) );
+       };
+
        /* Getters */
 
        /**
index 1ea7e04..a719ffe 100644 (file)
@@ -84,7 +84,7 @@
                        modes: 'insert'
                },
                {
-                       flags: [ 'primary', 'constructive' ],
+                       flags: [ 'primary', 'progressive' ],
                        label: mw.msg( 'upload-dialog-button-save' ),
                        action: 'save',
                        modes: 'info'
index 170e124..0b3ea04 100644 (file)
                                        ]
                                };
                                break;
-                       case 'error1':
-                       case 'error2':
-                       case 'error3':
-                       case 'error4':
-                               dialogConfig = {
-                                       title: mw.msg( 'feedback-error-title' ),
-                                       message: mw.msg( 'feedback-' + status ),
-                                       actions: [
-                                               {
-                                                       action: 'accept',
-                                                       label: mw.msg( 'feedback-close' ),
-                                                       flags: 'primary'
-                                               }
-                                       ]
-                               };
-                               break;
                }
 
                // Show the message dialog
                {
                        action: 'submit',
                        label: mw.msg( 'feedback-submit' ),
-                       flags: [ 'primary', 'constructive' ]
+                       flags: [ 'primary', 'progressive' ]
                },
                {
                        action: 'external',
                        label: mw.msg( 'feedback-external-bug-report-button' ),
-                       flags: 'constructive'
+                       flags: 'progressive'
                },
                {
                        action: 'cancel',
                                }, function () {
                                        fb.status = 'error4';
                                        mw.log.warn( 'Feedback report failed because MessagePoster could not be fetched' );
-                               } ).always( function () {
+                               } ).then( function () {
                                        fb.close();
+                               }, function () {
+                                       return fb.getErrorMessage();
                                } );
                        }, this );
                }
                return mw.Feedback.Dialog.parent.prototype.getActionProcess.call( this, action );
        };
 
+       /**
+        * Returns an error message for the current status.
+        *
+        * @private
+        *
+        * @return {OO.ui.Error}
+        */
+       mw.Feedback.Dialog.prototype.getErrorMessage = function () {
+               switch ( this.status ) {
+                       case 'error1':
+                       case 'error2':
+                       case 'error3':
+                       case 'error4':
+                               // Messages: feedback-error1, feedback-error2, feedback-error3, feedback-error4
+                               return new OO.ui.Error( mw.msg( 'feedback-' + this.status ) );
+               }
+       };
+
        /**
         * Posts the message
         *
index 6ca851e..a0d6b22 100644 (file)
@@ -59,11 +59,6 @@ class ParserTestRunner {
         */
        private $dbClone;
 
-       /**
-        * @var DjVuSupport
-        */
-       private $djVuSupport;
-
        /**
         * @var TidySupport
         */
@@ -138,7 +133,6 @@ class ParserTestRunner {
                $this->runDisabled = !empty( $options['run-disabled'] );
                $this->runParsoid = !empty( $options['run-parsoid'] );
 
-               $this->djVuSupport = new DjVuSupport();
                $this->tidySupport = new TidySupport( !empty( $options['use-tidy-config'] ) );
                if ( !$this->tidySupport->isEnabled() ) {
                        $this->recorder->warning(
@@ -456,7 +450,6 @@ class ParserTestRunner {
                }
                $this->setupDone[$funcName] = true;
                return function () use ( $funcName ) {
-                       wfDebug( "markSetupDone unmarked $funcName" );
                        $this->setupDone[$funcName] = false;
                };
        }
@@ -752,14 +745,6 @@ class ParserTestRunner {
                $user = $context->getUser();
                $options = ParserOptions::newFromContext( $context );
 
-               if ( isset( $opts['djvu'] ) ) {
-                       if ( !$this->djVuSupport->isEnabled() ) {
-                               $this->recorder->skipped( $test,
-                                       'djvu binaries do not exist or are not executable' );
-                               return false;
-                       }
-               }
-
                if ( isset( $opts['tidy'] ) ) {
                        if ( !$this->tidySupport->isEnabled() ) {
                                $this->recorder->skipped( $test, 'tidy extension is not installed' );
@@ -1028,7 +1013,6 @@ class ParserTestRunner {
                };
 
                // Set content language. This invalidates the magic word cache and title services
-               wfDebug( "Setting up language $langCode" );
                $lang = Language::factory( $langCode );
                $setup['wgContLang'] = $lang;
                $reset = function () {
index 45a7ce5..e53a958 100644 (file)
@@ -336,6 +336,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 
                JobQueueGroup::destroySingletons();
                ObjectCache::clear();
+               $services = MediaWikiServices::getInstance();
+               $services->resetServiceForTesting( 'MainObjectStash' );
+               $services->resetServiceForTesting( 'LocalServerObjectCache' );
+               $services->getMainWANObjectCache()->clearProcessCache();
                FileBackendGroup::destroySingleton();
 
                // TODO: move global state into MediaWikiServices
index a05e39d..f054c0e 100644 (file)
@@ -312,6 +312,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
                        'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
                        'WatchedItemQueryService' => [ 'WatchedItemQueryService', WatchedItemQueryService::class ],
+                       'CryptRand' => [ 'CryptRand', CryptRand::class ],
                        'MediaHandlerFactory' => [ 'MediaHandlerFactory', MediaHandlerFactory::class ],
                        'GenderCache' => [ 'GenderCache', GenderCache::class ],
                        'LinkCache' => [ 'LinkCache', LinkCache::class ],
@@ -320,8 +321,11 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
                        'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
                        'TitleParser' => [ 'TitleParser', TitleParser::class ],
-                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
-                       'ProxyLookup' => [ 'ProxyLookup', ProxyLookup::class ]
+                       'ProxyLookup' => [ 'ProxyLookup', ProxyLookup::class ],
+                       'MainObjectStash' => [ 'MainObjectStash', BagOStuff::class ],
+                       'MainWANObjectCache' => [ 'MainWANObjectCache', WANObjectCache::class ],
+                       'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff::class ],
+                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ]
                ];
        }
 
index f679f63..dc6fc62 100644 (file)
@@ -2674,7 +2674,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $this->assertEquals( 0, $session->getUser()->getId() );
                $this->assertSame( [
                        [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
-                       [ LogLevel::ERROR, '{username} failed with message {message}' ],
+                       [ LogLevel::ERROR, '{username} failed with message {msg}' ],
                ], $logger->getBuffer() );
                $logger->clearBuffer();
                $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
index 0f4484e..aed2d83 100644 (file)
@@ -43,7 +43,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                ];
 
                $this->hideDeprecated( '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details' );
-               $result = LBFactoryMW::getLBFactoryClass( $config );
+               $result = MWLBFactory::getLBFactoryClass( $config );
 
                $this->assertEquals( $expected, $result );
        }
index 6eb96b1..881f5e1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * A MemoizedCallable subclass that stores function return values
- * in an instance property rather than APC.
+ * in an instance property rather than APC or APCu.
  */
 class ArrayBackedMemoizedCallable extends MemoizedCallable {
        private $cache = [];
@@ -44,7 +44,7 @@ class MemoizedCallableTest extends PHPUnit_Framework_TestCase {
         * Consecutive calls to the memoized callable with the same arguments
         * should result in just one invocation of the underlying callable.
         *
-        * @requires function apc_store
+        * @requires function apc_store/apcu_store
         */
        public function testCallableMemoized() {
                $observer = $this->getMock( 'stdClass', [ 'computeSomething' ] );
diff --git a/tests/phpunit/includes/libs/WaitConditionLoopTest.php b/tests/phpunit/includes/libs/WaitConditionLoopTest.php
deleted file mode 100644 (file)
index 9ce93d6..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-<?php
-
-class WaitConditionLoopFakeTime extends WaitConditionLoop {
-       protected $wallClock = 1;
-
-       function __construct( callable $condition, $timeout, array $busyCallbacks ) {
-               parent::__construct( $condition, $timeout, $busyCallbacks );
-       }
-
-       function usleep( $microseconds ) {
-               $this->wallClock += $microseconds / 1e6;
-       }
-
-       function getCpuTime() {
-               return 0.0;
-       }
-
-       function getWallTime() {
-               return $this->wallClock;
-       }
-
-       public function setWallClock( &$timestamp ) {
-               $this->wallClock =& $timestamp;
-       }
-}
-
-class WaitConditionLoopTest extends PHPUnit_Framework_TestCase {
-       public function testCallbackReached() {
-               $wallClock = microtime( true );
-
-               $count = 0;
-               $status = new StatusValue();
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, $status ) {
-                               ++$count;
-                               $status->value = 'cookie';
-
-                               return WaitConditionLoop::CONDITION_REACHED;
-                       },
-                       10.0,
-                       $this->newBusyWork( $x, $y, $z )
-               );
-               $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() );
-               $this->assertEquals( 1, $count );
-               $this->assertEquals( 'cookie', $status->value );
-               $this->assertEquals( [ 0, 0, 0 ], [ $x, $y, $z ], "No busy work done" );
-
-               $count = 0;
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, &$wallClock ) {
-                               $wallClock += 1;
-                               ++$count;
-
-                               return $count >= 2 ? WaitConditionLoop::CONDITION_REACHED : false;
-                       },
-                       7.0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock )
-               );
-               $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke(),
-                       "Busy work did not cause timeout" );
-               $this->assertEquals( [ 1, 0, 0 ], [ $x, $y, $z ] );
-
-               $count = 0;
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, &$wallClock ) {
-                               $wallClock += .1;
-                               ++$count;
-
-                               return $count > 80 ? true : false;
-                       },
-                       50.0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock, $dontCallMe, $badCalls )
-               );
-               $this->assertEquals( 0, $badCalls, "Callback exception not yet called" );
-               $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() );
-               $this->assertEquals( [ 1, 1, 1 ], [ $x, $y, $z ], "Busy work done" );
-               $this->assertEquals( 1, $badCalls, "Bad callback ran and was exception caught" );
-
-               try {
-                       $e = null;
-                       $dontCallMe();
-               } catch ( Exception $e ) {
-               }
-
-               $this->assertInstanceOf( 'RunTimeException', $e );
-               $this->assertEquals( 1, $badCalls, "Callback exception cached" );
-       }
-
-       public function testCallbackTimeout() {
-               $count = 0;
-               $wallClock = microtime( true );
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, &$wallClock ) {
-                               $wallClock += 3;
-                               ++$count;
-
-                               return $count > 300 ? true : false;
-                       },
-                       50.0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock )
-               );
-               $loop->setWallClock( $wallClock );
-               $this->assertEquals( $loop::CONDITION_TIMED_OUT, $loop->invoke() );
-               $this->assertEquals( [ 1, 1, 1 ], [ $x, $y, $z ], "Busy work done" );
-
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, &$wallClock ) {
-                               $wallClock += 3;
-                               ++$count;
-
-                               return true;
-                       },
-                       0.0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock )
-               );
-               $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() );
-
-               $count = 0;
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$count, &$wallClock ) {
-                               $wallClock += 3;
-                               ++$count;
-
-                               return $count > 10 ? true : false;
-                       },
-                       0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock )
-               );
-               $this->assertEquals( $loop::CONDITION_FAILED, $loop->invoke() );
-       }
-
-       public function testCallbackAborted() {
-               $x = 0;
-               $wallClock = microtime( true );
-               $loop = new WaitConditionLoopFakeTime(
-                       function () use ( &$x, &$wallClock ) {
-                               $wallClock += 2;
-                               ++$x;
-
-                               return $x > 2 ? WaitConditionLoop::CONDITION_ABORTED : false;
-                       },
-                       10.0,
-                       $this->newBusyWork( $x, $y, $z, $wallClock )
-               );
-               $loop->setWallClock( $wallClock );
-               $this->assertEquals( $loop::CONDITION_ABORTED, $loop->invoke() );
-       }
-
-       private function newBusyWork(
-               &$x, &$y, &$z, &$wallClock = 1, &$dontCallMe = null, &$badCalls = 0
-       ) {
-               $x = $y = $z = 0;
-               $badCalls = 0;
-
-               $list = [];
-               $list[] = function () use ( &$x, &$wallClock ) {
-                       $wallClock += 1;
-
-                       return ++$x;
-               };
-               $dontCallMe = function () use ( &$badCalls ) {
-                       ++$badCalls;
-                       throw new RuntimeException( "TrollyMcTrollFace" );
-               };
-               $list[] =& $dontCallMe;
-               $list[] = function () use ( &$y, &$wallClock ) {
-                       $wallClock += 15;
-
-                       return ++$y;
-               };
-               $list[] = function () use ( &$z, &$wallClock ) {
-                       $wallClock += 0.1;
-
-                       return ++$z;
-               };
-
-               return $list;
-       }
-}
index 2072752..3cde3e2 100644 (file)
@@ -28,6 +28,7 @@ class ComposerJsonTest extends MediaWikiTestCase {
        }
 
        /**
+        * @covers ComposerJson::__construct
         * @covers ComposerJson::getRequiredDependencies
         */
        public function testGetRequiredDependencies() {
index 75eb62c..3d5e8d3 100644 (file)
@@ -19,6 +19,7 @@ class ComposerLockTest extends MediaWikiTestCase {
        }
 
        /**
+        * @covers ComposerLock::__construct
         * @covers ComposerLock::getInstalledDependencies
         */
        public function testGetInstalledDependencies() {
index e55efee..4b7ebd3 100644 (file)
@@ -92,6 +92,9 @@ class WikiPageTest extends MediaWikiLangTestCase {
 
        /**
         * @covers WikiPage::doEditContent
+        * @covers WikiPage::doModify
+        * @covers WikiPage::doCreate
+        * @covers WikiPage::doEditUpdates
         */
        public function testDoEditContent() {
                $page = $this->newPage( "WikiPageTest_testDoEditContent" );
@@ -213,30 +216,6 @@ class WikiPageTest extends MediaWikiLangTestCase {
                $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
        }
 
-       /**
-        * @covers WikiPage::doQuickEditContent
-        */
-       public function testDoQuickEditContent() {
-               global $wgUser;
-
-               $page = $this->createPage(
-                       "WikiPageTest_testDoQuickEditContent",
-                       "original text",
-                       CONTENT_MODEL_WIKITEXT
-               );
-
-               $content = ContentHandler::makeContent(
-                       "quick text",
-                       $page->getTitle(),
-                       CONTENT_MODEL_WIKITEXT
-               );
-               $page->doQuickEditContent( $content, $wgUser, "testing q" );
-
-               # ---------------------
-               $page = new WikiPage( $page->getTitle() );
-               $this->assertTrue( $content->equals( $page->getContent() ) );
-       }
-
        /**
         * @covers WikiPage::doDeleteArticle
         */
index 018d978..0e0b943 100644 (file)
  */
 
 class MockDjVuHandler extends DjVuHandler {
+       function isEnabled() {
+               return true;
+       }
+
        function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
                if ( !$this->normaliseParams( $image, $params ) ) {
                        return new TransformParameterError( $params );