Merge "Add support for Laki"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 10 Feb 2016 08:03:54 +0000 (08:03 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 10 Feb 2016 08:03:54 +0000 (08:03 +0000)
153 files changed:
.jshintrc
.travis.yml
INSTALL
RELEASE-NOTES-1.27
autoload.php
composer.json
docs/contenthandler.txt
includes/DefaultSettings.php
includes/EditPage.php
includes/EventRelayerGroup.php [new file with mode: 0644]
includes/Linker.php
includes/MergeHistory.php [new file with mode: 0644]
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/Setup.php
includes/actions/EditAction.php
includes/api/ApiMain.php
includes/api/ApiMergeHistory.php [new file with mode: 0644]
includes/api/i18n/de.json
includes/api/i18n/en.json
includes/api/i18n/fr.json
includes/api/i18n/he.json
includes/api/i18n/lb.json
includes/api/i18n/qqq.json
includes/api/i18n/sh.json [new file with mode: 0644]
includes/api/i18n/zh-hans.json
includes/content/ContentHandler.php
includes/deferred/CdnCacheUpdate.php
includes/installer/Installer.php
includes/installer/LocalSettingsGenerator.php
includes/installer/WebInstallerExistingWiki.php
includes/installer/i18n/nb.json
includes/installer/i18n/pl.json
includes/installer/i18n/sh.json
includes/interwiki/Interwiki.php
includes/page/WikiPage.php
includes/parser/LinkHolderArray.php
includes/parser/ParserOptions.php
includes/resourceloader/ResourceLoaderModule.php
includes/session/BotPasswordSessionProvider.php
includes/session/CookieSessionProvider.php
includes/session/PHPSessionHandler.php
includes/session/SessionBackend.php
includes/session/SessionManager.php
includes/specials/SpecialLog.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialUndelete.php
includes/user/User.php
languages/data/Names.php
languages/i18n/ar.json
languages/i18n/azb.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/ce.json
languages/i18n/crh-cyrl.json
languages/i18n/cu.json
languages/i18n/de.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/frr.json
languages/i18n/he.json
languages/i18n/is.json
languages/i18n/ja.json
languages/i18n/ku-latn.json
languages/i18n/la.json
languages/i18n/lb.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/sh.json
languages/i18n/sl.json
languages/i18n/tl.json
languages/i18n/tt-cyrl.json
languages/i18n/uk.json
languages/i18n/war.json
languages/i18n/wuu.json
languages/i18n/zh-hans.json
languages/messages/MessagesGom.php
languages/messages/MessagesGom_deva.php
maintenance/jsduck/custom_tags.rb
maintenance/updateSearchIndex.php
package.json
resources/lib/oojs-ui/i18n/eo.json
resources/lib/oojs-ui/i18n/om.json
resources/lib/oojs-ui/i18n/sd.json
resources/lib/oojs-ui/i18n/sr-el.json
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/lib/oojs-ui/themes/apex/icons-content.json
resources/lib/oojs-ui/themes/apex/icons-editing-advanced.json
resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/language-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/language-ltr.svg
resources/lib/oojs-ui/themes/apex/images/icons/language-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/language-rtl.svg
resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/icons-editing-advanced.json
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl.svg
resources/src/jquery/jquery.tablesorter.less
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.skinning/images/sort_both_readonly.png [deleted file]
resources/src/mediawiki.skinning/images/sort_both_readonly.svg [deleted file]
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki/mediawiki.Title.js
resources/src/mediawiki/mediawiki.checkboxtoggle.js
resources/src/mediawiki/mediawiki.feedback.js
resources/src/startup.js
tests/phpunit/includes/EditPageTest.php
tests/phpunit/includes/MergeHistoryTest.php [new file with mode: 0644]
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/session/BotPasswordSessionProviderTest.php
tests/phpunit/includes/session/CookieSessionProviderTest.php
tests/phpunit/includes/session/PHPSessionHandlerTest.php
tests/phpunit/includes/session/SessionManagerTest.php

index b776e8f..62b9d82 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -2,14 +2,15 @@
        // Enforcing
        "bitwise": true,
        "eqeqeq": true,
-       "es3": true,
+       "esversion": 3,
        "freeze": true,
-       "latedef": true,
+       "futurehostile": true,
+       "latedef": "nofunc",
        "noarg": true,
        "nonew": true,
+       "strict": false,
        "undef": true,
        "unused": true,
-       "strict": false,
 
        // Relaxing
        "laxbreak": true,
index 2d07596..9062194 100644 (file)
@@ -12,9 +12,9 @@ matrix:
   fast_finish: true
   include:
     - env: dbtype=mysql
-      php: 5.3
+      php: 5.5
     - env: dbtype=postgres
-      php: 5.3
+      php: 5.5
     - env: dbtype=mysql
       php: hhvm
     - env: dbtype=mysql
diff --git a/INSTALL b/INSTALL
index 2054a57..4651a0c 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki
 "in-place", as long as you have the necessary prerequisites available.
 
 Required software:
-* Web server with PHP 5.3.3 or higher.
+* Web server with PHP 5.5.9 or higher.
 * A SQL server, the following types are supported
 ** MySQL 5.0.3 or higher
 ** PostgreSQL 8.3 or higher
index 5b9b2b8..6e2ca15 100644 (file)
@@ -8,6 +8,10 @@ THIS IS NOT A RELEASE YET
 MediaWiki 1.27 is an alpha-quality branch and is not recommended for use in
 production.
 
+=== PHP version requirement ===
+As of 1.27, MediaWiki now requires PHP 5.5.9 or higher. This corresponds with
+HHVM 3.1.
+
 === Configuration changes in 1.27 ===
 * $wgUseLinkNamespaceDBFields was removed.
 * Deprecated $wgResourceLoaderMinifierStatementsOnOwnLine and
@@ -158,6 +162,13 @@ production.
   aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns.
 * Removed "presentation" restriction on the HTML role attribute in wikitext.
   All values are now allowed for the role attribute.
+* $wgContentHandlers now also supports callbacks to create an instance of the
+  appropriate ContentHandler subclass.
+* Added $wgAuthenticationTokenVersion, which if non-null prevents the
+  user_token database field from being exposed in cookies. Setting this would
+  be a good idea, but will log out all current sessions.
+* $wgEventRelayerConfig was added, for managing PubSub event relay configuration,
+  specifically for reliable CDN url purges.
 
 === External library changes in 1.27 ===
 
@@ -312,7 +323,7 @@ changes to languages because of Phabricator reports.
 
 == Compatibility ==
 
-MediaWiki 1.27 requires PHP 5.3.3 or later. There is experimental support for
+MediaWiki 1.27 requires PHP 5.5.9 or later. There is experimental support for
 HHVM 3.6.5 or later.
 
 MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
index 4d48de0..0a9d80c 100644 (file)
@@ -52,6 +52,7 @@ $wgAutoloadLocalClasses = array(
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
        'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
        'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php',
+       'ApiMergeHistory' => __DIR__ . '/includes/api/ApiMergeHistory.php',
        'ApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
        'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
        'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
@@ -394,6 +395,7 @@ $wgAutoloadLocalClasses = array(
        'EraseArchivedFile' => __DIR__ . '/maintenance/eraseArchivedFile.php',
        'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php',
        'EventRelayer' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php',
+       'EventRelayerGroup' => __DIR__ . '/includes/EventRelayerGroup.php',
        'EventRelayerMCRD' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerMCRD.php',
        'EventRelayerNull' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php',
        'Exif' => __DIR__ . '/includes/media/Exif.php',
@@ -825,6 +827,7 @@ $wgAutoloadLocalClasses = array(
        'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
        'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
        'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
+       'MergeHistory' => __DIR__ . '/includes/MergeHistory.php',
        'MergeHistoryPager' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
        'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
        'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
index f326dcb..0b50c2a 100644 (file)
@@ -21,9 +21,9 @@
                "ext-iconv": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.15.2",
+               "oojs/oojs-ui": "0.15.3",
                "oyejorge/less.php": "1.7.0.10",
-               "php": ">=5.3.3",
+               "php": ">=5.5.9",
                "psr/log": "1.0.0",
                "wikimedia/assert": "0.2.2",
                "wikimedia/base-convert": "1.0.1",
index 5f9a0b0..f1f478e 100644 (file)
@@ -148,7 +148,8 @@ using a model or format different from the default will result in an error.
 
 There are some new globals that can be used to control the behavior of the ContentHandler facility:
 
-* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses.
+* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses
+  or callbacks that create an instance of the appropriate ContentHandler subclass.
 
 * $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace.
 
index 6a41152..a6a0c75 100644 (file)
@@ -904,7 +904,8 @@ $wgMediaHandlers = array(
 
 /**
  * Plugins for page content model handling.
- * Each entry in the array maps a model id to a class name.
+ * Each entry in the array maps a model id to a class name or callback
+ * that creates an instance of the appropriate ContentHandler subclass.
  *
  * @since 1.21
  */
@@ -7964,6 +7965,25 @@ $wgPopularPasswordFile = __DIR__ . '/../serialized/commonpasswords.cdb';
  */
 $wgMaxUserDBWriteDuration = false;
 
+/**
+ * Mapping of event channels to EventRelayer configuration.
+ *
+ * By setting up a PubSub system (like Kafka) and enabling a corresponding EventRelayer class
+ * that uses it, MediaWiki can broadcast events to all subscribers. Certain features like WAN
+ * cache purging and CDN cache purging will emit events to this system. Appropriate listers can
+ * subscribe to the channel and take actions based on the events. For example, a local daemon
+ * can run on each CDN cache node and perfom local purges based on the URL purge channel events.
+ *
+ * The 'default' channel is for all channels without an explicit entry here.
+ *
+ * @since 1.27
+ */
+$wgEventRelayerConfig = array(
+       'default' => array(
+               'class' => 'EventRelayerNull',
+       )
+);
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 277a6cc..914bad4 100644 (file)
@@ -2232,8 +2232,6 @@ class EditPage {
                        $wgOut->addModules( 'mediawiki.action.edit.stash' );
                }
 
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
                # Enabled article-related sidebar, toplinks, etc.
                $wgOut->setArticleRelated( true );
 
diff --git a/includes/EventRelayerGroup.php b/includes/EventRelayerGroup.php
new file mode 100644 (file)
index 0000000..3af756d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Factory class for spawning EventRelayer objects using configuration
+ *
+ * @author Aaron Schulz
+ * @since 1.27
+ */
+class EventRelayerGroup {
+       /** @var array[] */
+       protected $configByChannel = array();
+
+       /** @var EventRelayer[] */
+       protected $relayers = array();
+
+       /** @var EventRelayerGroup */
+       protected static $instance = null;
+
+       /**
+        * @param Config $config
+        */
+       protected function __construct( Config $config ) {
+               $this->configByChannel = $config->get( 'EventRelayerConfig' );
+       }
+
+       /**
+        * @return EventRelayerGroup
+        */
+       public static function singleton() {
+               if ( !self::$instance ) {
+                       self::$instance = new self( RequestContext::getMain()->getConfig() );
+               }
+
+               return self::$instance;
+       }
+
+       /**
+        * @param string $channel
+        * @return EventRelayer Relayer instance that handles the given channel
+        */
+       public function getRelayer( $channel ) {
+               $channelKey = isset( $this->configByChannel[$channel] )
+                       ? $channel
+                       : 'default';
+
+               if ( !isset( $this->relayers[$channelKey] ) ) {
+                       if ( !isset( $this->configByChannel[$channelKey] ) ) {
+                               throw new UnexpectedValueException( "No config for '$channelKey'" );
+                       }
+
+                       $config = $this->configByChannel[$channelKey];
+                       $class = $config['class'];
+
+                       $this->relayers[$channelKey] = new $class( $config );
+               }
+
+               return $this->relayers[$channelKey];
+       }
+}
index 4b9b963..f0a2963 100644 (file)
@@ -188,6 +188,7 @@ class Linker {
         *       Has compatibility issues on some setups, so avoid wherever possible.
         *     'http': Force a full URL with http:// as the scheme.
         *     'https': Force a full URL with https:// as the scheme.
+        *     'stubThreshold' => (int): Stub threshold to use when determining link classes.
         * @return string HTML <a> attribute
         */
        public static function link(
@@ -218,7 +219,7 @@ class Linker {
                $target = self::normaliseSpecialPage( $target );
 
                # If we don't know whether the page exists, let's find out.
-               if ( !in_array( 'known', $options ) && !in_array( 'broken', $options ) ) {
+               if ( !in_array( 'known', $options, true ) && !in_array( 'broken', $options, true ) ) {
                        if ( $target->isKnown() ) {
                                $options[] = 'known';
                        } else {
@@ -227,14 +228,14 @@ class Linker {
                }
 
                $oldquery = array();
-               if ( in_array( "forcearticlepath", $options ) && $query ) {
+               if ( in_array( "forcearticlepath", $options, true ) && $query ) {
                        $oldquery = $query;
                        $query = array();
                }
 
                # Note: we want the href attribute first, for prettiness.
                $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
-               if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
+               if ( in_array( 'forcearticlepath', $options, true ) && $oldquery ) {
                        $attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery );
                }
 
@@ -277,7 +278,7 @@ class Linker {
        private static function linkUrl( $target, $query, $options ) {
                # We don't want to include fragments for broken links, because they
                # generally make no sense.
-               if ( in_array( 'broken', $options ) && $target->hasFragment() ) {
+               if ( in_array( 'broken', $options, true ) && $target->hasFragment() ) {
                        $target = clone $target;
                        $target->setFragment( '' );
                }
@@ -285,15 +286,15 @@ class Linker {
                # If it's a broken link, add the appropriate query pieces, unless
                # there's already an action specified, or unless 'edit' makes no sense
                # (i.e., for a nonexistent special page).
-               if ( in_array( 'broken', $options ) && empty( $query['action'] )
+               if ( in_array( 'broken', $options, true ) && empty( $query['action'] )
                        && !$target->isSpecialPage() ) {
                        $query['action'] = 'edit';
                        $query['redlink'] = '1';
                }
 
-               if ( in_array( 'http', $options ) ) {
+               if ( in_array( 'http', $options, true ) ) {
                        $proto = PROTO_HTTP;
-               } elseif ( in_array( 'https', $options ) ) {
+               } elseif ( in_array( 'https', $options, true ) ) {
                        $proto = PROTO_HTTPS;
                } else {
                        $proto = PROTO_RELATIVE;
@@ -316,11 +317,11 @@ class Linker {
                global $wgUser;
                $defaults = array();
 
-               if ( !in_array( 'noclasses', $options ) ) {
+               if ( !in_array( 'noclasses', $options, true ) ) {
                        # Now build the classes.
                        $classes = array();
 
-                       if ( in_array( 'broken', $options ) ) {
+                       if ( in_array( 'broken', $options, true ) ) {
                                $classes[] = 'new';
                        }
 
@@ -328,8 +329,11 @@ class Linker {
                                $classes[] = 'extiw';
                        }
 
-                       if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
-                               $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
+                       if ( !in_array( 'broken', $options, true ) ) { # Avoid useless calls to LinkCache (see r50387)
+                               $colour = self::getLinkColour(
+                                       $target,
+                                       isset( $options['stubThreshold'] ) ? $options['stubThreshold'] : $wgUser->getStubThreshold()
+                               );
                                if ( $colour !== '' ) {
                                        $classes[] = $colour; # mw-redirect or stub
                                }
@@ -343,7 +347,7 @@ class Linker {
                if ( $target->getPrefixedText() == '' ) {
                        # A link like [[#Foo]].  This used to mean an empty title
                        # attribute, but that's silly.  Just don't output a title.
-               } elseif ( in_array( 'known', $options ) ) {
+               } elseif ( in_array( 'known', $options, true ) ) {
                        $defaults['title'] = $target->getPrefixedText();
                } else {
                        // This ends up in parser cache!
@@ -1845,7 +1849,7 @@ class Linker {
                }
 
                $editCount = false;
-               if ( in_array( 'verify', $options ) ) {
+               if ( in_array( 'verify', $options, true ) ) {
                        $editCount = self::getRollbackEditCount( $rev, true );
                        if ( $editCount === false ) {
                                return '';
@@ -1854,7 +1858,7 @@ class Linker {
 
                $inner = self::buildRollbackLink( $rev, $context, $editCount );
 
-               if ( !in_array( 'noBrackets', $options ) ) {
+               if ( !in_array( 'noBrackets', $options, true ) ) {
                        $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
                }
 
diff --git a/includes/MergeHistory.php b/includes/MergeHistory.php
new file mode 100644 (file)
index 0000000..a3861ee
--- /dev/null
@@ -0,0 +1,351 @@
+<?php
+
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon <geofbot@gmail.com>
+ *
+ * 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
+ */
+
+/**
+ * Handles the backend logic of merging the histories of two
+ * pages.
+ *
+ * @since 1.27
+ */
+class MergeHistory {
+
+       /** @const int Maximum number of revisions that can be merged at once (avoid too much slave lag) */
+       const REVISION_LIMIT = 5000;
+
+       /** @var Title Page from which history will be merged */
+       protected $source;
+
+       /** @var Title Page to which history will be merged */
+       protected $dest;
+
+       /** @var DatabaseBase Database that we are using */
+       protected $dbw;
+
+       /** @var MWTimestamp Maximum timestamp that we can use (oldest timestamp of dest) */
+       protected $maxTimestamp;
+
+       /** @var string SQL WHERE condition that selects source revisions to insert into destination */
+       protected $timeWhere;
+
+       /** @var MWTimestamp|boolean Timestamp upto which history from the source will be merged */
+       protected $timestampLimit;
+
+       /** @var integer Number of revisions merged (for Special:MergeHistory success message) */
+       protected $revisionsMerged;
+
+       /**
+        * MergeHistory constructor.
+        * @param Title $source Page from which history will be merged
+        * @param Title $dest Page to which history will be merged
+        * @param string|boolean $timestamp Timestamp up to which history from the source will be merged
+        */
+       public function __construct( Title $source, Title $dest, $timestamp = false ) {
+               // Save the parameters
+               $this->source = $source;
+               $this->dest = $dest;
+
+               // Get the database
+               $this->dbw = wfGetDB( DB_MASTER );
+
+               // Max timestamp should be min of destination page
+               $firstDestTimestamp = $this->dbw->selectField(
+                       'revision',
+                       'MIN(rev_timestamp)',
+                       array( 'rev_page' => $this->dest->getArticleID() ),
+                       __METHOD__
+               );
+               $this->maxTimestamp = new MWTimestamp( $firstDestTimestamp );
+
+               // Get the timestamp pivot condition
+               try {
+                       if ( $timestamp ) {
+                               // If we have a requested timestamp, use the
+                               // latest revision up to that point as the insertion point
+                               $mwTimestamp = new MWTimestamp( $timestamp );
+                               $lastWorkingTimestamp = $this->dbw->selectField(
+                                       'revision',
+                                       'MAX(rev_timestamp)',
+                                       array(
+                                               'rev_timestamp <= ' . $this->dbw->timestamp( $mwTimestamp ),
+                                               'rev_page' => $this->source->getArticleID()
+                                       ),
+                                       __METHOD__
+                               );
+                               $mwLastWorkingTimestamp = new MWTimestamp( $lastWorkingTimestamp );
+
+                               $timeInsert = $mwLastWorkingTimestamp;
+                               $this->timestampLimit = $mwLastWorkingTimestamp;
+                       } else {
+                               // If we don't, merge entire source page history into the
+                               // beginning of destination page history
+
+                               // Get the latest timestamp of the source
+                               $lastSourceTimestamp = $this->dbw->selectField(
+                                       array( 'page', 'revision' ),
+                                       'rev_timestamp',
+                                       array( 'page_id' => $this->source->getArticleID(),
+                                               'page_latest = rev_id'
+                                       ),
+                                       __METHOD__
+                               );
+                               $lasttimestamp = new MWTimestamp( $lastSourceTimestamp );
+
+                               $timeInsert = $this->maxTimestamp;
+                               $this->timestampLimit = $lasttimestamp;
+                       }
+
+                       $this->timeWhere = "rev_timestamp <= {$this->dbw->timestamp( $timeInsert )}";
+               } catch ( TimestampException $ex ) {
+                       // The timestamp we got is screwed up and merge cannot continue
+                       // This should be detected by $this->isValidMerge()
+                       $this->timestampLimit = false;
+               }
+       }
+
+       /**
+        * Get the number of revisions that will be moved
+        * @return int
+        */
+       public function getRevisionCount() {
+               $count = $this->dbw->selectRowCount( 'revision', '1',
+                       array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ),
+                       __METHOD__,
+                       array( 'LIMIT' => self::REVISION_LIMIT + 1 )
+               );
+
+               return $count;
+       }
+
+       /**
+        * Get the number of revisions that were moved
+        * Used in the SpecialMergeHistory success message
+        * @return int
+        */
+       public function getMergedRevisionCount() {
+               return $this->revisionsMerged;
+       }
+
+       /**
+        * Check if the merge is possible
+        * @param User $user
+        * @param string $reason
+        * @return Status
+        */
+       public function checkPermissions( User $user, $reason ) {
+               $status = new Status();
+
+               // Check if user can edit both pages
+               $errors = wfMergeErrorArrays(
+                       $this->source->getUserPermissionsErrors( 'edit', $user ),
+                       $this->dest->getUserPermissionsErrors( 'edit', $user )
+               );
+
+               // Convert into a Status object
+               if ( $errors ) {
+                       foreach ( $errors as $error ) {
+                               call_user_func_array( array( $status, 'fatal' ), $error );
+                       }
+               }
+
+               // Anti-spam
+               if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+                       // This is kind of lame, won't display nice
+                       $status->fatal( 'spamprotectiontext' );
+               }
+
+               // Check mergehistory permission
+               if ( !$user->isAllowed( 'mergehistory' ) ) {
+                       // User doesn't have the right to merge histories
+                       $status->fatal( 'mergehistory-fail-permission' );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Does various sanity checks that the merge is
+        * valid. Only things based on the two pages
+        * should be checked here.
+        *
+        * @return Status
+        */
+       public function isValidMerge() {
+               $status = new Status();
+
+               // If either article ID is 0, then revisions cannot be reliably selected
+               if ( $this->source->getArticleID() === 0 ) {
+                       $status->fatal( 'mergehistory-fail-invalid-source' );
+               }
+               if ( $this->dest->getArticleID() === 0 ) {
+                       $status->fatal( 'mergehistory-fail-invalid-dest' );
+               }
+
+               // Make sure page aren't the same
+               if ( $this->source->equals( $this->dest ) ) {
+                       $status->fatal( 'mergehistory-fail-self-merge' );
+               }
+
+               // Make sure the timestamp is valid
+               if ( !$this->timestampLimit ) {
+                       $status->fatal( 'mergehistory-fail-bad-timestamp' );
+               }
+
+               // $this->timestampLimit must be older than $this->maxTimestamp
+               if ( $this->timestampLimit > $this->maxTimestamp ) {
+                       $status->fatal( 'mergehistory-fail-timestamps-overlap' );
+               }
+
+               // Check that there are not too many revisions to move
+               if ( $this->timestampLimit && $this->getRevisionCount() > self::REVISION_LIMIT ) {
+                       $status->fatal( 'mergehistory-fail-toobig', Message::numParam( self::REVISION_LIMIT ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Actually attempt the history move
+        *
+        * @todo if all versions of page A are moved to B and then a user
+        * tries to do a reverse-merge via the "unmerge" log link, then page
+        * A will still be a redirect (as it was after the original merge),
+        * though it will have the old revisions back from before (as expected).
+        * The user may have to "undo" the redirect manually to finish the "unmerge".
+        * Maybe this should delete redirects at the source page of merges?
+        *
+        * @param User $user
+        * @param string $reason
+        * @return Status status of the history merge
+        */
+       public function merge( User $user, $reason = '' ) {
+               $status = new Status();
+
+               // Check validity and permissions required for merge
+               $validCheck = $this->isValidMerge(); // Check this first to check for null pages
+               if ( !$validCheck->isOK() ) {
+                       return $validCheck;
+               }
+               $permCheck = $this->checkPermissions( $user, $reason );
+               if ( !$permCheck->isOK() ) {
+                       return $permCheck;
+               }
+
+               $this->dbw->update(
+                       'revision',
+                       array( 'rev_page' => $this->dest->getArticleID() ),
+                       array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ),
+                       __METHOD__
+               );
+
+               // Check if this did anything
+               $this->revisionsMerged = $this->dbw->affectedRows();
+               if ( $this->revisionsMerged < 1 ) {
+                       $status->fatal( 'mergehistory-fail-no-change' );
+                       return $status;
+               }
+
+               // Make the source page a redirect if no revisions are left
+               $haveRevisions = $this->dbw->selectField(
+                       'revision',
+                       'rev_timestamp',
+                       array( 'rev_page' => $this->source->getArticleID() ),
+                       __METHOD__,
+                       array( 'FOR UPDATE' )
+               );
+               if ( !$haveRevisions ) {
+                       if ( $reason ) {
+                               $reason = wfMessage(
+                                       'mergehistory-comment',
+                                       $this->source->getPrefixedText(),
+                                       $this->dest->getPrefixedText(),
+                                       $reason
+                               )->inContentLanguage()->text();
+                       } else {
+                               $reason = wfMessage(
+                                       'mergehistory-autocomment',
+                                       $this->source->getPrefixedText(),
+                                       $this->dest->getPrefixedText()
+                               )->inContentLanguage()->text();
+                       }
+
+                       $contentHandler = ContentHandler::getForTitle( $this->source );
+                       $redirectContent = $contentHandler->makeRedirectContent(
+                               $this->dest,
+                               wfMessage( 'mergehistory-redirect-text' )->inContentLanguage()->plain()
+                       );
+
+                       if ( $redirectContent ) {
+                               $redirectPage = WikiPage::factory( $this->source );
+                               $redirectRevision = new Revision( array(
+                                       'title' => $this->source,
+                                       'page' => $this->source->getArticleID(),
+                                       'comment' => $reason,
+                                       'content' => $redirectContent ) );
+                               $redirectRevision->insertOn( $this->dbw );
+                               $redirectPage->updateRevisionOn( $this->dbw, $redirectRevision );
+
+                               // Now, we record the link from the redirect to the new title.
+                               // It should have no other outgoing links...
+                               $this->dbw->delete(
+                                       'pagelinks',
+                                       array( 'pl_from' => $this->dest->getArticleID() ),
+                                       __METHOD__
+                               );
+                               $this->dbw->insert( 'pagelinks',
+                                       array(
+                                               'pl_from' => $this->dest->getArticleID(),
+                                               'pl_from_namespace' => $this->dest->getNamespace(),
+                                               'pl_namespace' => $this->dest->getNamespace(),
+                                               'pl_title' => $this->dest->getDBkey() ),
+                                       __METHOD__
+                               );
+                       } else {
+                               // Warning if we couldn't create the redirect
+                               $status->warning( 'mergehistory-warning-redirect-not-created' );
+                       }
+               } else {
+                       $this->source->invalidateCache(); // update histories
+               }
+               $this->dest->invalidateCache(); // update histories
+
+               // Update our logs
+               $logEntry = new ManualLogEntry( 'merge', 'merge' );
+               $logEntry->setPerformer( $user );
+               $logEntry->setComment( $reason );
+               $logEntry->setTarget( $this->source );
+               $logEntry->setParameters( array(
+                       '4::dest' => $this->dest->getPrefixedText(),
+                       '5::mergepoint' => $this->timestampLimit->getTimestamp( TS_MW )
+               ) );
+               $logId = $logEntry->insert();
+               $logEntry->publish( $logId );
+
+               Hooks::run( 'ArticleMergeComplete', array( $this->source, $this->dest ) );
+
+               return $status;
+       }
+}
index 3adef5b..317c126 100644 (file)
@@ -1573,11 +1573,42 @@ class OutputPage extends ContextSource {
         * @return ParserOptions
         */
        public function parserOptions( $options = null ) {
+               if ( $options !== null && !empty( $options->isBogus ) ) {
+                       // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
+                       // been changed somehow, and keep it if so.
+                       $anonPO = ParserOptions::newFromAnon();
+                       $anonPO->setEditSection( false );
+                       if ( !$options->matches( $anonPO ) ) {
+                               wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
+                               $options->isBogus = false;
+                       }
+               }
+
                if ( !$this->mParserOptions ) {
+                       if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
+                               // $wgUser isn't unstubbable yet, so don't try to get a
+                               // ParserOptions for it. And don't cache this ParserOptions
+                               // either.
+                               $po = ParserOptions::newFromAnon();
+                               $po->setEditSection( false );
+                               $po->isBogus = true;
+                               if ( $options !== null ) {
+                                       $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
+                               }
+                               return $po;
+                       }
+
                        $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
                        $this->mParserOptions->setEditSection( false );
                }
-               return wfSetVar( $this->mParserOptions, $options );
+
+               if ( $options !== null && !empty( $options->isBogus ) ) {
+                       // They're trying to restore the bogus pre-$wgUser PO. Do the right
+                       // thing.
+                       return wfSetVar( $this->mParserOptions, null, true );
+               } else {
+                       return wfSetVar( $this->mParserOptions, $options );
+               }
        }
 
        /**
index eaab9c8..1eafcfa 100644 (file)
@@ -31,7 +31,7 @@
  */
 function wfEntryPointCheck( $entryPoint ) {
        $mwVersion = '1.27';
-       $minimumVersionPHP = '5.3.3';
+       $minimumVersionPHP = '5.5.9';
        $phpVersion = PHP_VERSION;
 
        if ( !function_exists( 'version_compare' )
index ba3d628..b9a1c37 100644 (file)
@@ -796,13 +796,15 @@ foreach ( $wgExtensionFunctions as $func ) {
 
 // If the session user has a 0 id but a valid name, that means we need to
 // autocreate it.
-$sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser();
-if ( $sessionUser->getId() === 0 && User::isValidUserName( $sessionUser->getName() ) ) {
-       $ps_autocreate = Profiler::instance()->scopedProfileIn( $fname . '-autocreate' );
-       MediaWiki\Session\SessionManager::autoCreateUser( $sessionUser );
-       Profiler::instance()->scopedProfileOut( $ps_autocreate );
+if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
+       $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser();
+       if ( $sessionUser->getId() === 0 && User::isValidUserName( $sessionUser->getName() ) ) {
+               $ps_autocreate = Profiler::instance()->scopedProfileIn( $fname . '-autocreate' );
+               MediaWiki\Session\SessionManager::autoCreateUser( $sessionUser );
+               Profiler::instance()->scopedProfileOut( $ps_autocreate );
+       }
+       unset( $sessionUser );
 }
-unset( $sessionUser );
 
 wfDebug( "Fully initialised\n" );
 $wgFullyInitialised = true;
index 643d1c4..9b70994 100644 (file)
@@ -43,8 +43,9 @@ class EditAction extends FormlessAction {
        public function show() {
                $this->useTransactionalTimeLimit();
 
+               $out = $this->getOutput();
+               $out->setRobotPolicy( 'noindex,nofollow' );
                if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
-                       $out = $this->getOutput();
                        $out->addModuleStyles( array(
                                'mediawiki.ui.input',
                                'mediawiki.ui.checkbox',
index 458fd18..f8192e5 100644 (file)
@@ -90,6 +90,7 @@ class ApiMain extends ApiBase {
                'revisiondelete' => 'ApiRevisionDelete',
                'managetags' => 'ApiManageTags',
                'tag' => 'ApiTag',
+               'mergehistory' => 'ApiMergeHistory',
        );
 
        /**
diff --git a/includes/api/ApiMergeHistory.php b/includes/api/ApiMergeHistory.php
new file mode 100644 (file)
index 0000000..8fa9d28
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon <geofbot@gmail.com>
+ *
+ * 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
+ */
+
+/**
+ * API Module to merge page histories
+ * @ingroup API
+ */
+class ApiMergeHistory extends ApiBase {
+
+       public function execute() {
+               $this->useTransactionalTimeLimit();
+
+               $user = $this->getUser();
+               $params = $this->extractRequestParams();
+
+               $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
+               $this->requireOnlyOneParameter( $params, 'to', 'toid' );
+
+               // Get page objects (nonexistant pages get caught in MergeHistory::isValidMerge())
+               if ( isset( $params['from'] ) ) {
+                       $fromTitle = Title::newFromText( $params['from'] );
+                       if ( !$fromTitle || $fromTitle->isExternal() ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', $params['from'] ) );
+                       }
+               } elseif ( isset( $params['fromid'] ) ) {
+                       $fromTitle = Title::newFromID( $params['fromid'] );
+                       if ( !$fromTitle ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', $params['fromid'] ) );
+                       }
+               }
+
+               if ( isset( $params['to'] ) ) {
+                       $toTitle = Title::newFromText( $params['to'] );
+                       if ( !$toTitle || $toTitle->isExternal() ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', $params['to'] ) );
+                       }
+               } elseif ( isset( $params['toid'] ) ) {
+                       $toTitle = Title::newFromID( $params['toid'] );
+                       if ( !$toTitle ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', $params['toid'] ) );
+                       }
+               }
+
+               $reason = $params['reason'];
+               $timestamp = $params['timestamp'];
+
+               // Merge!
+               $status = $this->merge( $fromTitle, $toTitle, $timestamp, $reason );
+               if ( !$status->isOK() ) {
+                       $this->dieStatus( $status );
+               }
+
+               $r = array(
+                       'from' => $fromTitle->getPrefixedText(),
+                       'to' => $toTitle->getPrefixedText(),
+                       'timestamp' => wfTimestamp( TS_ISO_8601, $params['timestamp'] ),
+                       'reason' => $params['reason']
+               );
+               $result = $this->getResult();
+
+               $result->addValue( null, $this->getModuleName(), $r );
+       }
+
+       /**
+        * @param Title $from
+        * @param Title $to
+        * @param string $timestamp
+        * @param string $reason
+        * @return Status
+        */
+       protected function merge( Title $from, Title $to, $timestamp, $reason ) {
+               $mh = new MergeHistory( $from, $to, $timestamp );
+
+               return $mh->merge( $this->getUser(), $reason );
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'from' => null,
+                       'fromid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
+                       'to' => null,
+                       'toid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
+                       'timestamp' => array(
+                               ApiBase::PARAM_TYPE => 'timestamp'
+                       ),
+                       'reason' => '',
+               );
+       }
+
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       protected function getExamplesMessages() {
+               return array(
+                       'action=mergehistory&from=Oldpage&to=Newpage&token=123ABC&' .
+                       'reason=Reason'
+                       => 'apihelp-mergehistory-example-merge',
+                       'action=mergehistory&from=Oldpage&to=Newpage&token=123ABC&' .
+                       'reason=Reason&timestamp=2015-12-31T04%3A37%3A41Z' // TODO
+                       => 'apihelp-mergehistory-example-merge-timestamp',
+               );
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Mergehistory';
+       }
+}
index 47e8e22..0d965e6 100644 (file)
        "apihelp-managetags-example-delete": "Löscht die <kbd>vandlaism</kbd>-Markierung mit der Begründung <kbd>Misspelt</kbd>.",
        "apihelp-managetags-example-activate": "Aktiviert eine Markierung namens <kbd>spam</kbd> mit der Begründung <kbd>For use in edit patrolling</kbd> (für die Eingangskontrolle).",
        "apihelp-managetags-example-deactivate": "Deaktiviert eine Markierung namens <kbd>spam</kbd> mit der Begründung <kbd>No longer required</kbd> (nicht mehr benötigt).",
+       "apihelp-mergehistory-description": "Führt Versionsgeschichten von Seiten zusammen.",
        "apihelp-move-description": "Eine Seite verschieben.",
        "apihelp-move-param-from": "Titel der zu verschiebenden Seite. Kann nicht zusammen mit <var>$1fromid</var> verwendet werden.",
        "apihelp-move-param-fromid": "Seitenkennung der zu verschiebenden Seite. Kann nicht zusammen mit <var>$1from</var> verwendet werden.",
        "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "Ergänzt den Titel des Interwikis.",
        "apihelp-query+iwbacklinks-param-dir": "Die Auflistungsrichtung.",
        "apihelp-query+iwlinks-paramvalue-prop-url": "Ergänzt die vollständige URL.",
+       "apihelp-query+iwlinks-param-limit": "Wie viele Interwiki-Links zurückgegeben werden sollen.",
+       "apihelp-query+iwlinks-param-prefix": "Gibt nur Interwiki-Links mit diesem Präfix zurück.",
        "apihelp-query+iwlinks-param-dir": "Die Auflistungsrichtung.",
        "apihelp-query+langbacklinks-param-limit": "Wie viele Gesamtseiten zurückgegeben werden sollen.",
        "apihelp-query+langbacklinks-param-dir": "Die Auflistungsrichtung.",
        "apihelp-query+langlinks-param-limit": "Wie viele Sprachlinks zurückgegeben werden sollen.",
        "apihelp-query+langlinks-paramvalue-prop-url": "Ergänzt die vollständige URL.",
        "apihelp-query+langlinks-param-dir": "Die Auflistungsrichtung.",
+       "apihelp-query+links-param-limit": "Wie viele Links zurückgegeben werden sollen.",
        "apihelp-query+links-param-dir": "Die Auflistungsrichtung.",
        "apihelp-query+links-example-simple": "Links von der <kbd>Hauptseite</kbd> abrufen",
        "apihelp-query+linkshere-description": "Alle Seiten finden, die auf die angegebenen Seiten verlinken.",
        "apihelp-query+linkshere-paramvalue-prop-pageid": "Die Seitenkennung jeder Seite.",
+       "apihelp-query+linkshere-paramvalue-prop-title": "Titel jeder Seite.",
        "apihelp-query+logevents-description": "Ereignisse von den Logbüchern abrufen.",
+       "apihelp-query+logevents-paramvalue-prop-type": "Ergänzt den Typ des Logbuchereignisses.",
+       "apihelp-query+logevents-paramvalue-prop-comment": "Ergänzt den Kommentar des Logbuchereignisses.",
        "apihelp-query+logevents-example-simple": "Listet die letzten Logbuch-Ereignisse auf.",
        "apihelp-query+pageswithprop-paramvalue-prop-ids": "Fügt die Seitenkennung hinzu.",
        "apihelp-query+pageswithprop-param-limit": "Die maximale Anzahl zurückzugebender Seiten.",
        "apihelp-query+prefixsearch-param-search": "Such-Zeichenfolge.",
        "apihelp-query+prefixsearch-param-offset": "Anzahl der zu überspringenden Ergebnisse.",
+       "apihelp-query+querypage-param-limit": "Anzahl der zurückzugebenden Ergebnisse.",
+       "apihelp-query+recentchanges-description": "Listet die letzten Änderungen auf.",
        "apihelp-query+recentchanges-paramvalue-prop-timestamp": "Ergänzt den Zeitstempel für die Bearbeitung.",
        "apihelp-query+recentchanges-paramvalue-prop-tags": "Listet Markierungen für den Eintrag auf.",
        "apihelp-query+recentchanges-example-simple": "Listet die letzten Änderungen auf.",
index a1b303f..4a1f2f1 100644 (file)
        "apihelp-managetags-example-activate": "Activate a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-deactivate": "Deactivate a tag named <kbd>spam</kbd> with the reason <kbd>No longer required</kbd>",
 
+       "apihelp-mergehistory-description": "Merge page histories.",
+       "apihelp-mergehistory-param-from": "Title of the page from which history will be merged. Cannot be used together with <var>$1fromid</var>.",
+       "apihelp-mergehistory-param-fromid": "Page ID of the page from which history will be merged. Cannot be used together with <var>$1from</var>.",
+       "apihelp-mergehistory-param-to": "Title of the page to which history will be merged. Cannot be used together with <var>$1toid</var>.",
+       "apihelp-mergehistory-param-toid": "Page ID of the page to which history will be merged. Cannot be used together with <var>$1to</var>.",
+       "apihelp-mergehistory-param-timestamp": "Timestamp up to which revisions will be moved from the source page's history to the destination page's history. If omitted, the entire page history of the source page will be merged into the destination page.",
+       "apihelp-mergehistory-param-reason": "Reason for the history merge.",
+       "apihelp-mergehistory-example-merge": "Merge the entire history of <kbd>Oldpage</kbd> into <kbd>Newpage</kbd>.",
+       "apihelp-mergehistory-example-merge-timestamp": "Merge the page revisions of <kbd>Oldpage</kbd> dating up to <kbd>2015-12-31T04:37:41Z</kbd> into <kbd>Newpage</kbd>.",
+
        "apihelp-move-description": "Move a page.",
        "apihelp-move-param-from": "Title of the page to rename. Cannot be used together with <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "Page ID of the page to rename. Cannot be used together with <var>$1from</var>.",
index a0af3b2..309724c 100644 (file)
        "apihelp-managetags-example-delete": "Supprimer la balise <kbd>vandlaism</kbd> avec le motif <kbd>Misspelt</kbd>",
        "apihelp-managetags-example-activate": "Activer une balise nommée <kbd>spam</kbd> avec le motif <kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-deactivate": "Désactiver une balise nommée <kbd>spam</kbd> avec le motif <kbd>No longer required</kbd>",
+       "apihelp-mergehistory-description": "Fusionner les historiques des pages.",
+       "apihelp-mergehistory-param-from": "Titre de la page depuis laquelle l’historique sera fusionné. Impossible à utiliser avec <var>$1fromid</var>.",
+       "apihelp-mergehistory-param-fromid": "ID de la page depuis laquelle l’historique sera fusionné. Impossible à utiliser avec <var>$1from</var>.",
+       "apihelp-mergehistory-param-to": "Titre de la page vers laquelle l’historique sera fusionné. Impossible à utiliser avec <var>$1toid</var>.",
+       "apihelp-mergehistory-param-toid": "ID de la page vers laquelle l’historique sera fusionné. Impossible à utiliser avec <var>$1to</var>.",
+       "apihelp-mergehistory-param-timestamp": "Horodatage jusqu’auquel les révisions seront déplacées de l’historique de la page source vers l’historique de la page de destination. S’il est omis, tout l’historique de la page source sera fusionné avec celui de la page de destination.",
+       "apihelp-mergehistory-param-reason": "Raison pour fusionner l’historique.",
+       "apihelp-mergehistory-example-merge": "Fusionner l’historique complet de  <kbd>AnciennePage</kbd> dans <kbd>NouvellePage</kbd>.",
+       "apihelp-mergehistory-example-merge-timestamp": "Fusionner les révisions de la page <kbd>AnciennePage</kbd> jusqu’au <kbd>2015-12-31T04:37:41Z</kbd> dans <kbd>NouvellePage</kbd>.",
        "apihelp-move-description": "Déplacer une page.",
        "apihelp-move-param-from": "Titre de la page à renommer. Impossible de l’utiliser avec <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "ID de la page à renommer. Impossible à utiliser avec <var>$1from</var>.",
index 4da0430..e18cd60 100644 (file)
        "apihelp-managetags-example-delete": "מחיקת התג <kbd>vandlaism</kbd> עם הסיבה <kbd>Misspelt</kbd>",
        "apihelp-managetags-example-activate": "הפעלת התג <kbd>spam</kbd> עם הסיבה <kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-deactivate": "כיבוי התג <kbd>spam</kbd> עם הסיבה <kbd>No longer required</kbd>",
+       "apihelp-mergehistory-description": "מיזוג גרסאות של דפים.",
+       "apihelp-mergehistory-param-from": "כותרת הדף שההיסטוריה שלו תמוזג. לא ניתן להשתמש בזה יחד עם <var>$1fromid</var>.",
+       "apihelp-mergehistory-param-fromid": "מזהה הדף שממנו תמוזג ההיסטוריה. לא ניתן להשתמש בזה יחד עם <var>$1from</var>.",
+       "apihelp-mergehistory-param-to": "כותרת הדף שההיסטוריה תמוזג אליו. לא ניתן להשתמש בזה יחד עם <var>$1toid</var>.",
+       "apihelp-mergehistory-param-toid": "מזהה הדף שההיסטוריה תמוזג אליו. לא ניתן להשתמש בזה יחד עם <var>$1to</var>.",
+       "apihelp-mergehistory-param-timestamp": "חותם־הזמן שהגרסאות עד אליו יועברו מההיסטוריה של דף המקור על ההיסטוריה של דף היעד. אם מושמט, כל ההיסטוריה של דף המקור תמוזג עם דף היעד.",
+       "apihelp-mergehistory-param-reason": "סיבה למיזוג ההיסטוריה.",
+       "apihelp-mergehistory-example-merge": "מיזוג כל ההיסטוריה של <kbd>Oldpage</kbd> אל <kbd>Newpage</kbd>.",
+       "apihelp-mergehistory-example-merge-timestamp": "מיזוג גרסאות הדפים של <kbd>Oldpage</kbd> עד <kbd dir=\"ltr\">2015-12-31T04:37:41Z</kbd> אל <kbd>Newpage</kbd>.",
        "apihelp-move-description": "העברת עמוד.",
        "apihelp-move-param-from": "שם הדף ששמו ישונה. לא יכול לשמש יחד עם <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "מזהה הדף של הדף שצריך לשנות את שמו. לא יכול לשמש עם <var>$1from</var>.",
index e7bfbe1..8ac4d51 100644 (file)
        "api-help-datatypes-header": "Datentypen",
        "api-help-param-type-user": "Typ: {{PLURAL:$1|1=Benotzernumm|2=Lëscht vu Benotzernimm}}",
        "api-help-examples": "{{PLURAL:$1|Beispill|Beispiler}}:",
-       "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:"
+       "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:",
+       "api-help-open-in-apisandbox": "<small>[an der Sandkëscht opmaachen]</small>"
 }
index e3354aa..df4f881 100644 (file)
        "apihelp-managetags-example-delete": "{{doc-apihelp-example|managetags|info={{doc-important|The text \"vandlaism\" in this message is intentionally misspelled; the example being documented by this message is the deletion of a misspelled tag.}}}}",
        "apihelp-managetags-example-activate": "{{doc-apihelp-example|managetags}}",
        "apihelp-managetags-example-deactivate": "{{doc-apihelp-example|managetags}}",
+       "apihelp-mergehistory-description": "{{doc-apihelp-description|mergehistory}}",
+       "apihelp-mergehistory-param-from": "{{doc-apihelp-param|mergehistory|from}}",
+       "apihelp-mergehistory-param-fromid": "{{doc-apihelp-param|mergehistory|fromid}}",
+       "apihelp-mergehistory-param-to": "{{doc-apihelp-param|mergehistory|to}}",
+       "apihelp-mergehistory-param-toid": "{{doc-apihelp-param|mergehistory|toid}}",
+       "apihelp-mergehistory-param-timestamp": "{{doc-apihelp-param|mergehistory|timestamp}}",
+       "apihelp-mergehistory-param-reason": "{{doc-apihelp-param|mergehistory|reason}}",
+       "apihelp-mergehistory-example-merge": "{{doc-apihelp-example|mergehistory}}",
+       "apihelp-mergehistory-example-merge-timestamp": "{{doc-apihelp-example|mergehistory}}",
        "apihelp-move-description": "{{doc-apihelp-description|move}}",
        "apihelp-move-param-from": "{{doc-apihelp-param|move|from}}",
        "apihelp-move-param-fromid": "{{doc-apihelp-param|move|fromid}}",
diff --git a/includes/api/i18n/sh.json b/includes/api/i18n/sh.json
new file mode 100644 (file)
index 0000000..19d31d4
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Conquistador"
+               ]
+       },
+       "apihelp-login-param-password": "Lozinka."
+}
index a2b2f73..f28f94d 100644 (file)
@@ -17,7 +17,8 @@
                        "RyRubyy",
                        "Umherirrender",
                        "Apflu",
-                       "Hzy980512"
+                       "Hzy980512",
+                       "PhiLiP"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|文档]]\n* [[mw:API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>本页所展示的所有特性都应正常工作,但是API仍在开发当中,将会随时变化。请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:API:Errors_and_warnings|API: 错误与警告]]。\n\n<strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。",
        "apihelp-managetags-example-delete": "删除<kbd>vandlaism</kbd>标签,原因<kbd>Misspelt</kbd>",
        "apihelp-managetags-example-activate": "激活一个名为<kbd>spam</kbd>的标签,原因<kbd>For use in edit patrolling</kbd>",
        "apihelp-managetags-example-deactivate": "停用一个名为<kbd>spam</kbd>的标签,原因<kbd>No longer required</kbd>",
+       "apihelp-mergehistory-description": "合并页面历史。",
+       "apihelp-mergehistory-param-from": "将被合并历史的页面的标题。不能与<var>$1fromid</var>一起使用。",
+       "apihelp-mergehistory-param-fromid": "将被合并历史的页面的页面ID。不能与<var>$1from</var>一起使用。",
+       "apihelp-mergehistory-param-to": "将要合并历史的页面的标题。不能与<var>$1toid</var>一起使用。",
+       "apihelp-mergehistory-param-toid": "将要合并历史的页面的页面ID。不能与<var>$1to</var>一起使用。",
+       "apihelp-mergehistory-param-reason": "历史合并的原因。",
+       "apihelp-mergehistory-example-merge": "将<kbd>Oldpage</kbd>的完整历史合并至<kbd>Newpage</kbd>。",
+       "apihelp-mergehistory-example-merge-timestamp": "将<kbd>Oldpage</kbd>直到<kbd>2015-12-31T04:37:41Z</kbd>的页面修订版本合并至<kbd>Newpage</kbd>。",
        "apihelp-move-description": "移动一个页面。",
        "apihelp-move-param-from": "要重命名的页面标题。不能与<var>$1fromid</var>一起使用。",
        "apihelp-move-param-fromid": "您希望移动的页面ID。不能与<var>$1from</var>一起使用。",
        "apihelp-query+allrevisions-param-generatetitles": "当作为生成器使用时,生成标题而不是修订ID。",
        "apihelp-query+allrevisions-example-user": "列出由用户<kbd>Example</kbd>作出的最近50次贡献。",
        "apihelp-query+allrevisions-example-ns-main": "列举主名字空间中的前50次修订。",
-       "apihelp-query+mystashedfiles-description": "获取当前用户的上传藏匿中的文件列表。",
+       "apihelp-query+mystashedfiles-description": "获取当前用户上传暂存库中的文件列表。",
        "apihelp-query+mystashedfiles-param-prop": "要检索文件的属性。",
        "apihelp-query+mystashedfiles-paramvalue-prop-size": "检索文件大小和图片尺寸。",
        "apihelp-query+mystashedfiles-paramvalue-prop-type": "检索文件的MIME类型和媒体类型。",
-       "apihelp-query+mystashedfiles-param-limit": "获取多少文件。",
+       "apihelp-query+mystashedfiles-param-limit": "要获取文件的数量。",
+       "apihelp-query+mystashedfiles-example-simple": "获取当前用户上传暂存库中的文件的filekey、大小和像素尺寸。",
        "apihelp-query+alltransclusions-description": "列出所有嵌入页面(使用&#123;&#123;x&#125;&#125;嵌入的页面),包括不存在的。",
        "apihelp-query+alltransclusions-param-from": "要列举的起始嵌入标题。",
        "apihelp-query+alltransclusions-param-to": "要列举的最终嵌入标题。",
        "apihelp-query+imageinfo-param-urlwidth": "如果$2prop=url被设定,将返回至缩放到此宽度的一张图片的URL。\n由于性能原因,如果此消息被使用,将不会返回超过$1张被缩放的图片。",
        "apihelp-query+imageinfo-param-urlheight": "与$1urlwidth类似。",
        "apihelp-query+imageinfo-param-metadataversion": "要使用的元数据版本。如果<kbd>latest</kbd>被指定,则使用最新版本。默认为<kbd>1</kbd>以便向下兼容。",
-       "apihelp-query+imageinfo-param-extmetadatalanguage": "要取得extmetadata的语言。This affects both which translation to fetch, if multiple are available, as well as how things like numbers and various values are formatted.",
+       "apihelp-query+imageinfo-param-extmetadatalanguage": "要取得extmetadata的语言。这会影响到抓取翻译的选择,如果有多个可用的话,还会影响到数字等数值的格式。",
        "apihelp-query+imageinfo-param-extmetadatamultilang": "如果用于extmetadata属性的翻译可用,则全部取得。",
        "apihelp-query+imageinfo-param-extmetadatafilter": "如果指定且非空,则只为$1prop=extmetadata返回这些键。",
        "apihelp-query+imageinfo-param-urlparam": "处理器特定的参数字符串。例如PDF可能使用<kbd>page15-100px</kbd>。<var>$1urlwidth</var>必须被使用,并与<var>$1urlparam</var>一致。",
        "apihelp-query+redirects-param-show": "只显示符合这些标准的项目:\n;fragment:只显示带碎片的重定向。\n;!fragment:只显示不带碎片的重定向。",
        "apihelp-query+redirects-example-simple": "获取至[[Main Page]]的重定向列表。",
        "apihelp-query+redirects-example-generator": "获取所有重定向至[[Main Page]]的信息。",
-       "apihelp-query+revisions-description": "获取修订版本信息。\n\n可用于以下几个方面:\n# Get data about a set of pages (last revision), by setting titles or pageids.\n# Get revisions for one given page, by using titles or pageids with start, end, or limit.\n# Get data about a set of revisions by setting their IDs with revids.",
+       "apihelp-query+revisions-description": "获取修订版本信息。\n\n可用于以下几个方面:\n# 通过设置title或pageid获取一批页面(最新修订)的数据。\n# 通过使用带start、end或limit的title或pageid获取给定页面的多个修订。\n# 通过revid设置一批修订的ID获取它们的数据。",
        "apihelp-query+revisions-paraminfo-singlepageonly": "可能只能与单一页面使用(模式#2)。",
        "apihelp-query+revisions-param-startid": "从哪个修订版本ID开始列举。",
        "apihelp-query+revisions-param-endid": "在此修订版本ID停止修订列举。",
        "apihelp-query+usercontribs-param-start": "返回的起始时间戳。",
        "apihelp-query+usercontribs-param-end": "返回的最终时间戳。",
        "apihelp-query+usercontribs-param-user": "要检索贡献的用户。",
+       "apihelp-query+usercontribs-param-userprefix": "取得所有用户名以这个值开头的用户的贡献。覆盖$1user。",
        "apihelp-query+usercontribs-param-namespace": "只列出这些名字空间的贡献。",
        "apihelp-query+usercontribs-param-prop": "包含额外的信息束:",
        "apihelp-query+usercontribs-paramvalue-prop-ids": "添加页面ID和修订ID。",
        "apihelp-watch-example-unwatch": "取消监视页面<kbd>Main Page</kbd>。",
        "apihelp-watch-example-generator": "监视主名字空间中的最少几个页面。",
        "apihelp-format-example-generic": "返回查询结果为$1格式。",
+       "apihelp-format-param-wrappedhtml": "作为一个JSON对象返回渲染好的HTML和关联的ResouceLoader模块。",
        "apihelp-json-description": "输出数据为JSON格式。",
        "apihelp-json-param-callback": "如果指定,将输出内容包裹在一个指定的函数调用中。出于安全考虑,所有用户相关的数据将被限制。",
        "apihelp-json-param-utf8": "如果指定,使用十六进制转义序列将大多数(但不是全部)非ASCII的字符编码为UTF-8,而不是替换它们。默认当<var>formatversion</var>不是<kbd>1</kbd>时。",
index acaa288..e898e72 100644 (file)
@@ -357,11 +357,16 @@ abstract class ContentHandler {
                                throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
                        }
                } else {
-                       $class = $wgContentHandlers[$modelId];
-                       $handler = new $class( $modelId );
+                       $classOrCallback = $wgContentHandlers[$modelId];
+
+                       if ( is_callable( $classOrCallback ) ) {
+                               $handler = call_user_func( $classOrCallback, $modelId );
+                       } else {
+                               $handler = new $classOrCallback( $modelId );
+                       }
 
                        if ( !( $handler instanceof ContentHandler ) ) {
-                               throw new MWException( "$class from \$wgContentHandlers is not " .
+                               throw new MWException( "$classOrCallback from \$wgContentHandlers is not " .
                                        "compatible with ContentHandler" );
                        }
                }
index 9f7d8ca..b5e323b 100644 (file)
@@ -108,10 +108,22 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
 
                wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
 
+               // Reliably broadcast the purge to all edge nodes
+               $relayer = EventRelayerGroup::singleton()->getRelayer( 'cdn-url-purges' );
+               $relayer->notify(
+                       'cdn-url-purges',
+                       array(
+                               'urls' => array_values( $urlArr ), // JSON array
+                               'timestamp' => microtime( true )
+                       )
+               );
+
+               // Send lossy UDP broadcasting if enabled
                if ( $wgHTCPRouting ) {
                        self::HTCPPurge( $urlArr );
                }
 
+               // Do direct server purges if enabled (this does not scale very well)
                if ( $wgSquidServers ) {
                        // Maximum number of parallel connections per squid
                        $maxSocketsPerSquid = 8;
index de84199..e61e2d2 100644 (file)
@@ -223,6 +223,7 @@ abstract class Installer {
                // $wgLogo is probably wrong (bug 48084); set something that will work.
                // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
                'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png',
+               'wgAuthenticationTokenVersion' => 1,
        );
 
        /**
index 3b6a37f..4f20c70 100644 (file)
@@ -65,7 +65,7 @@ class LocalSettingsGenerator {
                                'wgRightsText', '_MainCacheType', 'wgEnableUploads',
                                '_MemCachedServers', 'wgDBserver', 'wgDBuser',
                                'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
-                               'wgMetaNamespace', 'wgLogo',
+                               'wgMetaNamespace', 'wgLogo', 'wgAuthenticationTokenVersion',
                        ),
                        $db->getGlobalNames()
                );
@@ -396,6 +396,9 @@ ${serverSetting}
 
 \$wgSecretKey = \"{$this->values['wgSecretKey']}\";
 
+# Changing this will log out all existing sessions.
+\$wgAuthenticationTokenVersion = \"{$this->values['wgAuthenticationTokenVersion']}\";
+
 # Site upgrade key. Must be set to a string (default provided) to turn on the
 # web installer while LocalSettings.php is in place
 \$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
index 2c08c9c..1d17c94 100644 (file)
@@ -178,6 +178,13 @@ class WebInstallerExistingWiki extends WebInstallerPage {
                // All good
                $this->setVar( '_ExistingDBSettings', true );
 
+               // Copy $wgAuthenticationTokenVersion too, if it exists
+               $this->setVar( 'wgAuthenticationTokenVersion',
+                       isset( $vars['wgAuthenticationTokenVersion'] )
+                               ? $vars['wgAuthenticationTokenVersion']
+                               : null
+               );
+
                return $status;
        }
 
index c86698b..6ba3151 100644 (file)
@@ -6,7 +6,8 @@
                        "아라",
                        "Danmichaelo",
                        "Jeblad",
-                       "Macofe"
+                       "Macofe",
+                       "SuperPotato"
                ]
        },
        "config-desc": "Installasjonsprogrammet for MediaWiki",
@@ -15,7 +16,7 @@
        "config-localsettings-upgrade": "En <code>LocalSettings.php</code>-fil har blitt oppdaget.\nFor å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.\nDu finner denne i <code>LocalSettings.php</code>.",
        "config-localsettings-cli-upgrade": "Filen ''<code>LocalSettings.php</code>'' er funnet.\nFor å oppgradere denne installasjonen, vennligst kjør ''update.php'' i stedet",
        "config-localsettings-key": "Oppgraderingsnøkkel:",
-       "config-localsettings-badkey": "Nøkkelen du oppga er feil.",
+       "config-localsettings-badkey": "Oppgraderingsnøkkelen du oppga er feil.",
        "config-upgrade-key-missing": "En eksisterende installasjon av MediaWiki er funnet.\nFor å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''<code>LocalSettings.php</code>''-fil:\n\n$1",
        "config-localsettings-incomplete": "Den eksisterende ''<code>LocalSettings.php</code>'' ser ut til å være ufullstendig.\nVariabelen $1 har ingen verdi.\nVær vennlig å endre ''<code>LocalSettings.php</code>'' slik at variabelen får en verdi, og klikk ''{{int:Config-continue}}''.",
        "config-localsettings-connection-error": "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''<code>LocalSettings.php</code>'' eller ''<code>AdminSettings.php</code>''. Vær vennlig å rette opp disse innstillingene og prøv igjen.\n\n$1",
@@ -64,7 +65,7 @@
        "config-magic-quotes-sybase": "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''\nDette alternativet ødelegger inndata på en uforutsigbar måte.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
        "config-mbstring": "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''\nDette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
        "config-safe-mode": "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.\nDet kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
-       "config-xml-bad": "PHPs XML-modul mangler.\nMediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.\nHvis du kjører Mandrak, installer pakken php-xml.",
+       "config-xml-bad": "PHPs XML-modul mangler.\nMediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.\nDu må kanskje laste ned php-xml RPM pakken.",
        "config-pcre-old": "'''Alvorlig:''' PCRE $1 eller senere kreves.\nDin PHP-kode er lenket med PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Nærmere informasjon].",
        "config-pcre-no-utf8": "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.\nMediaWiki krever UTF-8-støtte for å fungere riktig.",
        "config-memory-raised": "PHPs <code>memory_limit</code> er $1, økt til $2.",
@@ -76,6 +77,7 @@
        "config-apc": "[http://www.php.net/apc APC] er innstallert",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] er installert",
        "config-no-cache": "'''Advarsel:''' Kunne ikke finne [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nObjekthurtiglagring er ikke aktivert.",
+       "config-no-cache-apcu": "<strong>Advarsel:</strong> Kunne ikke finne [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nObjekthurtiglagring er ikke aktivert.",
        "config-mod-security": "'''Advarsel''': Din web-tjener har [http://modsecurity.org/ mod_security] påslått. Hvis denne er feilinnstilt, kan det gi problemer for MediaWiki eller annen programvare som tillater brukere å poste vilkårlig innhold.\nSjekk [http://modsecurity.org/documentation/ mod_security-dokumentasjonen] eller ta kontakt med din nettleverandør hvis du opplever tilfeldige feil.",
        "config-diff3-bad": "GNU diff3 ikke funnet.",
        "config-git": "Har funnet Git version control software: <code>$1</code>.",
        "config-nofile": "Filen \"$1\" ble ikke funnet. Kan den være blitt slettet?",
        "config-extension-link": "Visste du at wikien din kan brukes sammen med en mengde [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions utvidelser]?\n\nDu kan sjekke gjennom [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category utvidelser per kategori] eller [//www.mediawiki.org/wiki/Extension_Matrix utvidelsesmatrisen] for å se den komplette listen av utvidelser.",
        "mainpagetext": "'''MediaWiki-programvaren er nå installert.'''",
-       "mainpagedocfooter": "Sjekk [//meta.wikimedia.org/wiki/Help:Contents brukerveiledningen] for å få informasjon om hvordan du bruker wiki-programvaren.\n\n==Hvordan komme igang==\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Innstillingsliste]\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ofte stilte spørsmål om MediaWiki]\n*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Tilpass MediaWiki for ditt språk]"
+       "mainpagedocfooter": "Sjekk [//meta.wikimedia.org/wiki/Help:Contents brukerveiledningen] for å få informasjon om hvordan du bruker wiki-programvaren.\n\n==Hvordan komme igang==\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Innstillingsliste]\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ofte stilte spørsmål om MediaWiki]\n*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Tilpass MediaWiki for ditt språk]\n*[//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Lær deg å beskytte deg mot spam på wikien din]"
 }
index b3999f6..8051d35 100644 (file)
        "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] jest funkcją, która pozwala wiki używać obrazów, dźwięków i innych mediów znalezionych na  witrynie [//commons.wikimedia.org/ Wikimedia Commons].\nAby to zrobić, MediaWiki wymaga dostępu do internetu.\n\nAby uzyskać więcej informacji na temat tej funkcji, w tym instrukcje dotyczące sposobu ustawiania go na wiki innych niż Wikimedia Commons, sprawdź w [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos podręczniku].",
        "config-cc-error": "Wybieranie licencji Creative Commons nie dało wyniku.\nWpisz nazwę licencji ręcznie.",
        "config-cc-again": "Wybierz jeszcze raz...",
-       "config-cc-not-chosen": "Wybierz którą chcesz licencję Creative Commons i kliknij „proceed”.",
+       "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.",
index da8a16e..f6ccb97 100644 (file)
@@ -2,9 +2,11 @@
        "@metadata": {
                "authors": [
                        "OC Ripper",
-                       "Seb35"
+                       "Seb35",
+                       "Conquistador"
                ]
        },
+       "config-admin-password": "Lozinka:",
        "mainpagetext": "'''MediaWiki softver is uspješno instaliran.'''",
        "mainpagedocfooter": "Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.\n\n== Početak ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista postavki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki najčešće postavljana pitanja]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]"
 }
index be16bcf..d50e381 100644 (file)
@@ -281,18 +281,16 @@ class Interwiki {
         * @since 1.19
         */
        protected static function getAllPrefixesCached( $local ) {
-               global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
-               static $db, $site;
+               global $wgInterwikiScopes, $wgInterwikiFallbackSite;
+               static $site;
 
                wfDebug( __METHOD__ . "()\n" );
                $data = array();
                try {
-                       if ( !$db ) {
-                               $db = CdbReader::open( $wgInterwikiCache );
-                       }
                        /* Resolve site name */
                        if ( $wgInterwikiScopes >= 3 && !$site ) {
-                               $site = $db->get( '__sites:' . wfWikiID() );
+                               $site = self::getCacheValue( '__sites:' . wfWikiID() );
+
                                if ( $site == '' ) {
                                        $site = $wgInterwikiFallbackSite;
                                }
@@ -311,9 +309,9 @@ class Interwiki {
                        $sources[] = wfWikiID();
 
                        foreach ( $sources as $source ) {
-                               $list = $db->get( "__list:{$source}" );
+                               $list = self::getCacheValue( '__list:' . $source );
                                foreach ( explode( ' ', $list ) as $iw_prefix ) {
-                                       $row = $db->get( "{$source}:{$iw_prefix}" );
+                                       $row = self::getCacheValue( "{$source}:{$iw_prefix}" );
                                        if ( !$row ) {
                                                continue;
                                        }
index b7eef8f..598d956 100644 (file)
@@ -1793,6 +1793,8 @@ class WikiPage implements Page, IDBAccessObject {
 
                $changed = !$content->equals( $oldContent );
 
+               $dbw = wfGetDB( DB_MASTER );
+
                if ( $changed ) {
                        $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                        $status->merge( $prepStatus );
@@ -1800,14 +1802,13 @@ class WikiPage implements Page, IDBAccessObject {
                                return $status;
                        }
 
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->begin( __METHOD__ );
+                       $dbw->startAtomic( __METHOD__ );
                        // Get the latest page_latest value while locking it.
                        // Do a CAS style check to see if it's the same as when this method
                        // started. If it changed then bail out before touching the DB.
                        $latestNow = $this->lockAndGetLatest();
                        if ( $latestNow != $oldid ) {
-                               $dbw->commit( __METHOD__ );
+                               $dbw->endAtomic( __METHOD__ );
                                // Page updated or deleted in the mean time
                                $status->fatal( 'edit-conflict' );
 
@@ -1855,7 +1856,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                        $user->incEditCount();
 
-                       $dbw->commit( __METHOD__ );
+                       $dbw->endAtomic( __METHOD__ );
                        $this->mTimestamp = $now;
                } else {
                        // Bug 32948: revision ID must be set to page {{REVISIONID}} and
@@ -1863,17 +1864,6 @@ class WikiPage implements Page, IDBAccessObject {
                        $revision->setId( $this->getLatest() );
                }
 
-               // Update links tables, site stats, etc.
-               $this->doEditUpdates(
-                       $revision,
-                       $user,
-                       array(
-                               'changed' => $changed,
-                               'oldcountable' => $meta['oldCountable'],
-                               'oldrevision' => $meta['oldRevision']
-                       )
-               );
-
                if ( $changed ) {
                        // Return the new revision to the caller
                        $status->value['revision'] = $revision;
@@ -1884,11 +1874,32 @@ class WikiPage implements Page, IDBAccessObject {
                        $this->mTitle->invalidateCache( $now );
                }
 
-               // Trigger post-save hook
-               $hook_args = array( &$this, &$user, $content, $summary,
-                       $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $meta['baseRevId'] );
-               ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
-               Hooks::run( 'PageContentSaveComplete', $hook_args );
+               // Do secondary updates once the main changes have been committed...
+               $that = $this;
+               $dbw->onTransactionIdle(
+                       function () use (
+                               $dbw, &$that, $revision, &$user, $content, $summary, &$flags,
+                               $changed, $meta, &$status
+                       ) {
+                               // Do per-page updates in a transaction
+                               $dbw->setFlag( DBO_TRX );
+                               // Update links tables, site stats, etc.
+                               $that->doEditUpdates(
+                                       $revision,
+                                       $user,
+                                       array(
+                                               'changed' => $changed,
+                                               'oldcountable' => $meta['oldCountable'],
+                                               'oldrevision' => $meta['oldRevision']
+                                       )
+                               );
+                               // Trigger post-save hook
+                               $params = array( &$that, &$user, $content, $summary, $flags & EDIT_MINOR,
+                                       null, null, &$flags, $revision, &$status, $meta['baseRevId'] );
+                               ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $params );
+                               Hooks::run( 'PageContentSaveComplete', $params );
+                       }
+               );
 
                return $status;
        }
index 6329fd7..7fc9a16 100644 (file)
@@ -440,8 +440,11 @@ class LinkHolderArray {
                # Make interwiki link HTML
                $output = $this->parent->getOutput();
                $replacePairs = array();
+               $options = array(
+                       'stubThreshold' => $this->parent->getOptions()->getStubThreshold(),
+               );
                foreach ( $this->interwikis as $key => $link ) {
-                       $replacePairs[$key] = Linker::link( $link['title'], $link['text'] );
+                       $replacePairs[$key] = Linker::link( $link['title'], $link['text'], array(), array(), $options );
                        $output->addInterwikiLink( $link['title'] );
                }
                $replacer = new HashtableReplacer( $replacePairs, 1 );
index 0e8d76d..49c6ce9 100644 (file)
@@ -601,6 +601,7 @@ class ParserOptions {
 
        /**
         * Get a ParserOptions object for an anonymous user
+        * @since 1.27
         * @return ParserOptions
         */
        public static function newFromAnon() {
index bcd159f..14132d6 100644 (file)
@@ -434,16 +434,28 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                try {
                        // If the list has been modified since last time we cached it, update the cache
                        if ( $localFileRefs !== $this->getFileDependencies( $context ) ) {
+                               $cache = ObjectCache::getLocalClusterInstance();
+                               $key = $cache->makeKey( __METHOD__, $this->getName() );
+                               $scopeLock = $cache->getScopedLock( $key, 0 );
+                               if ( !$scopeLock ) {
+                                       return; // T124649; avoid write slams
+                               }
+
                                $vary = $context->getSkin() . '|' . $context->getLanguage();
                                $dbw = wfGetDB( DB_MASTER );
                                $dbw->replace( 'module_deps',
-                                       array( array( 'md_module', 'md_skin' ) ), array(
+                                       array( array( 'md_module', 'md_skin' ) ),
+                                       array(
                                                'md_module' => $this->getName(),
                                                'md_skin' => $vary,
                                                // Use relative paths to avoid ghost entries when $IP changes (T111481)
                                                'md_deps' => FormatJson::encode( self::getRelativePaths( $localFileRefs ) ),
                                        )
                                );
+
+                               $dbw->onTransactionIdle( function () use ( &$scopeLock ) {
+                                       ScopedCallback::consume( $scopeLock ); // release after commit
+                               } );
                        }
                } catch ( Exception $e ) {
                        wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" );
index d9c60c7..81c7ebf 100644 (file)
@@ -118,26 +118,44 @@ class BotPasswordSessionProvider extends ImmutableSessionProviderWithCookie {
                        array_keys( $metadata )
                );
                if ( $missingKeys ) {
-                       $this->logger->info( "Session $info: Missing metadata: " . join( ', ', $missingKeys ) );
+                       $this->logger->info( 'Session "{session}": Missing metadata: {missing}', array(
+                               'session' => $info,
+                               'missing' => join( ', ', $missingKeys ),
+                       ) );
                        return false;
                }
 
                $bp = BotPassword::newFromCentralId( $metadata['centralId'], $metadata['appId'] );
                if ( !$bp ) {
                        $this->logger->info(
-                               "Session $info: No BotPassword for {$metadata['centralId']} {$metadata['appId']}"
-                       );
+                               'Session "{session}": No BotPassword for {centralId} {appId}',
+                               array(
+                                       'session' => $info,
+                                       'centralId' => $metadata['centralId'],
+                                       'appId' => $metadata['appId'],
+                       ) );
                        return false;
                }
 
                if ( !hash_equals( $metadata['token'], $bp->getToken() ) ) {
-                       $this->logger->info( "Session $info: BotPassword token check failed" );
+                       $this->logger->info( 'Session "{session}": BotPassword token check failed', array(
+                               'session' => $info,
+                               'centralId' => $metadata['centralId'],
+                               'appId' => $metadata['appId'],
+                       ) );
                        return false;
                }
 
                $status = $bp->getRestrictions()->check( $request );
                if ( !$status->isOk() ) {
-                       $this->logger->info( "Session $info: Restrictions check failed", $status->getValue() );
+                       $this->logger->info(
+                               'Session "{session}": Restrictions check failed',
+                               array(
+                                       'session' => $info,
+                                       'restrictions' => $status->getValue(),
+                                       'centralId' => $metadata['centralId'],
+                                       'appId' => $metadata['appId'],
+                       ) );
                        return false;
                }
 
index f989cbc..3177dc2 100644 (file)
@@ -123,11 +123,28 @@ class CookieSessionProvider extends SessionProvider {
 
                        // Sanity check
                        if ( $userName !== null && $userInfo->getName() !== $userName ) {
+                               $this->logger->warning(
+                                       'Session "{session}" requested with mismatched UserID and UserName cookies.',
+                                       array(
+                                               'session' => $info['id'],
+                                               'mismatch' => array(
+                                                       'userid' => $userId,
+                                                       'cookie_username' => $userName,
+                                                       'username' => $userInfo->getName(),
+                                               ),
+                               ) );
                                return null;
                        }
 
                        if ( $token !== null ) {
                                if ( !hash_equals( $userInfo->getToken(), $token ) ) {
+                                       $this->logger->warning(
+                                               'Session "{session}" requested with invalid Token cookie.',
+                                               array(
+                                                       'session' => $info['id'],
+                                                       'userid' => $userId,
+                                                       'username' => $userInfo->getName(),
+                                        ) );
                                        return null;
                                }
                                $info['userInfo'] = $userInfo->verified();
@@ -140,6 +157,15 @@ class CookieSessionProvider extends SessionProvider {
                        }
                } elseif ( isset( $info['id'] ) ) {
                        // No UserID cookie, so insist that the session is anonymous.
+                       // Note: this event occurs for several normal activities:
+                       // * anon visits Special:UserLogin
+                       // * anon browsing after seeing Special:UserLogin
+                       // * anon browsing after edit or preview
+                       $this->logger->debug(
+                               'Session "{session}" requested without UserID cookie',
+                               array(
+                                       'session' => $info['id'],
+                       ) );
                        $info['userInfo'] = UserInfo::newAnonymous();
                } else {
                        // No session ID and no user is the same as an empty session, so
index 4dea274..795e253 100644 (file)
@@ -247,8 +247,10 @@ class PHPSessionHandler {
                        // This can happen under normal circumstances, if the session exists but is
                        // invalid. Let's emit a log warning instead of a PHP warning.
                        $this->logger->warning(
-                               __METHOD__ . ": Session \"$id\" cannot be loaded, skipping write."
-                       );
+                               __METHOD__ . ': Session "{session}" cannot be loaded, skipping write.',
+                               array(
+                                       'session' => $id,
+                       ) );
                        return true;
                }
 
index 2bff173..a79c5cb 100644 (file)
@@ -138,7 +138,11 @@ final class SessionBackend {
                        $this->data = array();
                        $this->dataDirty = true;
                        $this->metaDirty = true;
-                       $this->logger->debug( "SessionBackend $this->id is unsaved, marking dirty in constructor" );
+                       $this->logger->debug(
+                               'SessionBackend "{session}" is unsaved, marking dirty in constructor',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                } else {
                        $this->data = $blob['data'];
                        if ( isset( $blob['metadata']['loggedOut'] ) ) {
@@ -149,8 +153,10 @@ final class SessionBackend {
                        } else {
                                $this->metaDirty = true;
                                $this->logger->debug(
-                                       "SessionBackend $this->id metadata dirty due to missing expiration timestamp"
-                               );
+                                       'SessionBackend "{session}" metadata dirty due to missing expiration timestamp',
+                               array(
+                                       'session' => $this->id,
+                               ) );
                        }
                }
                $this->dataHash = md5( serialize( $this->data ) );
@@ -218,8 +224,11 @@ final class SessionBackend {
                        $this->provider->sessionIdWasReset( $this, $oldId );
                        $this->metaDirty = true;
                        $this->logger->debug(
-                               "SessionBackend $this->id metadata dirty due to ID reset (formerly $oldId)"
-                       );
+                               'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")',
+                               array(
+                                       'session' => $this->id,
+                                       'oldId' => $oldId,
+                       ) );
 
                        if ( $restart ) {
                                session_id( (string)$this->id );
@@ -263,7 +272,11 @@ final class SessionBackend {
                        $this->persist = true;
                        $this->forcePersist = true;
                        $this->metaDirty = true;
-                       $this->logger->debug( "SessionBackend $this->id force-persist due to persist()" );
+                       $this->logger->debug(
+                               'SessionBackend "{session}" force-persist due to persist()',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                        $this->autosave();
                } else {
                        $this->renew();
@@ -288,7 +301,11 @@ final class SessionBackend {
                if ( $this->remember !== (bool)$remember ) {
                        $this->remember = (bool)$remember;
                        $this->metaDirty = true;
-                       $this->logger->debug( "SessionBackend $this->id metadata dirty due to remember-user change" );
+                       $this->logger->debug(
+                               'SessionBackend "{session}" metadata dirty due to remember-user change',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                        $this->autosave();
                }
        }
@@ -345,7 +362,11 @@ final class SessionBackend {
 
                $this->user = $user;
                $this->metaDirty = true;
-               $this->logger->debug( "SessionBackend $this->id metadata dirty due to user change" );
+               $this->logger->debug(
+                       'SessionBackend "{session}" metadata dirty due to user change',
+                       array(
+                               'session' => $this->id,
+               ) );
                $this->autosave();
        }
 
@@ -377,7 +398,11 @@ final class SessionBackend {
                if ( $this->forceHTTPS !== (bool)$force ) {
                        $this->forceHTTPS = (bool)$force;
                        $this->metaDirty = true;
-                       $this->logger->debug( "SessionBackend $this->id metadata dirty due to force-HTTPS change" );
+                       $this->logger->debug(
+                               'SessionBackend "{session}" metadata dirty due to force-HTTPS change',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                        $this->autosave();
                }
        }
@@ -400,8 +425,10 @@ final class SessionBackend {
                        $this->loggedOut = $ts;
                        $this->metaDirty = true;
                        $this->logger->debug(
-                               "SessionBackend $this->id metadata dirty due to logged-out-timestamp change"
-                       );
+                               'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                        $this->autosave();
                }
        }
@@ -428,8 +455,10 @@ final class SessionBackend {
                        $this->providerMetadata = $metadata;
                        $this->metaDirty = true;
                        $this->logger->debug(
-                               "SessionBackend $this->id metadata dirty due to provider metadata change"
-                       );
+                               'SessionBackend "{session}" metadata dirty due to provider metadata change',
+                               array(
+                                       'session' => $this->id,
+                       ) );
                        $this->autosave();
                }
        }
@@ -461,8 +490,11 @@ final class SessionBackend {
                                $data[$key] = $value;
                                $this->dataDirty = true;
                                $this->logger->debug(
-                                       "SessionBackend $this->id data dirty due to addData(): " . wfGetAllCallers( 5 )
-                               );
+                                       'SessionBackend "{session}" data dirty due to addData(): {callers}',
+                                       array(
+                                               'session' => $this->id,
+                                               'callers' => wfGetAllCallers( 5 ),
+                               ) );
                        }
                }
        }
@@ -474,8 +506,11 @@ final class SessionBackend {
        public function dirty() {
                $this->dataDirty = true;
                $this->logger->debug(
-                       "SessionBackend $this->id data dirty due to dirty(): " . wfGetAllCallers( 5 )
-               );
+                       'SessionBackend "{session}" data dirty due to dirty(): {callers}',
+                       array(
+                               'session' => $this->id,
+                               'callers' => wfGetAllCallers( 5 ),
+               ) );
        }
 
        /**
@@ -488,13 +523,19 @@ final class SessionBackend {
                if ( time() + $this->lifetime / 2 > $this->expires ) {
                        $this->metaDirty = true;
                        $this->logger->debug(
-                               "SessionBackend $this->id metadata dirty for renew(): " . wfGetAllCallers( 5 )
-                       );
+                               'SessionBackend "{callers}" metadata dirty for renew(): {callers}',
+                               array(
+                                       'session' => $this->id,
+                                       'callers' => wfGetAllCallers( 5 ),
+                       ) );
                        if ( $this->persist ) {
                                $this->forcePersist = true;
                                $this->logger->debug(
-                                       "SessionBackend $this->id force-persist for renew(): " . wfGetAllCallers( 5 )
-                               );
+                                       'SessionBackend "{session}" force-persist for renew(): {callers}',
+                                       array(
+                                               'session' => $this->id,
+                                               'callers' => wfGetAllCallers( 5 ),
+                               ) );
                        }
                }
                $this->autosave();
@@ -535,9 +576,12 @@ final class SessionBackend {
        public function save( $closing = false ) {
                if ( $this->provider->getManager()->isUserSessionPrevented( $this->user->getName() ) ) {
                        $this->logger->debug(
-                               "SessionBackend $this->id not saving, " .
-                                       "user {$this->user} was passed to SessionManager::preventSessionsForUser"
-                       );
+                               'SessionBackend "{session}" not saving, user {user} was ' .
+                               'passed to SessionManager::preventSessionsForUser',
+                               array(
+                                       'session' => $this->id,
+                                       'user' => $this->user,
+                       ) );
                        return;
                }
 
@@ -546,8 +590,11 @@ final class SessionBackend {
                $anon = $this->user->isAnon();
                if ( !$anon && !$this->user->getToken( false ) ) {
                        $this->logger->debug(
-                               "SessionBackend $this->id creating token for user {$this->user} on save"
-                       );
+                               'SessionBackend "{session}" creating token for user {user} on save',
+                               array(
+                                       'session' => $this->id,
+                                       'user' => $this->user,
+                       ) );
                        $this->user->setToken();
                        if ( !wfReadOnly() ) {
                                $this->user->saveSettings();
@@ -559,8 +606,13 @@ final class SessionBackend {
                if ( !$this->metaDirty && !$this->dataDirty &&
                        $this->dataHash !== md5( serialize( $this->data ) )
                ) {
-                       $this->logger->debug( "SessionBackend $this->id data dirty due to hash mismatch, " .
-                               "$this->dataHash !== " . md5( serialize( $this->data ) ) );
+                       $this->logger->debug(
+                               'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}',
+                               array(
+                                       'session' => $this->id,
+                                       'expected' => $this->dataHash,
+                                       'got' => md5( serialize( $this->data ) ),
+                       ) );
                        $this->dataDirty = true;
                }
 
@@ -568,11 +620,15 @@ final class SessionBackend {
                        return;
                }
 
-               $this->logger->debug( "SessionBackend $this->id save: " .
-                       'dataDirty=' . (int)$this->dataDirty . ' ' .
-                       'metaDirty=' . (int)$this->metaDirty . ' ' .
-                       'forcePersist=' . (int)$this->forcePersist
-               );
+               $this->logger->debug(
+                       'SessionBackend "{session}" save: dataDirty={dataDirty} ' .
+                       'metaDirty={metaDirty} forcePersist={forcePersist}',
+                       array(
+                               'session' => $this->id,
+                               'dataDirty' => (int)$this->dataDirty,
+                               'metaDirty' => (int)$this->metaDirty,
+                               'forcePersist' => (int)$this->forcePersist,
+               ) );
 
                // Persist to the provider, if flagged
                if ( $this->persist && ( $this->metaDirty || $this->forcePersist ) ) {
@@ -644,7 +700,11 @@ final class SessionBackend {
                        if ( $this->usePhpSessionHandling && session_id() === '' && PHPSessionHandler::isEnabled() &&
                                SessionManager::getGlobalSession()->getId() === (string)$this->id
                        ) {
-                               $this->logger->debug( "SessionBackend $this->id: Taking over PHP session" );
+                               $this->logger->debug(
+                                       'SessionBackend "{session}" Taking over PHP session',
+                                       array(
+                                               'session' => $this->id,
+                               ) );
                                session_id( (string)$this->id );
                                \MediaWiki\quietCall( 'session_start' );
                        }
index f03260f..07291e9 100644 (file)
@@ -216,8 +216,11 @@ final class SessionManager implements SessionManagerInterface {
                        try {
                                $session = $this->getEmptySessionInternal( $request, $id );
                        } catch ( \Exception $ex ) {
-                               $this->logger->error( __METHOD__ . ': failed to create empty session: ' .
-                                       $ex->getMessage() );
+                               $this->logger->error( 'Failed to create empty session: {exception}',
+                                       array(
+                                               'method' => __METHOD__,
+                                               'exception' => $ex,
+                               ) );
                                $session = null;
                        }
                }
@@ -462,14 +465,21 @@ final class SessionManager implements SessionManagerInterface {
 
                // Checks passed, create the user...
                $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
-               $logger->info( __METHOD__ . ": creating new user ($userName) - from: $from" );
+               $logger->info( __METHOD__ . ': creating new user ({username}) - from: {url}',
+                       array(
+                               'username' => $userName,
+                               'url' => $from,
+               ) );
 
                try {
                        // Insert the user into the local DB master
                        $status = $user->addToDatabase();
                        if ( !$status->isOK() ) {
                                // @codeCoverageIgnoreStart
-                               $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText() );
+                               $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText(),
+                                       array(
+                                               'username' => $userName,
+                               ) );
                                $user->setId( 0 );
                                $user->loadFromId();
                                return false;
@@ -477,7 +487,10 @@ final class SessionManager implements SessionManagerInterface {
                        }
                } catch ( \Exception $ex ) {
                        // @codeCoverageIgnoreStart
-                       $logger->error( __METHOD__ . ': failed with exception ' . $ex->getMessage() );
+                       $logger->error( __METHOD__ . ': failed with exception {exception}', array(
+                               'exception' => $ex,
+                               'username' => $userName,
+                       ) );
                        // Do not keep throwing errors for a while
                        $cache->set( $backoffKey, 1, 600 );
                        // Bubble up error; which should normally trigger DB rollbacks
@@ -662,7 +675,9 @@ final class SessionManager implements SessionManagerInterface {
                if ( $blob !== false ) {
                        // Sanity check: blob must be an array, if it's saved at all
                        if ( !is_array( $blob ) ) {
-                               $this->logger->warning( "Session $info: Bad data" );
+                               $this->logger->warning( 'Session "{session}": Bad data', array(
+                                       'session' => $info,
+                               ) );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -671,7 +686,9 @@ final class SessionManager implements SessionManagerInterface {
                        if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) ||
                                !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
                        ) {
-                               $this->logger->warning( "Session $info: Bad data structure" );
+                               $this->logger->warning( 'Session "{session}": Bad data structure', array(
+                                       'session' => $info,
+                               ) );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -686,7 +703,9 @@ final class SessionManager implements SessionManagerInterface {
                                !array_key_exists( 'userToken', $metadata ) ||
                                !array_key_exists( 'provider', $metadata )
                        ) {
-                               $this->logger->warning( "Session $info: Bad metadata" );
+                               $this->logger->warning( 'Session "{session}": Bad metadata', array(
+                                       'session' => $info,
+                               ) );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -696,13 +715,21 @@ final class SessionManager implements SessionManagerInterface {
                        if ( $provider === null ) {
                                $newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
                                if ( !$provider ) {
-                                       $this->logger->warning( "Session $info: Unknown provider, " . $metadata['provider'] );
+                                       $this->logger->warning(
+                                               'Session "{session}": Unknown provider ' . $metadata['provider'],
+                                               array(
+                                                       'session' => $info,
+                                               )
+                                       );
                                        $this->store->delete( $key );
                                        return false;
                                }
                        } elseif ( $metadata['provider'] !== (string)$provider ) {
-                               $this->logger->warning( "Session $info: Wrong provider, " .
-                                       $metadata['provider'] . ' !== ' . $provider );
+                               $this->logger->warning( 'Session "{session}": Wrong provider ' .
+                                       $metadata['provider'] . ' !== ' . $provider,
+                                       array(
+                                               'session' => $info,
+                               ) );
                                return false;
                        }
 
@@ -720,7 +747,12 @@ final class SessionManager implements SessionManagerInterface {
                                                        $newParams['metadata'] = $newProviderMetadata;
                                                }
                                        } catch ( \UnexpectedValueException $ex ) {
-                                               $this->logger->warning( "Session $info: Metadata merge failed: " . $ex->getMessage() );
+                                               $this->logger->warning(
+                                                       'Session "{session}": Metadata merge failed: {exception}',
+                                                       array(
+                                                               'session' => $info,
+                                                               'exception' => $ex,
+                                               ) );
                                                return false;
                                        }
                                }
@@ -739,7 +771,10 @@ final class SessionManager implements SessionManagerInterface {
                                                $userInfo = UserInfo::newAnonymous();
                                        }
                                } catch ( \InvalidArgumentException $ex ) {
-                                       $this->logger->error( "Session $info: " . $ex->getMessage() );
+                                       $this->logger->error( 'Session "{session}": {exception}', array(
+                                               'session' => $info,
+                                               'exception' => $ex,
+                                       ) );
                                        return false;
                                }
                                $newParams['userInfo'] = $userInfo;
@@ -748,8 +783,13 @@ final class SessionManager implements SessionManagerInterface {
                                // is no saved ID and the names match.
                                if ( $metadata['userId'] ) {
                                        if ( $metadata['userId'] !== $userInfo->getId() ) {
-                                               $this->logger->warning( "Session $info: User ID mismatch, " .
-                                                       $metadata['userId'] . ' !== ' . $userInfo->getId() );
+                                               $this->logger->warning(
+                                                       'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
+                                                       array(
+                                                               'session' => $info,
+                                                               'uid_a' => $metadata['userId'],
+                                                               'uid_b' => $userInfo->getId(),
+                                               ) );
                                                return false;
                                        }
 
@@ -757,24 +797,35 @@ final class SessionManager implements SessionManagerInterface {
                                        if ( $metadata['userName'] !== null &&
                                                $userInfo->getName() !== $metadata['userName']
                                        ) {
-                                               $this->logger->warning( "Session $info: User ID matched but name didn't (rename?), " .
-                                                       $metadata['userName'] . ' !== ' . $userInfo->getName() );
+                                               $this->logger->warning(
+                                                       'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
+                                                       array(
+                                                               'session' => $info,
+                                                               'uname_a' => $metadata['userName'],
+                                                               'uname_b' => $userInfo->getName(),
+                                               ) );
                                                return false;
                                        }
 
                                } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
                                        if ( $metadata['userName'] !== $userInfo->getName() ) {
-                                               $this->logger->warning( "Session $info: User name mismatch, " .
-                                                       $metadata['userName'] . ' !== ' . $userInfo->getName() );
+                                               $this->logger->warning(
+                                                       'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
+                                                       array(
+                                                               'session' => $info,
+                                                               'uname_a' => $metadata['userName'],
+                                                               'uname_b' => $userInfo->getName(),
+                                               ) );
                                                return false;
                                        }
                                } elseif ( !$userInfo->isAnon() ) {
                                        // Metadata specifies an anonymous user, but the passed-in
                                        // user isn't anonymous.
                                        $this->logger->warning(
-                                               "Session $info: Metadata has an anonymous user, " .
-                                                       'but a non-anon user was provided'
-                                       );
+                                               'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
+                                               array(
+                                                       'session' => $info,
+                                       ) );
                                        return false;
                                }
                        }
@@ -783,7 +834,9 @@ final class SessionManager implements SessionManagerInterface {
                        if ( $metadata['userToken'] !== null &&
                                $userInfo->getToken() !== $metadata['userToken']
                        ) {
-                               $this->logger->warning( "Session $info: User token mismatch" );
+                               $this->logger->warning( 'Session "{session}": User token mismatch', array(
+                                       'session' => $info,
+                               ) );
                                return false;
                        }
                        if ( !$userInfo->isVerified() ) {
@@ -806,7 +859,11 @@ final class SessionManager implements SessionManagerInterface {
                } else {
                        // No metadata, so we can't load the provider if one wasn't given.
                        if ( $info->getProvider() === null ) {
-                               $this->logger->warning( "Session $info: Null provider and no metadata" );
+                               $this->logger->warning(
+                                       'Session "{session}": Null provider and no metadata',
+                                       array(
+                                               'session' => $info,
+                               ) );
                                return false;
                        }
 
@@ -816,14 +873,18 @@ final class SessionManager implements SessionManagerInterface {
                                        $newParams['userInfo'] = UserInfo::newAnonymous();
                                } else {
                                        $this->logger->info(
-                                               "Session $info: No user provided and provider cannot set user"
-                                       );
+                                               'Session "{session}": No user provided and provider cannot set user',
+                                               array(
+                                                       'session' => $info,
+                                       ) );
                                        return false;
                                }
                        } elseif ( !$info->getUserInfo()->isVerified() ) {
                                $this->logger->warning(
-                                       "Session $info: Unverified user provided and no metadata to auth it"
-                               );
+                                       'Session "{session}": Unverified user provided and no metadata to auth it',
+                                       array(
+                                               'session' => $info,
+                               ) );
                                return false;
                        }
 
@@ -863,7 +924,9 @@ final class SessionManager implements SessionManagerInterface {
                        'SessionCheckInfo',
                        array( &$reason, $info, $request, $metadata, $data )
                ) ) {
-                       $this->logger->warning( "Session $info: $reason" );
+                       $this->logger->warning( 'Session "{session}": ' . $reason, array(
+                               'session' => $info,
+                       ) );
                        return false;
                }
 
index 28ff1c7..a164c1e 100644 (file)
@@ -262,15 +262,15 @@ class SpecialLog extends SpecialPage {
                // Select: All, None, Invert
                $links = array();
                $links[] = Html::element(
-                       'a', array( 'href' => '#', 'id' => 'checkbox-all' ),
+                       'a', array( 'href' => '#', 'class' => 'mw-checkbox-all' ),
                        $this->msg( 'checkbox-all' )->text()
                );
                $links[] = Html::element(
-                       'a', array( 'href' => '#', 'id' => 'checkbox-none' ),
+                       'a', array( 'href' => '#', 'class' => 'mw-checkbox-none' ),
                        $this->msg( 'checkbox-none' )->text()
                );
                $links[] = Html::element(
-                       'a', array( 'href' => '#', 'id' => 'checkbox-invert' ),
+                       'a', array( 'href' => '#', 'class' => 'mw-checkbox-invert' ),
                        $this->msg( 'checkbox-invert' )->text()
                );
 
index 0a25180..0cefb38 100644 (file)
@@ -347,138 +347,17 @@ class SpecialMergeHistory extends SpecialPage {
                if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
                        return false;
                }
-               # Verify that this timestamp is valid
-               # Must be older than the destination page
-               $dbw = wfGetDB( DB_MASTER );
-               # Get timestamp into DB format
-               $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp( $this->mTimestamp ) : '';
-               # Max timestamp should be min of destination page
-               $maxtimestamp = $dbw->selectField(
-                       'revision',
-                       'MIN(rev_timestamp)',
-                       array( 'rev_page' => $this->mDestID ),
-                       __METHOD__
-               );
-               # Destination page must exist with revisions
-               if ( !$maxtimestamp ) {
-                       $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
 
-                       return false;
-               }
-               # Get the latest timestamp of the source
-               $lasttimestamp = $dbw->selectField(
-                       array( 'page', 'revision' ),
-                       'rev_timestamp',
-                       array( 'page_id' => $this->mTargetID, 'page_latest = rev_id' ),
-                       __METHOD__
-               );
-               # $this->mTimestamp must be older than $maxtimestamp
-               if ( $this->mTimestamp >= $maxtimestamp ) {
-                       $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+               // MergeHistory object
+               $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
 
+               // Merge!
+               $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
+               if ( !$mergeStatus->isOK() ) {
+                       // Failed merge
+                       $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
                        return false;
                }
-               # Get the timestamp pivot condition
-               if ( $this->mTimestamp ) {
-                       $timewhere = "rev_timestamp <= {$this->mTimestamp}";
-                       $timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
-               } else {
-                       $timewhere = "rev_timestamp <= {$maxtimestamp}";
-                       $timestampLimit = wfTimestamp( TS_MW, $lasttimestamp );
-               }
-               # Check that there are not too many revisions to move
-               $limit = 5000; // avoid too much slave lag
-               $count = $dbw->selectRowCount( 'revision', '1',
-                       array( 'rev_page' => $this->mTargetID, $timewhere ),
-                       __METHOD__,
-                       array( 'LIMIT' => $limit + 1 )
-               );
-               if ( $count > $limit ) {
-                       $this->getOutput()->addWikiMsg( 'mergehistory-fail-toobig' );
-
-                       return false;
-               }
-               # Do the moving...
-               $dbw->update(
-                       'revision',
-                       array( 'rev_page' => $this->mDestID ),
-                       array( 'rev_page' => $this->mTargetID, $timewhere ),
-                       __METHOD__
-               );
-
-               $count = $dbw->affectedRows();
-               # Make the source page a redirect if no revisions are left
-               $haveRevisions = $dbw->selectField(
-                       'revision',
-                       'rev_timestamp',
-                       array( 'rev_page' => $this->mTargetID ),
-                       __METHOD__,
-                       array( 'FOR UPDATE' )
-               );
-               if ( !$haveRevisions ) {
-                       if ( $this->mComment ) {
-                               $comment = $this->msg(
-                                       'mergehistory-comment',
-                                       $targetTitle->getPrefixedText(),
-                                       $destTitle->getPrefixedText(),
-                                       $this->mComment
-                               )->inContentLanguage()->text();
-                       } else {
-                               $comment = $this->msg(
-                                       'mergehistory-autocomment',
-                                       $targetTitle->getPrefixedText(),
-                                       $destTitle->getPrefixedText()
-                               )->inContentLanguage()->text();
-                       }
-
-                       $contentHandler = ContentHandler::getForTitle( $targetTitle );
-                       $redirectContent = $contentHandler->makeRedirectContent( $destTitle );
-
-                       if ( $redirectContent ) {
-                               $redirectPage = WikiPage::factory( $targetTitle );
-                               $redirectRevision = new Revision( array(
-                                       'title' => $targetTitle,
-                                       'page' => $this->mTargetID,
-                                       'comment' => $comment,
-                                       'content' => $redirectContent ) );
-                               $redirectRevision->insertOn( $dbw );
-                               $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
-
-                               # Now, we record the link from the redirect to the new title.
-                               # It should have no other outgoing links...
-                               $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
-                               $dbw->insert( 'pagelinks',
-                                       array(
-                                               'pl_from' => $this->mDestID,
-                                               'pl_from_namespace' => $destTitle->getNamespace(),
-                                               'pl_namespace' => $destTitle->getNamespace(),
-                                               'pl_title' => $destTitle->getDBkey() ),
-                                       __METHOD__
-                               );
-                       } else {
-                               // would be nice to show a warning if we couldn't create a redirect
-                       }
-               } else {
-                       $targetTitle->invalidateCache(); // update histories
-               }
-               $destTitle->invalidateCache(); // update histories
-               # Check if this did anything
-               if ( !$count ) {
-                       $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
-
-                       return false;
-               }
-               # Update our logs
-               $logEntry = new ManualLogEntry( 'merge', 'merge' );
-               $logEntry->setPerformer( $this->getUser() );
-               $logEntry->setComment( $this->mComment );
-               $logEntry->setTarget( $targetTitle );
-               $logEntry->setParameters( array(
-                       '4::dest' => $destTitle->getPrefixedText(),
-                       '5::mergepoint' => $timestampLimit
-               ) );
-               $logId = $logEntry->insert();
-               $logEntry->publish( $logId );
 
                $targetLink = Linker::link(
                        $targetTitle,
@@ -490,11 +369,9 @@ class SpecialMergeHistory extends SpecialPage {
                $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
                        ->rawParams( $targetLink )
                        ->params( $destTitle->getPrefixedText() )
-                       ->numParams( $count )
+                       ->numParams( $mh->getMergedRevisionCount() )
                );
 
-               Hooks::run( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
-
                return true;
        }
 
index 078f032..5e08e51 100644 (file)
@@ -675,13 +675,14 @@ class PageArchive {
  * @ingroup SpecialPage
  */
 class SpecialUndelete extends SpecialPage {
-       private $mAction;
+       private $mAction;
        private $mTarget;
        private $mTimestamp;
        private $mRestore;
+       private $mRevdel;
        private $mInvert;
        private $mFilename;
-       private $mTargetTimestamp;
+       private $mTargetTimestamp;
        private $mAllowed;
        private $mCanView;
        private $mComment;
@@ -719,6 +720,7 @@ class SpecialUndelete extends SpecialPage {
                $posted = $request->wasPosted() &&
                        $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
                $this->mRestore = $request->getCheck( 'restore' ) && $posted;
+               $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
                $this->mInvert = $request->getCheck( 'invert' ) && $posted;
                $this->mPreview = $request->getCheck( 'preview' ) && $posted;
                $this->mDiff = $request->getCheck( 'diff' );
@@ -831,13 +833,42 @@ class SpecialUndelete extends SpecialPage {
                        } else {
                                $this->showFile( $this->mFilename );
                        }
-               } elseif ( $this->mRestore && $this->mAction == 'submit' ) {
-                       $this->undelete();
+               } elseif ( $this->mAction === "submit" ) {
+                       if ( $this->mRestore ) {
+                               $this->undelete();
+                       } elseif ( $this->mRevdel ) {
+                               $this->redirectToRevDel();
+                       }
+
                } else {
                        $this->showHistory();
                }
        }
 
+       /**
+        * Convert submitted form data to format expected by RevisionDelete and
+        * redirect the request
+        */
+       private function redirectToRevDel() {
+               $archive = new PageArchive( $this->mTargetObj );
+
+               $revisions = array();
+
+               foreach ( $this->getRequest()->getValues() as $key => $val ) {
+                       $matches = array();
+                       if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
+                               $revisions[ $archive->getRevision( $matches[1] )->getId() ] = 1;
+                       }
+               }
+               $query = array(
+                       "type" => "revision",
+                       "ids" => $revisions,
+                       "target" => wfUrlencode( $this->mTargetObj->getPrefixedText() )
+               );
+               $url = SpecialPage::getTitleFor( "RevisionDelete" )->getFullURL( $query );
+               $this->getOutput()->redirect( $url );
+       }
+
        function showSearchForm() {
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
@@ -1356,7 +1387,20 @@ class SpecialUndelete extends SpecialPage {
                $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
 
                if ( $haveRevisions ) {
-                       # The page's stored (deleted) history:
+                       # Show the page's stored (deleted) history
+
+                       if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
+                               $out->addHTML( Html::element(
+                                       'button',
+                                       array(
+                                               'name' => 'revdel',
+                                               'type' => 'submit',
+                                               'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
+                                       ),
+                                       $this->msg( 'showhideselectedversions' )->text()
+                               ) . "\n" );
+                       }
+
                        $out->addHTML( '<ul>' );
                        $remaining = $revisions->numRows();
                        $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
@@ -1466,13 +1510,9 @@ class SpecialUndelete extends SpecialPage {
                        $attribs['class'] = implode( ' ', $classes );
                }
 
-               // Revision delete links
-               $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
-
-               $revisionRow = $this->msg( 'undelete-revision-row' )
+               $revisionRow = $this->msg( 'undelete-revision-row2' )
                        ->rawParams(
                                $checkBox,
-                               $revdlink,
                                $last,
                                $pageLink,
                                $userLink,
index da63075..6638fb7 100644 (file)
@@ -1789,14 +1789,14 @@ class User implements IDBAccessObject {
                        // ip-based limits
                        if ( isset( $limits['ip'] ) ) {
                                $ip = $this->getRequest()->getIP();
-                               $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
+                               $keys[wfMemcKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
                        }
                        // subnet-based limits
                        if ( isset( $limits['subnet'] ) ) {
                                $ip = $this->getRequest()->getIP();
                                $subnet = IP::getSubnet( $ip );
                                if ( $subnet !== false ) {
-                                       $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+                                       $keys[wfMemcKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
                                }
                        }
                }
index da02109..c2d3ee1 100644 (file)
@@ -170,9 +170,9 @@ class Names {
                'gl' => 'galego',               # Galician
                'glk' => 'گیلکی',  # Gilaki
                'gn' => 'Avañe\'ẽ',  # Guaraní, Paraguayan
-               'gom' => 'à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gova Konknni',      # Goan Konkani
-               'gom-deva' => 'à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80',        # Goan Konkani (Devanagari script)
-               'gom-latn' => 'Gova Konknni',   # Goan Konkani (Latin script)
+               'gom' => 'à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gõychi Konknni',     # Goan Konkani
+               'gom-deva' => 'à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80',  # Goan Konkani (Devanagari script)
+               'gom-latn' => 'Gõychi Konknni',        # Goan Konkani (Latin script)
                'got' => '𐌲𐌿𐍄𐌹𐍃𐌺',    # Gothic
                'grc' => 'Ἀρχαία ἑλληνικὴ', # Ancient Greek
                'gsw' => 'Alemannisch', # Alemannic
index 0b35f9b..bcedf4a 100644 (file)
        "apisandbox-reset": "إفراغ",
        "apisandbox-retry": "أعد المحاولة",
        "apisandbox-examples": "أمثلة",
-       "apisandbox-results": "النتيجة",
+       "apisandbox-results": "النتائج",
        "apisandbox-request-url-label": "مسار الطلب:",
        "apisandbox-request-time": "وقت الطلب: $1",
        "booksources": "مصادر كتاب",
index 0e03f7f..4804991 100644 (file)
        "subject": "قونو:",
        "minoredit": "بو بیر کیچیک دَییشدیرمه‌دیر",
        "watchthis": "بو صفحه‌نی ایزله",
-       "savearticle": "صÙ\81Ø­Ù\87â\80\8cÙ\86Û\8c Ø³Ø§Ø®Ù\84ا",
+       "savearticle": "صÙ\81Ø­Ù\87â\80\8cÙ\86Û\8c Ø°Ø®Û\8cرÙ\87 Ø§Ø¦Øª",
        "preview": "اؤن‌گؤستریش",
        "showpreview": "سیناق گؤستریش",
        "showdiff": "دَییشیکلیکلری گؤستر",
        "import-rootpage-invalid": "وئریلن کؤک صحیفه‌‌سی اعتبارسیز آددیر.",
        "import-rootpage-nosubpage": "آد فضا سی  \"$1\" آنا باسئ ٔآلت صحیفه اوچون اجازه وئرمیر.",
        "importlogpage": "چیخاریلما گونده‌لیگی",
-       "importlogpagetext": "باشÙ\82ا Ù\88Û\8cÚ©Û\8cÙ\84ردÙ\86Ø\8c Ø¯Ù\8eÛ\8cÛ\8cØ´Û\8cÚ©Ù\84Û\8cÚ© Ú¯Ø¦Ú\86Ù\85Û\8cØ´Ù\84رÛ\8cÙ\84Ù\87 Ø¨Û\8cرÙ\84Û\8cÚ©â\80\8cدÙ\87 Ú¯ØªÛ\8cرÛ\8cÙ\84Ù\85Û\8cØ´ ØµØ­Û\8cÙ\81ه‌لر.",
+       "importlogpagetext": "Ø¢Û\8cرÛ\8c Ù\88Û\8cÚ©Û\8cÙ\84ردÙ\86Ø\8c Ø¯Ù\8eÛ\8cÛ\8cØ´Û\8cÚ©Ù\84Û\8cÚ© Ú¯Ø¦Ú\86Ù\85Û\8cØ´Ù\84رÛ\8cÙ\84Ù\87 Ø¨Û\8cرÙ\84Û\8cÚ©â\80\8cدÙ\87 Ú¯ØªÛ\8cرÛ\8cÙ\84Ù\85Û\8cØ´ ØµÙ\81Ø­ه‌لر.",
        "import-logentry-upload-detail": "{{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
        "import-logentry-interwiki-detail": "$2-دن {{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
        "javascripttest": "جاوااسکریپت تِستی",
        "revdelete-unrestricted": "ایداره‌چیلرین محدودیتلرینی گؤتوردو",
        "logentry-block-block": "$1 {{GENDER:$4|$3}}-نی {{GENDER:$2|باغلادی}}. قۇرتارماق تاریخی: $5 $6",
        "logentry-block-unblock": "$1 {{GENDER:$4|$3}}-نین {{GENDER:$2|بلوکلاماغینی قالدیردی}}",
+       "logentry-import-upload": "$1 $3-نی فایل یوکله‌مه یولو ایله {{GENDER:$2|ایچری گتیردی}}",
+       "logentry-import-upload-details": "$1 $3-نی فایل یوکله‌مه یولو ایله {{GENDER:$2|ایچری گتیردی}} ($4 {{PLURAL:$4|نوسخه}})",
+       "logentry-import-interwiki-details": "$1 $3-نی $5-دن {{GENDER:$2|ایچری گتیردی}} ($4 {{PLURAL:$4|نوسخه}})",
        "logentry-move-move": "$1، $3 صفحه‌سینی $4-ه {{GENDER:$2|آپاردی}}",
        "logentry-move-move-noredirect": "$1، $3 صفحه‌سینی، یوْل‌لاندیرما قوْیماماق‌لا، $4-ه {{GENDER:$2|آپاردی}}",
        "logentry-move-move_redir": "$1، $3 صفحه‌سینی، $4-ده یوْل‌لاندیرما اۆستونه {{GENDER:$2|آپاردی}}",
        "logentry-move-move_redir-noredirect": "$1، $3 صفحه‌سینی، یوْل‌لاندیرما قوْیماماق‌لا، یوْل‌لاندیرما اوْلان $4 اۆستونه {{GENDER:$2|آپاردی}}",
        "logentry-patrol-patrol": "$1، $3 صحیفه‌سینین $4 نوسخه‌سینی، نظارتلنمیش {{GENDER:$2|نیشانلادی}}",
-       "logentry-patrol-patrol-auto": "$1، $3 صحیفه‌سینین $4 نوسخه‌سینی، اوتوماتیک اولاراق نظارتلنمیش {{GENDER:$2|نیشانلادی}}",
+       "logentry-patrol-patrol-auto": "$1، $3 صفحه‌سینین $4 نوسخه‌سینی، اوْتوماتیک یوْخلانمیش {{GENDER:$2|علامتلدی}}",
        "logentry-newusers-newusers": " بیر ایستیفاده‌چی حسابی $1 {{GENDER:$2|یاراتدی}}",
        "logentry-newusers-create": "$1 ایشلدن حسابی {{GENDER:$2|یارادیلدی}}",
        "logentry-newusers-create2": "$1 ایستیفاده‌چی، $3 حسابی {{GENDER:$2|یاراتدی}}",
index 4d8cfae..8bc2270 100644 (file)
        "contributions": "{{GENDER:$1|Ҡатнашыусы}} башҡарған эш",
        "contributions-title": "$1 исемле ҡатнашыусы башҡарған эш",
        "mycontris": "Башҡарған эштәр",
+       "anoncontribs": "башҡарған эштәр",
        "contribsub2": "{{GENDER:$3|$1}} башҡарған эше ($2)",
        "nocontribs": "Күрһәтелгән шарттарға яуап биргән үҙгәртеүҙәр табылманы.",
        "uctop": "(ағымдағы)",
index 949d107..6c45c7d 100644 (file)
        "botpasswords-created-body": "Пароль робата «$1» быў пасьпяхова створаны.",
        "botpasswords-updated-title": "Пароль робата абноўлены",
        "botpasswords-updated-body": "Пароль робата «$1» быў пасьпяхова абноўлены.",
+       "botpasswords-deleted-title": "Пароль робата выдалены",
+       "botpasswords-deleted-body": "Пароль робата «$1» быў выдалены.",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider недаступны.",
        "resetpass_forbidden": "Пароль ня можа быць зьменены",
        "resetpass-no-info": "Для непасрэднага доступу да гэтай старонкі Вам неабходна ўвайсьці ў сыстэму.",
        "resetpass-submit-loggedin": "Зьмяніць пароль",
        "apisandbox-examples": "Прыклады",
        "apisandbox-results": "Вынікі",
        "apisandbox-request-url-label": "URL-адрас запыту:",
-       "apisandbox-request-time": "ЧаÑ\81 Ð°Ð¿Ñ\80аÑ\86оÑ\9eкÑ\96 Ð·Ð°Ð¿Ñ\8bÑ\82Ñ\83: $1",
+       "apisandbox-request-time": "ЧаÑ\81 Ð·Ð°Ð¿Ñ\8bÑ\82Ñ\83: {{PLURAL:$1|$1 Ð¼Ñ\81}}",
        "booksources": "Крыніцы кніг",
        "booksources-search-legend": "Пошук кніг",
        "booksources-isbn": "ISBN:",
index 44f55bc..c03e20b 100644 (file)
        "exif-gpsdatestamp": "Терахь",
        "exif-jpegfilecomment": "JPEG-файлан билгалдаккхар",
        "exif-keywords": "Коьрта дешнаш",
+       "exif-countrycreated": "Мохк, сурт дин хилла болу",
+       "exif-citycreated": "ГӀала, сурт дина йолу",
        "exif-objectname": "Йоцца цӀе",
        "exif-specialinstructions": "Къаьсттина тӀехьажор",
        "exif-headline": "Корта",
index d098b02..adc0dbc 100644 (file)
@@ -6,7 +6,8 @@
                        "Don Alessandro",
                        "Urhixidur",
                        "아라",
-                       "Исмаил Садуев"
+                       "Исмаил Садуев",
+                       "Умар"
                ]
        },
        "tog-underline": "Багълантыларнынъ тюбюни сызув:",
        "mytalk": "Музакере",
        "anontalk": "Бу IP-нинъ музакереси",
        "navigation": "Сайтта ёл тапув",
-       "and": "&#32;а",
+       "and": "&#32;ве",
        "qbfind": "Тап",
        "qbbrowse": "Бакъып чыкъ",
        "qbedit": "Денъиштир",
        "watchthisupload": "Бу файлны козет",
        "filewasdeleted": "Бу исимде бир файл бар эди, амма ёкъ этильген эди. Лютфен, текрар юклемеден эвель $1 тешкеринъиз.",
        "filename-bad-prefix": "Сиз юклеген файлнынъ ады '''\"$1\"'''-нен башлана. Бу, адетиндже, ракъамлы фотоаппаратлардан файл адына язылгъан манасыз ишаретлердир. Лютфен, бу файл ичюн анълыджа бир ад сайлап язынъыз.",
-       "upload-success-subj": "Юкленюв беджерильди",
        "upload-proto-error": "Янълыш протокол",
        "upload-proto-error-text": "Интернеттен бир ресим файлы юклемеге истесенъиз адрес <code>http://</code> я да <code>ftp://</code>нен башламалы.",
        "upload-file-error": "Ички хата",
        "wlheader-showupdated": "Сонъки зияретинъизден сонъ денъиштирильген саифелер '''къалын арифлернен''' косьтерильди.",
        "wlnote": "Ашагъыда саат $3, $4 ичюн сонъки {{PLURAL:$2|1=саат|'''$2''' саат}} ичинде япылгъан сонъки {{PLURAL:$1|1=денъиштирме|'''$1''' денъиштирме}} косьтериле.",
        "wlshowlast": "Сонъки $1 саат ичюн, $2 кунь ичюн я да  косьтер",
-       "watchlistall2": "эписини",
        "watchlist-options": "Козетюв джедвели сазламалары",
        "watching": "Козетюв джедвелине кирсетильмекте...",
        "unwatching": "Козетюв джедвелинден ёкъ этильмекте...",
index 86e4381..7d4dd42 100644 (file)
        "faqpage": "Project:Чѧстꙑ въпроси",
        "actions": "дѣиства",
        "namespaces": "имєнъ просторꙑ",
+       "variants": "обраꙁи",
        "navigation-heading": "плаваниѥ",
        "errorpagetitle": "блаꙁна",
        "returnto": "къ страници ⁖ $1 ⁖ въꙁвращєниѥ ⁙",
        "ncategories": "$1 {{PLURAL:$1|катигорїꙗ|катигорїи|катигорїѩ}}",
        "nlinks": "$1 {{PLURAL:$1|съвѧꙁь|съвѧꙁи|съвѧꙁии}}",
        "nmembers": "$1 {{PLURAL:$1|члѣнъ|члѣна|члѣни|члѣнъ}}",
+       "uncategorizedpages": "бєскатигорїинꙑ страницѧ",
+       "uncategorizedcategories": "бєскатигорїинꙑ катигорїѩ",
+       "uncategorizedimages": "бєскатигорїинꙑ дѣла",
+       "uncategorizedtemplates": "бєскатигорїинꙑ обраꙁьци",
+       "wantedcategories": "ноуждьни катигорїѩ",
+       "wantedpages": "ноуждьни страницѧ",
+       "wantedfiles": "ноуждьни дѣла",
+       "wantedtemplates": "ноуждьни обраꙁьци",
        "shortpages": "кратъкꙑ страницѧ",
        "longpages": "дльгꙑ страницѧ",
        "protectedpages-reason": "какъ съмꙑслъ",
index bbe7556..4b698e3 100644 (file)
        "search": "Suche",
        "searchbutton": "Suchen",
        "go": "Ausführen",
-       "searcharticle": "Suchen",
+       "searcharticle": "Seite",
        "history": "Versionen",
        "history_short": "Versionsgeschichte",
        "updatedmarker": "Änderung seit deinem letzten Besuch",
        "previewnote": "'''Dies ist nur eine Vorschau.'''\nDie Seite wurde noch nicht gespeichert!",
        "continue-editing": "Zum Bearbeitungsfeld gehen",
        "previewconflict": "Diese Vorschau gibt den Inhalt des oberen Textfeldes wieder. So wird die Seite aussehen, wenn du jetzt speicherst.",
-       "session_fail_preview": "'''Deine Bearbeitung konnte nicht gespeichert werden, da Sitzungsdaten verloren gegangen sind.\nBitte versuche es erneut, indem du unter der folgenden Textvorschau nochmals auf „Seite speichern“ klickst.\nSollte das Problem bestehen bleiben, [[Special:UserLogout|melde dich ab]] und danach wieder an.'''",
-       "session_fail_preview_html": "'''Deine Bearbeitung konnte nicht gespeichert werden, da Sitzungsdaten verloren gegangen sind.'''\n\n''Da in {{SITENAME}} das Speichern von reinem HTML aktiviert ist, wurde die Vorschau ausgeblendet, um JavaScript-Attacken vorzubeugen.''\n\n'''Bitte versuche es erneut, indem du unter der folgenden Textvorschau nochmals auf „Seite speichern“ klickst.\nSollte das Problem bestehen bleiben, [[Special:UserLogout|melde dich ab]] und danach wieder an.'''",
+       "session_fail_preview": "Entschuldigung! Wir konnten deine Bearbeitung nicht verarbeiten, da Sitzungsdaten verloren gegangen sind.\n\nDu wurdest eventuell abgemeldet. <strong>Bitte verifiziere, dass du noch angemeldet bist und versuche es erneut</strong>.\nFalls dies nicht funktioniert, versuche dich [[Special:UserLogout|abzumelden]] und anschließend wieder anzumelden und überprüfe, ob dein Browser Cookies von dieser Website akzeptiert.",
+       "session_fail_preview_html": "Deine Bearbeitung konnte nicht gespeichert werden, da Sitzungsdaten verloren gegangen sind.\n\n<em>Da in {{SITENAME}} das Speichern von reinem HTML aktiviert ist, wurde die Vorschau ausgeblendet, um JavaScript-Attacken vorzubeugen.</em>\n\n<strong>Bitte versuche es erneut, indem du unter der folgenden Textvorschau nochmals auf „Seite speichern“ klickst.</strong>\nSollte das Problem bestehen bleiben, [[Special:UserLogout|melde dich ab]] und danach wieder an. Überprüfe, ob dein Browser Cookies von dieser Website akzeptiert.",
        "token_suffix_mismatch": "'''Deine Bearbeitung wurde zurückgewiesen, da dein Browser Zeichen im Bearbeiten-Token verstümmelt hat.\nEine Speicherung kann den Seiteninhalt zerstören. Dies geschieht bisweilen durch die Benutzung eines anonymen Proxy-Dienstes, der fehlerhaft arbeitet.'''",
        "edit_form_incomplete": "'''Der Inhalt des Bearbeitungsformulars hat den Server nicht vollständig erreicht. Bitte prüfe deine Bearbeitungen auf Vollständigkeit und versuche es erneut.'''",
        "editing": "Bearbeiten von „$1“",
        "mergehistory-empty": "Es können keine Versionen vereinigt werden.",
        "mergehistory-done": "{{PLURAL:$3|Eine Version wurde|$3 Versionen wurden}} von „$1“ nach „[[:$2]]“ vereinigt.",
        "mergehistory-fail": "Versionsvereinigung nicht möglich, bitte prüfe die Seite und die Zeitangaben.",
+       "mergehistory-fail-bad-timestamp": "Ungültiger Zeitstempel.",
+       "mergehistory-fail-invalid-source": "Ungültige Quellseite.",
+       "mergehistory-fail-invalid-dest": "Ungültige Zielseite.",
+       "mergehistory-fail-no-change": "Keine Versionen zusammengeführt. Kontrolliere bitte die Seiten- und Zeitparameter erneut.",
+       "mergehistory-fail-permission": "Keine ausreichenden Berechtigungen, um die Versionsgeschichte zusammenzuführen.",
+       "mergehistory-fail-self-merge": "Quell- und Zielseiten sind identisch.",
+       "mergehistory-fail-timestamps-overlap": "Die Quellversionen überlappen sich oder kommen nach den Ziel-Versionen.",
        "mergehistory-fail-toobig": "Die Versionsgeschichtenzusammenführung konnte nicht ausgeführt werden, da sonst mehr als {{PLURAL:$1|eine Version|$1 Versionen}} verschoben werden {{PLURAL:$1|würde|würden}}.",
        "mergehistory-no-source": "Ursprungsseite „$1“ ist nicht vorhanden.",
        "mergehistory-no-destination": "Zielseite „$1“ ist nicht vorhanden.",
        "import-nonewrevisions": "Es wurden keine Versionen importiert. Entweder waren alle bereits vorhanden oder wurden aufgrund von Fehlern übersprungen.",
        "xml-error-string": "$1 Zeile $2, Spalte $3, (Byte $4): $5",
        "import-upload": "XML-Dateien importieren",
-       "import-token-mismatch": "Verlust der Sessiondaten. Bitte versuche es erneut.",
+       "import-token-mismatch": "Die Sitzungsdaten sind verloren gegangen.\n\nDu wurdest eventuell abgemeldet. <strong>Bitte verifiziere, dass du noch angemeldet bist und versuche es erneut</strong>.\nFalls dies nicht funktioniert, versuche dich [[Special:UserLogout|abzumelden]] und anschließend wieder anzumelden und überprüfe, ob dein Browser Cookies von dieser Website akzeptiert.",
        "import-invalid-interwiki": "Aus dem angegebenen Wiki ist kein Import möglich.",
        "import-error-edit": "Die Seite „$1“ wurde nicht importiert, da du nicht berechtigt bist, sie zu bearbeiten.",
        "import-error-create": "Die Seite „$1“ wurde nicht importiert, da du nicht berechtigt bist, sie zu erstellen.",
        "expand_templates_generate_xml": "XML-Parser-Baum zeigen",
        "expand_templates_generate_rawhtml": "Rohes HTML anzeigen",
        "expand_templates_preview": "Vorschau",
-       "expand_templates_preview_fail_html": "<em>Da {{SITENAME}} rohes HTML aktiviert hat und es einen Verlust deiner Sitzungsdaten gab, ist die Vorschau als Vorsichtsmaßnahme gegen JavaScript-Angriffe versteckt.</em>\n\n<strong>Falls dies ein zulässiger Vorschauversuch ist, versuche es bitte erneut.</strong>\nFalls dieses Problem weiterhin bestehen bleibt, versuche dich [[Special:UserLogout|abzumelden]] und erneut anzumelden.",
+       "expand_templates_preview_fail_html": "<em>Da {{SITENAME}} rohes HTML aktiviert hat und es einen Verlust deiner Sitzungsdaten gab, ist die Vorschau als Vorsichtsmaßnahme gegen JavaScript-Angriffe versteckt.</em>\n\n<strong>Falls dies ein zulässiger Vorschauversuch ist, versuche es bitte erneut.</strong>\nFalls dieses Problem weiterhin bestehen bleibt, versuche dich [[Special:UserLogout|abzumelden]] und erneut anzumelden und überprüfe, ob dein Browser Cookies von dieser Website akzeptiert.",
        "expand_templates_preview_fail_html_anon": "<em>Da {{SITENAME}} rohes HTML aktiviert hat und du nicht angemeldet bist, ist die Vorschau als Vorsichtsmaßnahme gegen JavaScript-Angriffe versteckt.</em>\n\n<strong>Falls dies ein zulässiger Vorschauversuch ist, [[Special:UserLogin|melde dich bitte an]] und versuche es erneut.</strong>",
        "expand_templates_input_missing": "Du musst mindestens einen Eingabetext angeben.",
        "pagelanguage": "Seitensprache ändern",
index 2e1df41..f220040 100644 (file)
        "previewnote": "<strong>Remember that this is only a preview.</strong>\nYour changes have not yet been saved!",
        "continue-editing": "Go to editing area",
        "previewconflict": "This preview reflects the text in the upper text editing area as it will appear if you choose to save.",
-       "session_fail_preview": "<strong>Sorry! We could not process your edit due to a loss of session data.</strong>\nPlease try again.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
-       "session_fail_preview_html": "<strong>Sorry! We could not process your edit due to a loss of session data.</strong>\n\n<em>Because {{SITENAME}} has raw HTML enabled, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate edit attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
+       "session_fail_preview": "Sorry! We could not process your edit due to a loss of session data.\n\nYou might have been logged out. <strong>Please verify that you're still logged in and try again</strong>.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
+       "session_fail_preview_html": "Sorry! We could not process your edit due to a loss of session data.\n\n<em>Because {{SITENAME}} has raw HTML enabled, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate edit attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
        "token_suffix_mismatch": "<strong>Your edit has been rejected because your client mangled the punctuation characters in the edit token.</strong>\nThe edit has been rejected to prevent corruption of the page text.\nThis sometimes happens when you are using a buggy web-based anonymous proxy service.",
        "edit_form_incomplete": "<strong>Some parts of the edit form did not reach the server; double-check that your edits are intact and try again.</strong>",
        "editing": "Editing $1",
        "mergehistory-empty": "No revisions can be merged.",
        "mergehistory-done": "$3 {{PLURAL:$3|revision|revisions}} of $1 {{PLURAL:$3|was|were}} merged into [[:$2]].",
        "mergehistory-fail": "Unable to perform history merge, please recheck the page and time parameters.",
-       "mergehistory-fail-toobig" : "Unable to perform history merge as more than the limit of $1 {{PLURAL:$1|revision|revisions}} would be moved.",
+       "mergehistory-fail-bad-timestamp": "Timestamp is invalid.",
+       "mergehistory-fail-invalid-source": "Source page is invalid.",
+       "mergehistory-fail-invalid-dest": "Destination page is invalid.",
+       "mergehistory-fail-no-change": "History merge did not merge any revisions. Please recheck the page and time parameters.",
+       "mergehistory-fail-permission": "Insufficient permissions to merge history.",
+       "mergehistory-fail-self-merge": "Source and destination pages are the same.",
+       "mergehistory-fail-timestamps-overlap": "Source revisions overlap or come after destination revisions.",
+       "mergehistory-fail-toobig": "Unable to perform history merge as more than the limit of $1 {{PLURAL:$1|revision|revisions}} would be moved.",
+       "mergehistory-warning-redirect-not-created": "",
        "mergehistory-no-source": "Source page $1 does not exist.",
        "mergehistory-no-destination": "Destination page $1 does not exist.",
        "mergehistory-invalid-source": "Source page must be a valid title.",
        "mergehistory-same-destination": "Source and destination pages cannot be the same",
        "mergehistory-reason": "Reason:",
        "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
+       "mergehistory-redirect-text": "",
        "mergelog": "Merge log",
        "pagemerge-logentry": "merged [[$1]] into [[$2]] (revisions up to $3)",
        "revertmerge": "Unmerge",
        "undelete-error-long": "Errors were encountered while undeleting the file:\n\n$1",
        "undelete-show-file-confirm": "Are you sure you want to view the deleted revision of the file \"<nowiki>$1</nowiki>\" from $2 at $3?",
        "undelete-show-file-submit": "Yes",
-       "undelete-revision-row": "$1 $2 ($3) $4 . . $5 $6 $7 $8 $9",
+       "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
        "namespace": "Namespace:",
        "invert": "Invert selection",
        "tooltip-invert": "Check this box to hide changes to pages within the selected namespace (and the associated namespace if checked)",
        "import-nonewrevisions": "No revisions imported (all were either already present, or skipped due to errors).",
        "xml-error-string": "$1 at line $2, col $3 (byte $4): $5",
        "import-upload": "Upload XML data",
-       "import-token-mismatch": "Loss of session data.\nPlease try again.",
+       "import-token-mismatch": "Loss of session data.\n\nYou might have been logged out. <strong>Please verify that you're still logged in and try again</strong>.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
        "import-invalid-interwiki": "Cannot import from the specified wiki.",
        "import-error-edit": "Page \"$1\" was not imported because you are not allowed to edit it.",
        "import-error-create": "Page \"$1\" was not imported because you are not allowed to create it.",
        "expand_templates_generate_xml": "Show XML parse tree",
        "expand_templates_generate_rawhtml": "Show raw HTML",
        "expand_templates_preview": "Preview",
-       "expand_templates_preview_fail_html": "<em>Because {{SITENAME}} has raw HTML enabled and there was a loss of session data, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
+       "expand_templates_preview_fail_html": "<em>Because {{SITENAME}} has raw HTML enabled and there was a loss of session data, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
        "expand_templates_preview_fail_html_anon": "<em>Because {{SITENAME}} has raw HTML enabled and you are not logged in, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please [[Special:UserLogin|log in]] and try again.</strong>",
        "expand_templates_input_missing": "You need to provide at least some input text.",
        "pagelanguage": "Change page language",
index 494033b..4ecff59 100644 (file)
                        "Cindie.Capel",
                        "ElGatoSaez",
                        "Joaquin1001",
-                       "YoViajo"
+                       "YoViajo",
+                       "Asierog"
                ]
        },
        "tog-underline": "Subrayar los enlaces:",
        "rcshowhidemine": "$1 mis ediciones",
        "rcshowhidemine-show": "Mostrar",
        "rcshowhidemine-hide": "Ocultar",
-       "rcshowhidecategorization": "$1 categorización de página",
+       "rcshowhidecategorization": "$1 categorización de páginas",
        "rcshowhidecategorization-show": "Mostrar",
        "rcshowhidecategorization-hide": "Ocultar",
        "rclinks": "Ver los últimos $1 cambios en los últimos $2 días.<br />$3",
        "apisandbox-retry": "Reintentar",
        "apisandbox-no-parameters": "Este módulo API no tiene parámetros.",
        "apisandbox-helpurls": "Enlaces de ayuda",
-       "apisandbox-examples": "Ejemplo",
+       "apisandbox-examples": "Ejemplos",
        "apisandbox-dynamic-parameters": "Parámetros adicionales",
        "apisandbox-dynamic-parameters-add-label": "Añadir parámetro:",
        "apisandbox-dynamic-parameters-add-placeholder": "Nombre del parámetro",
        "apisandbox-submit-invalid-fields-title": "Algunos campos no son válidos",
        "apisandbox-results": "Resultados",
        "apisandbox-sending-request": "Enviando pedido API...",
+       "apisandbox-loading-results": "Recibiendo resultados API...",
        "apisandbox-request-url-label": "URL solicitante:",
        "apisandbox-request-time": "Tiempo de solicitud: $1",
        "booksources": "Fuentes de libros",
        "listgrouprights-namespaceprotection-restrictedto": "Derechos de usuario para editar",
        "listgrants": "Subvenciones",
        "listgrants-grant": "Conceder",
-       "listgrants-rights": "Conceder",
+       "listgrants-rights": "Derechos",
        "trackingcategories": "Categorías de seguimiento",
        "trackingcategories-summary": "Esta página lista categorías de seguimiento que han sido generadas automáticamente por el software MediaWiki. Sus nombres pueden cambiarse editando su mensaje correspondiente en el espacio de nombres {{ns:8}}.",
        "trackingcategories-msg": "Categoría de seguimiento",
        "wlshowhideanons": "usuarios anónimos",
        "wlshowhidepatr": "ediciones verificadas",
        "wlshowhidemine": "mis ediciones",
-       "wlshowhidecategorization": "categorización de página",
+       "wlshowhidecategorization": "categorización de páginas",
        "watchlist-options": "Opciones de la lista de seguimiento",
        "watching": "Vigilando...",
        "unwatching": "Eliminando de la lista de seguimiento...",
index 0c3bbe5..8405d9f 100644 (file)
@@ -23,7 +23,8 @@
                        "Arkaitz Barnetik",
                        "Sator",
                        "Macofe",
-                       "Xð"
+                       "Xð",
+                       "Asierog"
                ]
        },
        "tog-underline": "Azpimarratu loturak:",
        "resetpass_submit": "Pasahitza definitu eta saioa hasi",
        "changepassword-success": "Zure pasahitza ondo aldatu da!",
        "changepassword-throttled": "Saioa hasteko saiakera gehiegi egin berri dituzu.\nBerriro saiatu aurretik $1 itxoin, mesedez.",
+       "botpasswords-label-create": "Sortu",
+       "botpasswords-label-update": "Eguneratu",
+       "botpasswords-label-delete": "Ezabatu",
        "resetpass_forbidden": "Ezin dira pasahitzak aldatu",
        "resetpass-no-info": "Orrialde honetara zuzenean sartzeko izena eman behar duzu.",
        "resetpass-submit-loggedin": "Pasahitza aldatu",
        "userrights": "Erabiltzaile baimenen kudeaketa",
        "userrights-lookup-user": "Erabiltzaile taldeak kudeatu",
        "userrights-user-editname": "Erabiltzaile izena idatzi:",
-       "editusergroup": "Erabiltzaile taldeak editatu",
+       "editusergroup": "{{GENDER:$1|Erabiltzaile}} taldeak editatu",
        "editinguser": "'''[[User:$1|$1]]''' $2 lankidearen erabiltzaile-eskubideak aldatzen",
        "userrights-editusergroup": "Erabiltzaile taldeak editatu",
        "saveusergroups": "Erabiltzaile taldeak gorde",
        "querypage-disabled": "Orrialde berezi hau desgaituta dago funtzionamendu arrazoiengatik.",
        "apihelp": "API laguntza",
        "apihelp-no-such-module": "Ez da \"$1\" modulua aurkitu.",
+       "apisandbox": "API proba orria",
+       "apisandbox-submit": "Egin eskaera",
+       "apisandbox-reset": "Garbitu",
+       "apisandbox-examples": "Adibideak",
+       "apisandbox-results": "Emaitzak",
        "booksources": "Iturri liburuak",
        "booksources-search-legend": "Liburuen bilaketa",
        "booksources-search": "Bilatu",
        "activeusers-noresult": "Ez da lankiderik aurkitu.",
        "listgrouprights": "Erabiltzaile talde eskumenak",
        "listgrouprights-summary": "Ondorengo zerrendak wikian dauden lankide taldeak agertzen dira, beraien eskubideekin.\nBadago [[{{MediaWiki:Listgrouprights-helppage}}|informazio osagarria]] banakako eskubideei buruz.",
-       "listgrouprights-key": "* <span class=\"listgrouprights-granted\">Eskubidea emanda</span>\n* <span class=\"listgrouprights-revoked\">Eskubidea kenduta</span>",
+       "listgrouprights-key": "Legenda\n* <span class=\"listgrouprights-granted\">Eskubidea emanda</span>\n* <span class=\"listgrouprights-revoked\">Eskubidea kenduta</span>",
        "listgrouprights-group": "Taldea",
        "listgrouprights-rights": "Eskumenak",
        "listgrouprights-helppage": "Help:Talde eskumenak",
        "wlshowlast": "Erakutsi azken $1 orduak, azken $2 egunak",
        "watchlist-hide": "Ezkutatu",
        "watchlist-submit": "Erakutsi",
-       "wlshowtime": "Erakutsi azkenak:",
+       "wlshowtime": "Erakusteko denboraldia:",
        "wlshowhideminor": "aldaketa txikiak",
        "wlshowhidebots": "bot-ak",
        "wlshowhideliu": "Erregistratutako erabiltzaileak",
index bc6a861..56605df 100644 (file)
@@ -48,7 +48,8 @@
                        "Macofe",
                        "Danialbehzadi",
                        "MRG90",
-                       "Mahdy Saffar"
+                       "Mahdy Saffar",
+                       "Arian Ar"
                ]
        },
        "tog-underline": "خط کشیدن زیر پیوندها:",
@@ -73,7 +74,7 @@
        "tog-enotifwatchlistpages": "اگر صفحه یا پرونده‌ای از فهرست پی‌گیری‌هایم ویرایش شد به من ایمیلی فرستاده شود",
        "tog-enotifusertalkpages": "هنگامی که در صفحهٔ بحث کاربری‌ام تغییری صورت می‌گیرد به من ایمیلی فرستاده شود",
        "tog-enotifminoredits": "برای تغییرات جزئی در صفحه‌ها و پرونده‌ها هم به من ایمیلی فرستاده شود",
-       "tog-enotifrevealaddr": "Ù\86شاÙ\86Û\8c Ù¾Ø³Øª Ø§Ù\84کترÙ\88Ù\86Û\8cÚ©Û\8c Ù\85Ù\86 Ø±Ø§ Ø¯Ø± Ø§Û\8cÙ\85Û\8cÙ\84â\80\8cÙ\87اÛ\8c Ø§Ø·Ù\84اعâ\80\8cرساÙ\86Û\8c Ù\87Ù\88Û\8cدا Ú¯Ø±Ø¯Ø¯",
+       "tog-enotifrevealaddr": "نشانی پست الکترونیکی من در ایمیل‌های اطلاع‌رسانی هویدا گردد",
        "tog-shownumberswatching": "شمار کاربران پی‌گیرندهٔ نمایش یابد",
        "tog-oldsig": "امضای کنونی:",
        "tog-fancysig": "امضا به صورت ویکی‌متن در نظر گرفته شود (بدون درج خودکار پیوند)",
        "create-this-page": "ایجاد این صفحه",
        "delete": "حذف",
        "deletethispage": "حذف این صفحه",
-       "undeletethispage": "بازگرداÙ\86ی این صفحه",
+       "undeletethispage": "احÛ\8cای این صفحه",
        "undelete_short": "احیای {{PLURAL:$1|یک ویرایش|$1 ویرایش}}",
        "viewdeleted_short": "نمایش {{PLURAL:$1|یک ویرایش حذف‌شده|$1 ویرایش حذف‌شده}}",
        "protect": "محافظت",
index 42f65fa..3e3a3dc 100644 (file)
@@ -85,7 +85,7 @@
        "tog-watchlistreloadautomatically": "Päivitä tarkkailulista automaattisesti aina kun jotakin suodatinta on muutettu (vaatii JavaScriptiä)",
        "tog-watchlisthideanons": "Piilota rekisteröitymättömien käyttäjien muokkaukset tarkkailulistalta",
        "tog-watchlisthidepatrolled": "Piilota muutostentarkastajien hyväksymät muokkaukset tarkkailulistalta",
-       "tog-watchlisthidecategorization": "Piilota muutokset, jotka koskevat sivujeen lisäämistä tai poistamista luokkiin",
+       "tog-watchlisthidecategorization": "Piilota sivujen luokitusmuutokset",
        "tog-ccmeonemails": "Lähetä minulle kopio MediaWikin kautta lähetetyistä sähköposteista",
        "tog-diffonly": "Älä näytä sivun sisältöä eroavaisuusvertailun alapuolella",
        "tog-showhiddencats": "Näytä piilotetut luokat",
        "changepassword-throttled": "Olet tehnyt liian monta äskettäistä kirjautumisyritystä.\nOdota $1 ennen kuin yrität uudelleen.",
        "botpasswords": "Botin salasanat",
        "botpasswords-disabled": "Botin salasanat on poistettu käytöstä.",
+       "botpasswords-label-create": "Luo",
+       "botpasswords-label-update": "Päivitä",
+       "botpasswords-label-cancel": "Peruuta",
+       "botpasswords-label-delete": "Poista",
+       "botpasswords-label-resetpassword": "Uudista salasana",
+       "botpasswords-label-grants": "Valittavissa olevat toimintaoikeudet:",
        "resetpass_forbidden": "Salasanoja ei voi vaihtaa.",
        "resetpass-no-info": "Et voi nähdä tätä sivua kirjautumatta sisään.",
        "resetpass-submit-loggedin": "Muuta salasana",
        "mergehistory-empty": "Mitään versioita ei voida yhdistää.",
        "mergehistory-done": "$3 {{PLURAL:$3|versio|versiota}} sivusta $1 yhdistettiin onnistuneesti sivuun [[:$2]].",
        "mergehistory-fail": "Sivuhistorioiden yhdistämistä ei voida suorittaa. Tarkista lähde- ja kohdesivujen nimet sekä versioiden aikamääritys.",
+       "mergehistory-fail-bad-timestamp": "Aikaleima ei ole kelvollinen.",
+       "mergehistory-fail-invalid-source": "Lähdesivu ei ole kelvollinen.",
+       "mergehistory-fail-invalid-dest": "Kohdesivu ei ole kelvollinen.",
+       "mergehistory-fail-no-change": "Sivuhistorian yhdistämistoiminto ei yhdistänyt mitään versioita. Katso, ovatko sivun ja ajankohdan määritykset oikein.",
+       "mergehistory-fail-permission": "Käyttöoikeutesi eivät riitä sivuhistorioiden yhdistämiseen.",
+       "mergehistory-fail-self-merge": "Lähdesivu ja kohdesivu ovat samat.",
+       "mergehistory-fail-timestamps-overlap": "Lähdesivun versiot menevät päällekkäin tai ovat myöhemmin tehtyjä kuin kohdesivun versiot.",
        "mergehistory-fail-toobig": "Sivuhistorian yhdistämistä ei voi tehdä, koska enemmän kuin sallittu määrä $1 {{PLURAL:$1|versio|versiota}} siirrettäisiin.",
        "mergehistory-no-source": "Lähdesivua $1 ei ole olemassa.",
        "mergehistory-no-destination": "Kohdesivua $1 ei ole olemassa.",
        "apisandbox": "API-hiekkalaatikko",
        "apisandbox-api-disabled": "API on poistettu käytöstä tällä sivustolla.",
        "apisandbox-intro": "Tämä on '''MediaWiki API:n''' hiekkalaatikko.\n[//www.mediawiki.org/wiki/API:Main_page API-dokumentaatio] kertoo lisää API:en käytöstä.",
+       "apisandbox-fullscreen": "Laajenna paneeli",
+       "apisandbox-unfullscreen": "Näytä sivu",
        "apisandbox-submit": "Tee pyyntö",
        "apisandbox-reset": "Tyhjennä",
-       "apisandbox-examples": "Esimerkki",
-       "apisandbox-results": "Tulos",
+       "apisandbox-retry": "Yritä uudestaan",
+       "apisandbox-no-parameters": "Tässä API-moduulissa ei ole parametreja.",
+       "apisandbox-helpurls": "Linkit ohjeisiin",
+       "apisandbox-examples": "Esimerkit",
+       "apisandbox-dynamic-parameters": "Lisäparametrit",
+       "apisandbox-dynamic-parameters-add-label": "Lisää parametri:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Parametrin nimi",
+       "apisandbox-dynamic-error-exists": "Parametri nimellä \"$1\" on ennestään olemassa.",
+       "apisandbox-deprecated-parameters": "Käytöstä poistuneet parametrit",
+       "apisandbox-fetch-token": "Lisää \"token\" automaattisesti",
+       "apisandbox-submit-invalid-fields-title": "Jotkin kentät ovat epäkelpoja",
+       "apisandbox-submit-invalid-fields-message": "Korjaa merkityt kentät ja yritä uudestaan.",
+       "apisandbox-results": "Tulokset",
+       "apisandbox-sending-request": "API-pyyntöä lähetetään...",
+       "apisandbox-loading-results": "API-tuloksia vastaanotetaan...",
        "apisandbox-request-url-label": "Pyynnön URL",
-       "apisandbox-request-time": "Pyyntöaika: $1",
+       "apisandbox-request-time": "Pyyntöön kulunut aika: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-alert-page": "Tällä sivulla olevat kentät eivät ole kelvollisia.",
+       "apisandbox-alert-field": "Tässä kentässä oleva arvo ei ole kelvollinen.",
        "booksources": "Kirjalähteet",
        "booksources-search-legend": "Etsi kirjalähteitä",
        "booksources-isbn": "ISBN",
        "lockedbyandtime": "(lukinnut {{GENDER:$1|$1}} $2 kello $3)",
        "move-page": "Siirrä $1",
        "move-page-legend": "Siirrä sivu",
-       "movepagetext": "Alla olevalla lomakkeella voit nimetä uudelleen sivuja, jolloin niiden koko historia siirtyy uuden nimen alle.\nVanhasta sivusta tulee ohjaussivu, joka osoittaa uuteen sivuun.\nVoit päivittää sivuun viittaavat ohjaukset automaattisesti ohjaamaan uudelle nimelle.\nJos et halua tätä tehtävän automaattisesti, muista tehdä tarkistukset [[Special:DoubleRedirects|kaksinkertaisten]] tai [[Special:BrokenRedirects|rikkinäisten]] ohjausten varalta.\nOlet vastuussa siitä, että linkit osoittavat sinne, mihin niiden on tarkoituskin osoittaa.\n\nHuomaa, että sivua '''ei''' siirretä mikäli uusi otsikko on olemassa olevan sivun käytössä, paitsi jos jälkimmäinen on ohjaus, jolla ei ole muokkaushistoriaa.\nTämä tarkoittaa sitä, että voit siirtää sivun takaisin vanhalle nimelleen mikäli teit virheen, mutta et voi kirjoittaa olemassa olevan sivun päälle.\n\nTämä saattaa olla suuri ja odottamaton muutos suositulle sivulle. Varmista, että tiedät seuraukset ennen kuin siirrät sivun.",
-       "movepagetext-noredirectfixer": "Alla olevalla lomakkeella voit nimetä uudelleen sivuja, jolloin niiden koko historia siirtyy uuden nimen alle. Vanhasta sivusta tulee ohjaussivu, joka osoittaa uuteen sivuun.\n\nTarkasta sivuun viittaavat ohjaukset [[Special:DoubleRedirects|kaksinkertaisten]] tai [[Special:BrokenRedirects|rikkinäisten]] ohjausten varalta. Olet vastuussa siitä, että linkit osoittavat sinne, mihin niiden on tarkoituskin osoittaa.\n\nHuomaa, että sivua '''ei''' siirretä mikäli uusi otsikko on olemassa olevan sivun käytössä, paitsi jos jälkimmäinen on ohjaus, jolla ei ole muokkaushistoriaa.\nTämä tarkoittaa sitä, että voit siirtää sivun takaisin vanhalle nimelleen mikäli teit virheen, mutta et voi kirjoittaa olemassa olevan sivun päälle.\n\nTämä saattaa olla suuri ja odottamaton muutos suositulle sivulle. Varmista, että tiedät seuraukset ennen kuin siirrät sivun.",
+       "movepagetext": "Alla olevalla lomakkeella voit nimetä uudelleen sivuja, jolloin niiden koko historia siirtyy uuden nimen alle.\nVanhasta sivusta tulee ohjaussivu, joka osoittaa uuteen sivuun.\nVoit päivittää sivuun viittaavat ohjaukset automaattisesti ohjaamaan uudelle nimelle.\nJos et halua tätä tehtävän automaattisesti, muista tehdä tarkistukset [[Special:DoubleRedirects|kaksinkertaisten]] tai [[Special:BrokenRedirects|rikkinäisten]] ohjausten varalta.\nOlet vastuussa siitä, että linkit osoittavat sinne, mihin niiden on tarkoituskin osoittaa.\n\nHuomaa, että sivua <strong>ei</strong> siirretä mikäli uusi otsikko on olemassa olevan sivun käytössä, paitsi jos jälkimmäinen on ohjaus, jolla ei ole muokkaushistoriaa.\nTämä tarkoittaa sitä, että voit siirtää sivun takaisin vanhalle nimelleen mikäli teit virheen, mutta et voi kirjoittaa olemassa olevan sivun päälle.\n\n<strong>Ota huomioon:</strong>\n\nTämä saattaa olla suuri ja odottamaton muutos suositulle sivulle. Varmista, että ymmärrät seuraukset ennen kuin siirrät sivun.",
+       "movepagetext-noredirectfixer": "Alla olevalla lomakkeella voit nimetä uudelleen sivuja, jolloin niiden koko historia siirtyy uuden nimen alle. Vanhasta sivusta tulee ohjaussivu, joka osoittaa uuteen sivuun.\n\nTarkasta sivuun viittaavat ohjaukset [[Special:DoubleRedirects|kaksinkertaisten]] tai [[Special:BrokenRedirects|rikkinäisten]] ohjausten varalta. Olet vastuussa siitä, että linkit osoittavat sinne, mihin niiden on tarkoituskin osoittaa.\n\nHuomaa, että sivua <strong>ei</strong> siirretä mikäli uusi otsikko on olemassa olevan sivun käytössä, paitsi jos jälkimmäinen on ohjaus, jolla ei ole muokkaushistoriaa.\nTämä tarkoittaa sitä, että voit siirtää sivun takaisin vanhalle nimelleen mikäli teit virheen, mutta et voi kirjoittaa olemassa olevan sivun päälle.\n\n<strong>Ota huomioon:</strong>\n\nTämä saattaa olla suuri ja odottamaton muutos suositulle sivulle. Varmista, että ymmärrät seuraukset ennen kuin siirrät sivun.",
        "movepagetalktext": "Jos valitset tämän vaihtoehdon, sivuun liittyvä keskustelusivu siirtyy automaattisesti uudelle nimelle, paitsi jos uudella nimellä on jo olemassa keskustelusivu, joka ei ole tyhjä.\n\nTällöin sivu täytyy siirtää tai yhdistää käsin, jos se on tarpeen.",
        "moveuserpage-warning": "'''Varoitus:''' Olet siirtämässä käyttäjäsivua. Huomaa, että vain sivu siirretään ja käyttäjää ''ei'' nimetä uudelleen.",
        "movecategorypage-warning": "<strong>Varoitus:</strong> Olet siirtämässä luokkasivua. Ota huomioon, että ainoastaan luokan oma sivu siirretään ja että tämä toiminto <em>ei</em> luokittele tai itsestään siirrä vanhassa luokassa olevia sivuja uuteen luokkaan.",
        "movenosubpage": "Tällä sivulla ei ole alasivuja.",
        "movereason": "Syy:",
        "revertmove": "kumoa siirto",
-       "delete_and_move_text": "==Poistamista edellyttävä siirto==\nKohdesivu [[:$1]] on jo olemassa. \nHaluatko poistaa sen, jotta nykyinen sivu voitaisiin siirtää?",
+       "delete_and_move_text": "Kohdesivu [[:$1]] on jo olemassa. \nHaluatko poistaa sen, jotta nykyinen sivu voitaisiin siirtää sen tilalle?",
        "delete_and_move_confirm": "Kyllä, poista kohdesivu",
        "delete_and_move_reason": "Sivu on sivun [[$1]] siirron tiellä.",
        "selfmove": "Lähteen ja kohteen nimi on sama.\nSivua ei voi siirtää itsensä päälle.",
        "move-leave-redirect": "Jätä paikalle ohjaus",
        "protectedpagemovewarning": "'''Varoitus:''' Tämä sivu on lukittu siten, että vain ylläpitäjät voivat siirtää sitä.\nAlla on viimeisin lokitapahtuma:",
        "semiprotectedpagemovewarning": "Tämä sivu on lukittu siten, että vain rekisteröityneet käyttäjät voivat siirtää sitä.\nAlla on viimeisin lokitapahtuma:",
-       "move-over-sharedrepo": "== Tiedosto on olemassa ==\n[[:$1]] on olemassa jaetussa tietovarastossa. Tiedoston siirtäminen tälle nimelle ohittaa jaetun tiedoston.",
+       "move-over-sharedrepo": "[[:$1]] on olemassa yhteisessä tietovarastossa. Tiedoston siirtäminen tälle nimelle korvaa yhteisen tiedoston.",
        "file-exists-sharedrepo": "Valittu tiedostonimi on jo käytössä jaetussa varastossa.\nValitse toinen nimi.",
        "export": "Vie sivuja",
        "exporttext": "Voit viedä (''export'') sivun tai usean sivun tekstin ja muokkaushistorian XML-muodossa.\nTämä tieto voidaan tuoda (''import'') toiseen wikiin käyttämällä MediaWikiä [[Special:Import|tuontisivun]] kautta.\n\nKirjoita sivujen nimet, jokainen nimike omalle rivilleen, alla olevaan tietolaatikkoon. Valitse, haluatko viedä sivun uusimman version sekä samalla kaikki vanhat versiot ja sivun historian tietorivit, vai haluatko viedä sivun uusimman version, jossa on tieto viimeisimmästä muokkauksesta.\n\nJälkimmäisessä tapauksessa voit myös käyttää linkkiä. Esimerkiksi sivun [[{{MediaWiki:Mainpage}}]] saa vietyä linkistä [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]].",
index 57ac1b5..b894567 100644 (file)
        "mergehistory-empty": "Aucune version ne peut être fusionnée.",
        "mergehistory-done": "$3 version{{PLURAL:$3||s}} de $1 {{PLURAL:$3|a été fusionnée|ont été fusionnées}} dans [[:$2]].",
        "mergehistory-fail": "Impossible de procéder à la fusion des historiques. Resélectionner la page ainsi que les paramètres de date.",
+       "mergehistory-fail-bad-timestamp": "L’horodatage n’est pas valide.",
+       "mergehistory-fail-invalid-source": "La page source n’est pas valide.",
+       "mergehistory-fail-invalid-dest": "La page de destination n’est pas valide.",
+       "mergehistory-fail-no-change": "La fusions d’historique n’a fusionné aucune révision. Veuillez vérifier de nouveau la page et les paramètres de temps.",
+       "mergehistory-fail-permission": "Droits insuffisants pour fusionner l’historique.",
+       "mergehistory-fail-self-merge": "Les pages source et destination sont identiques.",
+       "mergehistory-fail-timestamps-overlap": "Les révisions de la source chevauchent ou suivent les révisions de destination.",
        "mergehistory-fail-toobig": "Impossible d’effectuer la fusion de l’historique car un nombre de {{PLURAL:$1|révisions}} supérieur à la limite de $1 devrait être déplacé.",
        "mergehistory-no-source": "La page d'origine $1 n'existe pas.",
        "mergehistory-no-destination": "La page de destination $1 n'existe pas.",
        "undelete-error-long": "Des erreurs ont été rencontrées lors de la restauration du fichier :\n\n$1",
        "undelete-show-file-confirm": "Êtes-vous sûr{{GENDER:||e}} de vouloir consulter une version supprimée du fichier « <nowiki>$1</nowiki> » datant du $2 à $3 ?",
        "undelete-show-file-submit": "Oui",
-       "undelete-revision-row": "$1 $2 ($3) $4 — $5 $6 $7 $8 $9",
        "namespace": "Espace de noms :",
        "invert": "Inverser la sélection",
        "tooltip-invert": "Cochez cette case pour cacher les modifications des pages dans l'espace de noms sélectionné (et l'espace de noms associé si coché)",
index 7586ffc..1484ab3 100644 (file)
@@ -45,6 +45,7 @@
        "tog-watchlisthidebots": "Feranrangen faan bots bi a sidjen, diar ik uun't uug behual wal, fersteeg",
        "tog-watchlisthideminor": "Letj feranrangen bi a sidjen, diar ik uun't uug behual wal, fersteeg",
        "tog-watchlisthideliu": "Feranrangen faan uunmeldet brükern bi sidjen, diar ik uun't uug behual wal, fersteeg",
+       "tog-watchlistreloadautomatically": "List mä sidjen, diar dü uun't uug behual wel, nei loose, wan en filter anert wurden as (brükt JavaScript)",
        "tog-watchlisthideanons": "Feranrangen faan anonüüm brükern (IPs) bi sidjen, diar ik uun't uug behual wal, fersteeg",
        "tog-watchlisthidepatrolled": "Kontroliaret feranrangen bi a sidjen, diar ik uun't uug behual wal, fersteeg",
        "tog-watchlisthidecategorization": "Kategorisiarang faan sidjen fersteeg",
        "october-date": "$1. Oktuuber",
        "november-date": "$1. Nofember",
        "december-date": "$1. Detsember",
+       "period-am": "AM (iarmade)",
+       "period-pm": "PM (eftermade)",
        "pagecategories": "{{PLURAL:$1|Kategorii|Kategoriin}}",
        "category_header": "Sidjen uun kategorii \"$1\"",
        "subcategories": "Onerkategoriin",
        "databaseerror-query": "Uunfraag: $1",
        "databaseerror-function": "Funktjuun: $1",
        "databaseerror-error": "Feeler: $1",
+       "transaction-duration-limit-exceeded": "Transaktjuun ütj technisk grünjer ufbreegen. Det skriiwdüür ($1) wiar linger üs $2 {{PLURAL:$2|sekund|sekunden}}.\nWan dü flook objekten anerst, dial det ap tu letjer aktjuunen.",
        "laggedslavemode": "'''Paase üüb:''' Ferlicht wiset detdiar sidj ei a leetst stant.",
        "readonly": "Dootenbeenk speret.",
        "enterlockreason": "Wees so gud an du en grünj uun, huaram det dootenbeenk speret wurd skal, an hü loong det (amanbi) speret wurd skal.",
-       "readonlytext": "Det dootenbeenk as iarst ans speret för nei iindracher an feranrangen, woorskiinelk, auer diar jüst apredet woort. Ferschük det leeder man noch ans.\n\nGrünj för't sperin: $1",
+       "readonlytext": "Det dootenbeenk as iarst ans speret för nei iindracher an feranrangen. Ferschük det leeder man noch ans.\n\nGrünj för't sperin: $1",
        "missing-article": "Di tekst för „$1“ $2 as ei fünjen wurden uun't dootenbeenk.\n\nFerlicht as det sidj stregen of fersköwen wurden.\n\nWan't det ei as, do heest dü ferlicht en feeler uun't software fünjen. Wees so gud an skriiw det tu en [[Special:ListUsers/sysop|administraator]] an fertel ham, am hün URL det gongt.",
        "missingarticle-rev": "(Werjuunsnumer: $1)",
        "missingarticle-diff": "(Ferskeel tesken $1 an $2)",
        "readonly_lag": "Det dootenbeenk as speret wurden, amdat jo ferdiald dootenbeenken (slaves) jo mä di hoodserver (master) ufglik kön.",
+       "nonwrite-api-promise-error": "Di HTTP-header „Promise-Non-Write-API-Action“ as schüürd wurden, man det uunfraag ging tu en API-skriiwmodul.",
        "internalerror": "Süsteemfeeler",
        "internalerror_info": "Süsteemfeeler: $1",
        "internalerror-fatal-exception": "Böös ütjnoomfeeler faan di slach \"$1\"",
        "viewsource": "Kweltekst uunluke",
        "viewsource-title": "Code faan sidj $1 uunluke",
        "actionthrottled": "Taal faan aktjuunen limitiaret",
-       "actionthrottledtext": "Dü heest detdiar aktjuun tufölsis uun en kurten tidjrüm ütjfeerd.\nWees so gud an ferschük det glik noch ans weder.",
+       "actionthrottledtext": "Dü heest detdiar aktjuun tufölsis uun en kurten tidjrüm ütjfeerd. Am masbrük ütjtuslütjen, as din aktjuun ufbreegen wurden.\nWees so gud an ferschük det glik noch ans weder.",
        "protectedpagetext": "Detdiar sidj as seekert wurden, am dat diar näämen wat feranert.",
        "viewsourcetext": "Dü könst di kweltekst faan detdiar sidj uunluke an ham uk kopiare.",
        "viewyourtext": "Dü könst di code faan <strong>din feranrang</strong> faan detdiar sidj uunluke an kopiare.",
        "mypreferencesprotected": "Dü heest ei det brükerrocht, am din iinstelangen tu feranrin.",
        "ns-specialprotected": "Spezial-sidjen kön ei bewerket wurd.",
        "titleprotected": "En sidj mä didiar nööm koon ei uunlaanj wurd.\nDi brüker [[User:$1|$1]] hää det sidj speret, an di grünj as: \"''$2''\".",
-       "filereadonlyerror": "Det datei „$1“ koon ei feranert wurd, auer uun det fertiaknis „$2“ bluas leesen wurd koon.\nDi grünj faan di administraator as: „$3“.",
+       "filereadonlyerror": "Det datei „$1“ koon ei feranert wurd, auer uun det fertiaknis „$2“ bluas leesen wurd koon.\nDi grünj faan di süsteem-administraator as: „$3“.",
        "invalidtitle-knownnamespace": "Ferkiard auerskraft uun di nöömrüm „$2“ an tekst „$3“",
        "invalidtitle-unknownnamespace": "Ferkiard auerskraft uun di ünbekäänd nöömrüm „$1“ an tekst „$2“",
        "exception-nologin": "Ei uunmeldet",
        "createacct-reason": "Grünj",
        "createacct-reason-ph": "Huaram dü en ööder brükerkonto iinrachtst",
        "createacct-submit": "Din brükerkonto iinracht",
-       "createacct-another-submit": "En ööder brükerkonto iinracht",
+       "createacct-another-submit": "Brükerkonto iinracht",
        "createacct-benefit-heading": "{{SITENAME}} woort faan lidj üs di maaget.",
        "createacct-benefit-body1": "{{PLURAL:$1|feranrang|feranrangen}}",
        "createacct-benefit-body2": "{{PLURAL:$1|sidj|sidjen}}",
        "wrongpasswordempty": "Dü heest nian paaswurd iinden.\nFerschük det man noch ans.",
        "passwordtooshort": "Paaswurden skel tumanst {{PLURAL:$1|1 tiaken|$1 tiakens}} lung wees.",
        "passwordtoolong": "Paaswurden kön ei linger üs {{PLURAL:$1|1 tiaken|$1 tiakens}} wees.",
+       "passwordtoopopular": "Flooksis brükt paaswurden san ei tuläät. Wees so gud an schük en ööder, muar aparte paaswurd ütj.",
        "password-name-match": "Dü könst dan brükernööm ei üs paaswurd nem.",
        "password-login-forbidden": "Didiar brükernööm mä detdiar paaswurd as ei tuläät.",
        "mailmypassword": "Paaswurd turagsaat",
        "resetpass_submit": "Paaswurd saat an uunmelde",
        "changepassword-success": "Din paaswurd as feranert wurden!",
        "changepassword-throttled": "Dü heest tufölsis fersoocht, di uuntumeldin.\nWees so gud an teew $1, iar dü det noch ans ferschükst.",
+       "botpasswords": "Bot-paaswurden",
+       "botpasswords-summary": "Mä <em>bot-paaswurden</em> koon üüb en brükerkonto auer't API tugreben wurd. A brükerrochten bi uunmeldang mä en bot-paaswurd san kört.\n\nWan dü ei witjst, huaram dü det du wel, skulst dü det uk ei du. Näämen fraaget di, en paaswurd iinturachten, an det do widjertudun.",
+       "botpasswords-no-central-id": "Am bot-paaswurden tu brüken, skel dü bi en sentralisiaret brükerkonto uunmeldet wees.",
+       "botpasswords-existing": "Bot-paaswurden",
+       "botpasswords-createnew": "En nei bot-paaswurd maage",
+       "botpasswords-editexisting": "En bot-paaswurd feranre",
+       "botpasswords-label-appid": "Bot-nööm:",
+       "botpasswords-label-create": "Maage",
+       "botpasswords-label-update": "Aktualisiare",
+       "botpasswords-label-cancel": "Ufbreeg",
+       "botpasswords-label-delete": "Strik",
+       "botpasswords-label-resetpassword": "Paaswurd turagsaat",
+       "botpasswords-label-grants": "Mögelk brükerrochten:",
+       "botpasswords-help-grants": "Mä arke brükerrocht könst dü üüb ööder brükerrochten faan en brükerkonto tugrip. Luke iin uun det [[Special:ListGrants|tabel]] am muar informatjuun.",
+       "botpasswords-label-restrictions": "Kört brükerrochten:",
+       "botpasswords-label-grants-column": "Tugestenen",
+       "botpasswords-bad-appid": "Di bot-nööm \"$1\" gongt ei.",
+       "botpasswords-insert-failed": "Di bot-nööm \"$1\" küd ei apnimen wurd. Ferlicht as hi al diar?",
+       "botpasswords-update-failed": "Di bot-nööm \"$1\" küd ei apnimen wurd. As hi stregen wurden?",
+       "botpasswords-created-title": "Bot-paaswurd as iinracht wurden.",
+       "botpasswords-created-body": "Det bot-paaswurd \"$1\" as iinracht wurden an uun funktjuun.",
+       "botpasswords-updated-title": "Bot-paaswurd as aktualisiaret wurden.",
+       "botpasswords-updated-body": "Det bot-paaswurd \"$1\" as aktualisiaret wurden an uun funktjuun.",
+       "botpasswords-deleted-title": "Bot-paaswurd as stregen wurden.",
+       "botpasswords-deleted-body": "Det bot-paaswurd \"$1\" as stregen wurden.",
        "resetpass_forbidden": "Det paaswurd koon ei feranert wurd.",
        "resetpass-no-info": "Dü skel di uunmelde, am üüb det sidj tutugripen.",
        "resetpass-submit-loggedin": "Paaswurd feranre",
        "right-blockemail": "Brüker spere för't e-mail schüüren",
        "right-hideuser": "Brükernööm spere an fersteeg",
        "right-ipblock-exempt": "Ütjnoom faan IP-speren, automaatisk speren an range-speren",
-       "right-proxyunbannable": "Ütjnoom faan automaatisk proxy-speren",
        "right-unblockself": "Sper apheew för ään salew",
        "right-protect": "Sidjenseekerhaid feranre an kaskaaden-seekert sidjen bewerke",
        "right-editprotected": "Sidjen bewerke, diar mä „{{int:protect-level-sysop}}“ seekert san.",
        "watchthisupload": "Luke efter detdiar datei",
        "filewasdeleted": "En datei mä didiar nööm as al ans huuchschüürd an leederhen weder stregen wurden. Luke iarst ans iin uun $1, iar dü det datei würelk seekerst.",
        "filename-bad-prefix": "Di dateinööm begant mä '''„$1“'''. Sok nöömer kem miast faan digitaalkameras an sai ei föl ütj.\nNem en beedern nööm för det datei.",
-       "upload-success-subj": "Det huuchschüüren hää loket.",
-       "upload-success-msg": "Det huuchschüüren faan [$2] hää loket an stäänt nü diar: [[:{{ns:file}}:$1]]",
-       "upload-failure-subj": "Bi't huuchschüüren as wat skiaf gingen.",
-       "upload-failure-msg": "Diar as wat skiaf gingen bi't huuchschüüren faan [$2]:\n\n$1",
-       "upload-warning-subj": "Wäärnang",
-       "upload-warning-msg": "Diar as wat skiaf gingen bi't huuchschüüren faan [$2]. Gung turag tu't  [[Special:Upload/stash/$1|sidj för't huuchschüüren]], am det üüb a rä tu fun.",
        "upload-proto-error": "Ferkiard protokol",
        "upload-proto-error-text": "Det URL skal mä <code>http://</code> of <code>ftp://</code> began.",
        "upload-file-error": "Diar as wat skiaf gingen",
        "querypage-disabled": "Detdiar spezial-sidj as ei aktiif, am det süsteem ei tu auerläästin.",
        "apihelp": "Halep för API",
        "apihelp-no-such-module": "Moduul \"$1\" ei fünjen.",
+       "apisandbox": "API spelplaats",
        "booksources": "Schük efter ISBN-numer",
        "booksources-search-legend": "Schük efter bukloodens",
        "booksources-search": "Schük",
        "wlheader-showupdated": "Nei feranert sidjen wurd '''fäät''' uunwiset.",
        "wlnote": "Diar {{PLURAL:$1|stäänt det leetst feranrang|stun a leetst <strong>$1</strong> feranrangen}} faan a leetst {{PLURAL:$2|stünj|<strong>$2</strong> stünjen}}. Stant: $3, klook $4.",
        "wlshowlast": "Wise a feranrangen faan a leetst $1 stünjen, $2 daar.",
-       "watchlistall2": "aaltumaal",
        "watchlist-options": "Iinstelangen för't uunwisin",
        "watching": "Uun't uug behual ...",
        "unwatching": "Ei uun't uug behual ...",
        "special-characters-title-minus": "minus tiaken",
        "mw-widgets-dateinput-no-date": "Nian dootem ütjsoocht",
        "mw-widgets-titleinput-description-new-page": "sidj jaft at noch ei",
-       "mw-widgets-titleinput-description-redirect": "widjerfeerang tu $1"
+       "mw-widgets-titleinput-description-redirect": "widjerfeerang tu $1",
+       "randomrootpage": "Tufelag stamsidj"
 }
index 60c49d0..95d5c73 100644 (file)
@@ -40,7 +40,7 @@
        "tog-hideminor": "הסתרת שינויים משניים ברשימת השינויים האחרונים",
        "tog-hidepatrolled": "הסתרת שינויים בדוקים ברשימת השינויים האחרונים",
        "tog-newpageshidepatrolled": "הסתרת דפים בדוקים ברשימת הדפים החדשים",
-       "tog-hidecategorization": "×\94סתרת ×\94×\95ספ×\95ת ×\95×\94סר×\95ת ×©×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+       "tog-hidecategorization": "×\94סתרת ×¡×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
        "tog-extendwatchlist": "הרחבת רשימת המעקב כך שתציג את כל השינויים, לא רק את השינויים האחרונים בכל דף",
        "tog-usenewrc": "קיבוץ השינויים לפי דף בשינויים האחרונים וברשימת המעקב",
        "tog-numberheadings": "מספור כותרות אוטומטי",
@@ -71,7 +71,7 @@
        "tog-watchlistreloadautomatically": "רענון אוטומטי של רשימת המעקב בכל פעם שמסנן משתנה (נדרש JavaScript)",
        "tog-watchlisthideanons": "הסתרת עריכות של משתמשים אנונימיים ברשימת המעקב",
        "tog-watchlisthidepatrolled": "הסתרת עריכות בדוקות ברשימת המעקב",
-       "tog-watchlisthidecategorization": "×\94סתרת ×\94×\95ספ×\95ת ×\95×\94סר×\95ת ×©×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+       "tog-watchlisthidecategorization": "×\94סתרת ×¡×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
        "tog-ccmeonemails": "לשלוח אליי העתקים של הודעות דואר אלקטרוני ששלחתי למשתמשים אחרים",
        "tog-diffonly": "ביטול הצגת תוכן הדף מתחת להשוואות הגרסאות",
        "tog-showhiddencats": "הצגת קטגוריות מוסתרות",
        "mergehistory-empty": "אין גרסאות למיזוג.",
        "mergehistory-done": "{{PLURAL:$3|גרסה אחת|$3 גרסאות}} של $1 {{PLURAL:$3|מוזגה|מוזגו}} בהצלחה לתוך [[:$2]].",
        "mergehistory-fail": "לא ניתן לבצע את מיזוג הגרסאות, יש לבדוק שנית את הגדרות הדף והזמן.",
+       "mergehistory-fail-bad-timestamp": "חותם־הזמן אינו תקין.",
+       "mergehistory-fail-invalid-source": "דף המקור אינו תקין.",
+       "mergehistory-fail-invalid-dest": "דף היעד אינו תקין.",
+       "mergehistory-fail-no-change": "מיזוג ההיסטוריה לא מיזג שום גרסה. נא לבדוק מחדש את הדף ואת פרמטרי הזמן.",
+       "mergehistory-fail-permission": "הרשאות לא מספיקות למיזוג היסטוריה.",
+       "mergehistory-fail-self-merge": "דף המקור זהה לדף היעד.",
+       "mergehistory-fail-timestamps-overlap": "גרסאות המקור חופפות או מגיעות אחרי גרסאות היעד.",
        "mergehistory-fail-toobig": "לא ניתן לבצע את מיזוג הגרסאות כיוון שצריך להעביר יותר גרסאות מהמגבלה, שהיא {{PLURAL:$1|גרסה אחת|‏‏֫$1 גרסאות}}.",
        "mergehistory-no-source": "דף המקור $1 אינו קיים.",
        "mergehistory-no-destination": "דף היעד $1 אינו קיים.",
        "wlshowhideanons": "משתמשים אנונימיים",
        "wlshowhidepatr": "עריכות בדוקות",
        "wlshowhidemine": "עריכות שלי",
-       "wlshowhidecategorization": "×\94×\95ספ×\95ת ×\95×\94סר×\95ת ×©×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+       "wlshowhidecategorization": "ס×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
        "watchlist-options": "אפשרויות ברשימת המעקב",
        "watching": "בהוספה לרשימת המעקב…",
        "unwatching": "בהסרה מרשימת המעקב…",
index bf1c630..56f8168 100644 (file)
        "logentry-newusers-create2": "$1 {{GENDER:$2|stofnaði}} notandaaðganginn $3",
        "logentry-newusers-byemail": "Notandaaðgangurinn $3 var {{GENDER:$2|búinn til}} af $1 og lykilorðið var sent með tölvupósti",
        "logentry-newusers-autocreate": "Aðgangurinn $1 var {{GENDER:$2|stofnaður}} sjálfvirkt",
+       "logentry-protect-protect": "$1 {{GENDER:$2|verndaði}} $3 $4",
        "logentry-rights-rights": "$1 {{GENDER:$2|breytti}} réttindum $3 frá $4 í $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|breytti}} réttindum $3",
        "logentry-rights-autopromote": "$1 fékk sjálfvirkt {{GENDER:$2|aukin}} réttindi frá $4 til $5",
index b10b201..47bc5cd 100644 (file)
        "recentchangeslinked-summary": "これは指定したページからリンクされている (または指定したカテゴリに含まれている) ページの最近の変更の一覧です。\n[[Special:Watchlist|自分のウォッチリスト]]にあるページは<strong>太字</strong>で表示されます。",
        "recentchangeslinked-page": "ページ名:",
        "recentchangeslinked-to": "このページへのリンク元での変更の表示に切り替え",
-       "recentchanges-page-added-to-category": "[[:$1]] をカテゴリに追加",
+       "recentchanges-page-added-to-category": "[[:$1]]をカテゴリに追加",
        "recentchanges-page-added-to-category-bundled": "[[:$1]]と他{{PLURAL:$2|1ページ|$2ページ}}をカテゴリに追加",
-       "recentchanges-page-removed-from-category": "[[:$1]] をカテゴリから除外",
+       "recentchanges-page-removed-from-category": "[[:$1]]をカテゴリから除外",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]]と他{{PLURAL:$2|1ページ|$2ページ}}をカテゴリから除外",
        "autochange-username": "メディアウィキ自動変更",
        "upload": "ファイルをアップロード",
index f645a28..092734f 100644 (file)
        "preview": "Pêşdîtin",
        "showpreview": "Pêşdîtinê nîşan bide",
        "showdiff": "Guherandinan nîşan bide",
-       "anoneditwarning": "<strong>Hişyarî:<strong> Tu netêketî yî! Navnîşana IP'ya te wê di dîroka guherandina vê rûpelê de bê tomarkirin. Heke tu <strong>[$1 têkevî]</strong>, li gel sûdên te yên din guhertinên ku tu bikî jî wê ji nasnavê te re bê atfkirin.",
+       "anoneditwarning": "<strong>Hişyarî:<strong> Tu netêketî yî! Navnîşana IP'ya te wê di dîroka guherandina vê rûpelê de bê tomarkirin. Heke tu <strong>[$1 têkevî]</strong> an jî  <strong>[$2 hesabekî çêbikî]</strong>, li gel sûdên te yên din guhertinên ku tu bikî jî wê ji nasnavê te re bê atfkirin.",
        "anonpreviewwarning": "''Tu ne têketî yî. Tomarkirin wê navnîşana IP'ya te di dîroka guhertinan de nîşan bide.''",
        "missingsummary": "<span style=\"color:#990000;\">'''Zanibe:'''</span> Te nivîsekî kurt ji bo guherandinê ra nenivîsand. Eger tu niha carekî din li Tomar xê, guherandinê te vê nivîsekî kurt yê were tomarkirin.",
        "missingcommenttext": "Ji kerema xwe kurteya naverokê li jêr binivisîne.",
        "boteditletter": "b",
        "number_of_watching_users_pageview": "[{{PLURAL:$1|bikarhênerek|$1 bikarhêner}} vê rûpelê {{PLURAL:$1|dişopîne|dişopînin}}.]",
        "rc_categories_any": "Qet",
+       "rc-change-size-new": "Piştî guhertinê $1 {{PLURAL:$1|bayt}}",
        "newsectionsummary": "/* $1 */ beşeke nû",
        "rc-enhanced-expand": "Hûragahiyan nîşan bide",
        "rc-enhanced-hide": "Kitûmatan veşêre",
        "import-upload": "Daneyên XMLê bar bike",
        "importlogpage": "Têketina tevlîkirinê",
        "javascripttest": "JavaScript tê testkirin",
-       "tooltip-pt-userpage": "Rûpela min",
+       "tooltip-pt-userpage": "Rûpela {{GENDER:|Te}}",
        "tooltip-pt-anonuserpage": "Rûpela bikarhênerê ji bo navnîşana ÎP ku tu sererast dikî wekî",
-       "tooltip-pt-mytalk": "Gotûbêja min",
-       "tooltip-pt-preferences": "Hevyazên min",
+       "tooltip-pt-mytalk": "Gotûbêja {{GENDER:|Te}}",
+       "tooltip-pt-preferences": "Tercîhên {{GENDER:|te}}",
        "tooltip-pt-watchlist": "The list of pages you",
-       "tooltip-pt-mycontris": "Lîsteya beşdariyên min",
+       "tooltip-pt-mycontris": "Lîsteyekê beşdariyên {{GENDER:|te}}",
+       "tooltip-pt-login": "Têketina we tê teşwîqkirin; lêbelê, en ne elzem e",
        "tooltip-pt-logout": "Derkeve",
        "tooltip-ca-talk": "Gotûbêj li ser rûpela naverokê",
        "tooltip-ca-edit": "Vê rûpelê biguherîne",
        "tooltip-t-recentchangeslinked": "Recent changes in pages linking to this page",
        "tooltip-feed-rss": "RSS feed'ên ji bo rûpelê",
        "tooltip-feed-atom": "Atom feed'ên ji bo vê rûpelê",
-       "tooltip-t-contributions": "Lîsteya beşdariyên bikarhêner bibîne",
+       "tooltip-t-contributions": "Lîsteyekî beşdariyên {{GENDER:$1|vê bikarhênerê}} bibîne",
        "tooltip-t-emailuser": "Jê re name bişîne",
        "tooltip-t-info": "Bêhtir agahî di derbarê vê rûpelê de",
        "tooltip-t-upload": "Dosyeyan bar bike",
index aabcdc1..5bb32c1 100644 (file)
        "otherlanguages": "In aliis linguis",
        "redirectedfrom": "(Redirectum de $1)",
        "redirectpagesub": "Pagina redirectionis",
-       "lastmodifiedat": "Novissima mutatio die $2 hora $1 facta.",
+       "lastmodifiedat": "Novissima mutatio die $1 hora $2 facta.",
        "viewcount": "Haec pagina iam vista est {{PLURAL:$1|semel|$1 vices}}.",
        "protectedpage": "Pagina protecta",
        "jumpto": "Salire ad:",
index 416eafc..af139f6 100644 (file)
        "apisandbox-unfullscreen": "Säit weisen",
        "apisandbox-submit": "Ufro maachen",
        "apisandbox-reset": "Eidel maachen",
+       "apisandbox-retry": "Nach eng Kéier probéieren",
+       "apisandbox-helpurls": "Hëllef-Linken",
        "apisandbox-examples": "Beispiller",
        "apisandbox-dynamic-parameters": "Zousätzlech Parameteren",
        "apisandbox-dynamic-parameters-add-label": "Parameter derbäisetzen:",
        "movenosubpage": "Dës Säit huet keng Ënnersäiten.",
        "movereason": "Grond:",
        "revertmove": "zréck réckelen",
-       "delete_and_move_text": "== Läsche vun der Destinatiounssäit néideg ==\nD'Säit \"[[:$1]]\" existéiert schonn. \nWëll Dir se läsche fir d'Réckelen ze erméiglechen?",
+       "delete_and_move_text": "D'Zilsäit \"[[:$1]]\" gëtt et schonn. \nWëll Dir se läsche fir d'Réckelen ze erméiglechen?",
        "delete_and_move_confirm": "Jo, läsch d'Säit",
        "delete_and_move_reason": "Geläscht fir Plaz ze maache fir \"[[$1]]\" heihin ze réckelen",
        "selfmove": "Source- an Destinatiounsnumm sinn dselwecht; eng Säit kann net op sech selwer geréckelt ginn.",
index 8c2b0ef..2a4a566 100644 (file)
        "searchprofile-images-tooltip": "Søk etter filer",
        "searchprofile-everything-tooltip": "Søk i alt innhold (inkldert diskusjonssider)",
        "searchprofile-advanced-tooltip": "Søk i visse navnerom",
-       "search-result-size": "$1 ({{PLURAL:$2|ett|$2}} ord)",
+       "search-result-size": "$1 ({{PLURAL:$2|ett ord|$2 ord}})",
        "search-result-category-size": "{{PLURAL:$1|1 medlem|$1 medlemmer}} ({{PLURAL:$2|1 underkategori|$2 underkategorier}}, {{PLURAL:$3|1 fil|$3 filer}})",
        "search-redirect": "(omdirigering $1)",
        "search-section": "(avsnitt $1)",
        "sp-contributions-toponly": "Vis kun endringer som er gjeldende revisjoner",
        "sp-contributions-newonly": "Bare vis bidrag som er sideopprettinger",
        "sp-contributions-submit": "Søk",
-       "whatlinkshere": "Lenker hit",
+       "whatlinkshere": "Hva lenker hit",
        "whatlinkshere-title": "Sider som lenker til «$1»",
        "whatlinkshere-page": "Side:",
        "linkshere": "Følgende sider lenker til '''[[:$1]]''':",
        "tooltip-search-fulltext": "Søk etter sider som innholder denne teksten",
        "tooltip-p-logo": "Gå til hovedsiden",
        "tooltip-n-mainpage": "Gå til hovedsiden",
-       "tooltip-n-mainpage-description": "Gå til hovedsiden",
+       "tooltip-n-mainpage-description": "Besøk hovedsiden",
        "tooltip-n-portal": "Om prosjektet, hva du kan gjøre, hvor du kan finne ting",
        "tooltip-n-currentevents": "Finn bakgrunnsinformasjon om aktuelle hendelser",
        "tooltip-n-recentchanges": "Liste over siste endringer på wikien.",
        "metadata-help": "Denne filen inneholder tilleggsinformasjon, antagligvis lagt til av digitalkameraet eller skanneren brukt til å lage eller digitalisere det.\nHvis filen har blitt forandret fra utgangspunktet, kan enkelte detaljer være unøyaktige.",
        "metadata-expand": "Vis utvidede detaljer",
        "metadata-collapse": "Skjul utvidede detaljer",
-       "metadata-fields": "Bildemetadatafelt listet i denne meldingen inkluderes på bildesiden når metadatatabellen er slått sammen.\nAndre vil skjules som standard.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "Bildemetadatafelt listet i denne meldingen inkluderes på bildesiden når metadatatabellen har kollapset.\nAndre vil skjules som standard.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-imagewidth": "Bredde",
        "exif-imagelength": "Høyde",
        "exif-bitspersample": "Bits per komponent",
index 6152a32..e4f75f9 100644 (file)
        "apisandbox-intro": "Gebruik deze pagina om te experimenteren met de '''MediaWiki-API'''.\nZie de [//www.mediawiki.org/wiki/API:Main_page API-documentatie] voor verdere details over het gebruik van de API. Voorbeeld: [//www.mediawiki.org/wiki/API#A_simple_example hoe de inhoud van een Hoofdpagina ophalen]. Selecteer een handeling om meer voorbeelden te zien.\n\nHoewel dit een testfunctie is, kunnen sommige handelingen toch wijzigingen in de wiki maken.",
        "apisandbox-submit": "Verzoek uitvoeren",
        "apisandbox-reset": "Wissen",
-       "apisandbox-examples": "Voorbeeld",
+       "apisandbox-retry": "Opnieuw proberen",
+       "apisandbox-examples": "Voorbeelden",
        "apisandbox-results": "Resultaten",
        "apisandbox-request-url-label": "Verzoek-URL:",
        "apisandbox-request-time": "Doorlooptijd verzoek: $1",
index 90330a5..dc2e686 100644 (file)
        "upload-too-many-redirects": "URL-en inneheldt for mange omdirigeringar",
        "upload-http-error": "Ein HTTP-feil oppstod: $1",
        "upload-copy-upload-invalid-domain": "Kopiopplastingar er ikkje tilgjengelege frå dette domenet.",
+       "upload-form-label-select-file": "Vel fil",
        "backend-fail-stream": "Kunne ikkje strøyma fila «$1».",
        "backend-fail-backup": "Kunne ikkje tryggingskopiera fila «$1».",
        "backend-fail-notexists": "Fila $1 finst ikkje.",
        "logempty": "Ingen element i loggen passar.",
        "log-title-wildcard": "Søk i titlar som byrjar med denne teksten",
        "showhideselectedlogentries": "Vis/gøym valde loggoppføringar",
+       "checkbox-all": "Alle",
+       "checkbox-none": "Ingen",
+       "checkbox-invert": "Vreng",
        "allpages": "Alle sider",
        "nextpage": "Neste side ($1)",
        "prevpage": "Førre sida ($1)",
        "patrol-log-page": "Patruljeringslogg",
        "patrol-log-header": "Dette er ein logg over patruljerte sideversjonar.",
        "log-show-hide-patrol": "$1 patruljeringslogg",
+       "log-show-hide-tag": "$1 merkelogg",
        "deletedrevision": "Slett gammal versjon $1",
        "filedeleteerror-short": "Feil ved sletting av fila: $1",
        "filedeleteerror-long": "Det vart ein feil under filslettinga av:\n\n$1",
        "tags-create-reason": "Årsak:",
        "tags-create-submit": "Opprett",
        "tags-create-no-name": "Du må oppgje eit merkenamn.",
+       "tags-edit-existing-tags-none": "«Ingen»",
+       "tags-edit-chosen-placeholder": "Vel nokre merke",
        "comparepages": "Samanlikna sider",
        "compare-page1": "Side 1",
        "compare-page2": "Side 2",
index 74b16e5..32f5b49 100644 (file)
        "apisandbox-submit": "Fazer o pedido",
        "apisandbox-reset": "Limpar",
        "apisandbox-examples": "Exemplos",
-       "apisandbox-results": "Resultado",
+       "apisandbox-results": "Resultados",
        "apisandbox-request-url-label": "URL do pedido:",
        "apisandbox-request-time": "Tempo de processamento: $1",
        "booksources": "Fontes bibliográficas",
index fec3079..7cd96cc 100644 (file)
        "previewnote": "Note displayed when clicking on Show preview",
        "continue-editing": "{{doc-actionlink}}\nA link to the beginning of the editing textarea on the same page.\n\nDisplayed after {{msg-mw|previewnote}}.",
        "previewconflict": "Used in Preview page.",
-       "session_fail_preview": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
-       "session_fail_preview_html": "Used as error message in Preview page.",
+       "session_fail_preview": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}\n\n{{Identical|Loss of session data}}",
+       "session_fail_preview_html": "Used as error message in Preview page.\n\n{{Identical|Loss of session data}}",
        "token_suffix_mismatch": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
        "edit_form_incomplete": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
        "editing": "Shown as page title when editing a page. Parameters:\n* $1 - the name of the page that is being edited. e.g. \"Editing Main Page\"\n{{Related|Editing}}\n{{Identical|Editing}}",
        "mergehistory-empty": "Used in [[Special:MergeHistory]].",
        "mergehistory-done": "Success message shown on [[Special:MergeHistory]].\n* $1 - link to target page\n* $2 - destination page title\n* $3 - number of revisions which succeeded to merge",
        "mergehistory-fail": "Used as error message in [[Special:MergeHistory]].",
-       "mergehistory-fail-toobig": "Used as error message in [[Special:MergeHistory]].\n* $1 - maximum allowed number of revisions that can be moved",
+       "mergehistory-fail-bad-timestamp": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-invalid-source": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-invalid-dest": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-no-change": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-permission": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-self-merge": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-timestamps-overlap": "Used as error message in [[Special:MergeHistory]] API.",
+       "mergehistory-fail-toobig": "Used as error message in [[Special:MergeHistory]] and API.\n* $1 - maximum allowed number of revisions that can be moved",
+       "mergehistory-warning-redirect-not-created": "Used as warning message in [[Special:MergeHistory]] API.",
        "mergehistory-no-source": "Used as error message in [[Special:MergeHistory]].\n* $1 - source page title\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
        "mergehistory-no-destination": "Used as error message in [[Special:MergeHistory]].\n* $1 - destination page title\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
        "mergehistory-invalid-source": "Used as error message in [[Special:MergeHistory]].\n\nSee also:\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
        "mergehistory-same-destination": "Error message shown on [[Special:MergeHistory]] when the user entered the same page title to both source and destination\n\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}",
        "mergehistory-reason": "{{Identical|Reason}}",
        "mergehistory-revisionrow": "{{Optional}}\nA revision row in the merge history page. Parameters:\n* $1 - a radio button to indicate a merge point\n* $2 - a link to the last revision of a page ({{msg-mw|Last}})\n* $3 - a page link\n* $4 - a user link\n* $5 - a revision size\n* $6 - a revision comment",
+       "mergehistory-redirect-text": "{{ignored}}The text that's added to a redirected page when that redirect is created as part of a history merge.",
        "mergelog": "{{doc-logpage}}\n\nThis is the name of a log of merge actions done on [[Special:MergeHistory]]. This special page and this log is not enabled by default.",
        "pagemerge-logentry": "{{ignored}}This is a ''logentry'' message only used on IRC.\n\nParameters:\n* $1 - the page name of the source of the content to be merged\n* $2 - the page into which the content is merged\n* $3 - a timestamp of limit\n\nThe log and its associated special page 'MergeHistory' is not enabled by default.\n\nPlease note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.",
        "revertmerge": "Used as link text",
        "undelete-error-long": "Used as error message. Parameters:\n* $1 - ...\nSee also:\n* {{msg-mw|Undelete-error-short}}",
        "undelete-show-file-confirm": "A confirmation message shown on [[Special:Undelete]] when the request does not contain a valid token (e.g. when a user clicks a link received in mail).\n\nParameters:\n* $1 - the name of the file being undeleted\n* $2 - the date of the displayed revision\n* $3 - the time of the displayed revision\n{{Identical|Are you sure you want to view the deleted revision of the file...}}",
        "undelete-show-file-submit": "{{Identical|Yes}}",
-       "undelete-revision-row": "{{Optional}}\nA revision row in the undelete page. Parameters:\n* $1 is a checkBox to indicate whether to restore this specific revision\n* $2 is a link to the revision\n* $3 is a link to the last revision of a page ({{msg-mw|last}})\n* $4 is a link to the page\n* $5 is a link to the revision's user\n* $6 is the revision's minor edit identifier\n* $7 is the revision size\n* $8 is the revision comment\n* $9 is the revision's tags",
+       "undelete-revision-row2": "{{Optional}}\nA revision row in the undelete page. Parameters:\n* $1 is a checkBox to indicate whether to restore this specific revision\n* $2 is a link to the last revision of a page ({{msg-mw|last}})\n* $3 is a link to the page\n* $4 is a link to the revision's user\n* $5 is the revision's minor edit identifier\n* $6 is the revision size\n* $7 is the revision comment\n* $8 is the revision's tags",
        "namespace": "This message is located at [[Special:Contributions]].\n{{Identical|Namespace}}",
        "invert": "Displayed in [[Special:RecentChanges|RecentChanges]], [[Special:RecentChangesLinked|RecentChangesLinked]] and [[Special:Watchlist|Watchlist]].\n\nThis message means \"Invert selection of namespace\".\n\nThis message has a tooltip {{msg-mw|tooltip-invert}}\n{{Identical|Invert selection}}",
        "tooltip-invert": "Used in [[Special:Recentchanges]] as a tooltip for the invert checkbox. See also the message {{msg-mw|invert}}",
        "import-nonewrevisions": "Used in [[Special:Import]].",
        "xml-error-string": "Parameters:\n* $1 - Some kind of message, perhaps name of the error?\n* $2 - line number\n* $3 - column number\n* $4 - ?? $this->mByte . $this->mContext\n* $5 - error description\nExample:\n* Import failed: XML import parse failure at line 1, col 1 (byte 3; \"- <mediawiki xml\"): Empty document",
        "import-upload": "Used on [[Special:Import]].\n\nRelated messages:\n* {{msg-mw|right-importupload}} (the user right for this)",
-       "import-token-mismatch": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}",
+       "import-token-mismatch": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}\n\n{{Identical|Loss of session data}}",
        "import-invalid-interwiki": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}",
        "import-error-edit": "Import error message displayed when importing user has no edit rights for a page.\n\nParameters:\n* $1 - a page name\n{{Related|Import-error}}",
        "import-error-create": "Import error message displayed when importing user has no create rights for a page.\n\nParameters:\n* $1 - a page name\n{{Related|Import-error}}",
        "expand_templates_generate_xml": "Used as checkbox label.",
        "expand_templates_generate_rawhtml": "Used as checkbox label.",
        "expand_templates_preview": "{{Identical|Preview}}",
-       "expand_templates_preview_fail_html": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.",
+       "expand_templates_preview_fail_html": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.\n\n{{Identical|Loss of session data}}",
        "expand_templates_preview_fail_html_anon": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.",
        "expand_templates_input_missing": "Used on [[Special:ExpandTemplates]] as an error message, if no input text was provided.",
        "pagelanguage": "Title for page Special:PageLanguage",
index 8c05fca..df3c252 100644 (file)
        "thu": "чет-čet",
        "fri": "pet-пет",
        "sat": "sub-суб",
-       "january": "januar-сијечањ",
-       "february": "februar-вељача",
-       "march": "mart-ожујак",
-       "april": "april-травањ",
-       "may_long": "maj-свибањ",
-       "june": "jun-липањ",
-       "july": "jul-српањ",
-       "august": "avgust-коловоз",
-       "september": "septembar-рујан",
-       "october": "oktobar-листопад",
-       "november": "студени-novembar",
-       "december": "decembar-просинац",
-       "january-gen": "januara-сијечња",
-       "february-gen": "februara-вељаче",
-       "march-gen": "marta-ожујка",
-       "april-gen": "aprila-травња",
-       "may-gen": "маја-свибња",
-       "june-gen": "junа-липња",
-       "july-gen": "jula-српња",
-       "august-gen": "augusta-коловоза",
-       "september-gen": "septembra-рујна",
-       "october-gen": "oktobra-листопада",
-       "november-gen": "студенога-novembra",
-       "december-gen": "decembra-просинца",
-       "jan": "jan-сиј",
-       "feb": "feb-вељ",
-       "mar": "mar-ожу",
-       "apr": "apr-тра",
-       "may": "maj-сви",
-       "jun": "jun-лип",
-       "jul": "jul-срп",
-       "aug": "aug-кол",
-       "sep": "sep-руј",
-       "oct": "okt-лис",
-       "nov": "сту-nov",
-       "dec": "dec-про",
-       "january-date": "$1. januar",
-       "february-date": "$1. februar",
-       "march-date": "$1. mart",
-       "april-date": "$1. april",
-       "may-date": "$1. maj",
-       "june-date": "$1. jun",
-       "july-date": "$1. jul",
-       "august-date": "$1. august",
-       "september-date": "$1. septembar",
-       "october-date": "$1. oktobar",
-       "november-date": "$1. novembar",
-       "december-date": "$1. decembar",
+       "january": "januar",
+       "february": "februar",
+       "march": "mart",
+       "april": "april",
+       "may_long": "maj",
+       "june": "juni",
+       "july": "juli",
+       "august": "august",
+       "september": "septembar",
+       "october": "oktobar",
+       "november": "novembar",
+       "december": "decembar",
+       "january-gen": "januara",
+       "february-gen": "februara",
+       "march-gen": "marta",
+       "april-gen": "aprila",
+       "may-gen": "maja",
+       "june-gen": "juna",
+       "july-gen": "jula",
+       "august-gen": "augusta",
+       "september-gen": "septembra",
+       "october-gen": "oktobra",
+       "november-gen": "novembra",
+       "december-gen": "decembra",
+       "jan": "jan.",
+       "feb": "feb.",
+       "mar": "mar.",
+       "apr": "apr.",
+       "may": "maj",
+       "jun": "jun.",
+       "jul": "jul.",
+       "aug": "aug.",
+       "sep": "sep.",
+       "oct": "okt.",
+       "nov": "nov.",
+       "dec": "dec.",
+       "january-date": "$1. januara",
+       "february-date": "$1. februara",
+       "march-date": "$1. marta",
+       "april-date": "$1. aprila",
+       "may-date": "$1. maja",
+       "june-date": "$1. juna",
+       "july-date": "$1. jula",
+       "august-date": "$1. augusta",
+       "september-date": "$1. septembra",
+       "october-date": "$1. oktobra",
+       "november-date": "$1. novembra",
+       "december-date": "$1. decembra",
        "pagecategories": "{{PLURAL:$1|Kategorija|Kategorije}}",
        "category_header": "Stranice u kategoriji \"$1\"",
        "subcategories": "Potkategorije",
        "noindex-category": "Neindeksirane stranice",
        "broken-file-category": "Stranice sa neispravnim linkovima do datoteka",
        "about": "O...",
-       "article": "Stranica sadržaja (članak)",
+       "article": "Stranica sa sadržajem",
        "newwindow": "(otvara se u novom prozoru)",
-       "cancel": "Odustani - Одустани",
+       "cancel": "Otkaži",
        "moredotdotdot": "Još...",
        "morenotlisted": "Ovaj spisak nije kompletan.",
        "mypage": "Moja stranica",
        "errorpagetitle": "Greška - Грешка",
        "returnto": "Povratak na $1.",
        "tagline": "Izvor: {{SITENAME}}",
-       "help": "Pomoć / Помоћ",
+       "help": "Pomoć",
        "search": "Traži / Тражи",
        "searchbutton": "Traži",
        "go": "Idi / Иди",
        "history": "Historija stranice",
        "history_short": "Historija",
        "updatedmarker": "promjene od moje zadnje posjete",
-       "printableversion": "Za štampanje / За штампање",
+       "printableversion": "Verzija za ispis",
        "permalink": "Trajni link",
        "print": "Štampa",
        "view": "Vidi",
        "talkpagelinktext": "Razgovor",
        "specialpage": "Posebna stranica",
        "personaltools": "Lični alati",
-       "articlepage": "Pogledaj stranicu sa sadržajem (članak)",
+       "articlepage": "Vidi stranicu sa sadržajem",
        "talk": "Razgovor",
        "views": "Pregledi",
-       "toolbox": "Alatke / Алатке",
+       "toolbox": "Alati",
        "userpage": "Pogledaj korisničku stranicu - Погледај корисничку страницу",
        "projectpage": "Pogledajte stranicu projekta",
        "imagepage": "Vidi stranicu datoteke/fajla",
        "viewhelppage": "Pogledajte stranicu za pomoć",
        "categorypage": "Pogledaj stranicu kategorije",
        "viewtalkpage": "Pogledajte raspravu",
-       "otherlanguages": "Drugi jezici / Други језици",
+       "otherlanguages": "Na drugim jezicima",
        "redirectedfrom": "(Preusmjereno sa $1)",
        "redirectpagesub": "Preusmjeri stranicu",
        "redirectto": "Preusmjerenje na:",
        "copyrightpage": "{{ns:project}}:Autorska_prava",
        "currentevents": "Trenutni događaji",
        "currentevents-url": "Project:Novosti",
-       "disclaimers": "Odricanje odgovornosti",
-       "disclaimerpage": "Project:Uslovi korištenja, pravne napomene i odricanje odgovornosti",
+       "disclaimers": "Odricanje od odgovornosti",
+       "disclaimerpage": "Project:Opće odricanje od odgovornosti",
        "edithelp": "Pomoć pri uređivanju",
        "helppage-top-gethelp": "Pomoć",
-       "mainpage": "Glavna stranica / Главна страница",
-       "mainpage-description": "Glavna stranica / Главна страница",
+       "mainpage": "Glavna stranica",
+       "mainpage-description": "Glavna stranica",
        "policy-url": "Project:Pravila",
        "portal": "Portal zajednice",
        "portal-url": "Project:Portal_zajednice",
-       "privacy": "Politika privatnosti - Политика приватности",
+       "privacy": "Politika privatnosti",
        "privacypage": "Project:Pravila o anonimnosti",
        "badaccess": "Greška pri odobrenju",
        "badaccess-group0": "Nije vam dozvoljeno izvršiti akciju koju ste zahtjevali.",
        "sort-descending": "Poredaj opadajuće",
        "sort-ascending": "Poredaj rastuće",
        "nstab-main": "Članak / Чланак",
-       "nstab-user": "Korisnik / Корисник",
+       "nstab-user": "Stranica korisnika",
        "nstab-media": "Mediji",
        "nstab-special": "Posebna stranica",
        "nstab-project": "Stranica projekta",
        "nstab-image": "Datoteka",
        "nstab-mediawiki": "Poruka / Порука",
        "nstab-template": "Šablon / Шаблон",
-       "nstab-help": "Pomoć / Помоћ",
+       "nstab-help": "Pomoć",
        "nstab-category": "Kategorija / Категорија",
-       "mainpage-nstab": "Glavna stranica / Главна страница",
+       "mainpage-nstab": "Glavna stranica",
        "nosuchaction": "Nema takve akcije",
        "nosuchactiontext": "Akcija navedena u URL-u nije valjana.\nMožda ste pogriješili pri unosu URL-a ili ste slijedili pokvaren link.\nMoguće je i da je ovo greška u softveru koji koristi {{SITENAME}}.",
        "nosuchspecialpage": "Nema takve posebne stranice",
        "userlogin-yourname": "Korisničko ime",
        "userlogin-yourname-ph": "Unesite svoje korisničko ime",
        "createacct-another-username-ph": "Unesi korisničko ime",
-       "yourpassword": "Vaša šifra / Ваша шифра:",
-       "userlogin-yourpassword": "Lozinka/zaporka",
-       "userlogin-yourpassword-ph": "Unesite svoju lozinku/zaporku",
+       "yourpassword": "Lozinka:",
+       "userlogin-yourpassword": "Lozinka",
+       "userlogin-yourpassword-ph": "Unesite svoju lozinku",
        "createacct-yourpassword-ph": "Unesite lozinku/zaporku",
        "yourpasswordagain": "Ponovo upišite šifru / Поново упишите шифру",
        "createacct-yourpasswordagain": "Potvrdite lozinku/zaporku",
        "createacct-yourpasswordagain-ph": "Unesite lozinku/zaporku ponovno",
        "remembermypassword": "Upamti moju lozinku na ovom kompjuteru (za maksimum od $1 {{PLURAL:$1|dan|dana}})",
-       "userlogin-remembermypassword": "Držite me ulogiranog/u",
+       "userlogin-remembermypassword": "Zapamti prijavu",
        "userlogin-signwithsecure": "Koristite sigurnu vezu",
        "yourdomainname": "Vaš domen:",
        "password-change-forbidden": "Ne možete da promenite lozinku na ovom vikiju.",
        "userlogin-noaccount": "Nemate račun?",
        "userlogin-joinproject": "Pridružite se {{SITENAME}}",
        "nologin": "Nemate korisničko ime? '''$1'''.",
-       "nologinlink": "Otvorite račun",
-       "createaccount": "Napraviti novi nalog / Направити нови налог",
+       "nologinlink": "Izradi račun",
+       "createaccount": "Izradi račun",
        "gotaccount": "Imate račun? '''$1'''.",
        "gotaccountlink": "Prijavite se / Пријавите се",
        "userlogin-resetlink": "Zaboravili ste detalje vaše prijave?",
-       "userlogin-resetpassword-link": "Zaboravili ste lozinku/zaporku?",
+       "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-createanother": "Stvori još jedan račun",
+       "userlogin-createanother": "Izradi drugi račun",
        "createacct-emailrequired": "E-mail adresa",
        "createacct-emailoptional": "E-mail adresa (opcionalno)",
        "createacct-email-ph": "Unesite svoju E-mail adresu",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zašto stvarate novi račun",
        "createacct-submit": "Stvorite svoj račun",
-       "createacct-another-submit": "Napravi korisnički račun",
+       "createacct-another-submit": "Izradi račun",
        "createacct-benefit-heading": "{{SITENAME}} se stvara od ljudi poput vas.",
        "createacct-benefit-body1": "$1 {{PLURAL:$1|izmjena|izmjene}}",
        "createacct-benefit-body2": "$1 {{PLURAL:$1|stranica|stranice|stranica}}",
        "createacct-another-realname-tip": "Pravo ime nije obavezno.\nAko izaberete da date ime, biće korišteno za pripisivanje za vaš rad.",
        "pt-login": "Prijava",
        "pt-login-button": "Prijavi me / Пријави ме",
-       "pt-createaccount": "Napraviti novi nalog / Направити нови налог",
+       "pt-createaccount": "Izradi račun",
        "pt-userlogout": "Odjava",
        "php-mail-error-unknown": "Nepoznata greška u PHP funkciji mail()",
        "user-mail-no-addy": "Pokušaj slanja e-maila bez e-mail adrese.",
        "resetpass_submit": "Odredi lozinku i prijavi se",
        "changepassword-success": "Vaša šifra je uspiješno promjenjena! Prijava u toku...",
        "changepassword-throttled": "Previše puta ste se pokušali prijaviti.\nMolimo Vas da sačekate $1 prije nego što pokušate ponovo.",
+       "botpasswords-label-cancel": "Otkaži",
        "resetpass_forbidden": "Šifre ne mogu biti promjenjene",
        "resetpass-no-info": "Morate biti prijavljeni da bi ste pristupili ovoj stranici direktno.",
        "resetpass-submit-loggedin": "Promijeni lozinku",
-       "resetpass-submit-cancel": "Odustani",
+       "resetpass-submit-cancel": "Otkaži",
        "resetpass-wrong-oldpass": "Privremena ili trenutna lozinka nije valjana.\nMožda ste već uspješno promijenili Vašu lozinku ili ste tražili novu privremenu lozinku.",
        "resetpass-recycled": "Molimo resetirajte vašu lozinku/zaporku u nešto drugo od vaše trenutne lozinke/zaporke.",
        "resetpass-temp-emailed": "Prijavili ste se sa privremenim kodom iz e-pošte.\nDa biste završili prijavljivanje morate postaviti novu lozinku ovde:",
        "hr_tip": "Horizontalna linija (koristite rijetko)",
        "summary": "Sažetak:",
        "subject": "Tema:",
-       "minoredit": "Mala izmjena - Мала измена",
-       "watchthis": "Prati / Прати",
-       "savearticle": "Sačuvaj - Сачувај",
-       "preview": "Pretpregled / Претпреглед",
-       "showpreview": "Pretpregled - Претпреглед",
-       "showdiff": "Prikaži izmjene - Прикажи измене",
+       "minoredit": "Ovo je manje uređenje",
+       "watchthis": "Prati ovu stranicu",
+       "savearticle": "Spremi stranicu",
+       "preview": "Pregled",
+       "showpreview": "Prikaži pregled",
+       "showdiff": "Prikaži izmjene",
        "blankarticle": "<strong>Upozorenje:</strong> Napravili ste praznu stranicu.\nAko ponovno kliknete \"{{int:savearticle}}\", napravit ćete praznu stranicu bez sadržaja.",
        "anoneditwarning": "<strong>Upozorenje:</strong> Niste prijavljeni. \nVaša IP adresa će biti javno vidljiva ako napravite neku izmjenu. Ako se <strong>[$1 prijavite]</strong> ili <strong>[$2 napravite račun]</strong>, vaše izmjene će biti pripisane vašem korisničkom imenu, zajedno sa drugim pogodnostima.",
        "anonpreviewwarning": "''Niste prijavljeni. Vaša IP adresa će biti zabilježena u historiji ove stranice.''",
        "viewprevnext": "Pogledaj ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Postoji stranica pod nazivom \"[[:$1]]\" na ovoj wiki'''",
        "searchmenu-new": "<strong>Napravi stranicu \"[[:$1]]\" na ovoj wiki!</strong> {{PLURAL:$2|0=|Pogledajte također straniu pronađenu vašom pretragom.|Pogledajte također i vaše rezultate pretrage.}}",
-       "searchprofile-articles": "Stranice sadržaja",
+       "searchprofile-articles": "Stranice sa sadržajem",
        "searchprofile-images": "Multimedija",
        "searchprofile-everything": "Sve",
        "searchprofile-advanced": "Napredno",
        "prefs-edits": "Broj izmjena:",
        "prefsnologintext2": "Molimo Vas prijavite se da biste promijenili postavke.",
        "prefs-skin": "Izgled (skin)",
-       "skin-preview": "Pretpregled",
+       "skin-preview": "Pregled",
        "datedefault": "Bez preferenci",
        "prefs-labs": "Eksperimentalne mogućnosti",
        "prefs-user-pages": "Korisničke stranice",
        "prefs-personal": "Korisnički profil",
        "prefs-rc": "Podešavanje nedavnih izmjena",
-       "prefs-watchlist": "Praćene stranice / Списак надгледања",
+       "prefs-watchlist": "Lista praćenja",
        "prefs-editwatchlist": "Uredi popis praćenja",
        "prefs-editwatchlist-label": "Uredi unose na popisu praćenja:",
        "prefs-editwatchlist-edit": "Vidite i uklonite naslove na vašem popisu praćenja",
        "prefs-timeoffset": "Vremenska razlika",
        "prefs-advancedediting": "Opće opcije",
        "prefs-editor": "Uređivač",
-       "prefs-preview": "Pretpregled",
+       "prefs-preview": "Pregled",
        "prefs-advancedrc": "Napredne opcije",
        "prefs-advancedrendering": "Napredne opcije",
        "prefs-advancedsearchoptions": "Napredne opcije",
        "right-createtalk": "Pravljenje stranica za razgovor",
        "right-createaccount": "Pravljenje korisničkog računa",
        "right-minoredit": "Označavanje izmjena kao malih",
-       "right-move": "Preusmjeravanje stranica",
+       "right-move": "Premještanje stranica",
        "right-move-subpages": "Preusmjeravanje stranica sa svim podstranicama",
        "right-move-rootuserpages": "Premještanje stranica osnovnih korisnika",
        "right-move-categorypages": "Pomakni stranice kategorije",
        "rc-enhanced-hide": "Sakrij detalje",
        "rc-old-title": "prvobitno kreirano kao \"$1\"",
        "recentchangeslinked": "Srodne izmjene / Сродне измене",
-       "recentchangeslinked-feed": "Srodne izmjene",
-       "recentchangeslinked-toolbox": "Srodne izmjene",
-       "recentchangeslinked-title": "Srodne promjene sa \"$1\"",
+       "recentchangeslinked-feed": "Vezane izmjene",
+       "recentchangeslinked-toolbox": "Vezane izmjene",
+       "recentchangeslinked-title": "Izmjene vezane s \"$1\"",
        "recentchangeslinked-summary": "Ova posebna stranica prikazuje promjene na povezanim stranicama.\nStranice koje su na vašem [[Special:Watchlist|spisku praćenja]] su '''podebljane'''.",
        "recentchangeslinked-page": "Naslov stranice:",
        "recentchangeslinked-to": "Pokaži promjene stranica koji su povezane sa datom stranicom",
        "upload-http-error": "Desila se HTTP greška: $1",
        "upload-copy-upload-invalid-domain": "Kopije postavljanja nisu dostupni na ovom domenu.",
        "upload-dialog-title": "Postavi datoteku",
-       "upload-dialog-button-cancel": "Odustani",
+       "upload-dialog-button-cancel": "Otkaži",
        "upload-dialog-button-done": "Urađeno",
        "upload-dialog-button-save": "Snimi",
        "upload-dialog-button-upload": "Postavi",
        "statistics-header-edits": "Statistike izmjena",
        "statistics-header-users": "Statistike korisnika",
        "statistics-header-hooks": "Ostale statistike",
-       "statistics-articles": "Stranice sadržaja",
+       "statistics-articles": "Stranice sa sadržajem",
        "statistics-pages": "Stranice",
        "statistics-pages-desc": "Sve stranice na wikiju, uključujući stranice za razgovor, preusmjerenja itd.",
        "statistics-files": "Broj postavljenih datoteka",
        "trackingcategories-disabled": "Kategorija je onemogućena",
        "mailnologin": "Nema adrese za slanje",
        "mailnologintext": "Morate biti [[Special:UserLogin|prijavljeni]] i imati ispravnu adresu e-pošte u vašim [[Special:Preferences|podešavanjima]] da biste slali e-poštu drugim korisnicima.",
-       "emailuser": "Pošalji E-mail ovom korisniku",
+       "emailuser": "Pošalji e-mail ovom korisniku",
        "emailuser-title-target": "Slanje e-maila {{GENDER:$1|korisniku|korisnici|korisniku}}",
        "emailuser-title-notarget": "Slanje e-maila korisniku",
        "emailpagetext": "Možete da koristite donji obrazac da pošaljete e-mail {{GENDER:$1|ovom korisniku|ovoj korisnici|ovom korisniku|}}.\nE-mail koju ste uneli u vašim [[Special:Preferences|postavkama]] će se prikazati u polju \"Od:\", tako da će primalac moći da vam odgovori direktno.",
        "emailuserfooter": "Ovu e-poruku {{GENDER:$1|poslao|poslala}} je $1 {{GENDER:$2|korisniku|korisnici}} $2 pomoću funkcije \"{{int:emailuser}}\" s projekta {{SITENAME}}.",
        "usermessage-summary": "Ostavljanje sistemske poruke.",
        "usermessage-editor": "Sistem za poruke",
-       "watchlist": "Spisak praćenja / Списак праћења",
-       "mywatchlist": "Lista motrenja",
+       "watchlist": "Lista praćenja",
+       "mywatchlist": "Lista praćenja",
        "watchlistfor2": "Za $1 $2",
        "nowatchlist": "Nemate ništa na svom spisku praćenih članaka.",
        "watchlistanontext": "Morate biti prijavljeni kako biste vidjeli ili uređivali svoj spisak praćenih članaka.",
        "removewatch": "Ukloni sa spiska praćenja",
        "removedwatchtext": "Stranica „[[:$1]]“ i njena stranica za razgovor je uklonjena s vašeg [[Special:Watchlist|spiska nadgledanja]].",
        "removedwatchtext-short": "Stranica \"$1\" je uklonjena sa vašeg spiska praćenja.",
-       "watch": "Prati / Прати",
-       "watchthispage": "Prati / Прати",
+       "watch": "Prati",
+       "watchthispage": "Prati ovu stranicu",
        "unwatch": "Prekini praćenje",
-       "unwatchthispage": "Ukinite praćenje",
-       "notanarticle": "Nije članak",
+       "unwatchthispage": "Prestani pratiti",
+       "notanarticle": "Nije stranica sa sadržajem",
        "notvisiblerev": "Posljednja izmjena drugog korisnika je bila izbrisana",
        "watchlist-details": "{{PLURAL:$1|$1 stranica|$1 stranice|$1 stranica }} na vašem spisku praćenja, ne računajući posebno stranice za razgovor.",
        "wlheader-enotif": "* Obavještavanje e-poštom je omogućeno.",
        "contributions": "Doprinosi {{GENDER:$1|korisnika|korisnice|korisnika}}",
        "contributions-title": "Korisnički doprinosi od $1",
        "mycontris": "Doprinosi",
+       "anoncontribs": "Doprinosi",
        "contribsub2": "Za {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "Korisnički račun \"$1\" nije registrovan.",
        "nocontribs": "Nisu nađene promjene koje zadovoljavaju ove uslove.",
        "sp-contributions-toponly": "Prikaži samo izmjene koje su posljednje revizije",
        "sp-contributions-newonly": "Prikaži samo izmjene kojima su napravljene nove stranice",
        "sp-contributions-submit": "Traži",
-       "whatlinkshere": "Što vodi ovdje / Шта води овде",
+       "whatlinkshere": "Što upućuje ovamo",
        "whatlinkshere-title": "Stranice koje vode / Странице које воде до $1",
        "whatlinkshere-page": "Stranica:",
        "linkshere": "Sljedeće stranice vode na '''[[:$1]]''':",
        "databasenotlocked": "Baza podataka nije zaključana.",
        "lockedbyandtime": "(od $1 dana $2 u $3)",
        "move-page": "Preusmjeravanje $1",
-       "move-page-legend": "Premjestite stranicu",
+       "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 će postati stranica koja preusmjerava na članak pod novim imenom. \nMožete automatski izmjeniti 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 preusmjeren ukoliko već postoji članak pod imenom na koje namjeravate da preusmjerite 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 dobro razmislite prije nego što preimenujete stranicu.",
        "movepagetext-noredirectfixer": "Koristeći obrazac ispod ćete preimenovati stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv će postati preusmjerenje na novi naziv.\nMolimo provjerite da li postoje [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti da li su linkovi ispravni i da li vode tamo gdje bi trebali.\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 znali da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili a ne možete ponovo preimenovati postojeću stranicu.\n\n'''Pažnja!'''\nImajte na umu da preusmjeravanje popularnog članka može biti\ndrastična i neočekivana promjena za korisnike; molimo budite sigurni da ste shvatili posljedice prije nego što nastavite.",
        "movepagetalktext": "Ako označite ovu kutijucu, pridružena stranica za razgovor će se automatski premjestiti na novi naslov, ukoliko ne-prazna stranica razgovor sa istim imenom već postoji. U tom slučaju ćete morati, ako želite, ručno premjestiti ili spojiti stranicu.",
        "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 - Прати ову страницу",
-       "movepagebtn": "Premjesti stranicu – Премјести страницу",
+       "move-watch": "Prati izvornu i ciljnu stranicu",
+       "movepagebtn": "Premjesti stranicu",
        "pagemovedsub": "Premještanje uspjelo",
        "movepage-moved": "'''\"$1\" je premještena na \"$2\"'''",
        "movepage-moved-redirect": "Preusmjerenje je napravljeno.",
        "movepage-moved-noredirect": "Pravljenje preusmjerenja je onemogućeno.",
        "articleexists": "Stranica pod tim imenom već postoji, ili je ime koje ste izabrali neispravno.\nMolimo 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": "Premjesti i stranicu za diskusiju zajedno sa člankom (ako nije prazna).",
-       "move-subpages": "Premjesti sve podstranice (do $1)",
-       "move-talk-subpages": "Premjesti podstranice stranica za razgovor (do $1)",
+       "movetalk": "Premjesti pridruženu stranicu za razgovor",
+       "move-subpages": "Premjesti podstranice (sve do $1)",
+       "move-talk-subpages": "Premjesti podstranice stranice za razgovor (sve do $1)",
        "movepage-page-exists": "Stranica $1 već postoji i ne može biti automatski zamijenjena.",
        "movepage-page-moved": "Stranica $1 je premještena na $2.",
        "movepage-page-unmoved": "Stranica $1 ne može biti premještena na $2.",
        "nonfile-cannot-move-to-file": "Ne mogu se premjestiti podaci u datotečni imenski prostor",
        "imagetypemismatch": "Ekstenzija nove datoteke ne odgovara njenom tipu",
        "imageinvalidfilename": "Ciljno ime datoteke nije valjano",
-       "fix-double-redirects": "Ažuriraj sva preusmjerenja koja vode ka originalnom naslovu",
+       "fix-double-redirects": "Ažuriraj sva preusmjerenja koja vode na originalni naslov",
        "move-leave-redirect": "Ostavi preusmjerenje",
        "protectedpagemovewarning": "'''Upozorenje:''' Ova stranica je zaključana tako da je mogu premještati samo korisnici sa ovlastima administratora.\nPosljednja stavka evidencije je prikazana ispod kao referenca:",
        "semiprotectedpagemovewarning": "'''Napomena:''' Ova stranica je zaključana tako da je mogu uređivati samo registrovani korisnici.\nPosljednja stavka evidencije je prikazana ispod kao referenca:",
        "javascripttest-pagetext-frameworks": "Izaberite jedan od sledećih radnih okvira: $1",
        "javascripttest-pagetext-skins": "Izaberite s kojim skinom (interfejsom) želite da pokrenete probu:",
        "javascripttest-qunit-intro": "Pogledajte [$1 dokumentaciju za testiranje] na mediawiki.org.",
-       "tooltip-pt-userpage": "Vaša korisnička stranica",
+       "tooltip-pt-userpage": "{{GENDER:|Vaša korisnička}} stranica",
        "tooltip-pt-anonuserpage": "Korisnička stranica za ip koju Vi uređujete kao",
-       "tooltip-pt-mytalk": "Vaša stranica za razgovor",
+       "tooltip-pt-mytalk": "{{GENDER:|Vaša}} stranica za razgovor",
        "tooltip-pt-anontalk": "Razgovor o doprinosu sa ove IP adrese",
-       "tooltip-pt-preferences": "Vaše postavke",
-       "tooltip-pt-watchlist": "Lista stranica kojih izmjene motrite",
-       "tooltip-pt-mycontris": "Lista vaših doprinosa",
+       "tooltip-pt-preferences": "{{GENDER:|Vaše}} postavke",
+       "tooltip-pt-watchlist": "Lista stranica čije izmjene pratite",
+       "tooltip-pt-mycontris": "Lista {{GENDER:|vaših}} doprinosa",
+       "tooltip-pt-anoncontribs": "Lista uređenja napravljenih s ove IP adrese",
        "tooltip-pt-login": "Predlažem da se prijavite; međutim, to nije obavezno",
        "tooltip-pt-logout": "Odjava sa projekta {{SITENAME}}",
        "tooltip-pt-createaccount": "Ohrabrujemo vas da otvorite račun i prijavite se; to, međutim, nije obavezno",
-       "tooltip-ca-talk": "Razgovor o sadržaju stranice",
+       "tooltip-ca-talk": "Rasprava o stranici sa sadržajem",
        "tooltip-ca-edit": "Uredi ovu stranicu",
        "tooltip-ca-addsection": "Započnite novu sekciju.",
        "tooltip-ca-viewsource": "Ova stranica je zaštićena.\nMožete vidjeti njen izvor",
        "tooltip-ca-delete": "Izbriši ovu stranicu",
        "tooltip-ca-undelete": "Vratite izmjene koje su načinjene prije brisanja stranice",
        "tooltip-ca-move": "Premjesti ovu stranicu",
-       "tooltip-ca-watch": "Dodajte ovu stranicu na Vaš spisak praćenja",
+       "tooltip-ca-watch": "Dodaj ovu stranicu na svoju listu praćenja",
        "tooltip-ca-unwatch": "Izbrišite ovu stranicu sa spiska praćenja",
        "tooltip-search": "Traži ovaj Wiki / Тражи овај Вики [alt-f]",
        "tooltip-search-go": "Idi na stranicu s upravo ovakvim imenom ako postoji",
        "tooltip-search-fulltext": "Pretraga stranica sa ovim tekstom",
        "tooltip-p-logo": "Posjetite glavnu stranicu",
-       "tooltip-n-mainpage": "Posjetite glavnu stranu",
+       "tooltip-n-mainpage": "Posjetite glavnu stranicu",
        "tooltip-n-mainpage-description": "Posjetite glavnu stranicu",
        "tooltip-n-portal": "O projektu, što možete učiniti, gdje možete naći stvari",
        "tooltip-n-currentevents": "Pronađi dodatne informacije o trenutnim događajima",
        "tooltip-n-recentchanges": "Spisak nedavnih izmjena na wikiju.",
        "tooltip-n-randompage": "Otvorite slučajnu stranicu",
-       "tooltip-n-help": "Mjesto gdje možete nešto da naučite",
-       "tooltip-t-whatlinkshere": "Spisak svih stranica povezanih sa ovim",
+       "tooltip-n-help": "Mjesto za saznavanje",
+       "tooltip-t-whatlinkshere": "Lista svih wiki stranica koje upućuju ovamo",
        "tooltip-t-recentchangeslinked": "Nedavne izmjene ovdje povezanih stranica",
        "tooltip-feed-rss": "RSS feed za ovu stranicu",
        "tooltip-feed-atom": "Atom feed za ovu stranicu",
-       "tooltip-t-contributions": "Pogledajte listu doprinosa ovog korisnika",
+       "tooltip-t-contributions": "Lista doprinosa {{GENDER:$1|ovog korisnika}}",
        "tooltip-t-emailuser": "Pošaljite e-mail ovom korisniku",
        "tooltip-t-upload": "Postavi datoteke",
-       "tooltip-t-specialpages": "Popis svih posebnih stranica",
-       "tooltip-t-print": "Verzija ove stranice za štampanje",
+       "tooltip-t-specialpages": "Lista svih posebnih stranica",
+       "tooltip-t-print": "Verzija ove stranice za ispis",
        "tooltip-t-permalink": "Stalni link ove verzije stranice",
-       "tooltip-ca-nstab-main": "Pogledajte sadržaj stranice",
+       "tooltip-ca-nstab-main": "Vidi stranicu sa sadržajem",
        "tooltip-ca-nstab-user": "Pogledajte korisničku stranicu",
        "tooltip-ca-nstab-media": "Pogledajte medijski fajl",
        "tooltip-ca-nstab-special": "Ovo je posebna stranica, te se ne može zasebno uređivati",
        "tooltip-ca-nstab-image": "Vidi stranicu datoteke/fajla",
        "tooltip-ca-nstab-mediawiki": "Pogledajte sistemsku poruku",
        "tooltip-ca-nstab-template": "Pogledajte šablon",
-       "tooltip-ca-nstab-help": "Pogledajte stranicu za pomoć",
+       "tooltip-ca-nstab-help": "Vidi stranicu pomoći",
        "tooltip-ca-nstab-category": "Pogledajte stranicu kategorije",
-       "tooltip-minoredit": "Označite ovo kao manju izmjenu",
-       "tooltip-save": "Snimi izmjene - Сними измјене [alt-s]",
-       "tooltip-preview": "Prethodni pregled stranice, molimo koristiti prije snimanja!",
+       "tooltip-minoredit": "Označi ovo kao manje uređenje",
+       "tooltip-save": "Spremite svoje izmjene",
+       "tooltip-preview": "Pregledajte svoje izmjene. Molimo vas da ovo koristite prije spremanja.",
        "tooltip-diff": "Prikaz izmjena koje ste napravili u tekstu",
        "tooltip-compareselectedversions": "Pogledajte pazlike između dvije selektovane verzije ove stranice.",
-       "tooltip-watch": "Postavite ovu stranicu na Vaš spisak praćenja / Поставите ову страницу на Ваш списак праћења [alt-w]",
+       "tooltip-watch": "Dodaj ovu stranicu na svoju listu praćenja",
        "tooltip-watchlistedit-normal-submit": "Ukloni naslove",
        "tooltip-watchlistedit-raw-submit": "Ažuriraj spisak praćenja",
        "tooltip-recreate": "Ponovno pravljenje stranice iako je već brisana",
        "exif-attributionurl": "Kada ponovno koristite ovaj rad, molimo povežite ga na",
        "exif-preferredattributionname": "Kada ponovno koristite ovaj rad, molimo pripišite ga na",
        "exif-pngfilecomment": "PNG komentar datoteke",
-       "exif-disclaimer": "Odricanje odgovornosti",
+       "exif-disclaimer": "Odricanje od odgovornosti",
        "exif-contentwarning": "Upozorenje o sadržaju",
        "exif-giffilecomment": "GIF komentar datoteke",
        "exif-intellectualgenre": "Tip predmeta",
        "fileduplicatesearch-result-1": "Datoteka \"$1\" nema identičnih dvojnika.",
        "fileduplicatesearch-result-n": "Datoteka \"$1\" ima {{PLURAL:$2|1 identičnog|$2 identična|$2 identičnih}} dvojnika.",
        "fileduplicatesearch-noresults": "Nije pronađena datoteka sa imenom \"$1\".",
-       "specialpages": "Posebno / Посебно",
+       "specialpages": "Posebne stranice",
        "specialpages-note": "* Normalne posebne stranice.\n* <span class=\"mw-specialpagerestricted\">Ograničene posebne stranice.</span>\n* <span class=\"mw-specialpagecached\">Keširane posebne stranice (mogu biti zastarjele).</span>",
        "specialpages-group-maintenance": "Izvještaji o održavanju / Извјештаји о одржавању",
        "specialpages-group-other": "Ostale posebne stranice - Остале посебне странице",
        "feedback-bugcheck": "Izvrsno! Molimo provjerite da se ne radi o nekom [$1 poznatom \"bugu\"].",
        "feedback-bugnew": "Provereno. Prijavi novu grešku",
        "feedback-bugornote": "Ako ste spremni da detaljno opišete tehnički problem, onda [$1 prijavite grešku].\nU suprotnom, poslužite se jednostavnim obrascem ispod. Vaš komentar će stajati na stranici „[$3 $2]“, zajedno s korisničkim imenom i pregledačem koji koristite.",
-       "feedback-cancel": "Odustani",
+       "feedback-cancel": "Otkaži",
        "feedback-close": "Gotovo",
        "feedback-error1": "Greška: neprepoznat rezultat od API-ja",
        "feedback-error2": "Greška: Uređivanje nije uspjelo",
        "duration-centuries": "$1 {{PLURAL:$1|vijek|vijekova}}",
        "duration-millennia": "$1 {{PLURAL:$1|milenijum|milenijuma}}",
        "rotate-comment": "Slika rotirana za $1 {{PLURAL:$1|stepeni}} u smjeru kazaljke na satu",
-       "expand_templates_input": "Unos - Унос"
+       "expand_templates_input": "Unos - Унос",
+       "expand_templates_preview": "Pregled"
 }
index 180b534..bbbf385 100644 (file)
        "mergehistory-empty": "Redakcij ni moč združiti.",
        "mergehistory-done": "$3 {{PLURAL:$3|redakcija|redakciji|redakcije|redakcij}} $1 {{PLURAL:$3|smo}} spojili v [[:$2]].",
        "mergehistory-fail": "Ne morem izvesti združitev zgodovine, prosimo, ponovno preverite strani in parametre časa.",
+       "mergehistory-fail-bad-timestamp": "Časovni žig je neveljaven.",
+       "mergehistory-fail-invalid-source": "Izvorna strani je neveljavna.",
+       "mergehistory-fail-invalid-dest": "Ciljna stran je neveljavna.",
+       "mergehistory-fail-no-change": "Združevanje zgodovine ni združilo nobene redakcije. Prosimo, ponovno preverite parametre strani in časa.",
+       "mergehistory-fail-permission": "Nezadostna dovoljenja za združevanje zgodovine.",
+       "mergehistory-fail-self-merge": "Izvorna in ciljna stran sta enaki.",
+       "mergehistory-fail-timestamps-overlap": "Izvorne redakcije se prekrivajo ali pridejo po ciljnh redakcijah.",
        "mergehistory-fail-toobig": "Ne morem izvesti združitve zgodovine, saj bi moral premakniti več kot $1 {{PLURAL:$1|redakcijo|redakciji|redakcije|redakcij}}, kar je omejitev.",
        "mergehistory-no-source": "Izvirna stran $1 ne obstaja.",
        "mergehistory-no-destination": "Ciljna stran $1 ne obstaja.",
index 24b1699..e713b2d 100644 (file)
                        "Ianlopez1115",
                        "Leeheonjin",
                        "Macofe",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Stranger195"
                ]
        },
        "tog-underline": "Pagsasalungguhit ng link:",
        "tog-hideminor": "Itago ang mga maliliit na pagbabago mula sa mga huling binago",
        "tog-hidepatrolled": "Ikubli ang napatrolyang mga pagbabagong nasa kamakailang mga pagbabago",
        "tog-newpageshidepatrolled": "Itago ang napatrolyang mga pahina mula talaan ng bagong pahina",
+       "tog-hidecategorization": "Itago ang kategorisasyon ng mga pahina",
        "tog-extendwatchlist": "Palawigin ang talaan ng mga binabantayan upang maipakita ang lahat ng mga pagbabago, hindi lamang ang pinakakamakailan lamang",
-       "tog-usenewrc": "Mga pagbabago ng pangkat ayon sa pahina sa kamakailang mga pagbabago at bantayan (nangangailangan ng JavaScript)",
+       "tog-usenewrc": "Mga pagbabago ng pangkat ayon sa pahina sa kamakailang mga pagbabago at bantayan",
        "tog-numberheadings": "Automatikong bilangin ang mga pamagat",
-       "tog-showtoolbar": "Ipakita ang ''toolbar'' ng pagbabago (JavaScript)",
-       "tog-editondblclick": "Magbago ng mga pahina sa dalawahang pagpindot (JavaScript)",
-       "tog-editsectiononrightclick": "Payagan ang mga pagbabagong panseksyon sa pakanang pagpindot ng mga panseksyong pamagat (JavaScript)",
+       "tog-showtoolbar": "Ipakita ang ''toolbar'' ng pagbabago",
+       "tog-editondblclick": "Magbago ng mga pahina sa dalawahang pagpindot",
+       "tog-editsectiononrightclick": "Payagan ang mga pagbabagong panseksyon sa pakanang pagpindot ng mga panseksyong pamagat",
        "tog-watchcreations": "Idagdag sa aking tala ng mga binabantayan ang mga pahinang nilikha ko at mga talaksang ikinarga kong paitaas",
        "tog-watchdefault": "Idagdag sa aking tala ng mga binabantayan ang mga pahina at mga talaksang binago ko",
        "tog-watchmoves": "Idagdag sa aking tala ng mga binabantayan ang mga pahina at mga talaksang inilipat ko",
        "tog-watchdeletion": "Idagdag sa aking tala ng mga binabantayan ang mga pahina at mga talaksang binura ko",
+       "tog-watchrollback": "Magdagdag ng mga pahina kung saan ako nag-rollback sa aking bantayan",
        "tog-minordefault": "Markahan ang lahat ng pagbabago bilang maliit nang nakatakda",
        "tog-previewontop": "Ipakita ang paunang tingin bago ang kahon ng pagbabago",
        "tog-previewonfirst": "Ipakita ang paunang tingin sa unang pagbabago",
        "tog-shownumberswatching": "Ipakita ang bilang ng mga nagbabantay na tagagamit",
        "tog-oldsig": "Umiiral na lagda:",
        "tog-fancysig": "Ituring ang lagda bilang teksto ng wiki (walang automatikong pagkawing)",
-       "tog-uselivepreview": "Gamitin ang buhay na paunang tingin (JavaScript) (Eksperimental)",
+       "tog-uselivepreview": "Gamitin ang buhay na paunang tingin",
        "tog-forceeditsummary": "Pagsabihan ako kapag nagpapasok ng walang-lamang buod ng pagbabago",
        "tog-watchlisthideown": "Itago ang aking mga pagbabago mula sa tala ng mga binabantayan",
        "tog-watchlisthidebots": "Itago ang mga pagbabago ng mga bot mula sa tala ng mga binabantayan",
        "tog-watchlisthideminor": "Itago ang mga maliliit na pagbabago mula sa tala ng mga binabantayan",
        "tog-watchlisthideliu": "Itago ang mga pagbabago ng mga nakalagdang tagagamit mula sa tala ng mga binabantayan",
+       "tog-watchlistreloadautomatically": "I-karga muli ang bantayan ng awtomatiko kung kailan man nabago ang isang filter (kinakailangan ang JavaScript)",
        "tog-watchlisthideanons": "Itago ang mga pagbabago ng hindi nakikilalang mga tagagamit mula sa tala ng mga binabantayan",
        "tog-watchlisthidepatrolled": "Itago ang napatrolyang mga pagbabago mula sa tala ng mga binabantayan",
+       "tog-watchlisthidecategorization": "Itago ang kategorisasyon ng mga pahina",
        "tog-ccmeonemails": "Padalahan ako ng mga kopya ng mga ipinadala kong e-liham sa ibang mga tagagamit",
        "tog-diffonly": "Huwag ipakita ang nilalaman ng pahinang nasa ilalim ng mga pagkakaiba",
        "tog-showhiddencats": "Ipakita ang mga nakatagong kategorya",
        "october-date": "$1 Oktubre",
        "november-date": "$1 Nobyembre",
        "december-date": "$1 Disyembre",
+       "period-am": "ng umaga",
+       "period-pm": "ng gabi",
        "pagecategories": "{{PLURAL:$1|Kategorya|Mga kategorya}}",
        "category_header": "Mga pahina sa kategoryang \"$1\"",
        "subcategories": "Mga subkategorya",
        "morenotlisted": "Hindi kumpleto ang talang ito.",
        "mypage": "Pahina ko",
        "mytalk": "Usapan",
-       "anontalk": "Usapan para sa IP na ito",
+       "anontalk": "Usapan",
        "navigation": "Paglilibot (nabigasyon)",
        "and": ",&#32;at",
        "qbfind": "Hanapin",
        "view": "Tingnan",
        "view-foreign": "Tingnan sa $1",
        "edit": "Baguhin",
+       "edit-local": "Baguhin ang lokal na paglalarawan",
        "create": "Likhain",
+       "create-local": "Magdagdag ng lokal na paglalarawan",
        "editthispage": "Baguhin ang pahinang ito",
        "create-this-page": "Likhain ang pahinang ito",
        "delete": "Burahin",
        "deletethispage": "Burahin itong pahina",
        "undeletethispage": "Ibalik mula sa pagkakabura ang pahinang ito",
        "undelete_short": "Baligtarin ang pagbura ng {{PLURAL:$1|isang pagbabago|$1 pagbabago}}",
-       "viewdeleted_short": "Tingnan ang {{PLURAL:$1|isang binurang pagbabagp|$1 binurang pagbabago}}",
+       "viewdeleted_short": "Tingnan ang {{PLURAL:$1|isang binurang pagbabago|$1 binurang pagbabago}}",
        "protect": "Ipagsanggalang",
        "protect_change": "baguhin",
        "protectthispage": "Ipagsanggalang itong pahina",
        "otherlanguages": "Sa ibang wika",
        "redirectedfrom": "(Ikinarga mula sa $1)",
        "redirectpagesub": "Pahina ng pagkarga",
+       "redirectto": "Papuntahin sa:",
        "lastmodifiedat": "Huling binago ang pahinang ito noong $2, noong $1.",
-       "viewcount": "Namataan na pahinang ito nang {{PLURAL:$1|isang|$1}} beses.",
+       "viewcount": "Namataan ang pahinang ito nang {{PLURAL:$1|isang|$1}} beses.",
        "protectedpage": "Pahinang nakasanggalang",
        "jumpto": "Tumalon sa:",
        "jumptonavigation": "paglilibot",
        "jumptosearch": "paghahanap",
        "view-pool-error": "Paumanhin, ngunit masyado pong abala ang mga serbidor sa sandaling ito.\nMasyadong maraming tagagamit ay sinusubukang tingnan ang pahinang ito.\nMangyari lamang na maghintay po nang sandali bago niyo pong subukang mataanin muli ang pahinang ito.\n\n$1",
+       "generic-pool-error": "Paumanhin, ngunit masyado pong abala ang mga serbidor sa sandaling ito.\nMasyadong maraming tagagamit ay sinusubukang tingnan ang pahinang ito.\nMangyari lamang na maghintay po nang sandali bago niyo pong subukang mataanin muli ang pahinang ito.",
        "pool-timeout": "Ang pagpapahinga ay naghihintay ng kandado",
        "pool-queuefull": "Puno na ang pisan ng mga pila",
        "pool-errorunknown": "Hindi nalalamang kamalian",
+       "pool-servererror": "Hindi magagamit ang serbisyo ng pool counter ($1).",
+       "poolcounter-usage-error": "Pagkakamali sa paggamit: $1",
        "aboutsite": "Tungkol sa {{SITENAME}}",
        "aboutpage": "Project:Patungkol",
        "copyright": "Maaaring gamitin ang nilalaman sa ilalim ng $1 maliban kung nabanggit.",
        "backlinksubtitle": "← $1",
        "retrievedfrom": "Ikinuha mula sa \"$1\"",
        "youhavenewmessages": "Mayroon kang $1 ($2).",
-       "youhavenewmessagesfromusers": "Mayroon kang $1 magmula sa {{PLURAL:$3|ibang tagagamit|$3 mga tagagamit}} ($2).",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|Mayroon kang}} $1 magmula sa {{PLURAL:$3|ibang tagagamit|$3 mga tagagamit}} ($2).",
        "youhavenewmessagesmanyusers": "Mayroon kang $1 magmula sa maraming mga tagagamit ($2).",
-       "newmessageslinkplural": "{{PLURAL:$1|isang bagong mensahe|bagong mga mensahe}}",
-       "newmessagesdifflinkplural": "huling {{PLURAL:$1|pagbabago|mga pagbabago}}",
+       "newmessageslinkplural": "{{PLURAL:$1|isang bagong mensahe|999=bagong mensahe}}",
+       "newmessagesdifflinkplural": "huling {{PLURAL:$1|pagbabago|999=mga pagbabago}}",
        "youhavenewmessagesmulti": "Mayroon kang mga bagong mensahe sa $1",
        "editsection": "baguhin",
        "editold": "baguhin",
        "hidetoc": "itago",
        "collapsible-collapse": "Ibagsak",
        "collapsible-expand": "Ibuka",
+       "confirmable-confirm": "Sigurado {{GENDER:$1|ka}} ba?",
+       "confirmable-yes": "Oo",
+       "confirmable-no": "Hindi",
        "thisisdeleted": "Tingnan o ibalik ang $1?",
        "viewdeleted": "Tingnan ang $1?",
        "restorelink": "{{PLURAL:$1|isang binurang pagbabago|$1 binurang pagbabago}}",
        "nstab-template": "Padron",
        "nstab-help": "Pahina ng tulong",
        "nstab-category": "Kategorya",
+       "mainpage-nstab": "Unang pahina",
        "nosuchaction": "Walang ganitong kilos",
        "nosuchactiontext": "Hindi tanggap ang galaw na tinukoy ng URL.\nMaaaring nagkamali ka sa pagmamakinilya ng URL, o sumunod sa isang maling kawing.\nMaaari rin itong magpahiwatig ng isang depektong nasa loob ng {{SITENAME}}.",
        "nosuchspecialpage": "Walang ganyang natatanging pahina",
        "databaseerror": "Kamalian sa kalipunan ng datos",
        "databaseerror-text": "Mayroong kamalian sa pagtanong o pag-query sa database.\nMaaring ipinapahiwatig nito ang depekto o bug sa software.",
        "databaseerror-textcl": "May nangyaring depekto sa pag-query ng database.",
+       "databaseerror-query": "Tanong: $1",
+       "databaseerror-function": "Tungkulin: $1",
        "databaseerror-error": "Depekto: $1",
        "laggedslavemode": "'''Babala:''' Maaaring hindi naglalaman ang pahina ng mga huling dagdag.",
        "readonly": "Nakakandado ang kalipunan ng datos",
        "right-blockemail": "Harangin sa pagpapadala ng e-liham ang isang tagagamit",
        "right-hideuser": "Harangin ang isang tagagamit, na itinatago mula sa publiko",
        "right-ipblock-exempt": "Laktawan ang mga pagharang/paghadlang na pang-IP, kusang pagharang/paghadlang at mga saklaw ng pagharang/paghadlang",
-       "right-proxyunbannable": "Laktawan ang mga kusang pagharang ng mga kahalili",
        "right-unblockself": "Tanggalin ang pagkakaharang ng kanilang mga sarili",
        "right-protect": "Baguhin ang mga antas ng panananggalang at baguhin ang mga pahinang nakasanggalang",
        "right-editprotected": "Baguhin ang mga pahinang nakasanggalang (walang baita-baitang na panananggalang)",
        "filewasdeleted": "Isang talaksan na may ganitong pangalan ay naikarga dati at nabura. Kailangan mong tingnan ang $1 bago magpatuloy sa pagkarga nito muli.",
        "filename-bad-prefix": "Ang talaksan na ikakarga mo ay nagsisimula sa '''\"$1\"''', na isang hindi naglalarawang pangalan na karaniwang tinatakda ng mga kamerang digital. Paki pili ang isang mas naglalarawang pangalan para sa iyong talaksan.",
        "filename-prefix-blacklist": " #<!-- leave this line exactly as it is --> <pre>\n# Ang palaugnayan ay ang sumusunod:\n#   * Ang lahat ng mga bagay mula sa isang panitik na \"#\" hanggang sa katapusan ng isang guhit ay isang puna\n#   * Bawat isang guhit na mayroong laman ay isang unlapi para sa tipikal na mga pangalan ng talaksan na kusang itinalaga ng mga kamerang dihital\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # ilang mga teleponong mobilo\nIMG # heneriko\nJD # Jenoptik\nMGP # Pentax\nPICT # samu't sari\n #</pre> <!-- leave this line exactly as it is -->",
-       "upload-success-subj": "Matagumpay na pagkakarga",
-       "upload-success-msg": "Matagumpay ang ikinarga mo mula sa [$2].  Makukuha ito mula rito: [[:{{ns:file}}:$1]]",
-       "upload-failure-subj": "Problema sa pagkarga",
-       "upload-failure-msg": "May suliranin sa iyong pagkakarga mula sa [$2]:\n\n$1",
-       "upload-warning-subj": "Babala sa pagkakarga",
-       "upload-warning-msg": "May suliranin sa iyong pagkakarga mula sa [$2]. Maaari kang magbalik sa [[Special:Upload/stash/$1|upload form]] upang itama ang suliraning ito.",
        "upload-proto-error": "Maling protokolo",
        "upload-proto-error-text": "Nangangailangan ang malayong pagkarga ng mga URL na nagsisimula sa <code>http://</code> o <code>ftp://</code>.",
        "upload-file-error": "Panloob na kamalian",
        "pager-older-n": "{{PLURAL:$1|mas lumang 1|mas lumang $1}}",
        "suppress": "Tagapagingat-tago",
        "querypage-disabled": "Hindi pinagagana ang natatanging pahinang ito para sa mga dahilan ng pagganap.",
+       "apisandbox": "Kahong buhanginan ng API",
+       "apisandbox-api-disabled": "Hindi pinagagana ang API sa sityong ito.",
+       "apisandbox-intro": "Gamitin ang pahinang ito upang mag-eksperimento sa pamamagitan ng '''Paglilingkod na pangsangkasaputan ng API ng MediaWiki'''.\nSumangguni sa [//www.mediawiki.org/wiki/API:Main_page dokumentasyon ng API] para sa karagdagan pang mga detalye sa paggamit ng API. Halimbawa: [//www.mediawiki.org/wiki/API#A_simple_example kuhanin ang nilalaman ng isang Pangunahing Pahina]. Pumili ng isang galaw upang makakita ng mas marami pang mga halimbawa.",
+       "apisandbox-submit": "Gumawa ng kahilingan",
+       "apisandbox-reset": "Hawiin",
+       "apisandbox-examples": "Halimbawa",
+       "apisandbox-results": "Kinalabasan",
+       "apisandbox-request-url-label": "Hilingin ang URL:",
+       "apisandbox-request-time": "Oras ng paghiling: $1",
        "booksources": "Mga mapagkukunang aklat",
        "booksources-search-legend": "Maghanap ng mapagkukunang aklat",
        "booksources-isbn": "ISBN:",
        "wlheader-showupdated": "Ipinapakitang may '''makakapal na mga panitik''' ang nabagong/binagong mga pahina mula pa noong huli mong pagdalaw sa kanila",
        "wlnote": "Nasa ibaba ang {{PLURAL:$1|pinakahuling pagbabago|pinakahuling '''$1''' mga pagbabago}} sa loob ng huling {{PLURAL:$2|oras|'''$2''' mga oras}}, magmula noong $3 sa ganap na ika-$4.",
        "wlshowlast": "Ipakita ang huling $1 mga oras $2 mga araw",
-       "watchlistall2": "lahat",
        "watchlist-hide": "Itago",
        "wlshowhideminor": "mga maliliit na edit",
        "wlshowhidebots": "mga bot",
        "version-hook-name": "Pangalan ng pangkawit",
        "version-hook-subscribedby": "Sinuskribi ng/ni/nina",
        "version-version": "($1)",
-       "version-svn-revision": "(r$2)",
        "version-license": "Lisensiya",
        "version-poweredby-credits": "Ang wiking ito ay pinapatakbo ng '''[https://www.mediawiki.org/ MediaWiki]''', karapatang-ari © 2001-$1 $2.",
        "version-poweredby-others": "iba pa",
        "special-characters-group-khmer": "Khmer",
        "mw-widgets-dateinput-placeholder-day": "TTTT-BB-AA",
        "mw-widgets-dateinput-placeholder-month": "TTTT-BB",
-       "api-error-blacklisted": "Paki pumili ng isang naiibang mapaglarawang pamagat."
+       "api-error-blacklisted": "Paki pumili ng isang naiibang mapaglarawang pamagat.",
+       "randomrootpage": "Alin mang pinag-ugatang/pinagmulang pahina"
 }
index 9cb4c89..dadedcc 100644 (file)
        "qbfind": "Эзләү",
        "qbbrowse": "Карау",
        "qbedit": "Үзгәртү",
-       "qbpageoptions": "Ð\90гÓ\80онан Ñ\82одаÑ\80Ñ\88",
+       "qbpageoptions": "Ð\91Ñ\83 Ð±Ð¸Ñ\82",
        "qbmyoptions": "Битләрем",
        "faq": "ЕБС",
        "faqpage": "Project:ЕБС",
index a2a1411..110c281 100644 (file)
        "createacct-reason-ph": "Чому ви створюєте інший обліковий запис",
        "createacct-submit": "Створіть ваш обліковий запис",
        "createacct-another-submit": "Створити обліковий запис",
-       "createacct-benefit-heading": "{{SITENAME}} Ñ\81Ñ\82воÑ\80Ñ\8eÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\82акими Ñ\81амими Ð»Ñ\8eдÑ\8cми, Ñ\8fк Ñ\96 Ð²и.",
+       "createacct-benefit-heading": "{{SITENAME}} Ñ\81Ñ\82воÑ\80Ñ\8eÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\82акими Ñ\81амими Ð»Ñ\8eдÑ\8cми, Ñ\8fк Ñ\96 Ð\92и.",
        "createacct-benefit-body1": "{{PLURAL:$1|редагування|редагування|редагувань}}",
        "createacct-benefit-body2": "{{PLURAL:$1|сторінка|сторінки|сторінок}}",
        "createacct-benefit-body3": "{{PLURAL:$1|дописувач|дописувачі|дописувачів}} цього місяця",
index 6a481a4..104dbeb 100644 (file)
        "changepassword-success": "Malinamposon an pagbal-iw hit imo tigaman-panakob!",
        "changepassword-throttled": "Damo na nga mga paningkamot hin pagsakob an imo ginhimò.\nAlayon paghulat hin $1 san-o ka umutro.",
        "botpasswords": "Mga bot password",
+       "botpasswords-disabled": "Ginparong an mga bot password.",
+       "botpasswords-no-central-id": "Para han paggamit hin mga bot password, kinahanglan ka maglog-in ha centralized account.",
+       "botpasswords-existing": "Aada nga mga bot password.",
+       "botpasswords-createnew": "Pahimo hin bag-o nga bot password",
+       "botpasswords-editexisting": "Igliwat an aada nga bot password",
+       "botpasswords-label-appid": "Ngaran han bot:",
+       "botpasswords-label-create": "Paghimo",
+       "botpasswords-label-update": "Ig-update",
+       "botpasswords-label-cancel": "Pasagda",
+       "botpasswords-label-delete": "Paraa",
+       "botpasswords-label-resetpassword": "Igreset an password",
+       "botpasswords-label-grants": "Mga applicable grant",
+       "botpasswords-label-restrictions": "Mga gindidiri ha paggamit:",
+       "botpasswords-label-grants-column": "Ginhatag",
+       "botpasswords-bad-appid": "An ngaran han bot nga \"$1\" in diri puydi gamiton.",
+       "botpasswords-insert-failed": "Pakyas han pagdugang han ngaran han bot nga \"$1\". Naidugang na ini?",
+       "botpasswords-update-failed": "Pakyas han pag-update han bot nga ngaran nga \"$1\". Ginpara na ini?",
+       "botpasswords-created-title": "Nahimo an bot password",
+       "botpasswords-created-body": "An bot password nga \"$1\" in malinamposon nga nahimo.",
+       "botpasswords-updated-title": "Gin-update an bot password",
+       "botpasswords-updated-body": "An bot password nga \"$1\" in malinamposon nga na-update.",
+       "botpasswords-deleted-title": "Ginpara an bot password",
+       "botpasswords-deleted-body": "An bot password nga \"$1\" in ginpara.",
+       "botpasswords-newpassword": "An bag-o nga password para han pag log-in han <strong>$1</strong> in <strong>$2</strong>. <em>Alayon igrecord ini para han future reference.</em>",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider in waray dinhi.",
+       "botpasswords-restriction-failed": "An mga restriction han bot password in nagpupugong han pag-login hinin.",
+       "botpasswords-not-exist": "An gumaramit nga \"$1\" in waray bot password nga nakangaran hin \"$2\".",
        "resetpass_forbidden": "Diri mababalyoan an mga tigaman-pagsulod",
        "resetpass-no-info": "Kinahanglan mo paglog-in para direkta ka makasakob dinhi nga pakli.",
        "resetpass-submit-loggedin": "Igbal-iw an tigaman-pagsulod",
        "resetpass-temp-password": "Temporaryo nga tigaman-pagsakob:",
        "resetpass-abort-generic": "Ginpugong an pagbal-iw hin tigaman-panakob hin uska ekstensyon.",
        "resetpass-expired": "Naubosan na hin panahon an im tigaman-pansakob.  Alayon paghimo hin bag-o nga tigaman-pansakob basi ka makasakob.",
+       "resetpass-expired-soft": "An imo password in nag-expire ngan kinahanglan ig-reset. Alayon pagpili hin bag-o nga password yana, kun diri pidlita an \"{{int:resetpass-submit-cancel}}\" para ig-reset nuruniyan.",
+       "resetpass-validity-soft": "Diri puydi gamiton an imo password nga: $1\n\nAlayon pagpili hin bag-o nga password yana, kun diri pidlita an \"{{int:resetpass-submit-cancel}}\" para ig-reset nuruniyan.",
        "passwordreset": "igreset an tigaman-hit-pagsulod",
        "passwordreset-text-one": "Kompletoha ini nga porma paramakareset hin imo tigaman-panakob.",
        "passwordreset-text-many": "{{PLURAL:$1|Butanga it usa nga mga surodlan basi makakarawat ko hin temporaryo nga tigaman-pansulod pinaagi ha email.}}",
        "changeemail-password": "An imo {{SITENAME}} password:",
        "changeemail-submit": "Igbalyo an e-mail",
        "changeemail-throttled": "Nakadamo kada pag-log-in. Alayon paghulat hin $1 ugsa ka umutro.",
+       "changeemail-nochange": "Alayon pagbutang hin lain nga bag-o nga email address.",
        "resettokens": "Igrest an mga token",
        "resettokens-text": "Puydi nimo mareset an mga token para makahatag hin pipira nga pribado nga datos nga may pakahisumpay ha imo akawnt dinhi.\nKinahanglan mo ini buhaton kun aksidenti nim nasaro hira ha iba nga tawo o an imo akawnt in nakompromiso.",
        "resettokens-no-tokens": "Waray token nga marereset.",
        "sig_tip": "Imo pirma nga may-ada marka hin oras",
        "hr_tip": "Patumba nga bagis (hinay-hinay la it paggamit)",
        "summary": "Halipotay nga masisiring:",
-       "subject": "Katukiban:",
+       "subject": "Himangrawan:",
        "minoredit": "Gutiay ini nga pagliwat",
        "watchthis": "Bantayi ini nga pakli",
        "savearticle": "Igtipig an pakli",
        "missingcommenttext": "Alayon pagbutang hin komento ha ilarom.",
        "missingcommentheader": "'''Pahinumdom:''' Waray ka humatag hin subject/headline para hini nga komento.  Kun pinduton mo an \"{{int:savearticle}}\" utro, an imo pagliwat in matitipig bisan waray hini.",
        "summary-preview": "Pahiuna nga pagawas han dalikyat nga pulong:",
-       "subject-preview": "Pahiuna nga pagawas hit himangrawon:",
+       "subject-preview": "Pahiuna nga pagawas hit himangrawan:",
        "blockedtitle": "Ginpugngan ini nga gumaramit",
        "blockedtext": "'''An imo agnay-gumaramit o IP address in ginpugngan.'''\n\nAn pagpugong in ginhimo ni $1.\nAn rason nga ginhatag in ''$2''.\n\n* Pagtikang han pagpugong: $8\n* Paghuman han pagpugong: $6\n* Ginpupugngan: $7\n\nPuydi nimo bilngon hi $1 o iba liwat nga [[{{MediaWiki:Grouppage-sysop}}|magdudumara]] para makipaghimangraw hiunong hini nga pagpugong.\nDiri nimo magagamit an \"ig-email ini nga gumaramit\" nga feature antes may-ada balido nga email address nga nakabutang ha imo  [[Special:Preferences|mga preperensya han akawnt]] ngan waray ka pugngi paggamit hini.\nAn imo IP address yana in $3, ngan an imo pagpugong nga ID in #$5.  Alayon la paglakip han ngatanan nga aada ha igbaw nga mga detalye ha bisan ano nga mga pakiana nga karuyag mo buhaton.",
        "autoblockedtext": "An imo IP address in automatiko nga ginpugngan mahitungod nga ini in gingamit hin iba nga gumaramit, nga ginpugngan ni $1.\n\nAn rason nga ginhatag in ''$2''.\n\n* Pagtikang han pagpugong: $8\n* Paghuman han pagpugong: $6\n* Ginpupugngan: $7\n\nPuydi nimo bilngon hi $1 o iba liwat nga [[{{MediaWiki:Grouppage-sysop}}|magdudumara]] para makipaghimangraw hiunong hini nga pagpugong.\n\nGinpapasabot ka nga diri nimo magagamitan an \"ig-email ini nga gumaramit\" nga feature antes may-ada nimo balido nga email address nga nakarehistro ha imo  [[Special:Preferences|mga preperensya han gumaramit]] ngan waray ka pugngi hit paggamit hini.\n\nAn imo IP address yana in $3, ngan an imo pagpugong nga ID in #$5.  Alayon la paglakip han ngatanan nga aada ha igbaw nga mga detalye ha bisan ano nga mga pakiana nga karuyag mo buhaton.",
        "content-model-text": "yano nga teksto",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
+       "content-json-empty-object": "Empty object",
+       "content-json-empty-array": "Empty array",
        "post-expand-template-inclusion-warning": "'''Pahimatngon:''' An batakan nga ginlakip in sobra kadako.\nAn iba nga mga batakan in diri mauupod.",
        "post-expand-template-inclusion-category": "Mga pakli kun diin an mga nahilalakip nga kadako han batakan in nalabaw.",
        "post-expand-template-argument-warning": "'''Pahimatngaon:''' Ini nga pakli in nagsusulod hin pinakaguti usa nga argumento hin batakan nga may-ada sobra nga dako it padako nga kadako.\nIni nga mga argumento in ginlaktawan.",
        "history-feed-empty": "An imo ginpaalayon nga pakli in waray dida.\nBangin ini napara tikang ha wiki, o ginngaranan hin iba.\n\n[[Special:Search|pamilnga ha wiki]] para han may pagkahisumpay nga bag-o nga pakli.",
        "rev-deleted-comment": "(gintanggal an halipotay nga masisiring hiton pagliwat)",
        "rev-deleted-user": "(gintanggal an agnay hiton gumaramit)",
-       "rev-deleted-event": "(gintanggal an talaan han mga buhat)",
+       "rev-deleted-event": "(gintanggal an mga detalye han log)",
        "rev-deleted-user-contribs": "[gintanggal an agnay-hit-gumaramit o IP address - an pagliwat in gintago tikang han mga amot]",
        "rev-deleted-text-permission": "Ini nga rebisyon han pakli in '''ginpara'''.\nAn mga detalye in mabibilngan ha [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].",
        "rev-suppressed-no-diff": "Diri mo makikita ini nga kaibhan tungod nga usa ha mga rebisyon in '''ginpara'''.",
        "revdelete-show-file-submit": "Oo",
        "revdelete-hide-text": "Rebisyon nga sinurat",
        "revdelete-hide-image": "Tagoon an sulod han paypay",
-       "revdelete-hide-name": "Tagoon an buhat ngan kakadtoan",
+       "revdelete-hide-name": "Tagoon an target ngan mga parameter",
        "revdelete-hide-comment": "Halipotay nga masisiring hiton pagliwat",
+       "revdelete-hide-user": "An kanan magliliwat ngaran-gumaramit/IP address",
        "revdelete-radio-same": "(ayaw balyu-e)",
        "revdelete-radio-set": "Tinago",
        "revdelete-radio-unset": "Nakikit-an",
        "rows": "Mga rumbay pahigda:",
        "columns": "Mga rumbay patindog:",
        "searchresultshead": "Bilnga",
+       "stub-threshold-sample-link": "pananglitan",
        "stub-threshold-disabled": "Waray ginpagana",
        "recentchangesdays": "Kadamo hin adlaw nga igpapakita an mga kabag-ohan:",
        "recentchangesdays-max": "Pinakadamo $1 {{PLURAL:$1|ka adlaw|ka mga adlaw}}",
        "prefs-i18n": "Internasyonalisasyon",
        "prefs-signature": "Pirma",
        "prefs-dateformat": "Batakan han petsa",
+       "prefs-timeoffset": "Time offset",
        "prefs-advancedediting": "Mga kasahiran nga pagpipilian",
        "prefs-editor": "Editor",
        "prefs-preview": "Pahiuna nga pakita",
        "right-userrights-interwiki": "Igliwat an mga katungod han gumaramit han mga gumaramit ha iba nga mga wiki",
        "right-siteadmin": "Igtrangka ngan igrangka an database",
        "right-sendemail": "Padad-i hin e-mail ngada ha iba nga mga gumaramit",
+       "grant-group-email": "Padangat hin email",
+       "grant-createaccount": "Pahimo hin mga account",
+       "grant-createeditmovepage": "Paghimo, pagliwat, ngan pagbalhin hin mga pakli",
+       "grant-delete": "Pagpara hin mga pakli, mga rebisyon, ngan mga iginsulod ha log",
        "newuserlogpage": "Talaan han paghimo hin gumaramit",
        "newuserlogpagetext": "Ini an talaan han mga nagkahihimo nga mga gumaramit.",
        "rightslog": "Talaan hin mga katungod han gumaramit",
        "foreign-structured-upload-form-label-own-work": "Buhat ko ini",
        "foreign-structured-upload-form-label-infoform-categories": "Mga kategorya",
        "foreign-structured-upload-form-label-infoform-date": "Petsa",
+       "foreign-structured-upload-form-2-label-intro": "Salamat hit pagdonar hin imahe nga gagamiton ha {{SITENAME}}. Dapat mo la igpadayon kun may ada ini hin pipira nga kondisyones:",
+       "foreign-structured-upload-form-2-label-ownwork": "Dapat an kabug-osan in  <strong>imo hinimo</strong>, diri la kinuha ha Internet",
+       "foreign-structured-upload-form-2-label-noderiv": "Dapat nagsusulod ini hin <strong>waray binuhat hit iba nga tawo</strong>, o inspirado tikang ha ira",
+       "foreign-structured-upload-form-2-label-useful": "Dapat ini <strong>educational nga mahihigamitan</strong> hit pagtudlo ha iba",
+       "foreign-structured-upload-form-2-label-ccbysa": "Dapat ini <strong>OK mahipatik hin kanunay</strong> ha Internet ha ilarom han [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] nga lisensya",
+       "foreign-structured-upload-form-2-label-alternative": "Kun diri ngatanan nga aada ha igbaw tinuod, puydi ka la gihapon maka-upload hini nga file nga nagamit han[https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard], basta la ini mapapailarom han free license.",
        "foreign-structured-upload-form-3-label-yes": "Oo",
        "foreign-structured-upload-form-3-label-no": "Diri",
        "backend-fail-notexists": "Waray ngada an paypay nga $1.",
index ed1b46a..4717a2c 100644 (file)
        "badarticleerror": "呒处垃拉箇只页面进行箇只操作。",
        "cannotdelete": "无处删除页面或图像 \"$1\"。\n渠作兴已经拨别人家删除脱哉。",
        "cannotdelete-title": "\"$1\"箇页删弗爻",
-       "delete-hook-aborted": "删除畀钩子取消。\n渠弗曾畀出解释。",
+       "delete-hook-aborted": "删除畀扩展钩子中止。渠弗曾畀出解释。",
        "no-null-revision": "\"$1\"页呒处建新个修改",
        "badtitle": "坏标题",
        "badtitletext": "所请求页面个标题是无效个、弗存在,跨语言或跨wiki链接个标题错误。渠作兴包含一只或多只弗好用拉标题里向字符。",
        "virus-unknownscanner": "未知个反病毒扫描器:",
        "logouttext": "<strong>侬已经登出哉。</strong>\n\n请注意有星页面作兴还是会得搭侬登出前头一样显示,一脚到侬个浏览器缓存清脱为止。",
        "welcomeuser": "走来赞,$1!",
-       "welcomecreation-msg": "你个账号建起来哉。\n覅忘记哉走去改你个[[Special:Preferences|{{SITENAME}}个私人偏好]]。",
+       "welcomecreation-msg": "倷个账号建立好哉。倷可以更改自家个{{SITENAME}}[[Special:Preferences|偏好设定]]。",
        "yourname": "用户名:",
        "userlogin-yourname": "用户名",
        "userlogin-yourname-ph": "打进侬个用户名",
        "noemailcreate": "侬要提供只有效个电子邮件地址",
        "passwordsent": "用户\"$1\"个新密码已经寄往登记个电子邮件地址。\n请收着仔再登录。",
        "blocked-mailpassword": "侬个IP地址处于查封状态,弗允许编辑,为仔安全起见,密码恢复功能已经禁用。",
-       "eauthentsent": "一封确认信已经发送到指定个电子邮箱地址。\nå\9e\83æ\8b\89å\85¶å®\83é\82®ä»¶å\8f\91é\80\81å\88°ç®\87å\8fªè´¦æ\88·ä¹\8bå\89\8dï¼\8c侬å¿\85é¡»é¦\96å\85\88æ\8c\89ç\85§ç®\87å°\81ä¿¡é\87\8cå\90\91个æ\8c\87示ï¼\8c确认ç®\87å\8fªé\82®ç®±ç\9c\9få®\9eæ\9c\89æ\95\88ã\80\82",
+       "eauthentsent": "一封确认信已经发送到指定个电子邮箱地址。å\9e\83æ\8b\89å\85¶ä»\96é\82®ä»¶å\8f\91é\80\81å\88°æ\9c¬è´¦å\8f·ä¹\8bå\89\8dï¼\8c侬å¿\85é¡»é¦\96å\85\88æ\8c\89ç\85§ç®\87å°\81ä¿¡é\87\8cå\90\91个æ\8c\87示ï¼\8c确认ç®\87å\8fªé\82®ç®±ç\9c\9få®\9eæ\9c\89æ\95\88ã\80\82",
        "throttled-mailpassword": "密码转设电子信徕最近$1个钟头里发畀你侬哉。保险点,密码转设电子信$1个钟头只一垡好发。",
        "mailerror": "发送邮件错误:$1",
        "acct_creation_throttle_hit": "弗好意思,使用箇只IP个访客已经创建仔$1只账号,迭个是箇段辰光里向所允许个最大值。箇咾使用箇只IP个地址个访客暂时弗好再创建账户。",
        "accmailtitle": "密码已发送哉。",
        "accmailtext": "已经为[[User talk:$1|$1]]产生只随机密码,并且已经发送到$2。登录之后,侬可以垃拉<em>[[Special:ChangePassword|箇只页面]]</em>更改密码。",
        "newarticle": "(新)",
-       "newarticletext": "倷跟仔链接来着一个还弗勒里个页面。\n要创建该页面呢,就勒下底个框框里向开始写([$1 帮助页面]浪有更加多个信息)。\n要是倷是弗用心到该搭个说话,只要点击倷浏览器个'''返回'''揿钮。",
+       "newarticletext": "倷跟著链接来着一个还弗勒里个页面。要创建该页面呢,就勒下底个框里向开始写([$1 帮助页面]浪有更加多个信息)。要是倷是弗用心到该𡍲个说话,请点击浏览器个<strong>返回</strong>揿钮。",
        "anontalkpagetext": "---- ''箇是一个还弗曾建立账户个匿名用户个讨论页, 箇咾我伲只好用IP地址来搭渠联络。该IP地址可能由几名用户共享。如果侬是一名匿名用户并认为箇只页面高头个评语搭侬弗搭界,请 [[Special:UserLogin/signup|创建新账户]]或[[Special:UserLogin|登录]]来避免垃拉将来搭其他匿名用户混淆。''",
        "noarticletext": "箇只页面目前呒没文本。侬可以垃拉其他页面高头[[Special:Search/{{PAGENAME}}|寻该只标题]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 寻相关日志]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 编辑此页]</span>。",
        "noarticletext-nopermission": "箇只页面目前还呒不文本。侬好来别个页面[[Special:Search/{{PAGENAME}}|寻箇页标题]],或者<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 寻相关日志]</span>,但必过侬呒不权限建立箇只页面。",
        "previewconflict": "箇个预览显示了上头文字编辑区里向个内容。渠会得垃拉侬保存之后出现。",
        "session_fail_preview": "'''弗好意思!由于会话数据落失,我伲弗好处理侬个编辑。'''请重试。如果再次失败,请尝试[[Special:UserLogout|登出]]之后重新登录。",
        "session_fail_preview_html": "'''弗好意思!我伲弗好处理侬垃拉进程数据落失辰光个编辑。'''\n\n''由于{{SITENAME}}允许使用原始个 HTML,为著防范 JavaScript 攻击,预览已畀隐藏。''\n\n'''如果这是一次合法的编辑,请重新进行尝试。'''如果还不行,请 [[Special:UserLogout|退出]]并重新登录。",
-       "token_suffix_mismatch": "'''由于侬用户端里向个编辑令牌毁损仔一些标点符号字元,为防止编辑个文字损坏,侬个编辑已经畀回头。'''\n箇种情况通常出现垃拉使用含有交关bug、以网络为主个匿名代理服务个辰光。",
+       "token_suffix_mismatch": "<strong>由于侬用户端里向个编辑令牌毁损仔一些标点符号字元,为防止编辑个文字损坏,侬个编辑已经畀回头。</strong>箇种情况通常出现垃拉使用含有交关bug、以网络为主个匿名代理服务个辰光。",
        "editing": "来里编写$1",
        "creating": "创建“$1”",
        "editingsection": "来里编辑$1(段落)",
        "edit-no-change": "侬个编辑畀忽略,因为文本弗曾有改动。",
        "postedit-confirmation-created": "页面已创建。",
        "postedit-confirmation-restored": "页面已恢复。",
-       "postedit-confirmation-saved": "侬个编辑已保存。",
+       "postedit-confirmation-saved": "倷个编辑已经保存哉。",
        "edit-already-exists": "弗好创建新页面。\n已经有垃许。",
        "defaultmessagetext": "默认消息文本",
        "invalid-content-data": "无效内容数据",
        "grant-createaccount": "建立账号",
        "grant-createeditmovepage": "建立、编辑搭著捅荡页面",
        "grant-rollback": "畀修改擂轉到頁面",
-       "grant-sendemail": "發電子信畀各許用戶",
+       "grant-sendemail": "发电子邮件畀其他用户",
        "newuserlogpage": "用户创建日志",
        "newuserlogpagetext": "箇是用户创建个记录。",
        "rightslog": "用户权限日志",
        "nolinkstoimage": "呒不页面链接到该只文件。",
        "linkstoimage-redirect": "$1(文件轉戳到)$2",
        "sharedupload": "箇只文件来源于$1,渠作兴垃拉其它项目当中拨应用。",
-       "sharedupload-desc-here": "箇文件$1里个,作兴会畀别个项目使用。\n渠个[$2 描述页]里个说明显示如下。",
+       "sharedupload-desc-here": "箇文件$1里个,作兴会畀别个项目使用。渠个[$2 描述页]里个说明显示如下。",
        "uploadnewversion-linktext": "上载该文件个新版",
        "upload-disallowed-here": "你弗可以覆盖伊只文件。",
        "filerevert": "恢复$1",
        "newpages": "新页",
        "newpages-username": "用户名:",
        "ancientpages": "顶顶老个页面",
-       "move": "移å\88°",
+       "move": "移å\8a¨",
        "movethispage": "捅该只页面",
        "pager-newer-n": "新$1次",
        "pager-older-n": "旧$1次",
        "filemissing": "文件寻弗着哉",
        "tooltip-pt-userpage": "{{GENDER:|侬个用户}}页",
        "tooltip-pt-mytalk": "{{GENDER:|侬}}个讨论页",
+       "tooltip-pt-anontalk": "有关箇只IP地址编辑个讨论",
        "tooltip-pt-preferences": "{{GENDER:|侬}}个设置",
        "tooltip-pt-watchlist": "监控修改页面列表",
        "tooltip-pt-mycontris": "{{GENDER:|侬}}个贡献列表",
+       "tooltip-pt-anoncontribs": "箇只IP地址个编辑清单",
        "tooltip-pt-login": "鼓励大家登录进来,不过也弗是板定要求",
        "tooltip-pt-logout": "登出",
        "tooltip-pt-createaccount": "建议你建立一个账号并登录,但必过箇弗是板要个",
        "tooltip-ca-nstab-category": "望分类页",
        "tooltip-minoredit": "标作小编写",
        "tooltip-save": "保存侬个修改",
-       "tooltip-preview": "预览侬个改变。保存前头请检查一遍。",
+       "tooltip-preview": "预览倷个更改。请勒拉保存前头用用俚。",
        "tooltip-diff": "显示侬对文本个修改",
        "tooltip-compareselectedversions": "查看本页面两只选定个修订版个差异。",
        "tooltip-watch": "拿箇页加到侬个关注表里",
        "tooltip-summary": "打进短摘要",
        "interlanguage-link-title": "̩$1 - $2",
        "anonymous": "{{SITENAME}}上个匿名{{PLURAL:$1|用户}}",
-       "simpleantispam-label": "反垃圾检查。\n<strong>覅</strong>加进伊个!",
+       "simpleantispam-label": "反垃圾检查。<strong>弗要</strong>填伊个!",
        "pageinfo-toolboxlink": "页面信息",
        "deletedrevision": "拨删脱个旧修订 $1",
        "previousdiff": "←老版",
        "logentry-newusers-create": "用户账号$1畀{{GENDER:$2|创建}}",
        "logentry-newusers-create2": "用户账号$3畀$1{{GENDER:$2|创建}}",
        "logentry-newusers-autocreate": "用户账号$1畀自动{{GENDER:$2|创建}}",
+       "logentry-rights-rights": "$1{{GENDER:$2|更改}}$3个用户组从$4到$5",
        "logentry-upload-upload": "$1{{GENDER:$2|上传}}$3",
        "rightsnone": "(呒)",
        "revdelete-summary": "编辑摘要",
index aed8ee5..b6f37a2 100644 (file)
        "mergehistory-empty": "没有可以合并的版本。",
        "mergehistory-done": "$1的$3个{{PLURAL:$3|版本}}{{PLURAL:$3|已}}成功合并至[[:$2]]。",
        "mergehistory-fail": "不可以进行历史合并,请重新检查该页面以及时间参数。",
+       "mergehistory-fail-bad-timestamp": "时间戳无效。",
+       "mergehistory-fail-invalid-source": "来源页面无效。",
+       "mergehistory-fail-invalid-dest": "目标页面无效。",
+       "mergehistory-fail-no-change": "历史合并未合并任何修订版本。请重新检查页面和时间参数。",
+       "mergehistory-fail-permission": "没有足够权限合并历史。",
+       "mergehistory-fail-self-merge": "来源页面与目标页面相同。",
+       "mergehistory-fail-timestamps-overlap": "来源修订版本重复,或在目标修订版本之后出现。",
        "mergehistory-fail-toobig": "由于超出$1的限制而无法执行历史合并,$1个版本将被移动。",
        "mergehistory-no-source": "来源页面$1不存在。",
        "mergehistory-no-destination": "目的页面$1不存在。",
index 4adf154..fb26675 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Goan Konkani (à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gova Konknni)
+/** Goan Konkani (à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gõychi Konknni)
  *
  * To improve a translation please visit https://translatewiki.net
  *
index b5cc343..190bc4d 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Goan Konkani - Devanagari script (à¤\97à¥\8bवा कोंकणी)
+/** Goan Konkani - Devanagari script (à¤\97à¥\8bà¤\82यà¤\9aà¥\80 कोंकणी)
  *
  * To improve a translation please visit https://translatewiki.net
  *
index cc8069d..33008d1 100644 (file)
@@ -1,6 +1,5 @@
 # Custom tags for JSDuck 5.x
 # See also:
-# - https://github.com/senchalabs/jsduck/wiki/Tags
 # - https://github.com/senchalabs/jsduck/wiki/Custom-tags
 # - https://github.com/senchalabs/jsduck/wiki/Custom-tags/7f5c32e568eab9edc8e3365e935bcb836cb11f1d
 require 'jsduck/tag/tag'
index b2f2577..756de27 100644 (file)
@@ -39,7 +39,7 @@ class UpdateSearchIndex extends Maintenance {
 
        public function __construct() {
                parent::__construct();
-               $this->setDescription( 'Script for periodic off-peak updating of the search index' );
+               $this->addDescription( 'Script for periodic off-peak updating of the search index' );
                $this->addOption( 's', 'starting timestamp', false, true );
                $this->addOption( 'e', 'Ending timestamp', false, true );
                $this->addOption(
index 5a8257c..33a2039 100644 (file)
@@ -9,16 +9,16 @@
     "grunt": "0.4.5",
     "grunt-cli": "0.1.13",
     "grunt-banana-checker": "0.4.0",
-    "grunt-contrib-copy": "0.8.1",
-    "grunt-contrib-jshint": "0.11.3",
+    "grunt-contrib-copy": "0.8.2",
+    "grunt-contrib-jshint": "0.12.0",
     "grunt-contrib-watch": "0.6.1",
-    "grunt-jscs": "2.6.0",
+    "grunt-jscs": "2.7.0",
     "grunt-jsonlint": "1.0.7",
     "grunt-karma": "0.12.1",
     "karma": "0.13.19",
     "karma-chrome-launcher": "0.2.2",
     "karma-firefox-launcher": "0.1.7",
-    "karma-qunit": "0.1.5",
+    "karma-qunit": "0.1.9",
     "qunitjs": "1.18.0"
   }
 }
index e789565..ff2a2b3 100644 (file)
@@ -5,7 +5,8 @@
                        "KuboF",
                        "Shirayuki",
                        "Yekrats",
-                       "Kvardek du"
+                       "Kvardek du",
+                       "Psychoslave"
                ]
        },
        "ooui-outline-control-move-down": "Movi eron suben",
@@ -22,5 +23,6 @@
        "ooui-dialog-process-continue": "Daŭrigi",
        "ooui-selectfile-button-select": "Elekti dosieron",
        "ooui-selectfile-not-supported": "Dosieroselekto ne estas subtenata.",
-       "ooui-selectfile-placeholder": "Vi ne selektis dosieron"
+       "ooui-selectfile-placeholder": "Vi ne selektis dosieron",
+       "ooui-selectfile-dragdrop-placeholder": "Ĵetu dosieron ĉi tie."
 }
index a61083b..31344be 100644 (file)
@@ -17,6 +17,8 @@
        "ooui-dialog-process-dismiss": "Didi",
        "ooui-dialog-process-retry": "Itti deebi'ii yaali",
        "ooui-dialog-process-continue": "Itti fufi",
+       "ooui-selectfile-button-select": "Faayilii filadhu",
        "ooui-selectfile-not-supported": "Faayilii filachuun hin danda'amu.",
-       "ooui-selectfile-placeholder": "Faayiliin wayiiyyuu hin filatamne"
+       "ooui-selectfile-placeholder": "Faayiliin wayiiyyuu hin filatamne",
+       "ooui-selectfile-dragdrop-placeholder": "Faayilii as kaa'i"
 }
index 12c77e0..dc14339 100644 (file)
@@ -17,6 +17,7 @@
        "ooui-dialog-process-retry": "ٻيهر ڪوشش ڪريو",
        "ooui-dialog-process-continue": "جاري رکو",
        "ooui-selectfile-button-select": "ڪو فائيل چونڊِو",
+       "ooui-selectfile-not-supported": "فائيل جي چونڊ سپورٽ نٿي ڪئي وڃي",
        "ooui-selectfile-placeholder": "ڪوبه فائيل چونڊيو نه ويو آهي",
        "ooui-selectfile-dragdrop-placeholder": "فائيل کي هتي ڪيرايو"
 }
index 704a186..bdf6a64 100644 (file)
@@ -15,5 +15,7 @@
        "ooui-dialog-process-error": "Nešto je pošlo naopako",
        "ooui-dialog-process-dismiss": "Odbaci",
        "ooui-dialog-process-retry": "Pokušaj ponovo",
-       "ooui-dialog-process-continue": "Nastavi"
+       "ooui-dialog-process-continue": "Nastavi",
+       "ooui-selectfile-button-select": "Izaberi datoteku",
+       "ooui-selectfile-placeholder": "Nije izabrana nijedna datoteka"
 }
index 83ffbd7..3637818 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
index 168ab71..734ff71 100644 (file)
@@ -1,53 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-ms-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-o-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-element-hidden {
        display: none !important;
 }
index fe0d45b..51abc2d 100644 (file)
@@ -1,37 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-element-hidden {
        display: none !important;
 }
        left: 0.2em;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
-       background: #dddddd;
+       background-color: #dddddd;
        color: #ffffff;
        border: 1px solid #dddddd;
 }
index 2d5ed3a..5a43228 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
@@ -586,7 +586,7 @@ OO.ui.Element.static.infuse = function ( idOrNode ) {
  */
 OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
        // look for a cached result of a previous infusion.
-       var id, $elem, data, cls, parts, parent, obj, top, state;
+       var id, $elem, data, cls, parts, parent, obj, top, state, infusedChildren;
        if ( typeof idOrNode === 'string' ) {
                id = idOrNode;
                $elem = $( document.getElementById( id ) );
@@ -597,12 +597,28 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
        if ( !$elem.length ) {
                throw new Error( 'Widget not found: ' + id );
        }
-       data = $elem.data( 'ooui-infused' ) || $elem[ 0 ].oouiInfused;
+       if ( $elem[ 0 ].oouiInfused ) {
+               $elem = $elem[ 0 ].oouiInfused;
+       }
+       data = $elem.data( 'ooui-infused' );
        if ( data ) {
                // cached!
                if ( data === true ) {
                        throw new Error( 'Circular dependency! ' + id );
                }
+               if ( domPromise ) {
+                       // pick up dynamic state, like focus, value of form inputs, scroll position, etc.
+                       state = data.gatherPreInfuseState( $elem );
+                       // restore dynamic state after the new element is re-inserted into DOM under infused parent
+                       domPromise.done( data.restorePreInfuseState.bind( data, state ) );
+                       infusedChildren = $elem.data( 'ooui-infused-children' );
+                       if ( infusedChildren && infusedChildren.length ) {
+                               infusedChildren.forEach( function ( data ) {
+                                       var state = data.gatherPreInfuseState( $elem );
+                                       domPromise.done( data.restorePreInfuseState.bind( data, state ) );
+                               } );
+                       }
+               }
                return data;
        }
        data = $elem.attr( 'data-ooui' );
@@ -654,10 +670,17 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
        }
        $elem.data( 'ooui-infused', true ); // prevent loops
        data.id = id; // implicit
+       infusedChildren = [];
        data = OO.copy( data, null, function deserialize( value ) {
+               var infused;
                if ( OO.isPlainObject( value ) ) {
                        if ( value.tag ) {
-                               return OO.ui.Element.static.unsafeInfuse( value.tag, domPromise );
+                               infused = OO.ui.Element.static.unsafeInfuse( value.tag, domPromise );
+                               infusedChildren.push( infused );
+                               // Flatten the structure
+                               infusedChildren.push.apply( infusedChildren, infused.$element.data( 'ooui-infused-children' ) || [] );
+                               infused.$element.removeData( 'ooui-infused-children' );
+                               return infused;
                        }
                        if ( value.html ) {
                                return new OO.ui.HtmlSnippet( value.html );
@@ -681,11 +704,12 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
                        // This element is now gone from the DOM, but if anyone is holding a reference to it,
                        // let's allow them to OO.ui.infuse() it and do what they expect (T105828).
                        // Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design.
-                       $elem[ 0 ].oouiInfused = obj;
+                       $elem[ 0 ].oouiInfused = obj.$element;
                }
                top.resolve();
        }
        obj.$element.data( 'ooui-infused', obj );
+       obj.$element.data( 'ooui-infused-children', infusedChildren );
        // set the 'data-ooui' attribute so we can identify infused widgets
        obj.$element.attr( 'data-ooui', '' );
        // restore dynamic state after the new element is inserted into DOM
@@ -1084,7 +1108,7 @@ OO.ui.Element.static.scrollIntoView = function ( el, config ) {
                }
        }
        if ( !$.isEmptyObject( anim ) ) {
-               $sc.stop( true ).animate( anim, config.duration || 'fast' );
+               $sc.stop( true ).animate( anim, config.duration === undefined ? 'fast' : config.duration );
                if ( callback ) {
                        $sc.queue( function ( next ) {
                                callback();
@@ -1808,7 +1832,7 @@ OO.ui.mixin.ButtonElement.prototype.onMouseDown = function ( e ) {
  * Handles mouse up events.
  *
  * @protected
- * @param {jQuery.Event} e Mouse up event
+ * @param {MouseEvent} e Mouse up event
  */
 OO.ui.mixin.ButtonElement.prototype.onMouseUp = function ( e ) {
        if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
@@ -1854,7 +1878,7 @@ OO.ui.mixin.ButtonElement.prototype.onKeyDown = function ( e ) {
  * Handles key up events.
  *
  * @protected
- * @param {jQuery.Event} e Key up event
+ * @param {KeyboardEvent} e Key up event
  */
 OO.ui.mixin.ButtonElement.prototype.onKeyUp = function ( e ) {
        if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
@@ -4967,7 +4991,7 @@ OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
  * Handle mouse up events.
  *
  * @private
- * @param {jQuery.Event} e Mouse up event
+ * @param {MouseEvent} e Mouse up event
  */
 OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
        var item;
@@ -4995,7 +5019,7 @@ OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
  * Handle mouse move events.
  *
  * @private
- * @param {jQuery.Event} e Mouse move event
+ * @param {MouseEvent} e Mouse move event
  */
 OO.ui.SelectWidget.prototype.onMouseMove = function ( e ) {
        var item;
@@ -5007,7 +5031,6 @@ OO.ui.SelectWidget.prototype.onMouseMove = function ( e ) {
                        this.selecting = item;
                }
        }
-       return false;
 };
 
 /**
@@ -5043,7 +5066,7 @@ OO.ui.SelectWidget.prototype.onMouseLeave = function () {
  * Handle key down events.
  *
  * @protected
- * @param {jQuery.Event} e Key down event
+ * @param {KeyboardEvent} e Key down event
  */
 OO.ui.SelectWidget.prototype.onKeyDown = function ( e ) {
        var nextItem,
@@ -5093,7 +5116,6 @@ OO.ui.SelectWidget.prototype.onKeyDown = function ( e ) {
                }
 
                if ( handled ) {
-                       // Can't just return false, because e is not always a jQuery event
                        e.preventDefault();
                        e.stopPropagation();
                }
@@ -5135,7 +5157,7 @@ OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () {
  * Handle key press events.
  *
  * @protected
- * @param {jQuery.Event} e Key press event
+ * @param {KeyboardEvent} e Key press event
  */
 OO.ui.SelectWidget.prototype.onKeyPress = function ( e ) {
        var c, filter, item;
@@ -5183,7 +5205,8 @@ OO.ui.SelectWidget.prototype.onKeyPress = function ( e ) {
                item.scrollElementIntoView();
        }
 
-       return false;
+       e.preventDefault();
+       e.stopPropagation();
 };
 
 /**
@@ -5846,7 +5869,7 @@ OO.mixinClass( OO.ui.MenuSelectWidget, OO.ui.mixin.ClippableElement );
  * Handles document mouse down events.
  *
  * @protected
- * @param {jQuery.Event} e Key down event
+ * @param {MouseEvent} e Mouse down event
  */
 OO.ui.MenuSelectWidget.prototype.onDocumentMouseDown = function ( e ) {
        if (
@@ -6058,6 +6081,10 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
                        }
                        this.toggleClipping( true );
 
+                       if ( this.getSelectedItem() ) {
+                               this.getSelectedItem().scrollElementIntoView( { duration: 0 } );
+                       }
+
                        // Auto-hide
                        if ( this.autoHide ) {
                                this.getElementDocument().addEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
@@ -6155,7 +6182,7 @@ OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
        // Events
        this.$handle.on( {
                click: this.onClick.bind( this ),
-               keypress: this.onKeyPress.bind( this )
+               keydown: this.onKeyDown.bind( this )
        } );
        this.menu.connect( this, { select: 'onMenuSelect' } );
 
@@ -6227,14 +6254,25 @@ OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
 };
 
 /**
- * Handle key press events.
+ * Handle key down events.
  *
  * @private
- * @param {jQuery.Event} e Key press event
+ * @param {jQuery.Event} e Key down event
  */
-OO.ui.DropdownWidget.prototype.onKeyPress = function ( e ) {
-       if ( !this.isDisabled() &&
-               ( ( e.which === OO.ui.Keys.SPACE && !this.menu.isVisible() ) || e.which === OO.ui.Keys.ENTER )
+OO.ui.DropdownWidget.prototype.onKeyDown = function ( e ) {
+       if (
+               !this.isDisabled() &&
+               (
+                       e.which === OO.ui.Keys.ENTER ||
+                       (
+                               !this.menu.isVisible() &&
+                               (
+                                       e.which === OO.ui.Keys.SPACE ||
+                                       e.which === OO.ui.Keys.UP ||
+                                       e.which === OO.ui.Keys.DOWN
+                               )
+                       )
+               )
        ) {
                this.menu.toggle();
                return false;
@@ -6684,7 +6722,6 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) {
                .addClass( 'oo-ui-inputWidget' )
                .append( this.$input );
        this.setValue( config.value );
-       this.setAccessKey( config.accessKey );
        if ( config.dir ) {
                this.setDir( config.dir );
        }
@@ -6827,30 +6864,6 @@ OO.ui.InputWidget.prototype.setValue = function ( value ) {
        return this;
 };
 
-/**
- * Set the input's access key.
- * FIXME: This is the same code as in OO.ui.mixin.ButtonElement, maybe find a better place for it?
- *
- * @param {string} accessKey Input's access key, use empty string to remove
- * @chainable
- */
-OO.ui.InputWidget.prototype.setAccessKey = function ( accessKey ) {
-       accessKey = typeof accessKey === 'string' && accessKey.length ? accessKey : null;
-
-       if ( this.accessKey !== accessKey ) {
-               if ( this.$input ) {
-                       if ( accessKey !== null ) {
-                               this.$input.attr( 'accesskey', accessKey );
-                       } else {
-                               this.$input.removeAttr( 'accesskey' );
-                       }
-               }
-               this.accessKey = accessKey;
-       }
-
-       return this;
-};
-
 /**
  * Clean up incoming value.
  *
index e20b956..27a4657 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
index f433b15..0021721 100644 (file)
@@ -1,53 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-ms-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-o-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
        z-index: 4;
index 1f4262c..7b1e5b7 100644 (file)
@@ -1,37 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
        z-index: 4;
index a88984a..1d1ed87 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
@@ -1030,7 +1030,7 @@ OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) {
  * Handle captured mouse up and key up events.
  *
  * @protected
- * @param {Event} e Mouse up or key up event
+ * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
  */
 OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
        this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
@@ -1044,7 +1044,7 @@ OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
  * Handle mouse up and key up events.
  *
  * @protected
- * @param {jQuery.Event} e Mouse up or key up event
+ * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
  */
 OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
        var tool = this.getTargetTool( e );
@@ -1055,7 +1055,8 @@ OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
        ) {
                this.pressed.onSelect();
                this.pressed = null;
-               return false;
+               e.preventDefault();
+               e.stopPropagation();
        }
 
        this.pressed = null;
@@ -1831,7 +1832,7 @@ OO.ui.PopupToolGroup.prototype.setDisabled = function () {
  * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
  *
  * @protected
- * @param {jQuery.Event} e Mouse up or key up event
+ * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
  */
 OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
        // Only deactivate when clicking outside the dropdown element
index 252e402..071e33b 100644 (file)
@@ -1,53 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-ms-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-o-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-draggableElement {
        cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
 }
 .oo-ui-progressBarWidget.oo-ui-widget-disabled {
        opacity: 0.6;
 }
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
 .oo-ui-selectFileWidget {
        display: inline-block;
        vertical-align: middle;
 }
 .oo-ui-selectFileWidget-dropTarget {
        cursor: default;
+       height: 5.5em;
+       text-align: left;
+       padding: 0;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
-       cursor: pointer;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+       display: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+       height: 5.5em;
+       width: 5.5em;
+       position: absolute;
+       background-size: cover;
+       background-position: center center;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+       background-size: auto;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+       opacity: 0.4;
+       background-color: #cccccc;
+       height: 5.5em;
+       width: 5.5em;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       border: none;
+       background: none;
+       display: block;
+       height: 100%;
+       width: auto;
+       margin-left: 5.5em;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+       position: relative;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
+       display: block;
+       float: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       display: block;
+       float: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       position: absolute;
+       right: 0.5em;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
+       text-align: center;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
+       display: block;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       display: none;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+       display: block;
+       margin: 0.7em;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       text-align: center;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       margin: 0;
 }
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
 .oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
        right: 2em;
 }
-.oo-ui-selectFileWidget-dropTarget {
-       line-height: 3.5em;
-       background-color: #ffffff;
-       border: 1px dashed #aaaaaa;
-       padding: 0.5em 1em;
-       margin-bottom: 0.5em;
-       text-align: center;
-       vertical-align: middle;
-}
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover,
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop oo-ui-selectfilewidget-droptarget {
+.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop .oo-ui-selectFileWidget-dropTarget {
        background-color: #e1f3ff;
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
        color: #cccccc;
        text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
 }
+.oo-ui-selectFileWidget-dropTarget {
+       background-color: #ffffff;
+       border: 1px solid #aaaaaa;
+       margin-bottom: 0.5em;
+       vertical-align: middle;
+       border-radius: 0.25em;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
+       border-style: dashed;
+}
 .oo-ui-outlineOptionWidget {
        position: relative;
        cursor: pointer;
        color: #555555;
        border-radius: 0.25em;
 }
-.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
-       cursor: pointer;
-}
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
-       cursor: default;
-}
 .oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
+       display: inline-block;
        text-overflow: ellipsis;
        overflow: hidden;
 }
+.oo-ui-capsuleItemWidget .oo-ui-buttonElement {
+       margin-top: -1.6em;
+       padding-left: 0.3em;
+}
+.oo-ui-capsuleItemWidget:focus {
+       outline: none;
+       border-color: #087ecc;
+}
 .oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
        padding-right: 1.3375em;
 }
index d5f8298..8475dd1 100644 (file)
@@ -1,37 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-draggableElement {
        cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
 }
 .oo-ui-progressBarWidget.oo-ui-widget-disabled {
        opacity: 0.6;
 }
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
 .oo-ui-selectFileWidget {
        display: inline-block;
        vertical-align: middle;
 }
 .oo-ui-selectFileWidget-dropTarget {
        cursor: default;
+       height: 5.5em;
+       text-align: left;
+       padding: 0;
 }
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget {
-       cursor: pointer;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+       display: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+       height: 5.5em;
+       width: 5.5em;
+       position: absolute;
+       background-size: cover;
+       background-position: center center;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+       background-size: auto;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+       opacity: 0.4;
+       background-color: #cccccc;
+       height: 5.5em;
+       width: 5.5em;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       border: none;
+       background: none;
+       display: block;
+       height: 100%;
+       width: auto;
+       margin-left: 5.5em;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+       position: relative;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileName {
+       display: block;
+       float: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+       display: block;
+       float: none;
+}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+       position: absolute;
+       right: 0.5em;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
+       text-align: center;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
+       display: block;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       display: none;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+       display: block;
+       margin: 0.7em;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
+       text-align: center;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
+       margin: 0;
 }
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-clearButton,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-clearButton {
 .oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
        right: 2em;
 }
-.oo-ui-selectFileWidget-dropTarget {
-       line-height: 3.5em;
-       background-color: #ffffff;
-       border: 1px dashed #cccccc;
-       padding: 0.5em 1em;
-       margin-bottom: 0.5em;
-       text-align: center;
-       vertical-align: middle;
-}
-.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled .oo-ui-selectFileWidget-dropTarget:hover {
-       background-color: #eeeeee;
-}
 .oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop .oo-ui-selectFileWidget-dropTarget {
        background: rgba(52, 123, 255, 0.1);
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
 }
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
+       color: #cccccc;
+       text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget-dropTarget {
+       background-color: #ffffff;
+       border: 1px solid #cccccc;
+       margin-bottom: 0.5em;
+       vertical-align: middle;
+       overflow: hidden;
+       border-radius: 2px;
+}
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-dropTarget {
+       background-color: #eeeeee;
+       border-style: dashed;
+}
 .oo-ui-outlineOptionWidget {
        position: relative;
        cursor: pointer;
        color: #555555;
        border-radius: 2px;
 }
-.oo-ui-capsuleItemWidget > .oo-ui-iconElement-icon {
-       cursor: pointer;
-}
-.oo-ui-capsuleItemWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon {
-       cursor: default;
-}
 .oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
-       display: block;
+       display: inline-block;
        text-overflow: ellipsis;
        overflow: hidden;
 }
+.oo-ui-capsuleItemWidget .oo-ui-buttonElement {
+       margin-top: -1.6em;
+       padding-left: 0.3em;
+}
+.oo-ui-capsuleItemWidget:focus {
+       outline: none;
+       border-color: #347bff;
+}
 .oo-ui-capsuleItemWidget.oo-ui-indicatorElement > .oo-ui-labelElement-label {
        padding-right: 1.3375em;
 }
index 5dbca20..521dfbb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
@@ -3393,7 +3393,6 @@ OO.mixinClass( OO.ui.TabSelectWidget, OO.ui.mixin.TabIndexedElement );
  * @class
  * @extends OO.ui.Widget
  * @mixins OO.ui.mixin.ItemWidget
- * @mixins OO.ui.mixin.IndicatorElement
  * @mixins OO.ui.mixin.LabelElement
  * @mixins OO.ui.mixin.FlaggedElement
  * @mixins OO.ui.mixin.TabIndexedElement
@@ -3408,33 +3407,37 @@ OO.ui.CapsuleItemWidget = function OoUiCapsuleItemWidget( config ) {
        // Parent constructor
        OO.ui.CapsuleItemWidget.parent.call( this, config );
 
-       // Properties (must be set before mixin constructor calls)
-       this.$indicator = $( '<span>' );
-
        // Mixin constructors
        OO.ui.mixin.ItemWidget.call( this );
-       OO.ui.mixin.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$indicator, indicator: 'clear' } ) );
        OO.ui.mixin.LabelElement.call( this, config );
        OO.ui.mixin.FlaggedElement.call( this, config );
-       OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );
+       OO.ui.mixin.TabIndexedElement.call( this, config );
 
        // Events
-       this.$indicator.on( {
-               keydown: this.onCloseKeyDown.bind( this ),
-               click: this.onCloseClick.bind( this )
-       } );
+       this.closeButton = new OO.ui.ButtonWidget( {
+               framed: false,
+               indicator: 'clear',
+               tabIndex: -1
+       } ).on( 'click', this.onCloseClick.bind( this ) );
+
+       this.on( 'disable', function ( disabled ) {
+               this.closeButton.setDisabled( disabled );
+       }.bind( this ) );
 
        // Initialization
        this.$element
+               .on( {
+                       click: this.onClick.bind( this ),
+                       keydown: this.onKeyDown.bind( this )
+               } )
                .addClass( 'oo-ui-capsuleItemWidget' )
-               .append( this.$indicator, this.$label );
+               .append( this.$label, this.closeButton.$element );
 };
 
 /* Setup */
 
 OO.inheritClass( OO.ui.CapsuleItemWidget, OO.ui.Widget );
 OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.ItemWidget );
-OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.IndicatorElement );
 OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.LabelElement );
 OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.FlaggedElement );
 OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.TabIndexedElement );
@@ -3443,33 +3446,54 @@ OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.TabIndexedElement );
 
 /**
  * Handle close icon clicks
- * @param {jQuery.Event} event
  */
 OO.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
        var element = this.getElementGroup();
 
-       if ( !this.isDisabled() && element && $.isFunction( element.removeItems ) ) {
+       if ( element && $.isFunction( element.removeItems ) ) {
                element.removeItems( [ this ] );
                element.focus();
        }
 };
 
 /**
- * Handle close keyboard events
- * @param {jQuery.Event} event Key down event
+ * Handle click event for the entire capsule
  */
-OO.ui.CapsuleItemWidget.prototype.onCloseKeyDown = function ( e ) {
-       if ( !this.isDisabled() && $.isFunction( this.getElementGroup().removeItems ) ) {
-               switch ( e.which ) {
-                       case OO.ui.Keys.ENTER:
-                       case OO.ui.Keys.BACKSPACE:
-                       case OO.ui.Keys.SPACE:
-                               this.getElementGroup().removeItems( [ this ] );
-                               return false;
-               }
+OO.ui.CapsuleItemWidget.prototype.onClick = function () {
+       var element = this.getElementGroup();
+
+       if ( !this.isDisabled() && element && $.isFunction( element.editItem ) ) {
+               element.editItem( this );
        }
 };
 
+/**
+ * Handle keyDown event for the entire capsule
+ */
+OO.ui.CapsuleItemWidget.prototype.onKeyDown = function ( e ) {
+       var element = this.getElementGroup();
+
+       if ( e.keyCode === OO.ui.Keys.BACKSPACE || e.keyCode === OO.ui.Keys.DELETE ) {
+               element.removeItems( [ this ] );
+               element.focus();
+               return false;
+       } else if ( e.keyCode === OO.ui.Keys.ENTER ) {
+               element.editItem( this );
+               return false;
+       } else if ( e.keyCode === OO.ui.Keys.LEFT ) {
+               element.getPreviousItem( this ).focus();
+       } else if ( e.keyCode === OO.ui.Keys.RIGHT ) {
+               element.getNextItem( this ).focus();
+       }
+};
+
+/**
+ * Focuses the capsule
+ */
+OO.ui.CapsuleItemWidget.prototype.focus = function () {
+       this.$element.focus();
+};
+
 /**
  * CapsuleMultiSelectWidgets are something like a {@link OO.ui.ComboBoxInputWidget combo box widget}
  * that allows for selecting multiple values.
@@ -3514,16 +3538,19 @@ OO.ui.CapsuleItemWidget.prototype.onCloseKeyDown = function ( e ) {
  * @extends OO.ui.Widget
  * @mixins OO.ui.mixin.TabIndexedElement
  * @mixins OO.ui.mixin.GroupElement
+ * @uses OO.ui.CapsuleItemWidget
+ * @uses OO.ui.FloatingMenuSelectWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
  * @cfg {boolean} [allowArbitrary=false] Allow data items to be added even if not present in the menu.
- * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
+ * @cfg {Object} [menu] (required) Configuration options to pass to the
+ *  {@link OO.ui.MenuSelectWidget menu select widget}.
  * @cfg {Object} [popup] Configuration options to pass to the {@link OO.ui.PopupWidget popup widget}.
  *  If specified, this popup will be shown instead of the menu (but the menu
  *  will still be used for item labels and allowArbitrary=false). The widgets
- *  in the popup should use this.addItemsFromData() or this.addItems() as necessary.
- * @cfg {jQuery} [$overlay] Render the menu or popup into a separate layer.
+ *  in the popup should use {@link #addItemsFromData} or {@link #addItems} as necessary.
+ * @cfg {jQuery} [$overlay=this.$element] Render the menu or popup into a separate layer.
  *  This configuration is useful in cases where the expanded menu is larger than
  *  its containing `<div>`. The specified overlay layer is usually on top of
  *  the containing `<div>` and has a larger area. By default, the menu uses
@@ -3532,12 +3559,15 @@ OO.ui.CapsuleItemWidget.prototype.onCloseKeyDown = function ( e ) {
 OO.ui.CapsuleMultiSelectWidget = function OoUiCapsuleMultiSelectWidget( config ) {
        var $tabFocus;
 
-       // Configuration initialization
-       config = config || {};
-
        // Parent constructor
        OO.ui.CapsuleMultiSelectWidget.parent.call( this, config );
 
+       // Configuration initialization
+       config = $.extend( {
+               allowArbitrary: false,
+               $overlay: this.$element
+       }, config );
+
        // Properties (must be set before mixin constructor calls)
        this.$input = config.popup ? null : $( '<input>' );
        this.$handle = $( '<div>' );
@@ -3562,8 +3592,8 @@ OO.ui.CapsuleMultiSelectWidget = function OoUiCapsuleMultiSelectWidget( config )
 
        // Properties
        this.$content = $( '<div>' );
-       this.allowArbitrary = !!config.allowArbitrary;
-       this.$overlay = config.$overlay || this.$element;
+       this.allowArbitrary = config.allowArbitrary;
+       this.$overlay = config.$overlay;
        this.menu = new OO.ui.FloatingMenuSelectWidget( $.extend(
                {
                        widget: this,
@@ -3753,6 +3783,23 @@ OO.ui.CapsuleMultiSelectWidget.prototype.addItemsFromData = function ( datas ) {
        return this;
 };
 
+/**
+ * Add items to the capsule by providing a label
+ * @param {string} label
+ * @return {boolean} Whether the item was added or not
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.addItemFromLabel = function ( label ) {
+       var item = this.menu.getItemFromLabel( label, true );
+       if ( item ) {
+               this.addItemsFromData( [ item.data ] );
+               return true;
+       } else if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
+               this.addItemsFromData( [ label ] );
+               return true;
+       }
+       return false;
+};
+
 /**
  * Remove items by data
  * @chainable
@@ -3802,6 +3849,18 @@ OO.ui.CapsuleMultiSelectWidget.prototype.addItems = function ( items ) {
        return this;
 };
 
+/**
+ * Removes the item from the list and copies its label to `this.$input`.
+ *
+ * @param {Object} item
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.editItem = function ( item ) {
+       this.$input.val( item.label );
+       this.updateInputSize();
+       this.focus();
+       this.removeItems( [ item ] );
+};
+
 /**
  * @inheritdoc
  */
@@ -3839,6 +3898,56 @@ OO.ui.CapsuleMultiSelectWidget.prototype.clearItems = function () {
        return this;
 };
 
+/**
+ * Given an item, returns the item after it. If its the last item,
+ * returns `this.$input`. If no item is passed, returns the very first
+ * item.
+ *
+ * @param {OO.ui.CapsuleItemWidget} [item]
+ * @return {OO.ui.CapsuleItemWidget|jQuery|boolean}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.getNextItem = function ( item ) {
+       var itemIndex;
+
+       if ( item === undefined ) {
+               return this.items[ 0 ];
+       }
+
+       itemIndex = this.items.indexOf( item );
+       if ( itemIndex < 0 ) { // Item not in list
+               return false;
+       } else if ( itemIndex === this.items.length - 1 ) { // Last item
+               return this.$input;
+       } else {
+               return this.items[ itemIndex + 1 ];
+       }
+};
+
+/**
+ * Given an item, returns the item before it. If its the first item,
+ * returns `this.$input`. If no item is passed, returns the very last
+ * item.
+ *
+ * @param {OO.ui.CapsuleItemWidget} [item]
+ * @return {OO.ui.CapsuleItemWidget|jQuery|boolean}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.getPreviousItem = function ( item ) {
+       var itemIndex;
+
+       if ( item === undefined ) {
+               return this.items[ this.items.length - 1 ];
+       }
+
+       itemIndex = this.items.indexOf( item );
+       if ( itemIndex < 0 ) { // Item not in list
+               return false;
+       } else if ( itemIndex === 0 ) { // First item
+               return this.$input;
+       } else {
+               return this.items[ itemIndex - 1 ];
+       }
+};
+
 /**
  * Get the capsule widget's menu.
  * @return {OO.ui.MenuSelectWidget} Menu widget
@@ -3866,9 +3975,7 @@ OO.ui.CapsuleMultiSelectWidget.prototype.onInputFocus = function () {
  * @param {jQuery.Event} event
  */
 OO.ui.CapsuleMultiSelectWidget.prototype.onInputBlur = function () {
-       if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
-               this.addItemsFromData( [ this.$input.val() ] );
-       }
+       this.addItemFromLabel( this.$input.val() );
        this.clearInput();
 };
 
@@ -3893,7 +4000,7 @@ OO.ui.CapsuleMultiSelectWidget.prototype.onFocusForPopup = function () {
  * Handles popup focus out events.
  *
  * @private
- * @param {Event} e Focus out event
+ * @param {jQuery.Event} e Focus out event
  */
 OO.ui.CapsuleMultiSelectWidget.prototype.onPopupFocusOut = function () {
        var widget = this.popup;
@@ -3931,8 +4038,6 @@ OO.ui.CapsuleMultiSelectWidget.prototype.onMouseDown = function ( e ) {
  * @param {jQuery.Event} e Key press event
  */
 OO.ui.CapsuleMultiSelectWidget.prototype.onKeyPress = function ( e ) {
-       var item;
-
        if ( !this.isDisabled() ) {
                if ( e.which === OO.ui.Keys.ESCAPE ) {
                        this.clearInput();
@@ -3942,12 +4047,7 @@ OO.ui.CapsuleMultiSelectWidget.prototype.onKeyPress = function ( e ) {
                if ( !this.popup ) {
                        this.menu.toggle( true );
                        if ( e.which === OO.ui.Keys.ENTER ) {
-                               item = this.menu.getItemFromLabel( this.$input.val(), true );
-                               if ( item ) {
-                                       this.addItemsFromData( [ item.data ] );
-                                       this.clearInput();
-                               } else if ( this.allowArbitrary && this.$input.val().trim() !== '' ) {
-                                       this.addItemsFromData( [ this.$input.val() ] );
+                               if ( this.addItemFromLabel( this.$input.val() ) ) {
                                        this.clearInput();
                                }
                                return false;
@@ -3966,13 +4066,23 @@ OO.ui.CapsuleMultiSelectWidget.prototype.onKeyPress = function ( e ) {
  * @param {jQuery.Event} e Key down event
  */
 OO.ui.CapsuleMultiSelectWidget.prototype.onKeyDown = function ( e ) {
-       if ( !this.isDisabled() ) {
+       if (
+               !this.isDisabled() &&
+               this.$input.val() === '' &&
+               this.items.length
+       ) {
                // 'keypress' event is not triggered for Backspace
-               if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.$input.val() === '' ) {
-                       if ( this.items.length ) {
+               if ( e.keyCode === OO.ui.Keys.BACKSPACE ) {
+                       if ( e.metaKey || e.ctrlKey ) {
                                this.removeItems( this.items.slice( -1 ) );
+                       } else {
+                               this.editItem( this.items[ this.items.length - 1 ] );
                        }
                        return false;
+               } else if ( e.keyCode === OO.ui.Keys.LEFT ) {
+                       this.getPreviousItem().focus();
+               } else if ( e.keyCode === OO.ui.Keys.RIGHT ) {
+                       this.getNextItem().focus();
                }
        }
 };
@@ -4136,6 +4246,8 @@ OO.ui.CapsuleMultiSelectWidget.prototype.focus = function () {
  * @cfg {boolean} [droppable=true] Whether to accept files by drag and drop.
  * @cfg {boolean} [showDropTarget=false] Whether to show a drop target. Requires droppable to be true.
  * @cfg {boolean} [dragDropUI=false] Deprecated alias for showDropTarget
+ * @cfg {Number} [thumbnailSizeLimit=20] File size limit in MiB above which to not try and show a
+ *  preview (for performance)
  */
 OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
        var dragHandler;
@@ -4151,7 +4263,8 @@ OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
                placeholder: OO.ui.msg( 'ooui-selectfile-placeholder' ),
                notsupported: OO.ui.msg( 'ooui-selectfile-not-supported' ),
                droppable: true,
-               showDropTarget: false
+               showDropTarget: false,
+               thumbnailSizeLimit: 20
        }, config );
 
        // Parent constructor
@@ -4165,9 +4278,8 @@ OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
 
        // Properties
        this.$info = $( '<span>' );
-
-       // Properties
        this.showDropTarget = config.showDropTarget;
+       this.thumbnailSizeLimit = config.thumbnailSizeLimit;
        this.isSupported = this.constructor.static.isSupported();
        this.currentFile = null;
        if ( Array.isArray( config.accept ) ) {
@@ -4188,7 +4300,7 @@ OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
        this.clearButton = new OO.ui.ButtonWidget( {
                classes: [ 'oo-ui-selectFileWidget-clearButton' ],
                framed: false,
-               icon: 'remove',
+               icon: 'close',
                disabled: this.disabled
        } );
 
@@ -4211,23 +4323,35 @@ OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) {
 
        // Initialization
        this.addInput();
-       this.updateUI();
        this.$label.addClass( 'oo-ui-selectFileWidget-label' );
        this.$info
                .addClass( 'oo-ui-selectFileWidget-info' )
                .append( this.$icon, this.$label, this.clearButton.$element, this.$indicator );
-       this.$element
-               .addClass( 'oo-ui-selectFileWidget' )
-               .append( this.$info, this.selectButton.$element );
+
        if ( config.droppable && config.showDropTarget ) {
+               this.selectButton.setIcon( 'upload' );
+               this.$thumbnail = $( '<div>' ).addClass( 'oo-ui-selectFileWidget-thumbnail' );
+               this.setPendingElement( this.$thumbnail );
                this.$dropTarget = $( '<div>' )
                        .addClass( 'oo-ui-selectFileWidget-dropTarget' )
-                       .text( OO.ui.msg( 'ooui-selectfile-dragdrop-placeholder' ) )
                        .on( {
                                click: this.onDropTargetClick.bind( this )
-                       } );
-               this.$element.prepend( this.$dropTarget );
+                       } )
+                       .append(
+                               this.$thumbnail,
+                               this.$info,
+                               this.selectButton.$element,
+                               $( '<span>' )
+                                       .addClass( 'oo-ui-selectFileWidget-dropLabel' )
+                                       .text( OO.ui.msg( 'ooui-selectfile-dragdrop-placeholder' ) )
+                       );
+               this.$element.append( this.$dropTarget );
+       } else {
+               this.$element
+                       .addClass( 'oo-ui-selectFileWidget' )
+                       .append( this.$info, this.selectButton.$element );
        }
+       this.updateUI();
 };
 
 /* Setup */
@@ -4332,13 +4456,77 @@ OO.ui.SelectFileWidget.prototype.updateUI = function () {
                                );
                        }
                        this.setLabel( $label );
+
+                       if ( this.showDropTarget ) {
+                               this.pushPending();
+                               this.loadAndGetImageUrl().done( function ( url ) {
+                                       this.$thumbnail.css( 'background-image', 'url( ' + url + ' )' );
+                               }.bind( this ) ).fail( function () {
+                                       this.$thumbnail.append(
+                                               new OO.ui.IconWidget( {
+                                                       icon: 'attachment',
+                                                       classes: [ 'oo-ui-selectFileWidget-noThumbnail-icon' ]
+                                               } ).$element
+                                       );
+                               }.bind( this ) ).always( function () {
+                                       this.popPending();
+                               }.bind( this ) );
+                               this.$dropTarget.off( 'click' );
+                       }
                } else {
+                       if ( this.showDropTarget ) {
+                               this.$dropTarget.off( 'click' );
+                               this.$dropTarget.on( {
+                                       click: this.onDropTargetClick.bind( this )
+                               } );
+                               this.$thumbnail
+                                       .empty()
+                                       .css( 'background-image', '' );
+                       }
                        this.$element.addClass( 'oo-ui-selectFileWidget-empty' );
                        this.setLabel( this.placeholder );
                }
        }
 };
 
+/**
+ * If the selected file is an image, get its URL and load it.
+ *
+ * @return {jQuery.Promise} Promise resolves with the image URL after it has loaded
+ */
+OO.ui.SelectFileWidget.prototype.loadAndGetImageUrl = function () {
+       var deferred = $.Deferred(),
+               file = this.currentFile,
+               reader = new FileReader();
+
+       if (
+               file &&
+               ( OO.getProp( file, 'type' ) || '' ).indexOf( 'image/' ) === 0 &&
+               file.size < this.thumbnailSizeLimit * 1024 * 1024
+       ) {
+               reader.onload = function ( event ) {
+                       var img = document.createElement( 'img' );
+                       img.addEventListener( 'load', function () {
+                               if (
+                                       img.naturalWidth === 0 ||
+                                       img.naturalHeight === 0 ||
+                                       img.complete === false
+                               ) {
+                                       deferred.reject();
+                               } else {
+                                       deferred.resolve( event.target.result );
+                               }
+                       } );
+                       img.src = event.target.result;
+               };
+               reader.readAsDataURL( file );
+       } else {
+               deferred.reject();
+       }
+
+       return deferred.promise();
+};
+
 /**
  * Add the input to the widget
  *
@@ -4356,6 +4544,11 @@ OO.ui.SelectFileWidget.prototype.addInput = function () {
 
        this.$input = $( '<input type="file">' );
        this.$input.on( 'change', this.onFileSelectedHandler );
+       this.$input.on( 'click', function ( e ) {
+               // Prevents dropTarget to get clicked which calls
+               // a click on this input
+               e.stopPropagation();
+       } );
        this.$input.attr( {
                tabindex: -1
        } );
index 788b70d..adf3bfd 100644 (file)
@@ -1,53 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-ms-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-o-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
 }
index ead41ca..101673c 100644 (file)
@@ -1,37 +1,13 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:06Z
+ * Date: 2016-02-09T21:21:21Z
  */
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-window {
        background: transparent;
 }
index 1ccd433..1a56945 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.15.2
+ * OOjs UI v0.15.3
  * 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-02-02T22:07:00Z
+ * Date: 2016-02-09T21:21:16Z
  */
 ( function ( OO ) {
 
index 0886fa6..c565256 100644 (file)
@@ -5,6 +5,10 @@
                "articleRedirect": { "file": {
                        "ltr": "images/icons/articleRedirect-ltr.svg",
                        "rtl": "images/icons/articleRedirect-rtl.svg"
+               } },
+               "upload": { "file": {
+                       "ltr": "images/icons/upload-ltr.svg",
+                       "rtl": "images/icons/upload-rtl.svg"
                } }
        }
 }
index ab04d36..aae5201 100644 (file)
@@ -5,6 +5,10 @@
                "alignCentre": { "file": "images/icons/align-center.svg" },
                "alignLeft": { "file": "images/icons/align-float-left.svg" },
                "alignRight": { "file": "images/icons/align-float-right.svg" },
+               "attachment": { "file": {
+                       "ltr": "images/icons/attachment-ltr.svg",
+                       "rtl": "images/icons/attachment-rtl.svg"
+               } },
                "calendar": { "file": {
                        "ltr": "images/icons/calendar-ltr.svg",
                        "rtl": "images/icons/calendar-rtl.svg"
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.png b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.png
new file mode 100644 (file)
index 0000000..cbcd675
Binary files /dev/null and b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.png differ
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.svg b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-ltr.svg
new file mode 100644 (file)
index 0000000..74a4d64
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24">
+    <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.png b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.png
new file mode 100644 (file)
index 0000000..43a0161
Binary files /dev/null and b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.png differ
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.svg b/resources/lib/oojs-ui/themes/apex/images/icons/attachment-rtl.svg
new file mode 100644 (file)
index 0000000..f53f5a4
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24">
+    <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
+</svg>
index bdd9abe..e27be3c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/language-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/language-ltr.png differ
index 4f1fc10..d64b734 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <g id="translation">
-        <path id="english" d="M14.34 9l-3.53 10h2.064l.72-2.406h3.624l.72 2.406H20L16.465 9h-2.12zm1.065 1.53L16.75 15h-2.69z"/>
-        <path id="chinese" d="M8.97 4.22c-.43.29-.88.616-1.25.874l.186.312c.14.194.275.393.407.594H4.47v1.47h1.593c.43 1.41 1.11 2.624 2.03 3.624-1.008.664-2.192 1.248-3.624 1.75L4 13c.317.487.714.976 1.03 1.375l.25-.094c1.593-.59 2.91-1.263 4.032-2.06.818.63 1.71 1.16 2.657 1.595l.56-1.624a13.21 13.21 0 0 1-1.908-1.063c.284-.28.59-.634.906-1.156.46-.716.776-1.57 1-2.5h1.657V6h-4.063c-.283-.552-.596-1.083-.97-1.53l-.186-.25zM7.72 7.47h3.186c-.32 1.075-.83 1.937-1.53 2.624-.713-.705-1.26-1.568-1.657-2.625zm6.31 5.31l-.467 1.658c.292-.514.577-1.075.812-1.532l-.344-.125z"/>
+    <g id="A">
+        <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
+    </g>
+    <g id="文">
+        <path d="M8.325 6.573h.787l-.875-1.75h-1.75l.438.875a1.56 1.56 0 0 0 1.4.875z"/>
+        <path d="m 9.202,12.874 c 0.7,0.525 1.486,0.963 2.45,1.225 l -0.438,1.31 A 9.17,9.17 0 0 1 8.151,13.835 c -1.49,1.137 -3.063,1.837 -4.813,2.363 L 2.9,14.885 C 4.386,14.36 5.874,13.835 7.1,12.872 5.962,11.648 5.174,10.335 4.65,8.758 l -1.663,0 0,-1.31 10.85,0 -0.438,1.312 -1.75,0 c -0.308,1.33 -1.255,2.957 -2.45,4.114 z m 1.05,-4.114 -4.114,0 c 0.35,1.226 1.138,2.363 2.013,3.238 0.926,-1 1.617,-1.957 2.1,-3.237 z"/>
     </g>
 </svg>
index ed64644..a7fd0cd 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/language-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/language-rtl.png differ
index 081252e..cf6c1d5 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <g id="translation">
-        <path id="english" d="M7.53 9L4 19h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 9H7.53zm1.064 1.53L9.938 15H7.25z"/>
-        <path id="chinese" d="M14.594 4.22c-.43.29-.88.616-1.25.874l.187.312c.14.194.28.393.41.594h-3.843v1.47h1.594c.43 1.41 1.11 2.624 2.03 3.624-.662.437-1.413.82-2.25 1.187l.563 1.567a15.882 15.882 0 0 0 2.908-1.625 13.82 13.82 0 0 0 3.97 2.125l.28.094c.293-.514.578-1.075.813-1.532l-.375-.125c-1.38-.49-2.49-1.05-3.375-1.654.284-.28.59-.635.906-1.157.46-.717.775-1.572 1-2.5h1.656V6H15.75c-.283-.552-.596-1.083-.97-1.53l-.186-.25zm-1.25 3.25h3.187c-.315 1.075-.825 1.937-1.53 2.624-.71-.705-1.26-1.568-1.653-2.625zM9.97 12.874L9.624 13c.196.3.406.594.625.875l-.28-1z"/>
+    <g id="A">
+        <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
+    </g>
+    <g id="文">
+        <path d="M16.384 6.573h.787l-.873-1.75h-1.75l.438.875c.26.535.805.874 1.4.875z" id="path7"/>
+        <path d="M15.15 12.874c-.7.525-1.486.963-2.45 1.225l.438 1.31a9.17 9.17 0 0 0 3.063-1.575c1.49 1.137 3.064 1.837 4.814 2.363l.438-1.313c-1.486-.525-2.974-1.05-4.2-2.013 1.138-1.224 1.926-2.537 2.45-4.114h1.663v-1.31h-10.85l.438 1.312h1.75c.308 1.33 1.255 2.957 2.45 4.114zM14.1 8.76h4.114c-.35 1.226-1.138 2.363-2.013 3.238-.925-1-1.616-1.957-2.1-3.237z" id="path11-7"/>
     </g>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.png b/resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.png
new file mode 100644 (file)
index 0000000..fa2da24
Binary files /dev/null and b/resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.png differ
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.svg b/resources/lib/oojs-ui/themes/apex/images/icons/upload-ltr.svg
new file mode 100644 (file)
index 0000000..dc4676f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.png b/resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.png
new file mode 100644 (file)
index 0000000..1ac6106
Binary files /dev/null and b/resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.png differ
diff --git a/resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.svg b/resources/lib/oojs-ui/themes/apex/images/icons/upload-rtl.svg
new file mode 100644 (file)
index 0000000..ed3fe77
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
+</svg>
index 54f8aef..b03950c 100644 (file)
                "alignCentre": { "file": "images/icons/align-center.svg" },
                "alignLeft": { "file": "images/icons/align-float-left.svg" },
                "alignRight": { "file": "images/icons/align-float-right.svg" },
+               "attachment": { "file": {
+                       "ltr": "images/icons/attachment-ltr.svg",
+                       "rtl": "images/icons/attachment-rtl.svg"
+               } },
                "calendar": { "file": {
                        "ltr": "images/icons/calendar-ltr.svg",
                        "rtl": "images/icons/calendar-rtl.svg"
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.png
new file mode 100644 (file)
index 0000000..a77a23e
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.svg
new file mode 100644 (file)
index 0000000..67c3ae9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #ffffff }</style>
+    <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.png
new file mode 100644 (file)
index 0000000..cbcd675
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr.svg
new file mode 100644 (file)
index 0000000..74a4d64
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24">
+    <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.png
new file mode 100644 (file)
index 0000000..e2e2e02
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.svg
new file mode 100644 (file)
index 0000000..61e11d0
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #ffffff }</style>
+    <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.png
new file mode 100644 (file)
index 0000000..43a0161
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl.svg
new file mode 100644 (file)
index 0000000..f53f5a4
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24">
+    <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
+</svg>
index c75d14b..9de74f2 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.png differ
index 19e1e9f..429ee29 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
-    <g id="translation">
-        <path id="english" d="M14.34 9l-3.53 10h2.064l.72-2.406h3.624l.72 2.406H20L16.465 9h-2.12zm1.065 1.53L16.75 15h-2.69z"/>
-        <path id="chinese" d="M8.97 4.22c-.43.29-.88.616-1.25.874l.186.312c.14.194.275.393.407.594H4.47v1.47h1.593c.43 1.41 1.11 2.624 2.03 3.624-1.008.664-2.192 1.248-3.624 1.75L4 13c.317.487.714.976 1.03 1.375l.25-.094c1.593-.59 2.91-1.263 4.032-2.06.818.63 1.71 1.16 2.657 1.595l.56-1.624a13.21 13.21 0 0 1-1.908-1.063c.284-.28.59-.634.906-1.156.46-.716.776-1.57 1-2.5h1.657V6h-4.063c-.283-.552-.596-1.083-.97-1.53l-.186-.25zM7.72 7.47h3.186c-.32 1.075-.83 1.937-1.53 2.624-.713-.705-1.26-1.568-1.657-2.625zm6.31 5.31l-.467 1.658c.292-.514.577-1.075.812-1.532l-.344-.125z"/>
+    <g id="A">
+        <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
+    </g>
+    <g id="文">
+        <path d="M8.325 6.573h.787l-.875-1.75h-1.75l.438.875a1.56 1.56 0 0 0 1.4.875z"/>
+        <path d="m 9.202,12.874 c 0.7,0.525 1.486,0.963 2.45,1.225 l -0.438,1.31 A 9.17,9.17 0 0 1 8.151,13.835 c -1.49,1.137 -3.063,1.837 -4.813,2.363 L 2.9,14.885 C 4.386,14.36 5.874,13.835 7.1,12.872 5.962,11.648 5.174,10.335 4.65,8.758 l -1.663,0 0,-1.31 10.85,0 -0.438,1.312 -1.75,0 c -0.308,1.33 -1.255,2.957 -2.45,4.114 z m 1.05,-4.114 -4.114,0 c 0.35,1.226 1.138,2.363 2.013,3.238 0.926,-1 1.617,-1.957 2.1,-3.237 z"/>
     </g>
 </svg>
index bdd9abe..e27be3c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr.png differ
index 4f1fc10..d64b734 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <g id="translation">
-        <path id="english" d="M14.34 9l-3.53 10h2.064l.72-2.406h3.624l.72 2.406H20L16.465 9h-2.12zm1.065 1.53L16.75 15h-2.69z"/>
-        <path id="chinese" d="M8.97 4.22c-.43.29-.88.616-1.25.874l.186.312c.14.194.275.393.407.594H4.47v1.47h1.593c.43 1.41 1.11 2.624 2.03 3.624-1.008.664-2.192 1.248-3.624 1.75L4 13c.317.487.714.976 1.03 1.375l.25-.094c1.593-.59 2.91-1.263 4.032-2.06.818.63 1.71 1.16 2.657 1.595l.56-1.624a13.21 13.21 0 0 1-1.908-1.063c.284-.28.59-.634.906-1.156.46-.716.776-1.57 1-2.5h1.657V6h-4.063c-.283-.552-.596-1.083-.97-1.53l-.186-.25zM7.72 7.47h3.186c-.32 1.075-.83 1.937-1.53 2.624-.713-.705-1.26-1.568-1.657-2.625zm6.31 5.31l-.467 1.658c.292-.514.577-1.075.812-1.532l-.344-.125z"/>
+    <g id="A">
+        <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
+    </g>
+    <g id="文">
+        <path d="M8.325 6.573h.787l-.875-1.75h-1.75l.438.875a1.56 1.56 0 0 0 1.4.875z"/>
+        <path d="m 9.202,12.874 c 0.7,0.525 1.486,0.963 2.45,1.225 l -0.438,1.31 A 9.17,9.17 0 0 1 8.151,13.835 c -1.49,1.137 -3.063,1.837 -4.813,2.363 L 2.9,14.885 C 4.386,14.36 5.874,13.835 7.1,12.872 5.962,11.648 5.174,10.335 4.65,8.758 l -1.663,0 0,-1.31 10.85,0 -0.438,1.312 -1.75,0 c -0.308,1.33 -1.255,2.957 -2.45,4.114 z m 1.05,-4.114 -4.114,0 c 0.35,1.226 1.138,2.363 2.013,3.238 0.926,-1 1.617,-1.957 2.1,-3.237 z"/>
     </g>
 </svg>
index 680d726..97c0c09 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.png differ
index 123053c..f3c32eb 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
-    <g id="translation">
-        <path id="english" d="M7.53 9L4 19h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 9H7.53zm1.064 1.53L9.938 15H7.25z"/>
-        <path id="chinese" d="M14.594 4.22c-.43.29-.88.616-1.25.874l.187.312c.14.194.28.393.41.594h-3.843v1.47h1.594c.43 1.41 1.11 2.624 2.03 3.624-.662.437-1.413.82-2.25 1.187l.563 1.567a15.882 15.882 0 0 0 2.908-1.625 13.82 13.82 0 0 0 3.97 2.125l.28.094c.293-.514.578-1.075.813-1.532l-.375-.125c-1.38-.49-2.49-1.05-3.375-1.654.284-.28.59-.635.906-1.157.46-.717.775-1.572 1-2.5h1.656V6H15.75c-.283-.552-.596-1.083-.97-1.53l-.186-.25zm-1.25 3.25h3.187c-.315 1.075-.825 1.937-1.53 2.624-.71-.705-1.26-1.568-1.653-2.625zM9.97 12.874L9.624 13c.196.3.406.594.625.875l-.28-1z"/>
+    <g id="A">
+        <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
+    </g>
+    <g id="文">
+        <path d="M16.384 6.573h.787l-.873-1.75h-1.75l.438.875c.26.535.805.874 1.4.875z" id="path7"/>
+        <path d="M15.15 12.874c-.7.525-1.486.963-2.45 1.225l.438 1.31a9.17 9.17 0 0 0 3.063-1.575c1.49 1.137 3.064 1.837 4.814 2.363l.438-1.313c-1.486-.525-2.974-1.05-4.2-2.013 1.138-1.224 1.926-2.537 2.45-4.114h1.663v-1.31h-10.85l.438 1.312h1.75c.308 1.33 1.255 2.957 2.45 4.114zM14.1 8.76h4.114c-.35 1.226-1.138 2.363-2.013 3.238-.925-1-1.616-1.957-2.1-3.237z" id="path11-7"/>
     </g>
 </svg>
index ed64644..a7fd0cd 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl.png differ
index 081252e..cf6c1d5 100644 (file)
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-    <g id="translation">
-        <path id="english" d="M7.53 9L4 19h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 9H7.53zm1.064 1.53L9.938 15H7.25z"/>
-        <path id="chinese" d="M14.594 4.22c-.43.29-.88.616-1.25.874l.187.312c.14.194.28.393.41.594h-3.843v1.47h1.594c.43 1.41 1.11 2.624 2.03 3.624-.662.437-1.413.82-2.25 1.187l.563 1.567a15.882 15.882 0 0 0 2.908-1.625 13.82 13.82 0 0 0 3.97 2.125l.28.094c.293-.514.578-1.075.813-1.532l-.375-.125c-1.38-.49-2.49-1.05-3.375-1.654.284-.28.59-.635.906-1.157.46-.717.775-1.572 1-2.5h1.656V6H15.75c-.283-.552-.596-1.083-.97-1.53l-.186-.25zm-1.25 3.25h3.187c-.315 1.075-.825 1.937-1.53 2.624-.71-.705-1.26-1.568-1.653-2.625zM9.97 12.874L9.624 13c.196.3.406.594.625.875l-.28-1z"/>
+    <g id="A">
+        <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
+    </g>
+    <g id="文">
+        <path d="M16.384 6.573h.787l-.873-1.75h-1.75l.438.875c.26.535.805.874 1.4.875z" id="path7"/>
+        <path d="M15.15 12.874c-.7.525-1.486.963-2.45 1.225l.438 1.31a9.17 9.17 0 0 0 3.063-1.575c1.49 1.137 3.064 1.837 4.814 2.363l.438-1.313c-1.486-.525-2.974-1.05-4.2-2.013 1.138-1.224 1.926-2.537 2.45-4.114h1.663v-1.31h-10.85l.438 1.312h1.75c.308 1.33 1.255 2.957 2.45 4.114zM14.1 8.76h4.114c-.35 1.226-1.138 2.363-2.013 3.238-.925-1-1.616-1.957-2.1-3.237z" id="path11-7"/>
     </g>
 </svg>
index 85c58d7..f4ef540 100644 (file)
@@ -1,20 +1,19 @@
 @import "mediawiki.mixins";
 
-// Table Sorting
+/* Table Sorting */
 
-.client-js table.jquery-tablesorter th.headerSort {
+table.jquery-tablesorter th.headerSort {
        .background-image-svg( 'images/sort_both.svg', 'images/sort_both.png' );
        cursor: pointer;
-       // Keep synchronized with mediawiki.skinning.content styles
        background-repeat: no-repeat;
        background-position: center right;
        padding-right: 21px;
 }
 
-.client-js table.jquery-tablesorter th.headerSortUp {
+table.jquery-tablesorter th.headerSortUp {
        .background-image-svg( 'images/sort_up.svg', 'images/sort_up.png' );
 }
 
-.client-js table.jquery-tablesorter th.headerSortDown {
+table.jquery-tablesorter th.headerSortDown {
        .background-image-svg( 'images/sort_down.svg', 'images/sort_down.png' );
 }
index a873cdf..c88d00d 100644 (file)
@@ -241,16 +241,3 @@ div.tright {
 div.tleft {
        margin: .5em 1.4em 1.3em 0;
 }
-
-/* Make space for the jquery.tablesorter icon and display a placeholder if JavaScript is loaded, while
-   tablesorter is still loading and setting up the tables for sorting. This avoids a flash of
-   unstyled content during page load (FOUC). The styles can also be used by WYSIWYG editors. */
-.client-js table.sortable th:not(.unsortable) {
-       background-image: url(images/sort_both_readonly.png);
-       /* @embed */
-       background-image: linear-gradient(transparent, transparent), url(images/sort_both_readonly.svg);
-       /* Keep synchronised with jquery.tablesorter styles */
-       background-repeat: no-repeat;
-       background-position: center right;
-       padding-right: 21px;
-}
diff --git a/resources/src/mediawiki.skinning/images/sort_both_readonly.png b/resources/src/mediawiki.skinning/images/sort_both_readonly.png
deleted file mode 100644 (file)
index bdb09e3..0000000
Binary files a/resources/src/mediawiki.skinning/images/sort_both_readonly.png and /dev/null differ
diff --git a/resources/src/mediawiki.skinning/images/sort_both_readonly.svg b/resources/src/mediawiki.skinning/images/sort_both_readonly.svg
deleted file mode 100644 (file)
index 3b97000..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 9" height="9" width="21">
-    <path d="M14.5 5l-4 4-4-4zM14.5 4l-4-4-4 4z" style="fill-opacity: 0.5;"/>
-</svg>
index 32ccdcd..10664fa 100644 (file)
        ApiSandbox.PageLayout.prototype.loadParamInfo = function () {
                var dynamicFieldset, dynamicParamNameWidget,
                        that = this,
+                       removeDynamicParamWidget = function ( name, layout ) {
+                               dynamicFieldset.removeItems( [ layout ] );
+                               delete that.widgets[ name ];
+                       },
                        addDynamicParamWidget = function () {
                                var name, layout, widget, button;
 
                                widget.focus();
 
                                dynamicParamNameWidget.setValue( '' );
-                       },
-                       removeDynamicParamWidget = function ( name, layout ) {
-                               dynamicFieldset.removeItems( [ layout ] );
-                               delete that.widgets[ name ];
                        };
 
                this.$element.empty()
index ec2a4b1..ffb3041 100644 (file)
@@ -6,17 +6,15 @@
 ( function ( mw, $ ) {
        /*jshint latedef:false */
 
-       // jscs:disable jsDoc
        /**
-        * @class mw.Title
-        *
         * Parse titles into an object structure. Note that when using the constructor
         * directly, passing invalid titles will result in an exception. Use #newFromText to use the
         * logic directly and get null for invalid titles which is easier to work with.
         *
-        * @constructor
-        *
-        * Note that in the constructor amd #newFromText method, `namespace` is the **default** namespace
+        * @class mw.Title
+        */
+       /**
+        * Note that in the constructor and #newFromText method, `namespace` is the **default** namespace
         * only, and can be overridden by a namespace prefix in `title`. If you do not want this behavior,
         * use #makeTitle. Compare:
         *
@@ -32,6 +30,7 @@
         *     mw.Title.newFromText( 'Template:Foo', NS_TEMPLATE ).getPrefixedText(); // => 'Template:Foo'
         *     mw.Title.makeTitle( NS_TEMPLATE, 'Template:Foo' ).getPrefixedText();   // => 'Template:Template:Foo'
         *
+        * @method constructor
         * @param {string} title Title of the page. If no second argument given,
         *  this will be searched for a namespace
         * @param {number} [namespace=NS_MAIN] If given, will used as default namespace for the given title
@@ -50,7 +49,6 @@
 
                return this;
        }
-       // jscs:enable jsDoc
 
        /* Private members */
 
index 4be4a8d..a2386b3 100644 (file)
                $checkboxes.prop( 'checked', check );
        }
 
-       $( '#checkbox-all' ).click( function ( e ) {
+       $( '.mw-checkbox-all' ).click( function ( e ) {
                selectAll( true );
                e.preventDefault();
        } );
-       $( '#checkbox-none' ).click( function ( e ) {
+       $( '.mw-checkbox-none' ).click( function ( e ) {
                selectAll( false );
                e.preventDefault();
        } );
-       $( '#checkbox-invert' ).click( function ( e ) {
+       $( '.mw-checkbox-invert' ).click( function ( e ) {
                $checkboxes.each( function () {
                        $( this ).prop( 'checked', !$( this ).is( ':checked' ) );
                } );
index 97a94b8..8a3784c 100644 (file)
@@ -6,7 +6,7 @@
  * @author Moriel Schottlender, 2015
  * @since 1.19
  */
-/*jshint es3:false */
+/*jshint esversion:5 */
 /*global OO*/
 ( function ( mw, $ ) {
        /**
index 0c2d6d6..e53e5f3 100644 (file)
@@ -4,7 +4,7 @@
  * even the most ancient of browsers, so be very careful when editing.
  */
 /*jshint unused: false, evil: true */
-/*globals mw, RLQ: true, $VARS, $CODE, performance */
+/*globals mw, RLQ: true, NORLQ: true, $VARS, $CODE, performance */
 
 var mediaWikiLoadStart = ( new Date() ).getTime(),
 
@@ -67,11 +67,29 @@ function isCompatible( ua ) {
 
 // Conditional script injection
 ( function () {
+       var NORLQ, script;
        if ( !isCompatible() ) {
                // Undo class swapping in case of an unsupported browser.
                // See OutputPage::getHeadScripts().
                document.documentElement.className = document.documentElement.className
                        .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
+
+               NORLQ = window.NORLQ || [];
+               while ( NORLQ.length ) {
+                       NORLQ.shift()();
+               }
+               window.NORLQ = {
+                       push: function ( fn ) {
+                               fn();
+                       }
+               };
+
+               // Clear and disable the other queue
+               window.RLQ = {
+                       // No-op
+                       push: function () {}
+               };
+
                return;
        }
 
@@ -96,9 +114,15 @@ function isCompatible( ua ) {
                                fn();
                        }
                };
+
+               // Clear and disable the other queue
+               window.NORLQ = {
+                       // No-op
+                       push: function () {}
+               };
        }
 
-       var script = document.createElement( 'script' );
+       script = document.createElement( 'script' );
        script.src = $VARS.baseModulesUri;
        script.onload = script.onreadystatechange = function () {
                if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
index 90ee1bb..ce9e1d6 100644 (file)
@@ -364,6 +364,25 @@ class EditPageTest extends MediaWikiLangTestCase {
        }
 
        public function testUpdatePage() {
+               $checkIds = array();
+
+               $this->setMwGlobals( 'wgHooks', array(
+                       'PageContentInsertComplete' => array( function (
+                               WikiPage &$page, User &$user, Content $content,
+                               $summary, $minor, $u1, $u2, &$flags, Revision $revision
+                       ) {
+                               // types/refs checked
+                       } ),
+                       'PageContentSaveComplete' => array( function (
+                               WikiPage &$page, User &$user, Content $content,
+                               $summary, $minor, $u1, $u2, &$flags, Revision $revision,
+                               Status &$status, $baseRevId
+                       ) use ( &$checkIds ) {
+                               $checkIds[] = $status->value['revision']->getId();
+                               // types/refs checked
+                       } ),
+               ) );
+
                $text = "one";
                $edit = array(
                        'wpTextbox1' => $text,
@@ -373,6 +392,7 @@ class EditPageTest extends MediaWikiLangTestCase {
                $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
                        EditPage::AS_SUCCESS_UPDATE, $text,
                        "expected successfull update with given text" );
+               $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
 
                $this->forceRevisionDate( $page, '20120101000000' );
 
@@ -385,6 +405,62 @@ class EditPageTest extends MediaWikiLangTestCase {
                $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
                        EditPage::AS_SUCCESS_UPDATE, $text,
                        "expected successfull update with given text" );
+               $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
+               $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
+       }
+
+       public function testUpdatePageTrx() {
+               $text = "one";
+               $edit = array(
+                       'wpTextbox1' => $text,
+                       'wpSummary' => 'first update',
+               );
+
+               $page = $this->assertEdit( 'EditPageTest_testTrxUpdatePage', "zero", null, $edit,
+                       EditPage::AS_SUCCESS_UPDATE, $text,
+                       "expected successfull update with given text" );
+
+               $this->forceRevisionDate( $page, '20120101000000' );
+
+               $checkIds = array();
+               $this->setMwGlobals( 'wgHooks', array(
+                       'PageContentSaveComplete' => array( function (
+                               WikiPage &$page, User &$user, Content $content,
+                               $summary, $minor, $u1, $u2, &$flags, Revision $revision,
+                               Status &$status, $baseRevId
+                       ) use ( &$checkIds ) {
+                               $checkIds[] = $status->value['revision']->getId();
+                               // types/refs checked
+                       } ),
+               ) );
+
+               wfGetDB( DB_MASTER )->begin( __METHOD__ );
+
+               $text = "two";
+               $edit = array(
+                       'wpTextbox1' => $text,
+                       'wpSummary' => 'second update',
+               );
+
+               $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
+                       EditPage::AS_SUCCESS_UPDATE, $text,
+                       "expected successfull update with given text" );
+
+               $text = "three";
+               $edit = array(
+                       'wpTextbox1' => $text,
+                       'wpSummary' => 'third update',
+               );
+
+               $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
+                       EditPage::AS_SUCCESS_UPDATE, $text,
+                       "expected successfull update with given text" );
+
+               wfGetDB( DB_MASTER )->commit( __METHOD__ );
+
+               $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
+               $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
+               $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
        }
 
        public static function provideSectionEdit() {
diff --git a/tests/phpunit/includes/MergeHistoryTest.php b/tests/phpunit/includes/MergeHistoryTest.php
new file mode 100644 (file)
index 0000000..0c1a7a8
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @group Database
+ */
+class MergeHistoryTest extends MediaWikiTestCase {
+
+       /**
+        * Make some pages to work with
+        */
+       public function addDBData() {
+               // Pages that won't actually be merged
+               $this->insertPage( 'Test' );
+               $this->insertPage( 'Test2' );
+
+               // Pages that will be merged
+               $this->insertPage( 'Merge1' );
+               $this->insertPage( 'Merge2' );
+       }
+
+       /**
+        * @dataProvider provideIsValidMerge
+        * @covers MergeHistory::isValidMerge
+        * @param $source string Source page
+        * @param $dest string Destination page
+        * @param $timestamp string|bool Timestamp up to which revisions are merged (or false for all)
+        * @param $error string|bool Expected error for test (or true for no error)
+        */
+       public function testIsValidMerge( $source, $dest, $timestamp, $error ) {
+               $this->setMwGlobals( 'wgContentHandlerUseDB', false );
+               $mh = new MergeHistory(
+                       Title::newFromText( $source ),
+                       Title::newFromText( $dest ),
+                       $timestamp
+               );
+               $status = $mh->isValidMerge();
+               if ( $error === true ) {
+                       $this->assertTrue( $status->isGood() );
+               } else {
+                       $this->assertTrue( $status->hasMessage( $error ) );
+               }
+       }
+
+       public static function provideIsValidMerge() {
+               return array(
+                       // for MergeHistory::isValidMerge
+                       array( 'Test', 'Test2', false, true ),
+                       // Although this timestamp is after the latest timestamp of both pages,
+                       // MergeHistory should select the latest source timestamp up to this which should
+                       // still work for the merge.
+                       array( 'Test', 'Test2', strtotime( 'tomorrow' ), true ),
+                       array( 'Test', 'Test', false, 'mergehistory-fail-self-merge' ),
+                       array( 'Nonexistant', 'Test2', false, 'mergehistory-fail-invalid-source' ),
+                       array( 'Test', 'Nonexistant', false, 'mergehistory-fail-invalid-dest' ),
+                       array(
+                               'Test',
+                               'Test2',
+                               'This is obviously an invalid timestamp',
+                               'mergehistory-fail-bad-timestamp'
+                       ),
+               );
+       }
+
+       /**
+        * Test merge revision limit checking
+        * @covers MergeHistory::isValidMerge
+        */
+       public function testIsValidMergeRevisionLimit() {
+               $limit = MergeHistory::REVISION_LIMIT;
+
+               $mh = $this->getMockBuilder( 'MergeHistory' )
+                       ->setMethods( array( 'getRevisionCount' ) )
+                       ->setConstructorArgs( array(
+                               Title::newFromText( 'Test' ),
+                               Title::newFromText( 'Test2' ),
+                       ) )
+                       ->getMock();
+               $mh->expects( $this->once() )
+                       ->method( 'getRevisionCount' )
+                       ->will( $this->returnValue( $limit + 1 ) );
+
+               $status = $mh->isValidMerge();
+               $this->assertTrue( $status->hasMessage( 'mergehistory-fail-toobig' ) );
+               $errors = $status->getErrorsByType( 'error' );
+               $params = $errors[0]['params'];
+               $this->assertEquals( $params[0], Message::numParam( $limit ) );
+       }
+
+       /**
+        * Test user permission checking
+        * @covers MergeHistory::checkPermissions
+        */
+       public function testCheckPermissions() {
+               $mh = new MergeHistory(
+                       Title::newFromText( 'Test' ),
+                       Title::newFromText( 'Test2' )
+               );
+
+               // Sysop with mergehistory permission
+               $sysop = User::newFromName( 'UTSysop' );
+               $status = $mh->checkPermissions( $sysop, '' );
+               $this->assertTrue( $status->isOK() );
+
+               // Normal user
+               $notSysop = User::newFromName( 'UTNotSysop' );
+               $notSysop->addToDatabase();
+               $status = $mh->checkPermissions( $notSysop, '' );
+               $this->assertTrue( $status->hasMessage( 'mergehistory-fail-permission' ) );
+       }
+
+       /**
+        * Test merged revision count
+        * @covers MergeHistory::getMergedRevisionCount
+        */
+       public function testGetMergedRevisionCount() {
+               $mh = new MergeHistory(
+                       Title::newFromText( 'Merge1' ),
+                       Title::newFromText( 'Merge2' )
+               );
+
+               $mh->merge( User::newFromName( 'UTSysop' ) );
+               $this->assertEquals( $mh->getMergedRevisionCount(), 1 );
+       }
+}
index 8178c12..a72247b 100644 (file)
@@ -26,6 +26,9 @@ class ContentHandlerTest extends MediaWikiTestCase {
                                CONTENT_MODEL_CSS => 'CssContentHandler',
                                CONTENT_MODEL_TEXT => 'TextContentHandler',
                                'testing' => 'DummyContentHandlerForTesting',
+                               'testing-callbacks' => function( $modelId ) {
+                                       return new DummyContentHandlerForTesting( $modelId );
+                               }
                        ),
                ) );
 
@@ -378,4 +381,26 @@ class ContentHandlerTest extends MediaWikiTestCase {
 
                return true;
        }
+
+       public function provideGetModelForID() {
+               return array(
+                       array( CONTENT_MODEL_WIKITEXT, 'WikitextContentHandler' ),
+                       array( CONTENT_MODEL_JAVASCRIPT, 'JavaScriptContentHandler' ),
+                       array( CONTENT_MODEL_JSON, 'JsonContentHandler' ),
+                       array( CONTENT_MODEL_CSS, 'CssContentHandler' ),
+                       array( CONTENT_MODEL_TEXT, 'TextContentHandler' ),
+                       array( 'testing', 'DummyContentHandlerForTesting' ),
+                       array( 'testing-callbacks', 'DummyContentHandlerForTesting' ),
+               );
+       }
+
+       /**
+        * @dataProvider provideGetModelForID
+        */
+       public function testGetModelForID( $modelId, $handlerClass ) {
+               $handler = ContentHandler::getForModelID( $modelId );
+
+               $this->assertInstanceOf( $handlerClass, $handler );
+       }
+
 }
index 52872a4..e1ba0ba 100644 (file)
@@ -207,11 +207,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
        }
 
        public function testCheckSessionInfo() {
-               $logger = new \TestLogger( true, function ( $m ) {
-                       return preg_replace(
-                               '/^Session \[\d+\][a-zA-Z0-9_\\\\]+<(?:null|anon|[+-]:\d+:\w+)>\w+: /', 'Session X: ', $m
-                       );
-               } );
+               $logger = new \TestLogger( true );
                $provider = $this->getProvider();
                $provider->setLogger( $logger );
 
@@ -242,7 +238,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
 
                        $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
                        $this->assertSame( array(
-                               array( LogLevel::INFO, "Session X: Missing metadata: $key" )
+                               array( LogLevel::INFO, 'Session "{session}": Missing metadata: {missing}' )
                        ), $logger->getBuffer() );
                        $logger->clearBuffer();
                }
@@ -253,7 +249,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
                $metadata = $info->getProviderMetadata();
                $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
                $this->assertSame( array(
-                       array( LogLevel::INFO, "Session X: No BotPassword for {$bp->getUserCentralId()} Foobar" ),
+                       array( LogLevel::INFO, 'Session "{session}": No BotPassword for {centralId} {appId}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -263,7 +259,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
                $metadata = $info->getProviderMetadata();
                $this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'Session X: BotPassword token check failed' ),
+                       array( LogLevel::INFO, 'Session "{session}": BotPassword token check failed' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -275,7 +271,7 @@ class BotPasswordSessionProviderTest extends MediaWikiTestCase {
                $metadata = $info->getProviderMetadata();
                $this->assertFalse( $provider->refreshSessionInfo( $info, $request2, $metadata ) );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'Session X: Restrictions check failed' ),
+                       array( LogLevel::INFO, 'Session "{session}": Restrictions check failed' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
index 9ba67df..f5c8b05 100644 (file)
@@ -4,6 +4,7 @@ namespace MediaWiki\Session;
 
 use MediaWikiTestCase;
 use User;
+use Psr\Log\LogLevel;
 
 /**
  * @group Session
@@ -159,7 +160,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                        'cookieOptions' => array( 'prefix' => 'x' ),
                );
                $provider = new CookieSessionProvider( $params );
-               $provider->setLogger( new \TestLogger() );
+               $logger = new \TestLogger( true );
+               $provider->setLogger( $logger );
                $provider->setConfig( $this->getConfig() );
                $provider->setManager( new SessionManager() );
 
@@ -174,6 +176,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $request = new \FauxRequest();
                $info = $provider->provideSessionInfo( $request );
                $this->assertNull( $info );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // Session key only
                $request = new \FauxRequest();
@@ -188,6 +192,13 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( 0, $info->getUserInfo()->getId() );
                $this->assertNull( $info->getUserInfo()->getName() );
                $this->assertFalse( $info->forceHTTPS() );
+               $this->assertSame( array(
+                       array(
+                               LogLevel::DEBUG,
+                               'Session "{session}" requested without UserID cookie',
+                       ),
+               ), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User, no session key
                $request = new \FauxRequest();
@@ -203,6 +214,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $id, $info->getUserInfo()->getId() );
                $this->assertSame( $name, $info->getUserInfo()->getName() );
                $this->assertFalse( $info->forceHTTPS() );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User and session key
                $request = new \FauxRequest();
@@ -219,6 +232,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $id, $info->getUserInfo()->getId() );
                $this->assertSame( $name, $info->getUserInfo()->getName() );
                $this->assertFalse( $info->forceHTTPS() );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User with bad token
                $request = new \FauxRequest();
@@ -229,6 +244,13 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                ), '' );
                $info = $provider->provideSessionInfo( $request );
                $this->assertNull( $info );
+               $this->assertSame( array(
+                       array(
+                               LogLevel::WARNING,
+                               'Session "{session}" requested with invalid Token cookie.'
+                       ),
+               ), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User id with no token
                $request = new \FauxRequest();
@@ -245,6 +267,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $id, $info->getUserInfo()->getId() );
                $this->assertSame( $name, $info->getUserInfo()->getName() );
                $this->assertFalse( $info->forceHTTPS() );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                $request = new \FauxRequest();
                $request->setCookies( array(
@@ -252,6 +276,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                ), '' );
                $info = $provider->provideSessionInfo( $request );
                $this->assertNull( $info );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User and session key, with forceHTTPS flag
                $request = new \FauxRequest();
@@ -269,6 +295,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $id, $info->getUserInfo()->getId() );
                $this->assertSame( $name, $info->getUserInfo()->getName() );
                $this->assertTrue( $info->forceHTTPS() );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // Invalid user id
                $request = new \FauxRequest();
@@ -278,6 +306,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                ), '' );
                $info = $provider->provideSessionInfo( $request );
                $this->assertNull( $info );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User id with matching name
                $request = new \FauxRequest();
@@ -295,6 +325,8 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $id, $info->getUserInfo()->getId() );
                $this->assertSame( $name, $info->getUserInfo()->getName() );
                $this->assertFalse( $info->forceHTTPS() );
+               $this->assertSame( array(), $logger->getBuffer() );
+               $logger->clearBuffer();
 
                // User id with wrong name
                $request = new \FauxRequest();
@@ -305,6 +337,13 @@ class CookieSessionProviderTest extends MediaWikiTestCase {
                ), '' );
                $info = $provider->provideSessionInfo( $request );
                $this->assertNull( $info );
+               $this->assertSame( array(
+                       array(
+                               LogLevel::WARNING,
+                               'Session "{session}" requested with mismatched UserID and UserName cookies.',
+                       ),
+               ), $logger->getBuffer() );
+               $logger->clearBuffer();
        }
 
        public function testGetVaryCookies() {
index 3044aa7..e071132 100644 (file)
@@ -114,7 +114,8 @@ class PHPSessionHandlerTest extends MediaWikiTestCase {
 
                $store = new TestBagOStuff();
                $logger = new \TestLogger( true, function ( $m ) {
-                       return preg_match( '/^SessionBackend a{32} /', $m ) ? null : $m;
+                       // Discard all log events starting with expected prefix
+                       return preg_match( '/^SessionBackend "\{session\}" /', $m ) ? null : $m;
                } );
                $manager = new SessionManager( array(
                        'store' => $store,
index bb00121..6be8957 100644 (file)
@@ -2,8 +2,9 @@
 
 namespace MediaWiki\Session;
 
-use Psr\Log\LogLevel;
+use AuthPlugin;
 use MediaWikiTestCase;
+use Psr\Log\LogLevel;
 use User;
 
 /**
@@ -763,6 +764,9 @@ class SessionManagerTest extends MediaWikiTestCase {
 
                \ObjectCache::$instances[__METHOD__] = new TestBagOStuff();
                $this->setMwGlobals( array( 'wgMainCacheType' => __METHOD__ ) );
+               $this->setMWGlobals( array(
+                       'wgAuth' => new AuthPlugin,
+               ) );
 
                $this->stashMwGlobals( array( 'wgGroupPermissions' ) );
                $wgGroupPermissions['*']['createaccount'] = true;
@@ -778,7 +782,6 @@ class SessionManagerTest extends MediaWikiTestCase {
                                return null;
                        }
                        $m = str_replace( 'MediaWiki\Session\SessionManager::autoCreateUser: ', '', $m );
-                       $m = preg_replace( '/ - from: .*$/', ' - from: XXX', $m );
                        return $m;
                } );
                $manager->setLogger( $logger );
@@ -804,7 +807,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                        $user->getId(), User::idFromName( 'UTSessionAutoCreate1', User::READ_LATEST )
                );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate1) - from: XXX' ),
+                       array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -818,7 +821,10 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
                $session->clear();
                $this->assertSame( array(
-                       array( LogLevel::DEBUG, 'user is blocked from this wiki, blacklisting' ),
+                       array(
+                               LogLevel::DEBUG,
+                               'user is blocked from this wiki, blacklisting',
+                       ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -834,7 +840,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                        $user->getId(), User::idFromName( 'UTSessionAutoCreate2', User::READ_LATEST )
                );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate2) - from: XXX' ),
+                       array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -872,7 +878,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                        $user->getId(), User::idFromName( 'UTSessionAutoCreate3', User::READ_LATEST )
                );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate3) - from: XXX' ),
+                       array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1038,7 +1044,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                        'LocalUserCreated' => array(),
                ) );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate4) - from: XXX' ),
+                       array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
        }
@@ -1069,11 +1075,7 @@ class SessionManagerTest extends MediaWikiTestCase {
 
        public function testLoadSessionInfoFromStore() {
                $manager = $this->getManager();
-               $logger = new \TestLogger( true, function ( $m ) {
-                       return preg_replace(
-                               '/^Session \[\d+\]\w+<(?:null|anon|[+-]:\d+:\w+)>\w+: /', 'Session X: ', $m
-                       );
-               } );
+               $logger = new \TestLogger( true );
                $manager->setLogger( $logger );
                $request = new \FauxRequest();
 
@@ -1187,7 +1189,10 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Unverified user provided and no metadata to auth it' )
+                       array(
+                               LogLevel::WARNING,
+                               'Session "{session}": Unverified user provided and no metadata to auth it',
+                       )
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1198,7 +1203,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Null provider and no metadata' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Null provider and no metadata' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1221,7 +1226,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertFalse( $info->isIdSafe(), 'sanity check' );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::INFO, 'Session X: No user provided and provider cannot set user' )
+                       array( LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user' )
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1229,14 +1234,14 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->store->setRawSession( $id, true );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Bad data' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Bad data' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
                $this->store->setRawSession( $id, array( 'data' => array() ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1244,21 +1249,21 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->store->setRawSession( $id, array( 'metadata' => $metadata ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
                $this->store->setRawSession( $id, array( 'metadata' => $metadata, 'data' => true ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
                $this->store->setRawSession( $id, array( 'metadata' => true, 'data' => array() ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1268,7 +1273,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                        $this->store->setRawSession( $id, array( 'metadata' => $tmp, 'data' => array() ) );
                        $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                        $this->assertSame( array(
-                               array( LogLevel::WARNING, 'Session X: Bad metadata' ),
+                               array( LogLevel::WARNING, 'Session "{session}": Bad metadata' ),
                        ), $logger->getBuffer() );
                        $logger->clearBuffer();
                }
@@ -1294,7 +1299,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Wrong provider, Bad !== Mock' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1306,7 +1311,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Unknown provider, Bad' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Unknown provider Bad' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1329,7 +1334,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::ERROR, 'Session X: Invalid ID' ),
+                       array( LogLevel::ERROR, 'Session "{session}": {exception}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1342,7 +1347,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::ERROR, 'Session X: Invalid user name' ),
+                       array( LogLevel::ERROR, 'Session "{session}": {exception}', ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1357,7 +1362,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: User ID mismatch, 2 !== 1' ),
+                       array( LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1372,7 +1377,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: User name mismatch, X !== UTSysop' ),
+                       array( LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1388,7 +1393,8 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
                        array(
-                               LogLevel::WARNING, 'Session X: User ID matched but name didn\'t (rename?), X !== UTSysop'
+                               LogLevel::WARNING,
+                               'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}'
                        ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
@@ -1405,7 +1411,9 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
                        array(
-                               LogLevel::WARNING, 'Session X: Metadata has an anonymous user, but a non-anon user was provided'
+                               LogLevel::WARNING,
+                               'Session "{session}": Metadata has an anonymous user, ' .
+                               'but a non-anon user was provided',
                        ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
@@ -1489,7 +1497,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: User token mismatch' ),
+                       array( LogLevel::WARNING, 'Session "{session}": User token mismatch' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1533,7 +1541,10 @@ class SessionManagerTest extends MediaWikiTestCase {
                ) );
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Metadata merge failed: no merge!' ),
+                       array(
+                               LogLevel::WARNING,
+                               'Session "{session}": Metadata merge failed: {exception}',
+                       ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
 
@@ -1664,7 +1675,7 @@ class SessionManagerTest extends MediaWikiTestCase {
                $this->assertFalse( $loadSessionInfoFromStore( $info ) );
                $this->assertTrue( $called );
                $this->assertSame( array(
-                       array( LogLevel::WARNING, 'Session X: Hook aborted' ),
+                       array( LogLevel::WARNING, 'Session "{session}": Hook aborted' ),
                ), $logger->getBuffer() );
                $logger->clearBuffer();
        }