Merge "collation: Refactor getFirstLetterData() cache handling"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 12 May 2016 11:38:49 +0000 (11:38 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 12 May 2016 11:38:49 +0000 (11:38 +0000)
113 files changed:
RELEASE-NOTES-1.28
autoload.php
composer.json
docs/hooks.txt
includes/AuthPlugin.php
includes/DefaultSettings.php
includes/MediaWiki.php
includes/MediaWikiServices.php
includes/OutputPage.php
includes/ProtectionForm.php
includes/Revision.php
includes/ServiceWiring.php
includes/Title.php
includes/WebRequest.php
includes/api/ApiBase.php
includes/api/ApiMain.php
includes/api/i18n/jv.json [new file with mode: 0644]
includes/cache/GenderCache.php
includes/filerepo/FileRepo.php
includes/filerepo/file/File.php
includes/installer/i18n/jv.json
includes/installer/i18n/zh-hant.json
includes/interwiki/Interwiki.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/libs/objectcache/BagOStuff.php
includes/linker/LinkTarget.php
includes/objectcache/RedisBagOStuff.php
includes/parser/BlockLevelPass.php [new file with mode: 0644]
includes/parser/Parser.php
includes/poolcounter/PoolCounter.php
includes/poolcounter/PoolCounterWork.php
includes/poolcounter/PoolCounterWorkViaCallback.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderUserGroupsModule.php
includes/resourceloader/ResourceLoaderUserModule.php
includes/session/SessionInfo.php
includes/session/SessionManager.php
includes/session/SessionManagerInterface.php
includes/session/SessionProvider.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialUserlogin.php
includes/specials/SpecialWhatlinkshere.php
includes/title/MediaWikiTitleCodec.php
includes/title/TitleFormatter.php
includes/title/TitleValue.php
includes/user/User.php
languages/i18n/ar.json
languages/i18n/azb.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/cs.json
languages/i18n/cy.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/he.json
languages/i18n/inh.json
languages/i18n/it.json
languages/i18n/jv.json
languages/i18n/ko.json
languages/i18n/ksh.json
languages/i18n/lt.json
languages/i18n/nds-nl.json
languages/i18n/or.json
languages/i18n/pl.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/sv.json
languages/i18n/uk.json
languages/i18n/vi.json
languages/i18n/war.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesCs.php
maintenance/updateCollation.php
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/src/mediawiki.action/mediawiki.action.view.metadata.js
resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js
tests/parser/parserTest.inc
tests/phpunit/includes/LinkerTest.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/parser/NewParserTest.php
tests/phpunit/includes/poolcounter/PoolCounterTest.php
tests/phpunit/includes/session/SessionInfoTest.php
tests/phpunit/includes/session/SessionManagerTest.php
tests/phpunit/includes/session/SessionProviderTest.php
tests/phpunit/includes/title/MediaWikiTitleCodecTest.php
tests/phpunit/includes/title/TitleValueTest.php

index c950921..e365486 100644 (file)
@@ -11,6 +11,8 @@ production.
   user's language. If such access is attempted, an exception will be thrown.
 
 === New features in 1.28 ===
+* User::isBot() method for checking if an account is a bot role account.
+* Added a new hook, 'UserIsBot', to aid in determining if a user is a bot.
 
 
 === External library changes in 1.28 ===
index c7f8984..1e656e4 100644 (file)
@@ -180,6 +180,7 @@ $wgAutoloadLocalClasses = [
        'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
        'Blob' => __DIR__ . '/includes/db/DatabaseUtility.php',
        'Block' => __DIR__ . '/includes/Block.php',
+       'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
        'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
        'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
        'BmpHandler' => __DIR__ . '/includes/media/BMP.php',
index b614a4c..ef85ec4 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.1",
+               "oojs/oojs-ui": "0.17.2",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
index a7092ec..2d5f6bc 100644 (file)
@@ -2128,6 +2128,7 @@ $sk: The Skin that called OutputPage::headElement
 since the last visit.
 &$modifiedTimes: array of timestamps.
   The following keys are set: page, user, epoch
+$out: OutputPage object (since 1.28)
 
 'OutputPageMakeCategoryLinks': Links are about to be generated for the page's
 categories. Implementations should return false if they generate the category
@@ -3216,6 +3217,10 @@ $mime: (string) The uploaded file's MIME type, as detected by MediaWiki.
   representing the problem with the file, where the first element is the message
   key and the remaining elements are used as parameters to the message.
 
+'UserIsBot': when determining whether a user is a bot account
+$user: the user
+&$isBot: whether this is user a bot or not (boolean)
+
 'User::mailPasswordInternal': before creation and mailing of a user's new
 temporary password
 &$user: the user who sent the message out
index 6449d37..add5876 100644 (file)
@@ -352,6 +352,9 @@ class AuthPluginUser {
                return false;
        }
 
+       /**
+        * @deprecated since 1.28, use SessionManager::invalidateSessionForUser() instead.
+        */
        public function resetAuthToken() {
                # Override this!
                return true;
index b7f487e..383f0ad 100644 (file)
@@ -4492,9 +4492,9 @@ $wgPasswordConfig = [
        ],
        'pbkdf2' => [
                'class' => 'Pbkdf2Password',
-               'algo' => 'sha256',
-               'cost' => '10000',
-               'length' => '128',
+               'algo' => 'sha512',
+               'cost' => '30000',
+               'length' => '64',
        ],
 ];
 
index 3dd7420..ff469e4 100644 (file)
@@ -487,6 +487,7 @@ class MediaWiki {
                        $trxProfiler = Profiler::instance()->getTransactionProfiler();
                        if ( $request->wasPosted() && !$action->doesWrites() ) {
                                $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
+                               $request->markAsSafeRequest();
                        }
 
                        # Let CDN cache things if we can purge them.
index 882b2ce..5bb5597 100644 (file)
@@ -4,6 +4,7 @@ namespace MediaWiki;
 use Config;
 use ConfigFactory;
 use EventRelayerGroup;
+use GenderCache;
 use GlobalVarConfig;
 use Hooks;
 use LBFactory;
@@ -19,6 +20,8 @@ use SiteLookup;
 use SiteStore;
 use WatchedItemStore;
 use SkinFactory;
+use TitleFormatter;
+use TitleParser;
 
 /**
  * Service locator for MediaWiki core services.
@@ -450,6 +453,29 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'WatchedItemStore' );
        }
 
+       /**
+        * @since 1.28
+        * @return GenderCache
+        */
+       public function getGenderCache() {
+               return $this->getService( 'GenderCache' );
+       }
+       /**
+        * @since 1.28
+        * @return TitleFormatter
+        */
+       public function getTitleFormatter() {
+               return $this->getService( 'TitleFormatter' );
+       }
+
+       /**
+        * @since 1.28
+        * @return TitleParser
+        */
+       public function getTitleParser() {
+               return $this->getService( 'TitleParser' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in
index c724207..67e9a4f 100644 (file)
@@ -780,7 +780,7 @@ class OutputPage extends ContextSource {
                        // bug 44570: the core page itself may not change, but resources might
                        $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
                }
-               Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes ] );
+               Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
 
                $maxModified = max( $modifiedTimes );
                $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
index 70192b9..451635e 100644 (file)
@@ -70,7 +70,9 @@ class ProtectionForm {
                // Check if the form should be disabled.
                // If it is, the form will be available in read-only to show levels.
                $this->mPermErrors = $this->mTitle->getUserPermissionsErrors(
-                       'protect', $this->mContext->getUser()
+                       'protect',
+                       $this->mContext->getUser(),
+                       $this->mContext->getRequest()->wasPosted() ? 'secure' : 'full' // T92357
                );
                if ( wfReadOnly() ) {
                        $this->mPermErrors[] = [ 'readonlytext', wfReadOnlyReason() ];
index 40daf3d..0e45b25 100644 (file)
@@ -1553,7 +1553,6 @@ class Revision implements IDBAccessObject {
                        }
                        $text = $cache->get( $key );
                        if ( is_string( $text ) ) {
-                               wfDebug( __METHOD__ . ": got id $textId from cache\n" );
                                $processCache->set( $key, $text );
                                return $text;
                        }
index 8e95034..aa99a71 100644 (file)
@@ -139,6 +139,28 @@ return [
                return $store;
        },
 
+       'GenderCache' => function( MediaWikiServices $services ) {
+               return new GenderCache();
+       },
+
+       '_MediaWikiTitleCodec' => function( MediaWikiServices $services ) {
+               global $wgContLang;
+
+               return new MediaWikiTitleCodec(
+                       $wgContLang,
+                       $services->getGenderCache(),
+                       $services->getMainConfig()->get( 'LocalInterwikis' )
+               );
+       },
+
+       'TitleFormatter' => function( MediaWikiServices $services ) {
+               return $services->getService( '_MediaWikiTitleCodec' );
+       },
+
+       'TitleParser' => function( MediaWikiServices $services ) {
+               return $services->getService( '_MediaWikiTitleCodec' );
+       },
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service here, don't forget to add a getter function
        // in the MediaWikiServices class. The convenience getter should just call
index 65b2d3a..d070609 100644 (file)
@@ -22,7 +22,6 @@
  * @file
  */
 use MediaWiki\Linker\LinkTarget;
-
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -159,42 +158,6 @@ class Title implements LinkTarget {
        private $mIsBigDeletion = null;
        // @}
 
-       /**
-        * B/C kludge: provide a TitleParser for use by Title.
-        * Ideally, Title would have no methods that need this.
-        * Avoid usage of this singleton by using TitleValue
-        * and the associated services when possible.
-        *
-        * @return MediaWikiTitleCodec
-        */
-       private static function getMediaWikiTitleCodec() {
-               global $wgContLang, $wgLocalInterwikis;
-
-               static $titleCodec = null;
-               static $titleCodecFingerprint = null;
-
-               // $wgContLang and $wgLocalInterwikis may change (especially while testing),
-               // make sure we are using the right one. To detect changes over the course
-               // of a request, we remember a fingerprint of the config used to create the
-               // codec singleton, and re-create it if the fingerprint doesn't match.
-               $fingerprint = spl_object_hash( $wgContLang ) . '|' . implode( '+', $wgLocalInterwikis );
-
-               if ( $fingerprint !== $titleCodecFingerprint ) {
-                       $titleCodec = null;
-               }
-
-               if ( !$titleCodec ) {
-                       $titleCodec = new MediaWikiTitleCodec(
-                               $wgContLang,
-                               GenderCache::singleton(),
-                               $wgLocalInterwikis
-                       );
-                       $titleCodecFingerprint = $fingerprint;
-               }
-
-               return $titleCodec;
-       }
-
        /**
         * B/C kludge: provide a TitleParser for use by Title.
         * Ideally, Title would have no methods that need this.
@@ -204,9 +167,7 @@ class Title implements LinkTarget {
         * @return TitleFormatter
         */
        private static function getTitleFormatter() {
-               // NOTE: we know that getMediaWikiTitleCodec() returns a MediaWikiTitleCodec,
-               //      which implements TitleFormatter.
-               return self::getMediaWikiTitleCodec();
+               return MediaWikiServices::getInstance()->getTitleFormatter();
        }
 
        function __construct() {
@@ -3334,9 +3295,11 @@ class Title implements LinkTarget {
                // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
                //        the parsing code with Title, while avoiding massive refactoring.
                // @todo: get rid of secureAndSplit, refactor parsing code.
-               $titleParser = self::getMediaWikiTitleCodec();
+               // @note: getTitleParser() returns a TitleParser implementation which does not have a
+               //        splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
+               $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
                // MalformedTitleException can be thrown here
-               $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
+               $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
 
                # Fill fields
                $this->setFragment( '#' . $parts['fragment'] );
index b159f79..2333c78 100644 (file)
@@ -80,6 +80,9 @@ class WebRequest {
         */
        protected $sessionId = null;
 
+       /** @var bool Whether this HTTP request is "safe" (even if it is an HTTP post) */
+       protected $markedAsSafe = false;
+
        public function __construct() {
                $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
                        ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
@@ -318,7 +321,7 @@ class WebRequest {
         *
         * @param string $path The URL path given from the client
         * @param array $bases One or more URLs, optionally with $1 at the end
-        * @param string $key If provided, the matching key in $bases will be
+        * @param string|bool $key If provided, the matching key in $bases will be
         *    passed on as the value of this URL parameter
         * @return array Array of URL variables to interpolate; empty if no match
         */
@@ -1022,7 +1025,7 @@ class WebRequest {
         * @param mixed $data
         */
        public function setSessionData( $key, $data ) {
-               return $this->getSession()->set( $key, $data );
+               $this->getSession()->set( $key, $data );
        }
 
        /**
@@ -1245,4 +1248,50 @@ HTML;
        public function setIP( $ip ) {
                $this->ip = $ip;
        }
+
+       /**
+        * Whether this request should be identified as being "safe"
+        *
+        * This means that the client is not requesting any state changes and that database writes
+        * are not inherently required. Ideally, no visible updates would happen at all. If they
+        * must, then they should not be publically attributed to the end user.
+        *
+        * In more detail:
+        *   - Cache populations and refreshes MAY occur.
+        *   - Private user session updates and private server logging MAY occur.
+        *   - Updates to private viewing activity data MAY occur via DeferredUpdates.
+        *   - Other updates SHOULD NOT occur (e.g. modifying content assets).
+        *
+        * @return bool
+        * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+        * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+        * @since 1.28
+        */
+       public function isSafeRequest() {
+               if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
+                       return false; // CLI mode
+               }
+
+               if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
+                       return $this->markedAsSafe;
+               } elseif ( in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS' ] ) ) {
+                       return true; // HTTP "safe methods"
+               }
+
+               return false; // PUT/DELETE
+       }
+
+       /**
+        * Mark this request is identified as being nullipotent even if it is a POST request
+        *
+        * POST requests are often used due to the need for a client payload, even if the request
+        * is otherwise equivalent to a "safe method" request.
+        *
+        * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+        * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+        * @since 1.28
+        */
+       public function markAsSafeRequest() {
+               $this->markedAsSafe = true;
+       }
 }
index da64c03..7d0ae32 100644 (file)
@@ -1522,20 +1522,20 @@ abstract class ApiBase extends ContextSource {
                        throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
                }
 
-               $errors = $status->getErrorsArray();
+               $errors = $status->getErrorsByType( 'error' );
                if ( !$errors ) {
                        // No errors? Assume the warnings should be treated as errors
-                       $errors = $status->getWarningsArray();
+                       $errors = $status->getErrorsByType( 'warning' );
                }
                if ( !$errors ) {
                        // Still no errors? Punt
-                       $errors = [ [ 'unknownerror-nocode' ] ];
+                       $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
                }
 
                // Cannot use dieUsageMsg() because extensions might return custom
                // error messages.
-               if ( $errors[0] instanceof Message ) {
-                       $msg = $errors[0];
+               if ( $errors[0]['message'] instanceof Message ) {
+                       $msg = $errors[0]['message'];
                        if ( $msg instanceof IApiMessage ) {
                                $extraData = $msg->getApiData();
                                $code = $msg->getApiCode();
@@ -1543,8 +1543,8 @@ abstract class ApiBase extends ContextSource {
                                $code = $msg->getKey();
                        }
                } else {
-                       $code = array_shift( $errors[0] );
-                       $msg = wfMessage( $code, $errors[0] );
+                       $code = $errors[0]['message'];
+                       $msg = wfMessage( $code, $errors[0]['params'] );
                }
                if ( isset( ApiBase::$messageMap[$code] ) ) {
                        // Translate message to code, for backwards compatibility
index 07642c4..685a9ef 100644 (file)
@@ -134,7 +134,9 @@ class ApiMain extends ApiBase {
        private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
        private $mAction;
        private $mEnableWrite;
-       private $mInternalMode, $mSquidMaxage, $mModule;
+       private $mInternalMode, $mSquidMaxage;
+       /** @var ApiBase */
+       private $mModule;
 
        private $mCacheMode = 'private';
        private $mCacheControl = [];
@@ -397,13 +399,7 @@ class ApiMain extends ApiBase {
                if ( $this->mInternalMode ) {
                        $this->executeAction();
                } else {
-                       $start = microtime( true );
                        $this->executeActionWithErrorHandling();
-                       if ( $this->isWriteMode() && $this->getRequest()->wasPosted() ) {
-                               $timeMs = 1000 * max( 0, microtime( true ) - $start );
-                               $this->getStats()->timing(
-                                       'api.' . $this->getModuleName() . '.executeTiming', $timeMs );
-                       }
                }
        }
 
@@ -433,8 +429,12 @@ class ApiMain extends ApiBase {
                $isError = false;
                try {
                        $this->executeAction();
-                       $this->logRequest( microtime( true ) - $t );
-
+                       $runTime = microtime( true ) - $t;
+                       $this->logRequest( $runTime );
+                       if ( $this->mModule->isWriteMode() && $this->getRequest()->wasPosted() ) {
+                               $this->getStats()->timing(
+                                       'api.' . $this->getModuleName() . '.executeTiming', 1000 * $runTime );
+                       }
                } catch ( Exception $e ) {
                        $this->handleException( $e );
                        $this->logRequest( microtime( true ) - $t, $e );
@@ -1355,6 +1355,7 @@ class ApiMain extends ApiBase {
                                $trxProfiler->setExpectations( $limits['POST'], __METHOD__ );
                        } else {
                                $trxProfiler->setExpectations( $limits['POST-nonwrite'], __METHOD__ );
+                               $this->getRequest()->markAsSafeRequest();
                        }
                } else {
                        $trxProfiler->setExpectations( $limits['GET'], __METHOD__ );
diff --git a/includes/api/i18n/jv.json b/includes/api/i18n/jv.json
new file mode 100644 (file)
index 0000000..af1ca9c
--- /dev/null
@@ -0,0 +1,11 @@
+{
+       "@metadata": {
+               "authors": [
+                       "NoiX180"
+               ]
+       },
+       "apihelp-delete-example-simple": "Busak <kbd>Kaca Pokok</kbd>.",
+       "apihelp-query+backlinks-example-simple": "Tuduhaké pranala menyang <kbd>Kaca utama</kbd>.",
+       "apihelp-query+backlinks-example-generator": "Deleng pratélan bab kaca-kaca sing nggayut <kbd>Kaca utama</kbd>.",
+       "apihelp-query+contributors-example-simple": "Tuduhaké para nyumbang <kbd>Kaca Utama</kbd>."
+}
index 19695df..80f04ce 100644 (file)
@@ -21,6 +21,7 @@
  * @author Niklas Laxström
  * @ingroup Cache
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Caches user genders when needed to use correct namespace aliases.
@@ -34,18 +35,11 @@ class GenderCache {
        protected $missLimit = 1000;
 
        /**
+        * @deprecated in 1.28 see MediaWikiServices::getInstance()->getGenderCache()
         * @return GenderCache
         */
        public static function singleton() {
-               static $that = null;
-               if ( $that === null ) {
-                       $that = new self();
-               }
-
-               return $that;
-       }
-
-       protected function __construct() {
+               return MediaWikiServices::getInstance()->getGenderCache();
        }
 
        /**
index 15821ea..9ad2428 100644 (file)
@@ -815,7 +815,6 @@ class FileRepo {
         * @param string $dstZone Destination zone
         * @param string $dstRel Destination relative path
         * @param int $flags Bitwise combination of the following flags:
-        *   self::DELETE_SOURCE     Delete the source file after upload
         *   self::OVERWRITE         Overwrite an existing destination file instead of failing
         *   self::OVERWRITE_SAME    Overwrite the file if the destination exists and has the
         *                           same contents as the source
@@ -838,7 +837,6 @@ class FileRepo {
         *
         * @param array $triplets (src, dest zone, dest rel) triplets as per store()
         * @param int $flags Bitwise combination of the following flags:
-        *   self::DELETE_SOURCE     Delete the source file after upload
         *   self::OVERWRITE         Overwrite an existing destination file instead of failing
         *   self::OVERWRITE_SAME    Overwrite the file if the destination exists and has the
         *                           same contents as the source
@@ -849,11 +847,14 @@ class FileRepo {
        public function storeBatch( array $triplets, $flags = 0 ) {
                $this->assertWritableRepo(); // fail out if read-only
 
+               if ( $flags & self::DELETE_SOURCE ) {
+                       throw new InvalidArgumentException( "DELETE_SOURCE not supported in " . __METHOD__ );
+               }
+
                $status = $this->newGood();
                $backend = $this->backend; // convenience
 
                $operations = [];
-               $sourceFSFilesToDelete = []; // cleanup for disk source files
                // Validate each triplet and get the store operation...
                foreach ( $triplets as $triplet ) {
                        list( $srcPath, $dstZone, $dstRel ) = $triplet;
@@ -881,12 +882,9 @@ class FileRepo {
 
                        // Get the appropriate file operation
                        if ( FileBackend::isStoragePath( $srcPath ) ) {
-                               $opName = ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy';
+                               $opName = 'copy';
                        } else {
                                $opName = 'store';
-                               if ( $flags & self::DELETE_SOURCE ) {
-                                       $sourceFSFilesToDelete[] = $srcPath;
-                               }
                        }
                        $operations[] = [
                                'op' => $opName,
@@ -903,12 +901,6 @@ class FileRepo {
                        $opts['nonLocking'] = true;
                }
                $status->merge( $backend->doOperations( $operations, $opts ) );
-               // Cleanup for disk source files...
-               foreach ( $sourceFSFilesToDelete as $file ) {
-                       MediaWiki\suppressWarnings();
-                       unlink( $file ); // FS cleanup
-                       MediaWiki\restoreWarnings();
-               }
 
                return $status;
        }
index c037516..8175b58 100644 (file)
@@ -1065,7 +1065,6 @@ abstract class File implements IDBAccessObject {
                        if ( $this->repo ) {
                                // Defer rendering if a 404 handler is set up...
                                if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
-                                       wfDebug( __METHOD__ . " transformation deferred.\n" );
                                        // XXX: Pass in the storage path even though we are not rendering anything
                                        // and the path is supposed to be an FS path. This is due to getScalerType()
                                        // getting called on the path and clobbering $thumb->getUrl() if it's false.
index d33e03b..ee8ba4b 100644 (file)
@@ -1,9 +1,11 @@
 {
        "@metadata": {
                "authors": [
-                       "Anggoro"
+                       "Anggoro",
+                       "NoiX180"
                ]
        },
+       "config-install-mainpage-failed": "Ora bisa nglebokaké kaca pokok: $1",
        "mainpagetext": "'''Prangkat empuk wiki wis suksès dipasang.'''",
        "mainpagedocfooter": "Mangga maca [//meta.wikimedia.org/wiki/Help:Contents User's Guide] kanggo katrangan luwih langkung prakara panggunan prangkat empuk wiki\n== Miwiti panggunan  ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daftar pangaturan préférènsi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index 4f57b1f..4f96f4b 100644 (file)
@@ -14,7 +14,8 @@
                        "S8321414",
                        "LNDDYL",
                        "NigelSoft",
-                       "Macofe"
+                       "Macofe",
+                       "Reke"
                ]
        },
        "config-desc": "MediaWiki 安裝程式",
@@ -23,7 +24,7 @@
        "config-localsettings-upgrade": "已偵測到 <code>LocalSettings.php</code> 檔案。\n要升級目前安裝的版本,請在下方輸入框中輸入 <code>$wgUpgradeKey</code> 的值。\n您可以從 <code>LocalSettings.php</code> 檔案中找到。",
        "config-localsettings-cli-upgrade": "已偵測到 <code>LocalSettings.php</code> 檔案。\n要升級目前安裝的版本,請執行 <code>update.php</code>。",
        "config-localsettings-key": "升級金鑰:",
-       "config-localsettings-badkey": "你提供的金鑰不正確。",
+       "config-localsettings-badkey": "你提供的升級金鑰不正確。",
        "config-upgrade-key-missing": "已偵測到先前安裝的 MediaWiki。\n要升級目前安裝的版本,請將下列文字附加到 <code>LocalSettings.php</code> 的檔案最下方:\n\n$1",
        "config-localsettings-incomplete": "目前的 <code>LocalSettings.php</code> 檔案不完整。\n未設定參數 $1。\n請將此參數設定至 <code>LocalSettings.php</code> 中,並點選 \"{{int:Config-continue}}\"。",
        "config-localsettings-connection-error": "使用 <code>LocalSettings.php</code> 中所指定的資料庫設定連線發生錯誤。 請修復相關設定並再試一次。\n\n$1",
index f68651b..5a0dd36 100644 (file)
@@ -171,7 +171,6 @@ class Interwiki {
                global $wgInterwikiScopes, $wgInterwikiFallbackSite;
                static $site;
 
-               wfDebug( __METHOD__ . "( $prefix )\n" );
                $value = false;
                try {
                        // Resolve site name
index 22afead..fbc1572 100644 (file)
@@ -70,7 +70,7 @@ class RecentChangesUpdateJob extends Job {
        }
 
        protected function purgeExpiredRows() {
-               global $wgRCMaxAge;
+               global $wgRCMaxAge, $wgUpdateRowsPerQuery;
 
                $lockKey = wfWikiID() . ':recentchanges-prune';
 
@@ -81,14 +81,13 @@ class RecentChangesUpdateJob extends Job {
                        return; // already in progress
                }
 
-               $batchSize = 100; // avoid slave lag
                $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
                do {
                        $rcIds = $dbw->selectFieldValues( 'recentchanges',
                                'rc_id',
                                [ 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ],
                                __METHOD__,
-                               [ 'LIMIT' => $batchSize ]
+                               [ 'LIMIT' => $wgUpdateRowsPerQuery ]
                        );
                        if ( $rcIds ) {
                                $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
@@ -96,7 +95,7 @@ class RecentChangesUpdateJob extends Job {
                        // Commit in chunks to avoid slave lag
                        $dbw->commit( __METHOD__, 'flush' );
 
-                       if ( count( $rcIds ) === $batchSize ) {
+                       if ( count( $rcIds ) === $wgUpdateRowsPerQuery ) {
                                // There might be more, so try waiting for slaves
                                try {
                                        wfGetLBFactory()->waitForReplication( [ 'timeout' => 3 ] );
index 8e3c0a5..bf46ce1 100644 (file)
@@ -285,8 +285,12 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
        protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10 ) {
                do {
                        $this->clearLastError();
+                       $reportDupes = $this->reportDupes;
+                       $this->reportDupes = false;
                        $casToken = null; // passed by reference
                        $currentValue = $this->getWithToken( $key, $casToken, self::READ_LATEST );
+                       $this->reportDupes = $reportDupes;
+
                        if ( $this->getLastError() ) {
                                return false; // don't spam retries (retry only on races)
                        }
@@ -342,7 +346,11 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
                }
 
                $this->clearLastError();
+               $reportDupes = $this->reportDupes;
+               $this->reportDupes = false;
                $currentValue = $this->get( $key, self::READ_LATEST );
+               $this->reportDupes = $reportDupes;
+
                if ( $this->getLastError() ) {
                        $success = false;
                } else {
index 7b59751..da48e00 100644 (file)
@@ -33,6 +33,14 @@ interface LinkTarget {
         */
        public function getNamespace();
 
+       /**
+        * Convenience function to test if it is in the namespace
+        *
+        * @param int $ns
+        * @return bool
+        */
+       public function inNamespace( $ns );
+
        /**
         * Get the link fragment (i.e. the bit after the #) in text form.
         *
index 61e6926..90508da 100644 (file)
@@ -310,7 +310,8 @@ class RedisBagOStuff extends BagOStuff {
         * @return mixed
         */
        protected function unserialize( $data ) {
-               return ctype_digit( $data ) ? intval( $data ) : unserialize( $data );
+               $int = intval( $data );
+               return $data === (string)$int ? $int : unserialize( $data );
        }
 
        /**
diff --git a/includes/parser/BlockLevelPass.php b/includes/parser/BlockLevelPass.php
new file mode 100644 (file)
index 0000000..cbacd34
--- /dev/null
@@ -0,0 +1,535 @@
+<?php
+
+/**
+ * This is the part of the wikitext parser which handles automatic paragraphs
+ * and conversion of start-of-line prefixes to HTML lists.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+class BlockLevelPass {
+       private $DTopen = false;
+       private $inPre = false;
+       private $lastSection = '';
+       private $linestart;
+       private $text;
+
+       # State constants for the definition list colon extraction
+       const COLON_STATE_TEXT = 0;
+       const COLON_STATE_TAG = 1;
+       const COLON_STATE_TAGSTART = 2;
+       const COLON_STATE_CLOSETAG = 3;
+       const COLON_STATE_TAGSLASH = 4;
+       const COLON_STATE_COMMENT = 5;
+       const COLON_STATE_COMMENTDASH = 6;
+       const COLON_STATE_COMMENTDASHDASH = 7;
+
+       /**
+        * Make lists from lines starting with ':', '*', '#', etc.
+        *
+        * @param string $text
+        * @param bool $lineStart Whether or not this is at the start of a line.
+        * @return string The lists rendered as HTML
+        */
+       public static function doBlockLevels( $text, $lineStart ) {
+               $pass = new self( $text, $lineStart );
+               return $pass->execute();
+       }
+
+       /**
+        * Private constructor
+        */
+       private function __construct( $text, $lineStart ) {
+               $this->text = $text;
+               $this->lineStart = $lineStart;
+       }
+
+       /**
+        * If a pre or p is open, return the corresponding close tag and update
+        * the state. If no tag is open, return an empty string.
+        * @return string
+        */
+       private function closeParagraph() {
+               $result = '';
+               if ( $this->lastSection !== '' ) {
+                       $result = '</' . $this->lastSection . ">\n";
+               }
+               $this->inPre = false;
+               $this->lastSection = '';
+               return $result;
+       }
+
+       /**
+        * getCommon() returns the length of the longest common substring
+        * of both arguments, starting at the beginning of both.
+        *
+        * @param string $st1
+        * @param string $st2
+        *
+        * @return int
+        */
+       private function getCommon( $st1, $st2 ) {
+               $shorter = min( strlen( $st1 ), strlen( $st2 ) );
+
+               for ( $i = 0; $i < $shorter; ++$i ) {
+                       if ( $st1[$i] !== $st2[$i] ) {
+                               break;
+                       }
+               }
+               return $i;
+       }
+
+       /**
+        * Open the list item element identified by the prefix character.
+        *
+        * @param string $char
+        *
+        * @return string
+        */
+       private function openList( $char ) {
+               $result = $this->closeParagraph();
+
+               if ( '*' === $char ) {
+                       $result .= "<ul><li>";
+               } elseif ( '#' === $char ) {
+                       $result .= "<ol><li>";
+               } elseif ( ':' === $char ) {
+                       $result .= "<dl><dd>";
+               } elseif ( ';' === $char ) {
+                       $result .= "<dl><dt>";
+                       $this->DTopen = true;
+               } else {
+                       $result = '<!-- ERR 1 -->';
+               }
+
+               return $result;
+       }
+
+       /**
+        * Close the current list item and open the next one.
+        * @param string $char
+        *
+        * @return string
+        */
+       private function nextItem( $char ) {
+               if ( '*' === $char || '#' === $char ) {
+                       return "</li>\n<li>";
+               } elseif ( ':' === $char || ';' === $char ) {
+                       $close = "</dd>\n";
+                       if ( $this->DTopen ) {
+                               $close = "</dt>\n";
+                       }
+                       if ( ';' === $char ) {
+                               $this->DTopen = true;
+                               return $close . '<dt>';
+                       } else {
+                               $this->DTopen = false;
+                               return $close . '<dd>';
+                       }
+               }
+               return '<!-- ERR 2 -->';
+       }
+
+       /**
+        * Close the current list item identified by the prefix character.
+        * @param string $char
+        *
+        * @return string
+        */
+       private function closeList( $char ) {
+               if ( '*' === $char ) {
+                       $text = "</li></ul>";
+               } elseif ( '#' === $char ) {
+                       $text = "</li></ol>";
+               } elseif ( ':' === $char ) {
+                       if ( $this->DTopen ) {
+                               $this->DTopen = false;
+                               $text = "</dt></dl>";
+                       } else {
+                               $text = "</dd></dl>";
+                       }
+               } else {
+                       return '<!-- ERR 3 -->';
+               }
+               return $text;
+       }
+
+       /**
+        * Execute the pass.
+        * @return string
+        */
+       private function execute() {
+               $text = $this->text;
+               # Parsing through the text line by line.  The main thing
+               # happening here is handling of block-level elements p, pre,
+               # and making lists from lines starting with * # : etc.
+               $textLines = StringUtils::explode( "\n", $text );
+
+               $lastPrefix = $output = '';
+               $this->DTopen = $inBlockElem = false;
+               $prefixLength = 0;
+               $pendingPTag = false;
+               $inBlockquote = false;
+
+               foreach ( $textLines as $inputLine ) {
+                       # Fix up $lineStart
+                       if ( !$this->lineStart ) {
+                               $output .= $inputLine;
+                               $this->lineStart = true;
+                               continue;
+                       }
+                       # * = ul
+                       # # = ol
+                       # ; = dt
+                       # : = dd
+
+                       $lastPrefixLength = strlen( $lastPrefix );
+                       $preCloseMatch = preg_match( '/<\\/pre/i', $inputLine );
+                       $preOpenMatch = preg_match( '/<pre/i', $inputLine );
+                       # If not in a <pre> element, scan for and figure out what prefixes are there.
+                       if ( !$this->inPre ) {
+                               # Multiple prefixes may abut each other for nested lists.
+                               $prefixLength = strspn( $inputLine, '*#:;' );
+                               $prefix = substr( $inputLine, 0, $prefixLength );
+
+                               # eh?
+                               # ; and : are both from definition-lists, so they're equivalent
+                               #  for the purposes of determining whether or not we need to open/close
+                               #  elements.
+                               $prefix2 = str_replace( ';', ':', $prefix );
+                               $t = substr( $inputLine, $prefixLength );
+                               $this->inPre = (bool)$preOpenMatch;
+                       } else {
+                               # Don't interpret any other prefixes in preformatted text
+                               $prefixLength = 0;
+                               $prefix = $prefix2 = '';
+                               $t = $inputLine;
+                       }
+
+                       # List generation
+                       if ( $prefixLength && $lastPrefix === $prefix2 ) {
+                               # Same as the last item, so no need to deal with nesting or opening stuff
+                               $output .= $this->nextItem( substr( $prefix, -1 ) );
+                               $pendingPTag = false;
+
+                               if ( substr( $prefix, -1 ) === ';' ) {
+                                       # The one nasty exception: definition lists work like this:
+                                       # ; title : definition text
+                                       # So we check for : in the remainder text to split up the
+                                       # title and definition, without b0rking links.
+                                       $term = $t2 = '';
+                                       if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
+                                               $t = $t2;
+                                               $output .= $term . $this->nextItem( ':' );
+                                       }
+                               }
+                       } elseif ( $prefixLength || $lastPrefixLength ) {
+                               # We need to open or close prefixes, or both.
+
+                               # Either open or close a level...
+                               $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
+                               $pendingPTag = false;
+
+                               # Close all the prefixes which aren't shared.
+                               while ( $commonPrefixLength < $lastPrefixLength ) {
+                                       $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
+                                       --$lastPrefixLength;
+                               }
+
+                               # Continue the current prefix if appropriate.
+                               if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
+                                       $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
+                               }
+
+                               # Open prefixes where appropriate.
+                               if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
+                                       $output .= "\n";
+                               }
+                               while ( $prefixLength > $commonPrefixLength ) {
+                                       $char = substr( $prefix, $commonPrefixLength, 1 );
+                                       $output .= $this->openList( $char );
+
+                                       if ( ';' === $char ) {
+                                               # @todo FIXME: This is dupe of code above
+                                               if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
+                                                       $t = $t2;
+                                                       $output .= $term . $this->nextItem( ':' );
+                                               }
+                                       }
+                                       ++$commonPrefixLength;
+                               }
+                               if ( !$prefixLength && $lastPrefix ) {
+                                       $output .= "\n";
+                               }
+                               $lastPrefix = $prefix2;
+                       }
+
+                       # If we have no prefixes, go to paragraph mode.
+                       if ( 0 == $prefixLength ) {
+                               # No prefix (not in list)--go to paragraph mode
+                               # @todo consider using a stack for nestable elements like span, table and div
+                               $openMatch = preg_match(
+                                       '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
+                                               . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
+                                       $t
+                               );
+                               $closeMatch = preg_match(
+                                       '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
+                                               . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
+                                               . Parser::MARKER_PREFIX
+                                               . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
+                                       $t
+                               );
+
+                               if ( $openMatch || $closeMatch ) {
+                                       $pendingPTag = false;
+                                       # @todo bug 5718: paragraph closed
+                                       $output .= $this->closeParagraph();
+                                       if ( $preOpenMatch && !$preCloseMatch ) {
+                                               $this->inPre = true;
+                                       }
+                                       $bqOffset = 0;
+                                       while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t,
+                                               $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset )
+                                       ) {
+                                               $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
+                                               $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
+                                       }
+                                       $inBlockElem = !$closeMatch;
+                               } elseif ( !$inBlockElem && !$this->inPre ) {
+                                       if ( ' ' == substr( $t, 0, 1 )
+                                               && ( $this->lastSection === 'pre' || trim( $t ) != '' )
+                                               && !$inBlockquote
+                                       ) {
+                                               # pre
+                                               if ( $this->lastSection !== 'pre' ) {
+                                                       $pendingPTag = false;
+                                                       $output .= $this->closeParagraph() . '<pre>';
+                                                       $this->lastSection = 'pre';
+                                               }
+                                               $t = substr( $t, 1 );
+                                       } else {
+                                               # paragraph
+                                               if ( trim( $t ) === '' ) {
+                                                       if ( $pendingPTag ) {
+                                                               $output .= $pendingPTag . '<br />';
+                                                               $pendingPTag = false;
+                                                               $this->lastSection = 'p';
+                                                       } else {
+                                                               if ( $this->lastSection !== 'p' ) {
+                                                                       $output .= $this->closeParagraph();
+                                                                       $this->lastSection = '';
+                                                                       $pendingPTag = '<p>';
+                                                               } else {
+                                                                       $pendingPTag = '</p><p>';
+                                                               }
+                                                       }
+                                               } else {
+                                                       if ( $pendingPTag ) {
+                                                               $output .= $pendingPTag;
+                                                               $pendingPTag = false;
+                                                               $this->lastSection = 'p';
+                                                       } elseif ( $this->lastSection !== 'p' ) {
+                                                               $output .= $this->closeParagraph() . '<p>';
+                                                               $this->lastSection = 'p';
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       # somewhere above we forget to get out of pre block (bug 785)
+                       if ( $preCloseMatch && $this->inPre ) {
+                               $this->inPre = false;
+                       }
+                       if ( $pendingPTag === false ) {
+                               $output .= $t;
+                               if ( $prefixLength === 0 ) {
+                                       $output .= "\n";
+                               }
+                       }
+               }
+               while ( $prefixLength ) {
+                       $output .= $this->closeList( $prefix2[$prefixLength - 1] );
+                       --$prefixLength;
+                       if ( !$prefixLength ) {
+                               $output .= "\n";
+                       }
+               }
+               if ( $this->lastSection !== '' ) {
+                       $output .= '</' . $this->lastSection . '>';
+                       $this->lastSection = '';
+               }
+
+               return $output;
+       }
+
+       /**
+        * Split up a string on ':', ignoring any occurrences inside tags
+        * to prevent illegal overlapping.
+        *
+        * @param string $str The string to split
+        * @param string &$before Set to everything before the ':'
+        * @param string &$after Set to everything after the ':'
+        * @throws MWException
+        * @return string The position of the ':', or false if none found
+        */
+       private function findColonNoLinks( $str, &$before, &$after ) {
+               $colonPos = strpos( $str, ':' );
+               if ( $colonPos === false ) {
+                       # Nothing to find!
+                       return false;
+               }
+
+               $ltPos = strpos( $str, '<' );
+               if ( $ltPos === false || $ltPos > $colonPos ) {
+                       # Easy; no tag nesting to worry about
+                       $before = substr( $str, 0, $colonPos );
+                       $after = substr( $str, $colonPos + 1 );
+                       return $colonPos;
+               }
+
+               # Ugly state machine to walk through avoiding tags.
+               $state = self::COLON_STATE_TEXT;
+               $level = 0;
+               $len = strlen( $str );
+               for ( $i = 0; $i < $len; $i++ ) {
+                       $c = $str[$i];
+
+                       switch ( $state ) {
+                       case self::COLON_STATE_TEXT:
+                               switch ( $c ) {
+                               case "<":
+                                       # Could be either a <start> tag or an </end> tag
+                                       $state = self::COLON_STATE_TAGSTART;
+                                       break;
+                               case ":":
+                                       if ( $level === 0 ) {
+                                               # We found it!
+                                               $before = substr( $str, 0, $i );
+                                               $after = substr( $str, $i + 1 );
+                                               return $i;
+                                       }
+                                       # Embedded in a tag; don't break it.
+                                       break;
+                               default:
+                                       # Skip ahead looking for something interesting
+                                       $colonPos = strpos( $str, ':', $i );
+                                       if ( $colonPos === false ) {
+                                               # Nothing else interesting
+                                               return false;
+                                       }
+                                       $ltPos = strpos( $str, '<', $i );
+                                       if ( $level === 0 ) {
+                                               if ( $ltPos === false || $colonPos < $ltPos ) {
+                                                       # We found it!
+                                                       $before = substr( $str, 0, $colonPos );
+                                                       $after = substr( $str, $colonPos + 1 );
+                                                       return $i;
+                                               }
+                                       }
+                                       if ( $ltPos === false ) {
+                                               # Nothing else interesting to find; abort!
+                                               # We're nested, but there's no close tags left. Abort!
+                                               break 2;
+                                       }
+                                       # Skip ahead to next tag start
+                                       $i = $ltPos;
+                                       $state = self::COLON_STATE_TAGSTART;
+                               }
+                               break;
+                       case self::COLON_STATE_TAG:
+                               # In a <tag>
+                               switch ( $c ) {
+                               case ">":
+                                       $level++;
+                                       $state = self::COLON_STATE_TEXT;
+                                       break;
+                               case "/":
+                                       # Slash may be followed by >?
+                                       $state = self::COLON_STATE_TAGSLASH;
+                                       break;
+                               default:
+                                       # ignore
+                               }
+                               break;
+                       case self::COLON_STATE_TAGSTART:
+                               switch ( $c ) {
+                               case "/":
+                                       $state = self::COLON_STATE_CLOSETAG;
+                                       break;
+                               case "!":
+                                       $state = self::COLON_STATE_COMMENT;
+                                       break;
+                               case ">":
+                                       # Illegal early close? This shouldn't happen D:
+                                       $state = self::COLON_STATE_TEXT;
+                                       break;
+                               default:
+                                       $state = self::COLON_STATE_TAG;
+                               }
+                               break;
+                       case self::COLON_STATE_CLOSETAG:
+                               # In a </tag>
+                               if ( $c === ">" ) {
+                                       $level--;
+                                       if ( $level < 0 ) {
+                                               wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
+                                               return false;
+                                       }
+                                       $state = self::COLON_STATE_TEXT;
+                               }
+                               break;
+                       case self::COLON_STATE_TAGSLASH:
+                               if ( $c === ">" ) {
+                                       # Yes, a self-closed tag <blah/>
+                                       $state = self::COLON_STATE_TEXT;
+                               } else {
+                                       # Probably we're jumping the gun, and this is an attribute
+                                       $state = self::COLON_STATE_TAG;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENT:
+                               if ( $c === "-" ) {
+                                       $state = self::COLON_STATE_COMMENTDASH;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENTDASH:
+                               if ( $c === "-" ) {
+                                       $state = self::COLON_STATE_COMMENTDASHDASH;
+                               } else {
+                                       $state = self::COLON_STATE_COMMENT;
+                               }
+                               break;
+                       case self::COLON_STATE_COMMENTDASHDASH:
+                               if ( $c === ">" ) {
+                                       $state = self::COLON_STATE_TEXT;
+                               } else {
+                                       $state = self::COLON_STATE_COMMENT;
+                               }
+                               break;
+                       default:
+                               throw new MWException( "State machine error in " . __METHOD__ );
+                       }
+               }
+               if ( $level > 0 ) {
+                       wfDebug( __METHOD__ . ": Invalid input; not enough close tags (level $level, state $state)\n" );
+                       return false;
+               }
+               return false;
+       }
+}
index a1d62e5..96674be 100644 (file)
@@ -99,16 +99,6 @@ class Parser {
        # Regular expression for a non-newline space
        const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
 
-       # State constants for the definition list colon extraction
-       const COLON_STATE_TEXT = 0;
-       const COLON_STATE_TAG = 1;
-       const COLON_STATE_TAGSTART = 2;
-       const COLON_STATE_CLOSETAG = 3;
-       const COLON_STATE_TAGSLASH = 4;
-       const COLON_STATE_COMMENT = 5;
-       const COLON_STATE_COMMENTDASH = 6;
-       const COLON_STATE_COMMENTDASHDASH = 7;
-
        # Flags for preprocessToDom
        const PTD_FOR_INCLUSION = 1;
 
@@ -176,14 +166,14 @@ class Parser {
         * @var ParserOutput
         */
        public $mOutput;
-       public $mAutonumber, $mDTopen;
+       public $mAutonumber;
 
        /**
         * @var StripState
         */
        public $mStripState;
 
-       public $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
+       public $mIncludeCount;
        /**
         * @var LinkHolderArray
         */
@@ -342,11 +332,7 @@ class Parser {
                $this->mOutput = new ParserOutput;
                $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
                $this->mAutonumber = 0;
-               $this->mLastSection = '';
-               $this->mDTopen = false;
                $this->mIncludeCount = [];
-               $this->mArgStack = false;
-               $this->mInPre = false;
                $this->mLinkHolders = new LinkHolderArray( $this );
                $this->mLinkID = 0;
                $this->mRevisionObject = $this->mRevisionTimestamp =
@@ -2401,127 +2387,6 @@ class Parser {
                return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
        }
 
-       /**#@+
-        * Used by doBlockLevels()
-        * @private
-        *
-        * @return string
-        */
-       public function closeParagraph() {
-               $result = '';
-               if ( $this->mLastSection != '' ) {
-                       $result = '</' . $this->mLastSection . ">\n";
-               }
-               $this->mInPre = false;
-               $this->mLastSection = '';
-               return $result;
-       }
-
-       /**
-        * getCommon() returns the length of the longest common substring
-        * of both arguments, starting at the beginning of both.
-        * @private
-        *
-        * @param string $st1
-        * @param string $st2
-        *
-        * @return int
-        */
-       public function getCommon( $st1, $st2 ) {
-               $fl = strlen( $st1 );
-               $shorter = strlen( $st2 );
-               if ( $fl < $shorter ) {
-                       $shorter = $fl;
-               }
-
-               for ( $i = 0; $i < $shorter; ++$i ) {
-                       if ( $st1[$i] != $st2[$i] ) {
-                               break;
-                       }
-               }
-               return $i;
-       }
-
-       /**
-        * These next three functions open, continue, and close the list
-        * element appropriate to the prefix character passed into them.
-        * @private
-        *
-        * @param string $char
-        *
-        * @return string
-        */
-       public function openList( $char ) {
-               $result = $this->closeParagraph();
-
-               if ( '*' === $char ) {
-                       $result .= "<ul><li>";
-               } elseif ( '#' === $char ) {
-                       $result .= "<ol><li>";
-               } elseif ( ':' === $char ) {
-                       $result .= "<dl><dd>";
-               } elseif ( ';' === $char ) {
-                       $result .= "<dl><dt>";
-                       $this->mDTopen = true;
-               } else {
-                       $result = '<!-- ERR 1 -->';
-               }
-
-               return $result;
-       }
-
-       /**
-        * TODO: document
-        * @param string $char
-        * @private
-        *
-        * @return string
-        */
-       public function nextItem( $char ) {
-               if ( '*' === $char || '#' === $char ) {
-                       return "</li>\n<li>";
-               } elseif ( ':' === $char || ';' === $char ) {
-                       $close = "</dd>\n";
-                       if ( $this->mDTopen ) {
-                               $close = "</dt>\n";
-                       }
-                       if ( ';' === $char ) {
-                               $this->mDTopen = true;
-                               return $close . '<dt>';
-                       } else {
-                               $this->mDTopen = false;
-                               return $close . '<dd>';
-                       }
-               }
-               return '<!-- ERR 2 -->';
-       }
-
-       /**
-        * @todo Document
-        * @param string $char
-        * @private
-        *
-        * @return string
-        */
-       public function closeList( $char ) {
-               if ( '*' === $char ) {
-                       $text = "</li></ul>";
-               } elseif ( '#' === $char ) {
-                       $text = "</li></ol>";
-               } elseif ( ':' === $char ) {
-                       if ( $this->mDTopen ) {
-                               $this->mDTopen = false;
-                               $text = "</dt></dl>";
-                       } else {
-                               $text = "</dd></dl>";
-                       }
-               } else {
-                       return '<!-- ERR 3 -->';
-               }
-               return $text;
-       }
-       /**#@-*/
-
        /**
         * Make lists from lines starting with ':', '*', '#', etc. (DBL)
         *
@@ -2531,365 +2396,7 @@ class Parser {
         * @return string The lists rendered as HTML
         */
        public function doBlockLevels( $text, $linestart ) {
-
-               # Parsing through the text line by line.  The main thing
-               # happening here is handling of block-level elements p, pre,
-               # and making lists from lines starting with * # : etc.
-               $textLines = StringUtils::explode( "\n", $text );
-
-               $lastPrefix = $output = '';
-               $this->mDTopen = $inBlockElem = false;
-               $prefixLength = 0;
-               $paragraphStack = false;
-               $inBlockquote = false;
-
-               foreach ( $textLines as $oLine ) {
-                       # Fix up $linestart
-                       if ( !$linestart ) {
-                               $output .= $oLine;
-                               $linestart = true;
-                               continue;
-                       }
-                       # * = ul
-                       # # = ol
-                       # ; = dt
-                       # : = dd
-
-                       $lastPrefixLength = strlen( $lastPrefix );
-                       $preCloseMatch = preg_match( '/<\\/pre/i', $oLine );
-                       $preOpenMatch = preg_match( '/<pre/i', $oLine );
-                       # If not in a <pre> element, scan for and figure out what prefixes are there.
-                       if ( !$this->mInPre ) {
-                               # Multiple prefixes may abut each other for nested lists.
-                               $prefixLength = strspn( $oLine, '*#:;' );
-                               $prefix = substr( $oLine, 0, $prefixLength );
-
-                               # eh?
-                               # ; and : are both from definition-lists, so they're equivalent
-                               #  for the purposes of determining whether or not we need to open/close
-                               #  elements.
-                               $prefix2 = str_replace( ';', ':', $prefix );
-                               $t = substr( $oLine, $prefixLength );
-                               $this->mInPre = (bool)$preOpenMatch;
-                       } else {
-                               # Don't interpret any other prefixes in preformatted text
-                               $prefixLength = 0;
-                               $prefix = $prefix2 = '';
-                               $t = $oLine;
-                       }
-
-                       # List generation
-                       if ( $prefixLength && $lastPrefix === $prefix2 ) {
-                               # Same as the last item, so no need to deal with nesting or opening stuff
-                               $output .= $this->nextItem( substr( $prefix, -1 ) );
-                               $paragraphStack = false;
-
-                               if ( substr( $prefix, -1 ) === ';' ) {
-                                       # The one nasty exception: definition lists work like this:
-                                       # ; title : definition text
-                                       # So we check for : in the remainder text to split up the
-                                       # title and definition, without b0rking links.
-                                       $term = $t2 = '';
-                                       if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
-                                               $t = $t2;
-                                               $output .= $term . $this->nextItem( ':' );
-                                       }
-                               }
-                       } elseif ( $prefixLength || $lastPrefixLength ) {
-                               # We need to open or close prefixes, or both.
-
-                               # Either open or close a level...
-                               $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
-                               $paragraphStack = false;
-
-                               # Close all the prefixes which aren't shared.
-                               while ( $commonPrefixLength < $lastPrefixLength ) {
-                                       $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
-                                       --$lastPrefixLength;
-                               }
-
-                               # Continue the current prefix if appropriate.
-                               if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
-                                       $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
-                               }
-
-                               # Open prefixes where appropriate.
-                               if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
-                                       $output .= "\n";
-                               }
-                               while ( $prefixLength > $commonPrefixLength ) {
-                                       $char = substr( $prefix, $commonPrefixLength, 1 );
-                                       $output .= $this->openList( $char );
-
-                                       if ( ';' === $char ) {
-                                               # @todo FIXME: This is dupe of code above
-                                               if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
-                                                       $t = $t2;
-                                                       $output .= $term . $this->nextItem( ':' );
-                                               }
-                                       }
-                                       ++$commonPrefixLength;
-                               }
-                               if ( !$prefixLength && $lastPrefix ) {
-                                       $output .= "\n";
-                               }
-                               $lastPrefix = $prefix2;
-                       }
-
-                       # If we have no prefixes, go to paragraph mode.
-                       if ( 0 == $prefixLength ) {
-                               # No prefix (not in list)--go to paragraph mode
-                               # XXX: use a stack for nestable elements like span, table and div
-                               $openmatch = preg_match(
-                                       '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
-                                               . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
-                                       $t
-                               );
-                               $closematch = preg_match(
-                                       '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
-                                               . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
-                                               . self::MARKER_PREFIX
-                                               . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
-                                       $t
-                               );
-
-                               if ( $openmatch || $closematch ) {
-                                       $paragraphStack = false;
-                                       # @todo bug 5718: paragraph closed
-                                       $output .= $this->closeParagraph();
-                                       if ( $preOpenMatch && !$preCloseMatch ) {
-                                               $this->mInPre = true;
-                                       }
-                                       $bqOffset = 0;
-                                       while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t,
-                                               $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset )
-                                       ) {
-                                               $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
-                                               $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
-                                       }
-                                       $inBlockElem = !$closematch;
-                               } elseif ( !$inBlockElem && !$this->mInPre ) {
-                                       if ( ' ' == substr( $t, 0, 1 )
-                                               && ( $this->mLastSection === 'pre' || trim( $t ) != '' )
-                                               && !$inBlockquote
-                                       ) {
-                                               # pre
-                                               if ( $this->mLastSection !== 'pre' ) {
-                                                       $paragraphStack = false;
-                                                       $output .= $this->closeParagraph() . '<pre>';
-                                                       $this->mLastSection = 'pre';
-                                               }
-                                               $t = substr( $t, 1 );
-                                       } else {
-                                               # paragraph
-                                               if ( trim( $t ) === '' ) {
-                                                       if ( $paragraphStack ) {
-                                                               $output .= $paragraphStack . '<br />';
-                                                               $paragraphStack = false;
-                                                               $this->mLastSection = 'p';
-                                                       } else {
-                                                               if ( $this->mLastSection !== 'p' ) {
-                                                                       $output .= $this->closeParagraph();
-                                                                       $this->mLastSection = '';
-                                                                       $paragraphStack = '<p>';
-                                                               } else {
-                                                                       $paragraphStack = '</p><p>';
-                                                               }
-                                                       }
-                                               } else {
-                                                       if ( $paragraphStack ) {
-                                                               $output .= $paragraphStack;
-                                                               $paragraphStack = false;
-                                                               $this->mLastSection = 'p';
-                                                       } elseif ( $this->mLastSection !== 'p' ) {
-                                                               $output .= $this->closeParagraph() . '<p>';
-                                                               $this->mLastSection = 'p';
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-                       # somewhere above we forget to get out of pre block (bug 785)
-                       if ( $preCloseMatch && $this->mInPre ) {
-                               $this->mInPre = false;
-                       }
-                       if ( $paragraphStack === false ) {
-                               $output .= $t;
-                               if ( $prefixLength === 0 ) {
-                                       $output .= "\n";
-                               }
-                       }
-               }
-               while ( $prefixLength ) {
-                       $output .= $this->closeList( $prefix2[$prefixLength - 1] );
-                       --$prefixLength;
-                       if ( !$prefixLength ) {
-                               $output .= "\n";
-                       }
-               }
-               if ( $this->mLastSection != '' ) {
-                       $output .= '</' . $this->mLastSection . '>';
-                       $this->mLastSection = '';
-               }
-
-               return $output;
-       }
-
-       /**
-        * Split up a string on ':', ignoring any occurrences inside tags
-        * to prevent illegal overlapping.
-        *
-        * @param string $str The string to split
-        * @param string &$before Set to everything before the ':'
-        * @param string &$after Set to everything after the ':'
-        * @throws MWException
-        * @return string The position of the ':', or false if none found
-        */
-       public function findColonNoLinks( $str, &$before, &$after ) {
-
-               $pos = strpos( $str, ':' );
-               if ( $pos === false ) {
-                       # Nothing to find!
-                       return false;
-               }
-
-               $lt = strpos( $str, '<' );
-               if ( $lt === false || $lt > $pos ) {
-                       # Easy; no tag nesting to worry about
-                       $before = substr( $str, 0, $pos );
-                       $after = substr( $str, $pos + 1 );
-                       return $pos;
-               }
-
-               # Ugly state machine to walk through avoiding tags.
-               $state = self::COLON_STATE_TEXT;
-               $stack = 0;
-               $len = strlen( $str );
-               for ( $i = 0; $i < $len; $i++ ) {
-                       $c = $str[$i];
-
-                       switch ( $state ) {
-                       # (Using the number is a performance hack for common cases)
-                       case 0: # self::COLON_STATE_TEXT:
-                               switch ( $c ) {
-                               case "<":
-                                       # Could be either a <start> tag or an </end> tag
-                                       $state = self::COLON_STATE_TAGSTART;
-                                       break;
-                               case ":":
-                                       if ( $stack == 0 ) {
-                                               # We found it!
-                                               $before = substr( $str, 0, $i );
-                                               $after = substr( $str, $i + 1 );
-                                               return $i;
-                                       }
-                                       # Embedded in a tag; don't break it.
-                                       break;
-                               default:
-                                       # Skip ahead looking for something interesting
-                                       $colon = strpos( $str, ':', $i );
-                                       if ( $colon === false ) {
-                                               # Nothing else interesting
-                                               return false;
-                                       }
-                                       $lt = strpos( $str, '<', $i );
-                                       if ( $stack === 0 ) {
-                                               if ( $lt === false || $colon < $lt ) {
-                                                       # We found it!
-                                                       $before = substr( $str, 0, $colon );
-                                                       $after = substr( $str, $colon + 1 );
-                                                       return $i;
-                                               }
-                                       }
-                                       if ( $lt === false ) {
-                                               # Nothing else interesting to find; abort!
-                                               # We're nested, but there's no close tags left. Abort!
-                                               break 2;
-                                       }
-                                       # Skip ahead to next tag start
-                                       $i = $lt;
-                                       $state = self::COLON_STATE_TAGSTART;
-                               }
-                               break;
-                       case 1: # self::COLON_STATE_TAG:
-                               # In a <tag>
-                               switch ( $c ) {
-                               case ">":
-                                       $stack++;
-                                       $state = self::COLON_STATE_TEXT;
-                                       break;
-                               case "/":
-                                       # Slash may be followed by >?
-                                       $state = self::COLON_STATE_TAGSLASH;
-                                       break;
-                               default:
-                                       # ignore
-                               }
-                               break;
-                       case 2: # self::COLON_STATE_TAGSTART:
-                               switch ( $c ) {
-                               case "/":
-                                       $state = self::COLON_STATE_CLOSETAG;
-                                       break;
-                               case "!":
-                                       $state = self::COLON_STATE_COMMENT;
-                                       break;
-                               case ">":
-                                       # Illegal early close? This shouldn't happen D:
-                                       $state = self::COLON_STATE_TEXT;
-                                       break;
-                               default:
-                                       $state = self::COLON_STATE_TAG;
-                               }
-                               break;
-                       case 3: # self::COLON_STATE_CLOSETAG:
-                               # In a </tag>
-                               if ( $c === ">" ) {
-                                       $stack--;
-                                       if ( $stack < 0 ) {
-                                               wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
-                                               return false;
-                                       }
-                                       $state = self::COLON_STATE_TEXT;
-                               }
-                               break;
-                       case self::COLON_STATE_TAGSLASH:
-                               if ( $c === ">" ) {
-                                       # Yes, a self-closed tag <blah/>
-                                       $state = self::COLON_STATE_TEXT;
-                               } else {
-                                       # Probably we're jumping the gun, and this is an attribute
-                                       $state = self::COLON_STATE_TAG;
-                               }
-                               break;
-                       case 5: # self::COLON_STATE_COMMENT:
-                               if ( $c === "-" ) {
-                                       $state = self::COLON_STATE_COMMENTDASH;
-                               }
-                               break;
-                       case self::COLON_STATE_COMMENTDASH:
-                               if ( $c === "-" ) {
-                                       $state = self::COLON_STATE_COMMENTDASHDASH;
-                               } else {
-                                       $state = self::COLON_STATE_COMMENT;
-                               }
-                               break;
-                       case self::COLON_STATE_COMMENTDASHDASH:
-                               if ( $c === ">" ) {
-                                       $state = self::COLON_STATE_TEXT;
-                               } else {
-                                       $state = self::COLON_STATE_COMMENT;
-                               }
-                               break;
-                       default:
-                               throw new MWException( "State machine error in " . __METHOD__ );
-                       }
-               }
-               if ( $stack > 0 ) {
-                       wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
-                       return false;
-               }
-               return false;
+               return BlockLevelPass::doBlockLevels( $text, $linestart );
        }
 
        /**
index acdbd81..bd7072a 100644 (file)
@@ -81,7 +81,7 @@ abstract class PoolCounter {
 
        /**
         * @param array $conf
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key
         */
        protected function __construct( $conf, $type, $key ) {
@@ -93,8 +93,9 @@ abstract class PoolCounter {
                }
 
                if ( $this->slots ) {
-                       $key = $this->hashKeyIntoSlots( $key, $this->slots );
+                       $key = $this->hashKeyIntoSlots( $type, $key, $this->slots );
                }
+
                $this->key = $key;
                $this->isMightWaitKey = !preg_match( '/^nowait:/', $this->key );
        }
@@ -102,7 +103,7 @@ abstract class PoolCounter {
        /**
         * Create a Pool counter. This should only be called from the PoolWorks.
         *
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key
         *
         * @return PoolCounter
@@ -192,18 +193,19 @@ abstract class PoolCounter {
        }
 
        /**
-        * Given a key (any string) and the number of lots, returns a slot number (an integer from
-        * the [0..($slots-1)] range). This is used for a global limit on the number of instances of
-        * a given type that can acquire a lock. The hashing is deterministic so that
+        * Given a key (any string) and the number of lots, returns a slot key (a prefix with a suffix
+        * integer from the [0..($slots-1)] range). This is used for a global limit on the number of
+        * instances of a given type that can acquire a lock. The hashing is deterministic so that
         * PoolCounter::$workers is always an upper limit of how many instances with the same key
         * can acquire a lock.
         *
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key PoolCounter instance key (any string)
         * @param int $slots The number of slots (max allowed value is 65536)
-        * @return int
+        * @return string Slot key with the type and slot number
         */
-       protected function hashKeyIntoSlots( $key, $slots ) {
-               return hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots;
+       protected function hashKeyIntoSlots( $type, $key, $slots ) {
+               return $type . ':' . ( hexdec( substr( sha1( $key ), 0, 4 ) ) % $slots );
        }
 }
 
index e61b65a..a570d78 100644 (file)
@@ -31,7 +31,7 @@ abstract class PoolCounterWork {
        protected $cacheable = false; // does this override getCachedWork() ?
 
        /**
-        * @param string $type The type of PoolCounter to use
+        * @param string $type The class of actions to limit concurrency for (task type)
         * @param string $key Key that identifies the queue this work is placed on
         */
        public function __construct( $type, $key ) {
index 85a7cef..834b8b1 100644 (file)
@@ -44,7 +44,7 @@ class PoolCounterWorkViaCallback extends PoolCounterWork {
         * If a 'doCachedWork' callback is provided, then execute() may wait for any prior
         * process in the pool to finish and reuse its cached result.
         *
-        * @param string $type
+        * @param string $type The class of actions to limit concurrency for
         * @param string $key
         * @param array $callbacks Map of callbacks
         * @throws MWException
index 8e0239a..85fc53d 100644 (file)
@@ -227,15 +227,17 @@ class ResourceLoaderContext {
         * Get the possibly-cached User object for the specified username
         *
         * @since 1.25
-        * @return User|bool false if a valid object cannot be created
+        * @return User
         */
        public function getUserObj() {
                if ( $this->userObj === null ) {
                        $username = $this->getUser();
                        if ( $username ) {
-                               $this->userObj = User::newFromName( $username );
+                               // Use provided username if valid, fallback to anonymous user
+                               $this->userObj = User::newFromName( $username ) ?: new User;
                        } else {
-                               $this->userObj = new User; // Anonymous user
+                               // Anonymous user
+                               $this->userObj = new User;
                        }
                }
 
index 13f13e6..121a6c9 100644 (file)
@@ -114,16 +114,6 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                return $this->origin;
        }
 
-       /**
-        * Set this module's origin. This is called by ResourceLoader::register()
-        * when registering the module. Other code should not call this.
-        *
-        * @param int $origin Origin
-        */
-       public function setOrigin( $origin ) {
-               $this->origin = $origin;
-       }
-
        /**
         * @param ResourceLoaderContext $context
         * @return bool
index e2a8e41..b225185 100644 (file)
@@ -40,7 +40,7 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
                }
 
                $user = $context->getUserObj();
-               if ( !$user || $user->isAnon() ) {
+               if ( $user->isAnon() ) {
                        return [];
                }
 
index d584165..c38f8d8 100644 (file)
@@ -43,7 +43,7 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
                }
 
                $user = $context->getUserObj();
-               if ( !$user || $user->isAnon() ) {
+               if ( $user->isAnon() ) {
                        return [];
                }
 
index 1b5a834..c235861 100644 (file)
@@ -54,6 +54,7 @@ class SessionInfo {
        private $remembered = false;
        private $forceHTTPS = false;
        private $idIsSafe = false;
+       private $forceUse = false;
 
        /** @var array|null */
        private $providerMetadata = null;
@@ -76,6 +77,10 @@ class SessionInfo {
         *  - idIsSafe: (bool) Set true if the 'id' did not come from the user.
         *    Generally you'll use this from SessionProvider::newEmptySession(),
         *    and not from any other method.
+        *  - forceUse: (bool) Set true if the 'id' is from
+        *    SessionProvider::hashToSessionId() to delete conflicting session
+        *    store data instead of discarding this SessionInfo. Ignored unless
+        *    both 'provider' and 'id' are given.
         *  - copyFrom: (SessionInfo) SessionInfo to copy other data items from.
         */
        public function __construct( $priority, array $data ) {
@@ -97,6 +102,7 @@ class SessionInfo {
                                'forceHTTPS' => $from->forceHTTPS,
                                'metadata' => $from->providerMetadata,
                                'idIsSafe' => $from->idIsSafe,
+                               'forceUse' => $from->forceUse,
                                // @codeCoverageIgnoreStart
                        ];
                        // @codeCoverageIgnoreEnd
@@ -110,6 +116,7 @@ class SessionInfo {
                                'forceHTTPS' => false,
                                'metadata' => null,
                                'idIsSafe' => false,
+                               'forceUse' => false,
                                // @codeCoverageIgnoreStart
                        ];
                        // @codeCoverageIgnoreEnd
@@ -137,9 +144,11 @@ class SessionInfo {
                if ( $data['id'] !== null ) {
                        $this->id = $data['id'];
                        $this->idIsSafe = $data['idIsSafe'];
+                       $this->forceUse = $data['forceUse'] && $this->provider;
                } else {
                        $this->id = $this->provider->getManager()->generateSessionId();
                        $this->idIsSafe = true;
+                       $this->forceUse = false;
                }
                $this->priority = (int)$priority;
                $this->userInfo = $data['userInfo'];
@@ -185,6 +194,20 @@ class SessionInfo {
                return $this->idIsSafe;
        }
 
+       /**
+        * Force use of this SessionInfo if validation fails
+        *
+        * The normal behavior is to discard the SessionInfo if validation against
+        * the data stored in the session store fails. If this returns true,
+        * SessionManager will instead delete the session store data so this
+        * SessionInfo may still be used.
+        *
+        * @return bool
+        */
+       final public function forceUse() {
+               return $this->forceUse;
+       }
+
        /**
         * Return the priority
         * @return int
index 6c17de5..777d3d6 100644 (file)
@@ -301,6 +301,19 @@ final class SessionManager implements SessionManagerInterface {
                return $this->getSessionFromInfo( $infos[0], $request );
        }
 
+       public function invalidateSessionsForUser( User $user ) {
+               global $wgAuth;
+
+               $user->setToken();
+               $user->saveSettings();
+
+               $wgAuth->getUserInstance( $user )->resetAuthToken();
+
+               foreach ( $this->getProviders() as $provider ) {
+                       $provider->invalidateSessionsForUser( $user );
+               }
+       }
+
        public function getVaryHeaders() {
                // @codeCoverageIgnoreStart
                if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
@@ -704,6 +717,20 @@ final class SessionManager implements SessionManagerInterface {
                $key = wfMemcKey( 'MWSession', $info->getId() );
                $blob = $this->store->get( $key );
 
+               // If we got data from the store and the SessionInfo says to force use,
+               // "fail" means to delete the data from the store and retry. Otherwise,
+               // "fail" is just return false.
+               if ( $info->forceUse() && $blob !== false ) {
+                       $failHandler = function () use ( $key, &$info, $request ) {
+                               $this->store->delete( $key );
+                               return $this->loadSessionInfoFromStore( $info, $request );
+                       };
+               } else {
+                       $failHandler = function () {
+                               return false;
+                       };
+               }
+
                $newParams = [];
 
                if ( $blob !== false ) {
@@ -713,7 +740,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        // Sanity check: blob has data and metadata arrays
@@ -724,7 +751,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        $data = $blob['data'];
@@ -741,7 +768,7 @@ final class SessionManager implements SessionManagerInterface {
                                        'session' => $info,
                                ] );
                                $this->store->delete( $key );
-                               return false;
+                               return $failHandler();
                        }
 
                        // First, load the provider from metadata, or validate it against the metadata.
@@ -756,7 +783,7 @@ final class SessionManager implements SessionManagerInterface {
                                                ]
                                        );
                                        $this->store->delete( $key );
-                                       return false;
+                                       return $failHandler();
                                }
                        } elseif ( $metadata['provider'] !== (string)$provider ) {
                                $this->logger->warning( 'Session "{session}": Wrong provider ' .
@@ -764,7 +791,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        // Load provider metadata from metadata, or validate it against the metadata
@@ -788,7 +815,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'exception' => $ex,
                                                        ] + $ex->getContext()
                                                );
-                                               return false;
+                                               return $failHandler();
                                        }
                                }
                        }
@@ -810,7 +837,7 @@ final class SessionManager implements SessionManagerInterface {
                                                'session' => $info,
                                                'exception' => $ex,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                                $newParams['userInfo'] = $userInfo;
                        } else {
@@ -825,7 +852,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uid_a' => $metadata['userId'],
                                                                'uid_b' => $userInfo->getId(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
 
                                        // If the user was renamed, probably best to fail here.
@@ -839,7 +866,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
 
                                } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
@@ -851,7 +878,7 @@ final class SessionManager implements SessionManagerInterface {
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
                                                ] );
-                                               return false;
+                                               return $failHandler();
                                        }
                                } elseif ( !$userInfo->isAnon() ) {
                                        // Metadata specifies an anonymous user, but the passed-in
@@ -861,7 +888,7 @@ final class SessionManager implements SessionManagerInterface {
                                                [
                                                        'session' => $info,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                        }
 
@@ -872,7 +899,7 @@ final class SessionManager implements SessionManagerInterface {
                                $this->logger->warning( 'Session "{session}": User token mismatch', [
                                        'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
                        if ( !$userInfo->isVerified() ) {
                                $newParams['userInfo'] = $userInfo->verified();
@@ -899,7 +926,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        // If no user was provided and no metadata, it must be anon.
@@ -912,7 +939,7 @@ final class SessionManager implements SessionManagerInterface {
                                                [
                                                        'session' => $info,
                                        ] );
-                                       return false;
+                                       return $failHandler();
                                }
                        } elseif ( !$info->getUserInfo()->isVerified() ) {
                                $this->logger->warning(
@@ -920,7 +947,7 @@ final class SessionManager implements SessionManagerInterface {
                                        [
                                                'session' => $info,
                                ] );
-                               return false;
+                               return $failHandler();
                        }
 
                        $data = false;
@@ -942,7 +969,7 @@ final class SessionManager implements SessionManagerInterface {
                // Allow the provider to check the loaded SessionInfo
                $providerMetadata = $info->getProviderMetadata();
                if ( !$info->getProvider()->refreshSessionInfo( $info, $request, $providerMetadata ) ) {
-                       return false;
+                       return $failHandler();
                }
                if ( $providerMetadata !== $info->getProviderMetadata() ) {
                        $info = new SessionInfo( $info->getPriority(), [
@@ -962,7 +989,7 @@ final class SessionManager implements SessionManagerInterface {
                        $this->logger->warning( 'Session "{session}": ' . $reason, [
                                'session' => $info,
                        ] );
-                       return false;
+                       return $failHandler();
                }
 
                return true;
index b3e28fe..d4e52c7 100644 (file)
@@ -24,6 +24,7 @@
 namespace MediaWiki\Session;
 
 use Psr\Log\LoggerAwareInterface;
+use User;
 use WebRequest;
 
 /**
@@ -72,6 +73,17 @@ interface SessionManagerInterface extends LoggerAwareInterface {
         */
        public function getEmptySession( WebRequest $request = null );
 
+       /**
+        * Invalidate sessions for a user
+        *
+        * After calling this, existing sessions should be invalid. For mutable
+        * session providers, this generally means the user has to log in again;
+        * for immutable providers, it generally means the loss of session data.
+        *
+        * @param User $user
+        */
+       public function invalidateSessionsForUser( User $user );
+
        /**
         * Return the HTTP headers that need varying on.
         *
index 3cd065d..ed113b7 100644 (file)
@@ -27,6 +27,7 @@ use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Config;
 use Language;
+use User;
 use WebRequest;
 
 /**
@@ -358,6 +359,19 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
                }
        }
 
+       /**
+        * Invalidate existing sessions for a user
+        *
+        * If the provider has its own equivalent of CookieSessionProvider's Token
+        * cookie (and doesn't use User::getToken() to implement it), it should
+        * reset whatever token it does use here.
+        *
+        * @protected For use by \MediaWiki\Session\SessionManager only
+        * @param User $user;
+        */
+       public function invalidateSessionsForUser( User $user ) {
+       }
+
        /**
         * Return the HTTP headers that need varying on.
         *
@@ -458,7 +472,9 @@ abstract class SessionProvider implements SessionProviderInterface, LoggerAwareI
         *
         * Generally this will only be used when self::persistsSessionId() is false and
         * the provider has to base the session ID on the verified user's identity
-        * or other static data.
+        * or other static data. The SessionInfo should then typically have the
+        * 'forceUse' flag set to avoid persistent session failure if validation of
+        * the stored data fails.
         *
         * @param string $data
         * @param string|null $key Defaults to $this->config->get( 'SecretKey' )
index 5c24ee0..4c869f9 100644 (file)
@@ -538,6 +538,7 @@ class SpecialPageFactory {
                        $trxProfiler = Profiler::instance()->getTransactionProfiler();
                        if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
                                $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
+                               $context->getRequest()->markAsSafeRequest();
                        }
                }
 
index b916c1f..162ef60 100644 (file)
  * @ingroup SpecialPage
  */
 class SpecialMergeHistory extends SpecialPage {
-       /** @var string */
-       protected $mAction;
+       /** @var FormOptions */
+       protected $mOpts;
 
-       /** @var string */
-       protected $mTarget;
+       /** @var Status */
+       protected $mStatus;
 
-       /** @var string */
-       protected $mDest;
-
-       /** @var string */
-       protected $mTimestamp;
-
-       /** @var int */
-       protected $mTargetID;
-
-       /** @var int */
-       protected $mDestID;
-
-       /** @var string */
-       protected $mComment;
-
-       /** @var bool Was posted? */
-       protected $mMerge;
-
-       /** @var bool Was submitted? */
-       protected $mSubmitted;
-
-       /** @var Title */
-       protected $mTargetObj;
-
-       /** @var Title */
-       protected $mDestObj;
+       /** @var Title|null */
+       protected $mTargetObj, $mDestObj;
 
        /** @var int[] */
        public $prevId;
@@ -72,124 +48,107 @@ class SpecialMergeHistory extends SpecialPage {
                return true;
        }
 
-       /**
-        * @return void
-        */
-       private function loadRequestParams() {
-               $request = $this->getRequest();
-               $this->mAction = $request->getVal( 'action' );
-               $this->mTarget = $request->getVal( 'target' );
-               $this->mDest = $request->getVal( 'dest' );
-               $this->mSubmitted = $request->getBool( 'submitted' );
-
-               $this->mTargetID = intval( $request->getVal( 'targetID' ) );
-               $this->mDestID = intval( $request->getVal( 'destID' ) );
-               $this->mTimestamp = $request->getVal( 'mergepoint' );
-               if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
-                       $this->mTimestamp = '';
-               }
-               $this->mComment = $request->getText( 'wpComment' );
-
-               $this->mMerge = $request->wasPosted()
-                       && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
-
-               // target page
-               if ( $this->mSubmitted ) {
-                       $this->mTargetObj = Title::newFromText( $this->mTarget );
-                       $this->mDestObj = Title::newFromText( $this->mDest );
-               } else {
-                       $this->mTargetObj = null;
-                       $this->mDestObj = null;
-               }
-       }
-
        public function execute( $par ) {
                $this->useTransactionalTimeLimit();
 
                $this->checkPermissions();
                $this->checkReadOnly();
 
-               $this->loadRequestParams();
-
                $this->setHeaders();
                $this->outputHeader();
 
-               if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
+               $this->addHelpLink( 'Help:Merge history' );
+
+               $opts = new FormOptions();
+
+               $opts->add( 'target', '' );
+               $opts->add( 'dest', '' );
+               $opts->add( 'target', '' );
+               $opts->add( 'mergepoint', '' );
+               $opts->add( 'reason', '' );
+               $opts->add( 'merge', false );
+
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+
+               $target = $opts->getValue( 'target' );
+               $dest = $opts->getValue( 'dest' );
+               $targetObj = Title::newFromText( $target );
+               $destObj = Title::newFromText( $dest );
+               $status = Status::newGood();
+
+               $this->mOpts = $opts;
+               $this->mTargetObj = $targetObj;
+               $this->mDestObj = $destObj;
+
+               if ( $opts->getValue( 'merge' ) && $targetObj &&
+                       $destObj && $opts->getValue( 'mergepoint' ) !== '' ) {
                        $this->merge();
 
                        return;
                }
 
-               if ( !$this->mSubmitted ) {
+               if ( $target === '' && $dest === '' ) {
                        $this->showMergeForm();
 
                        return;
                }
 
-               $errors = [];
-               if ( !$this->mTargetObj instanceof Title ) {
-                       $errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
-               } elseif ( !$this->mTargetObj->exists() ) {
-                       $errors[] = $this->msg( 'mergehistory-no-source',
-                               wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
-                       )->parseAsBlock();
+               if ( !$targetObj instanceof Title ) {
+                       $status->merge( Status::newFatal( 'mergehistory-invalid-source' ) );
+               } elseif ( !$targetObj->exists() ) {
+                       $status->merge( Status::newFatal( 'mergehistory-no-source',
+                               wfEscapeWikiText( $targetObj->getPrefixedText() )
+                       ) );
                }
 
-               if ( !$this->mDestObj instanceof Title ) {
-                       $errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
-               } elseif ( !$this->mDestObj->exists() ) {
-                       $errors[] = $this->msg( 'mergehistory-no-destination',
-                               wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
-                       )->parseAsBlock();
+               if ( !$destObj instanceof Title ) {
+                       $status->merge( Status::newFatal( 'mergehistory-invalid-destination' ) );
+               } elseif ( !$destObj->exists() ) {
+                       $status->merge( Status::newFatal( 'mergehistory-no-destination',
+                               wfEscapeWikiText( $destObj->getPrefixedText() )
+                       ) );
                }
 
-               if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) {
-                       $errors[] = $this->msg( 'mergehistory-same-destination' )->parseAsBlock();
+               if ( $targetObj && $destObj && $targetObj->equals( $destObj ) ) {
+                       $status->merge( Status::newFatal( 'mergehistory-same-destination' ) );
                }
 
-               if ( count( $errors ) ) {
-                       $this->showMergeForm();
-                       $this->getOutput()->addHTML( implode( "\n", $errors ) );
-               } else {
+               $this->mStatus = $status;
+
+               $this->showMergeForm();
+
+               if ( $status->isOK() ) {
                        $this->showHistory();
                }
        }
 
        function showMergeForm() {
-               $out = $this->getOutput();
-               $out->addWikiMsg( 'mergehistory-header' );
-
-               $out->addHTML(
-                       Xml::openElement( 'form', [
-                               'method' => 'get',
-                               'action' => wfScript() ] ) .
-                               '<fieldset>' .
-                               Xml::element( 'legend', [],
-                                       $this->msg( 'mergehistory-box' )->text() ) .
-                               Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
-                               Html::hidden( 'submitted', '1' ) .
-                               Html::hidden( 'mergepoint', $this->mTimestamp ) .
-                               Xml::openElement( 'table' ) .
-                               '<tr>
-                               <td>' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . '</td>
-                               <td>' . Xml::input( 'target', 30, $this->mTarget, [ 'id' => 'target' ] ) . '</td>
-                       </tr><tr>
-                               <td>' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . '</td>
-                               <td>' . Xml::input( 'dest', 30, $this->mDest, [ 'id' => 'dest' ] ) . '</td>
-                       </tr><tr><td>' .
-                               Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) .
-                               '</td></tr>' .
-                               Xml::closeElement( 'table' ) .
-                               '</fieldset>' .
-                               '</form>'
-               );
-
-               $this->addHelpLink( 'Help:Merge history' );
+               $formDescriptor = [
+                       'target' => [
+                               'type' => 'title',
+                               'name' => 'target',
+                               'label-message' => 'mergehistory-from',
+                               'required' => true,
+                       ],
+
+                       'dest' => [
+                               'type' => 'title',
+                               'name' => 'dest',
+                               'label-message' => 'mergehistory-into',
+                               'required' => true,
+                       ],
+               ];
+
+               $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->setIntro( $this->msg( 'mergehistory-header' ) )
+                       ->setWrapperLegendMsg( 'mergehistory-box' )
+                       ->setSubmitTextMsg( 'mergehistory-go' )
+                       ->setMethod( 'post' )
+                       ->prepareForm()
+                       ->displayForm( $this->mStatus );
        }
 
        private function showHistory() {
-               $this->showMergeForm();
-
                # List all stored revisions
                $revisions = new MergeHistoryPager(
                        $this, [], $this->mTargetObj, $this->mDestObj
@@ -197,62 +156,46 @@ class SpecialMergeHistory extends SpecialPage {
                $haveRevisions = $revisions && $revisions->getNumRows() > 0;
 
                $out = $this->getOutput();
-               $titleObj = $this->getPageTitle();
-               $action = $titleObj->getLocalURL( [ 'action' => 'submit' ] );
-               # Start the form here
-               $top = Xml::openElement(
-                       'form',
-                       [
-                               'method' => 'post',
-                               'action' => $action,
-                               'id' => 'merge'
-                       ]
-               );
-               $out->addHTML( $top );
-
-               if ( $haveRevisions ) {
-                       # Format the user-visible controls (comment field, submission button)
-                       # in a nice little table
-                       $table =
-                               Xml::openElement( 'fieldset' ) .
-                                       $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
-                                               $this->mDestObj->getPrefixedText() )->parse() .
-                                       Xml::openElement( 'table', [ 'id' => 'mw-mergehistory-table' ] ) .
-                                       '<tr>
-                                               <td class="mw-label">' .
-                                       Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
-                                       '</td>
-                                       <td class="mw-input">' .
-                                       Xml::input( 'wpComment', 50, $this->mComment, [ 'id' => 'wpComment' ] ) .
-                                       '</td>
-                                       </tr>
-                                       <tr>
-                                               <td>&#160;</td>
-                                               <td class="mw-submit">' .
-                                       Xml::submitButton(
-                                               $this->msg( 'mergehistory-submit' )->text(),
-                                               [ 'name' => 'merge', 'id' => 'mw-merge-submit' ]
-                                       ) .
-                                       '</td>
-                                       </tr>' .
-                                       Xml::closeElement( 'table' ) .
-                                       Xml::closeElement( 'fieldset' );
-
-                       $out->addHTML( $table );
-               }
-
-               $out->addHTML(
-                       '<h2 id="mw-mergehistory">' .
-                               $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
-               );
+               $header = '<h2 id="mw-mergehistory">' .
+                       $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n";
 
                if ( $haveRevisions ) {
-                       $out->addHTML( $revisions->getNavigationBar() );
-                       $out->addHTML( '<ul>' );
-                       $out->addHTML( $revisions->getBody() );
-                       $out->addHTML( '</ul>' );
-                       $out->addHTML( $revisions->getNavigationBar() );
+                       $hiddenFields = [
+                               'merge' => true,
+                               'target' => $this->mOpts->getValue( 'target' ),
+                               'dest' => $this->mOpts->getValue( 'dest' ),
+                       ];
+
+                       $formDescriptor = [
+                               'reason' => [
+                                       'type' => 'text',
+                                       'name' => 'reason',
+                                       'label-message' => 'mergehistory-reason',
+                               ],
+                       ];
+
+                       $mergeText = $this->msg( 'mergehistory-merge',
+                               $this->mTargetObj->getPrefixedText(),
+                               $this->mDestObj->getPrefixedText()
+                       )->parse();
+
+                       $history = $header .
+                               $revisions->getNavigationBar() .
+                               '<ul>' .
+                               $revisions->getBody() .
+                               '</ul>' .
+                               $revisions->getNavigationBar();
+
+                       $form = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                               ->addHiddenFields( $hiddenFields )
+                               ->setPreText( $mergeText )
+                               ->setFooterText( $history )
+                               ->setSubmitTextMsg( 'mergehistory-submit' )
+                               ->setMethod( 'post' )
+                               ->prepareForm()
+                               ->displayForm( false );
                } else {
+                       $out->addHTML( $header );
                        $out->addWikiMsg( 'mergehistory-empty' );
                }
 
@@ -260,18 +203,6 @@ class SpecialMergeHistory extends SpecialPage {
                $mergeLogPage = new LogPage( 'merge' );
                $out->addHTML( '<h2>' . $mergeLogPage->getName()->escaped() . "</h2>\n" );
                LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj );
-
-               # When we submit, go by page ID to avoid some nasty but unlikely collisions.
-               # Such would happen if a page was renamed after the form loaded, but before submit
-               $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() );
-               $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
-               $misc .= Html::hidden( 'target', $this->mTarget );
-               $misc .= Html::hidden( 'dest', $this->mDest );
-               $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
-               $misc .= Xml::closeElement( 'form' );
-               $out->addHTML( $misc );
-
-               return true;
        }
 
        function formatRevisionRow( $row ) {
@@ -281,7 +212,7 @@ class SpecialMergeHistory extends SpecialPage {
                $last = $this->msg( 'last' )->escaped();
 
                $ts = wfTimestamp( TS_MW, $row->rev_timestamp );
-               $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
+               $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mOpts->getValue( 'mergepoint' ) === $ts ) );
 
                $user = $this->getUser();
 
@@ -336,23 +267,24 @@ class SpecialMergeHistory extends SpecialPage {
         * @return bool Success
         */
        function merge() {
+               $opts = $this->mOpts;
+
                # Get the titles directly from the IDs, in case the target page params
                # were spoofed. The queries are done based on the IDs, so it's best to
                # keep it consistent...
-               $targetTitle = Title::newFromID( $this->mTargetID );
-               $destTitle = Title::newFromID( $this->mDestID );
-               if ( is_null( $targetTitle ) || is_null( $destTitle ) ) {
-                       return false; // validate these
-               }
-               if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
+               $targetObj = $this->mTargetObj;
+               $destObj = $this->mDestObj;
+
+               if ( is_null( $targetObj ) || is_null( $destObj ) ||
+                       $targetObj->getArticleID() == $destObj->getArticleID() ) {
                        return false;
                }
 
                // MergeHistory object
-               $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
+               $mh = new MergeHistory( $targetObj, $destObj, $opts->getValue( 'mergepoint' ) );
 
                // Merge!
-               $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
+               $mergeStatus = $mh->merge( $this->getUser(), $opts->getValue( 'reason' ) );
                if ( !$mergeStatus->isOK() ) {
                        // Failed merge
                        $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
@@ -360,7 +292,7 @@ class SpecialMergeHistory extends SpecialPage {
                }
 
                $targetLink = Linker::link(
-                       $targetTitle,
+                       $targetObj,
                        null,
                        [],
                        [ 'redirect' => 'no' ]
@@ -368,7 +300,7 @@ class SpecialMergeHistory extends SpecialPage {
 
                $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
                        ->rawParams( $targetLink )
-                       ->params( $destTitle->getPrefixedText() )
+                       ->params( $destObj->getPrefixedText() )
                        ->numParams( $mh->getMergedRevisionCount() )
                );
 
index a77c79e..45315a7 100644 (file)
@@ -699,7 +699,7 @@ class LoginForm extends SpecialPage {
 
                $u->setEmail( $this->mEmail );
                $u->setRealName( $this->mRealName );
-               $u->setToken();
+               SessionManager::singleton()->invalidateSessionsForUser( $u );
 
                Hooks::run( 'LocalUserCreated', [ $u, $autocreate ] );
                $oldUser = $u;
index baa55f0..b4ea732 100644 (file)
@@ -75,7 +75,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                $this->target = Title::newFromText( $opts->getValue( 'target' ) );
                if ( !$this->target ) {
                        if ( !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
+                               $this->buildForm();
                        }
 
                        return;
@@ -200,12 +200,8 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                ) {
                        if ( 0 == $level ) {
                                if ( !$this->including() ) {
-                                       $out->addHTML( $this->whatlinkshereForm() );
+                                       $this->buildForm();
 
-                                       // Show filters only if there are links
-                                       if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
-                                               $out->addHTML( $this->getFilterPanel() );
-                                       }
                                        $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
                                        $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
                                        $out->setStatusCode( 404 );
@@ -269,8 +265,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
 
                if ( $level == 0 ) {
                        if ( !$this->including() ) {
-                               $out->addHTML( $this->whatlinkshereForm() );
-                               $out->addHTML( $this->getFilterPanel() );
+                               $this->buildForm();
                                $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
 
                                $prevnext = $this->getPrevNext( $prevId, $nextId );
@@ -444,7 +439,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                return $this->msg( 'viewprevnext' )->rawParams( $prev, $next, $nums )->escaped();
        }
 
-       function whatlinkshereForm() {
+       protected function buildForm() {
                // We get nicer value from the title object
                $this->opts->consumeValue( 'target' );
                // Reset these for new requests
@@ -455,88 +450,57 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
                $nsinvert = $this->opts->consumeValue( 'invert' );
 
                # Build up the form
-               $f = Xml::openElement( 'form', [ 'action' => wfScript() ] );
 
-               # Values that should not be forgotten
-               $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
-               foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
-                       $f .= Html::hidden( $name, $value );
-               }
-
-               $f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
-
-               # Target input (.mw-searchInput enables suggestions)
-               $f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
-                       'mw-whatlinkshere-target', 40, $target, [ 'class' => 'mw-searchInput' ] );
+               $hiddenFields = [
+                       'title' => $this->getPageTitle()->getPrefixedDBkey(),
+               ];
 
-               $f .= ' ';
+               $formDescriptor = [
+                       'target' => [
+                               'type' => 'title',
+                               'name' => 'target',
+                               'label-message' => 'whatlinkshere-page',
+                               'default' => $this->opts->getValue( 'target' ),
+                       ],
 
-               # Namespace selector
-               $f .= Html::namespaceSelector(
-                       [
-                               'selected' => $namespace,
-                               'all' => '',
-                               'label' => $this->msg( 'namespace' )->text()
-                       ], [
+                       'namespace' => [
+                               'type' => 'namespaceselect',
                                'name' => 'namespace',
-                               'id' => 'namespace',
-                               'class' => 'namespaceselector',
-                       ]
-               );
-
-               $f .= '&#160;' .
-                       Xml::checkLabel(
-                               $this->msg( 'invert' )->text(),
-                               'invert',
-                               'nsinvert',
-                               $nsinvert,
-                               [ 'title' => $this->msg( 'tooltip-whatlinkshere-invert' )->text() ]
-                       );
-
-               $f .= ' ';
-
-               # Submit
-               $f .= Xml::submitButton( $this->msg( 'whatlinkshere-submit' )->text() );
-
-               # Close
-               $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n";
-
-               return $f;
-       }
-
-       /**
-        * Create filter panel
-        *
-        * @return string HTML fieldset and filter panel with the show/hide links
-        */
-       function getFilterPanel() {
-               $show = $this->msg( 'show' )->escaped();
-               $hide = $this->msg( 'hide' )->escaped();
-
-               $changed = $this->opts->getChangedValues();
-               unset( $changed['target'] ); // Already in the request title
+                               'label-message' => 'namespace',
+                               'all' => '',
+                       ],
+
+                       'invert' => [
+                               'type' => 'check',
+                               'name' => 'invert',
+                               'label-message' => 'invert',
+                               'default' => false,
+                       ],
+               ];
 
-               $links = [];
-               $types = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
-               if ( $this->target->getNamespace() == NS_FILE ) {
-                       $types[] = 'hideimages';
+               $filters = [ 'hidetrans', 'hidelinks', 'hideredirs' ];
+               if ( $this->target instanceof Title &&
+                       $this->target->getNamespace() == NS_FILE ) {
+                       $filters[] = 'hideimages';
                }
 
-               // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
-               // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
-               // To be sure they will be found by grep
-               foreach ( $types as $type ) {
-                       $chosen = $this->opts->getValue( $type );
-                       $msg = $chosen ? $show : $hide;
-                       $overrides = [ $type => !$chosen ];
-                       $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
-                               $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
+               foreach ( $filters as $filter ) {
+                       $formDescriptor[$filter] = [
+                               'type' => 'check',
+                               'name' => $filter,
+                               'label' => $this->msg( 'whatlinkshere-' . $filter ),
+                               'value' => false,
+                       ];
                }
 
-               return Xml::fieldset(
-                       $this->msg( 'whatlinkshere-filters' )->text(),
-                       $this->getLanguage()->pipeList( $links )
-               );
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+                       ->addHiddenFields( $hiddenFields )
+                       ->setWrapperLegendMsg( 'whatlinkshere' )
+                       ->setSubmitTextMsg( 'whatlinkshere-submit' )
+                       ->setAction( $this->getPageTitle()->getLocalURL() )
+                       ->setMethod( 'get' )
+                       ->prepareForm()
+                       ->displayForm( false );
        }
 
        /**
index 0e291ed..5c504f3 100644 (file)
@@ -181,6 +181,31 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                );
        }
 
+       /**
+        * @since 1.27
+        * @see TitleFormatter::getPrefixedDBkey()
+        * @param LinkTarget $target
+        * @return string
+        */
+       public function getPrefixedDBkey( LinkTarget $target ) {
+               $key = '';
+               if ( $target->isExternal() ) {
+                       $key .= $target->getInterwiki() . ':';
+               }
+               $nsName = $this->getNamespaceName(
+                       $target->getNamespace(),
+                       $target->getText()
+               );
+
+               if ( $nsName !== '' ) {
+                       $key .= $nsName . ':';
+               }
+
+               $key .= $target->getText();
+
+               return strtr( $key, ' ', '_' );
+       }
+
        /**
         * @see TitleFormatter::getText()
         *
index c081129..5177606 100644 (file)
@@ -68,6 +68,18 @@ interface TitleFormatter {
         */
        public function getPrefixedText( LinkTarget $title );
 
+       /**
+        * Return the title in prefixed database key form, with interwiki
+        * and namespace.
+        *
+        * @since 1.27
+        *
+        * @param LinkTarget $target
+        *
+        * @return string
+        */
+       public function getPrefixedDBkey( LinkTarget $target );
+
        /**
         * Returns the title formatted for display, with namespace and fragment.
         *
index 63c075f..597bf2f 100644 (file)
@@ -94,6 +94,15 @@ class TitleValue implements LinkTarget {
                return $this->namespace;
        }
 
+       /**
+        * @since 1.27
+        * @param int $ns
+        * @return bool
+        */
+       public function inNamespace( $ns ) {
+               return $this->namespace == $ns;
+       }
+
        /**
         * @return string
         */
index ee617a2..3d7d71c 100644 (file)
@@ -2489,9 +2489,9 @@ class User implements IDBAccessObject {
                        throw new PasswordError( wfMessage( 'externaldberror' )->text() );
                }
 
-               $this->setToken();
                $this->setOption( 'watchlisttoken', false );
                $this->setPasswordInternal( $str );
+               SessionManager::singleton()->invalidateSessionsForUser( $this );
 
                return true;
        }
@@ -2508,9 +2508,9 @@ class User implements IDBAccessObject {
                global $wgAuth;
 
                if ( $wgAuth->allowSetLocalPassword() ) {
-                       $this->setToken();
                        $this->setOption( 'watchlisttoken', false );
                        $this->setPasswordInternal( $str );
+                       SessionManager::singleton()->invalidateSessionsForUser( $this );
                }
        }
 
@@ -3398,6 +3398,19 @@ class User implements IDBAccessObject {
                return !$this->isLoggedIn();
        }
 
+       /**
+        * @return bool Whether this user is flagged as being a bot role account
+        * @since 1.28
+        */
+       public function isBot() {
+               $isBot = false;
+               if ( !Hooks::run( "UserIsBot", [ $this, &$isBot ] ) ) {
+                       return $isBot;
+               }
+
+               return ( $isBot || in_array( 'bot', $this->getGroups() ) );
+       }
+
        /**
         * Check if user is allowed to access a feature / make an action
         *
index ab9421f..0f46528 100644 (file)
        "newarticle": "(جديد)",
        "newarticletext": "لقد تبعت وصلة لصفحة لم يتم إنشائها بعد.\nلإنشاء هذه الصفحة ابدأ الكتابة في الصندوق بالأسفل (انظر في [$1 صفحة المساعدة] للمزيد من المعلومات).\nإذا كانت زيارتك لهذه الصفحة بالخطأ، اضغط على زر ''رجوع'' في متصفح الإنترنت لديك.",
        "anontalkpagetext": "----''هذه صفحة نقاش لمستخدم مجهول لم يقم بإنشاء حساب بعد أو لا يستعمل ذلك الحساب.\nلذا فيجب علينا استعمال رقم الأيبي للتعرف عليه/عليها.\nمثل هذا العنوان يمكن أن يشترك فيه عدة مستخدمين.\nلو كنت مستخدما مجهولا وتشعر بأن تعليقات لا تخصك تم توجيهها إليك، من فضلك [[Special:UserLogin/signup|أنشئ حسابا]] أو [[Special:UserLogin|سجل الدخول]] لتجنب الارتباك المستقبلي مع مستخدمين مجهولين آخرين.''",
-       "noarticletext": "لا يوجد حاليا أي نص في هذه الصفحة.\nيمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوان هذه الصفحة]] في الصفحات الأخرى،\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} البحث في السجلات المتعلقة]،\nأو [{{fullurl:{{FULLPAGENAME}}|action=edit}} تعديل هذه الصفحة]</span>.",
+       "noarticletext": "الصفحة خالية. يمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوانها]] في الصفحات الأخرى أو\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} البحث في السجلات] (لتعرف إذا حذفت)،\nأو '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} إنشاؤها بنفسك]'''</span>.",
        "noarticletext-nopermission": "لا يوجد حاليا أي نص في هذه الصفحة.\nيمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوان هذه الصفحة]] في الصفحات الأخرى، أو <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} البحث في السجلات المتعلقة بها]</span>، لكنك لست مخولاً لإنشاء هذه الصفحة.",
        "missing-revision": "المراجعة #$1 من الصفحة المسماة \"{{FULLPAGENAME}}\" غير موجودة.\n\nهذا يحدث عادة عن طريق اتباع وصلة تاريخ قديمة لصفحة تم حذفها.\nالتفاصيل يمكن إيجادها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل الحذف].",
        "userpage-userdoesnotexist": "حساب المستخدم \"<nowiki>$1</nowiki>\" غير مسجل.\nمن فضلك تأكد أنك تريد إنشاء/تعديل هذه الصفحة.",
index 81f5413..ad623cb 100644 (file)
        "uploadstash-summary": "بو صحیفه‌‌ يوکلنمیش(و يا يوکلنمکده دیر) آمما هله ویکیده نشر اولونمامیش فايللارا چاتماغی تعمین ائدر. بو فايللار يالنیز يوکله‌يه‌نی طرفیندن گؤروله بیلر.",
        "uploadstash-clear": "مووققتی فايللاری تمیزله",
        "uploadstash-nofiles": "سیزین هئچ آمبار اولموش فایلینیز یوخدور.",
-       "uploadstash-badtoken": "Ú\86اÙ\84Û\8cØ´Ù\85اÙ\86Û\8cÙ\86 Ø­Û\8cاتا Ú©Ø¦Ú\86Û\8cرÛ\8cÙ\84Ù\85Ù\87â\80\8cسÛ\8c Ø¨Ø§Ø´Ø§Ø±Û\8cسÛ\8cز Ø§Ù\88Ù\84دÙ\88Ø\8c Ø§Ø­ØªÛ\8cÙ\85اÙ\84Ù\84ا ØªÙ\86زیمله‌مه قايدالاری زامان آشیمینا معروض قالدی. يئنیدن چالیشین.",
+       "uploadstash-badtoken": "Ú\86اÙ\84Û\8cØ´Ù\85اÙ\86Û\8cÙ\86 Ø­Û\8cاتا Ú©Ø¦Ú\86Û\8cرÛ\8cÙ\84Ù\85Ù\87â\80\8cسÛ\8c Ø¨Ø§Ø´Ø§Ø±Û\8cسÛ\8cز Ø§Ù\88Ù\84دÙ\88Ø\8c Ø§Ø­ØªÛ\8cÙ\85اÙ\84Ù\84ا ØªÙ\86ظیمله‌مه قايدالاری زامان آشیمینا معروض قالدی. يئنیدن چالیشین.",
        "uploadstash-errclear": "فايللارین سیلینمه‌سی باشاریسیز اولدو.",
        "uploadstash-refresh": "فايل سیياهیسینی يئنیله",
        "invalid-chunk-offset": "اعتبارسیز یئربه‌یئر",
        "unblock": "ایستیفاده‌چی‌نین باغلانماسین گؤتور",
        "blockip": " {{GENDER:$1|ایشلدن}}ی باغلا",
        "blockip-legend": "ایستیفادچی نی باغلا",
-       "blockiptext": "آشاغی‌داکی فورمو ایستیفاده ائده‌رک مویین بیر ایپنین و یا قئیدیات‌دان کئچمیش ایستیفاده‌چی‌نین دییشیک‌لیک ائتمه‌سینی مانعه تؤره‌ده بیلرسینیز. بو یالنیز واندالیزمی قارشی‌سینی آلماق اوچون و [[{{MediaWiki:Policy-url}}|قایدا‌لارا]] اویغون اولا‌راق ائدیلمه‌لی. آشاغییا موتلق قاداغا ایله علاقه‌دار بیر شرح یازین. (نومونه:-بو-صحیفه‌لرده واندالیزم ائتمیش‌دیر).",
+       "blockiptext": "آشاغی‌داکی فورمو ایستیفاده ائده‌رک مۆعیّن بیر آی‌پی‌نین و یا قئیدیات‌دان کئچمیش ایستیفاده‌چی‌نین دییشیک‌لیک ائتمه‌سینی مانعه تؤره‌ده بیلرسینیز. بۇ یالنیز واندالیزمین قارشی‌سینی آلماق اۆچون و [[{{MediaWiki:Policy-url}}|قایدا‌لارا]] اۇیغون اوْلا‌راق ائدیلمه‌لی. آشاغی‌یا مۆطلق قاداغا ایله علاقه‌دار بیر شرح یازین. (اؤرنک:-بۇ-صفحه‌لرده واندالیزم ائتمیشدیر).",
        "ipaddressorusername": "آی-پی عونوانی و یا ایستیفاده‌چی آدی",
        "ipbexpiry": "بیتمه مدتی:",
        "ipbreason": "ندن:",
index aaf7ca8..1cb4117 100644 (file)
        "parser-unstrip-loop-warning": "Ябылмаған pre табылды",
        "parser-unstrip-recursion-limit": "($1) рекурсия сиге үтеп кителгән",
        "converter-manual-rule-error": "Тел әйлендереү ҡағиҙәһендә хата табылды",
-       "undo-success": "Был үҙгәртеүҙе кире алып була. Зинһар, улар һеҙҙе ҡыҙыҡһындырған үҙгәртеүҙәр булыуынан шикләнмәҫ өсөн версияларҙы сағыштырыуҙы ҡарағыҙ һәм үҙгәртеүҙәрҙе ғәмәлғә керетер өсөн «Битте һаҡларға» төймәһенә баҫығыҙ.",
+       "undo-success": "Был үҙгәртеүҙе кире алып була. Үҙгәртеүҙәр нәҡ һеҙ теләгәнсә булһа, версияларҙы сағыштырып ҡарағыҙ, ғәмәлгә индерер өсөн, «Битте һаҡларға» төймәһенә баҫығыҙ.",
        "undo-failure": "Ара үҙгәртеүҙәр тура килмәү сәбәпле төҙәтеүҙе кире алып булмай.",
        "undo-norev": "Үҙгәртеүҙе кире алып булмай, сөнки юҡ йәки юйылған.",
        "undo-nochange": "Төҙәтеү кире ҡайтарылған.",
index 7b4842d..c132dca 100644 (file)
@@ -58,7 +58,7 @@
        "tog-ccmeonemails": "Адпраўляць мне копіі лістоў, якія я дасылаю іншым удзельнікам",
        "tog-diffonly": "Не паказваць зьмест старонкі пад параўнаньнем зьменаў",
        "tog-showhiddencats": "Паказваць схаваныя катэгорыі",
-       "tog-norollbackdiff": "Не паказваць зьмены пасьля выкарыстаньня функцыі адкату",
+       "tog-norollbackdiff": "Не паказваць зьмены пасьля выкананьня адкату",
        "tog-useeditwarning": "Папярэджваць мяне, калі я буду пакідаць старонку рэдагаваньня зь незахаванымі зьменамі",
        "tog-prefershttps": "Заўсёды карыстацца бясьпечным злучэньнем па ўваходзе ў сыстэму",
        "underline-always": "Заўсёды",
        "userpage-userdoesnotexist": "Рахунак удзельніка «<nowiki>$1</nowiki>» не зарэгістраваны. Калі ласка, удакладніце, ці жадаеце Вы стварыць/рэдагаваць гэтую старонку.",
        "userpage-userdoesnotexist-view": "Рахунак «$1» ня створаны.",
        "blocked-notice-logextract": "Гэты ўдзельнік у дадзены момант заблякаваны.\nАпошні запіс з журналу блякаваньняў пададзены ніжэй для даведкі:",
-       "clearyourcache": "'''Заўвага:''' Каб пабачыць зьмены пасьля захаваньня, Вам можа спатрэбіцца ачысьціць кэш Вашага браўзэра. \n* '''Firefox / Safari:''' Трымайце ''Shift'' і націсьніце ''Reload'', ці націсьніце ''Ctrl-F5'' ці ''Ctrl-R'' (''⌘-R'' на Mac)\n* '''Google Chrome:''' Націсьніце ''Ctrl-Shift-R'' (''⌘-Shift-R'' на Mac)\n* '''Internet Explorer:''' Трымайце ''Ctrl'' і націсьніце ''Refresh'', ці націсьніце ''Ctrl-F5''\n* '''Opera:''' Ачысьціце кэш праз ''Tools → Preferences''",
+       "clearyourcache": "<strong>Заўвага:</strong> каб пабачыць зьмены пасьля захаваньня, Вам можа спатрэбіцца ачысьціць кэш Вашага браўзэра. \n* <strong>Firefox / Safari:</strong> трымайце <em>Shift</em> і націсьніце <em>Reload</em>, ці націсьніце <em>Ctrl-F5</em> ці <em>Ctrl-R</em> (<em>⌘-R</em> на Mac)\n* <strong>Google Chrome:</strong> націсьніце <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Mac)\n* <strong>Internet Explorer:</strong> трымайце <em>Ctrl</em> і націсьніце <em>Refresh</em>, ці націсьніце <em>Ctrl-F5</em>\n* <strong>Opera:</strong> перайдзіце ў <em>Menu → Settings</em> (<em>Opera → Preferences</em> на Mac), а потым у <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Падказка:''' выкарыстоўвайце кнопку «{{int:showpreview}}», каб паспрабаваць новы код CSS перад тым як яго запісаць.",
        "userjsyoucanpreview": "'''Падказка:''' выкарыстоўвайце кнопку «{{int:showpreview}}», каб паспрабаваць новы код JavaScript перад тым, як яго запісаць.",
        "usercsspreview": "'''Памятайце, што гэта толькі папярэдні прагляд Вашага CSS. Ён яшчэ не запісаны!'''",
        "apisandbox-request-url-label": "URL-адрас запыту:",
        "apisandbox-request-time": "Час запыту: {{PLURAL:$1|$1 мс}}",
        "apisandbox-results-fixtoken": "Выпраўце токен і паўтарыце адпраўку",
+       "apisandbox-results-fixtoken-fail": "Памылка пры атрыманьні токену «$1».",
        "apisandbox-alert-page": "Палі на гэтай старонцы няслушныя.",
        "apisandbox-alert-field": "Значэньне гэтага поля зьяўляецца няслушным.",
        "booksources": "Крыніцы кніг",
        "whatlinkshere-prev": "{{PLURAL:$1|папярэдняя|папярэднія}} $1",
        "whatlinkshere-next": "{{PLURAL:$1|наступная|наступныя}} $1",
        "whatlinkshere-links": "← спасылкі",
-       "whatlinkshere-hideredirs": "$1 перанакіраваньні",
+       "whatlinkshere-hideredirs": "Схаваць перанакіраваньні",
        "whatlinkshere-hidetrans": "$1 уключэньні",
        "whatlinkshere-hidelinks": "$1 спасылкі",
        "whatlinkshere-hideimages": "$1 спасылкі на выявы",
index eb1e5ae..828033c 100644 (file)
        "revdelete-show-file-submit": "Да",
        "revdelete-selected-text": "{{PLURAL:$1|Избрана версия|Избрани версии}} от [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Избрано събитие|Избрани събития}}:",
+       "revdelete-text-text": "Изтритите редакции ще продължат да се виждат в историята на страницата, но части от съдържанието ще бъдат публично недостъпни.",
+       "revdelete-text-others": "Другите администратори ще продължат да имат достъп до скритото съдържание и могат да го възстановят, освен ако не бъдат наложени допълнителни ограничения.",
        "revdelete-confirm": "Необходимо е да потвърдите, че желаете да извършите действието, разбирате последствията и го правите според [[{{MediaWiki:Policy-url}}|политиката]].",
        "revdelete-suppress-text": "Премахването трябва да се използва '''само''' при следните случаи:\n* Потенциално уязвима в правно отношение информация\n* Неподходяща лична информация\n*: ''домашни адреси и телефонни номера, номера за социално осигуряване и др.''",
        "revdelete-legend": "Задаване на ограничения:",
        "logempty": "Дневникът не съдържа записи, отговарящи на избрания критерий.",
        "log-title-wildcard": "Търсене на заглавия, започващи със",
        "showhideselectedlogentries": "Промяна на видимостта на избраните записи",
-       "checkbox-all": "Всички",
-       "checkbox-none": "Никои",
+       "checkbox-select": "Избери: $1",
+       "checkbox-all": "всички",
+       "checkbox-none": "никои",
+       "checkbox-invert": "обърни избора",
        "allpages": "Всички страници",
        "nextpage": "Следваща страница ($1)",
        "prevpage": "Предходна страница ($1)",
        "sqlite-no-fts": "$1 без поддръжка на пълнотекстово търсене",
        "logentry-delete-delete": "$1 {{GENDER:$2|изтри}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|възстанови}} страницата $3",
+       "logentry-delete-revision": "$1 {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една редакция|$5 редакции}} в страница $3: $4",
+       "logentry-delete-event-legacy": "$1 {{GENDER:$2|промени}} видимостта на събитията от дневниците за страница $3",
        "logentry-delete-revision-legacy": "$1 {{GENDER:$2|промени}} видимостта на версиите на страница $3",
        "logentry-suppress-revision": "$1 тайно {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една версия|$5 версии}} на страницата $3: $4",
        "logentry-suppress-revision-legacy": "$1 тайно {{GENDER:$2|промени}} видимостта на версиите на страница $3",
index 1b4d7b4..98b5cee 100644 (file)
        "changepassword-success": "Vaše heslo bylo změněno!",
        "changepassword-throttled": "Provedli jste příliš mnoho pokusů o přihlášení.\nČekejte prosím $1 a zkuste to znovu.",
        "botpasswords": "Hesla pro boty",
-       "botpasswords-summary": "<em>Hesla pro boty</em> umožňují přistupovat k uživatelskému účtu prostřednictví API bez použití hlavních přihlašovacích údajů účtu. Uživatelská oprávnění dostupná po přihlášení pomocí hesla pro boty mohou být omezena.\n\nPokud nevíte, k čemu byste to {{GENDER:|chtěl|chtěla|chtěli}} použít, pravděpodobně byste to používat {{GENDER:|neměl|neměla|neměli}}. Nikdo by vás nikdy neměl žádat, abyste si zde vygenerovali heslo a dali mu ho.",
+       "botpasswords-summary": "<em>Hesla pro boty</em> umožňují přistupovat k uživatelskému účtu prostřednictví API bez použití hlavních přihlašovacích údajů účtu. Uživatelská oprávnění dostupná po přihlášení pomocí hesla pro boty mohou být omezena.\n\nPokud nevíte, k čemu byste to {{GENDER:|chtěl|chtěla|chtěli}} použít, pravděpodobně byste to používat {{GENDER:|neměl|neměla|neměli}}. Nikdo by vás nikdy neměl žádat, abyste si zde {{GENDER:|vygeneroval heslo a dal|vygenerovala heslo a dala|vygenerovali heslo a dali}} mu ho.",
        "botpasswords-disabled": "Hesla pro boty jsou zakázána.",
        "botpasswords-no-central-id": "Abyste {{GENDER:|mohl|mohla|mohl(a)}} použít hesla pro boty, musíte být {{GENDER:|přihlášen|přihlášena|přihlášen(a)}} k centrálnímu účtu.",
        "botpasswords-existing": "Stávající hesla pro boty",
        "changecontentmodel-success-text": "Model obsahu stránky [[:$1]] byl změněn.",
        "changecontentmodel-cannot-convert": "Obsah stránky [[:$1]] nelze zkonvertovat na typ $2.",
        "changecontentmodel-nodirectediting": "Model obsahu $1 nepodporuje přímou editaci",
+       "changecontentmodel-emptymodels-title": "Nejsou k dispozici žádné modely obsahu",
+       "changecontentmodel-emptymodels-text": "Obsah stránky [[:$1]] nelze zkonvertovat na žádný typ.",
        "log-name-contentmodel": "Kniha změny modelů obsahu",
        "log-description-contentmodel": "Události týkající se modelů obsahu stránek",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|založil|založila}} stránku $3 za použití nestandardního modelu obsahu „$5“",
index 6ed4fad..4cbca02 100644 (file)
@@ -24,7 +24,7 @@
        "tog-hideminor": "Cuddio golygiadau bychain yn rhestr y newidiadau diweddar",
        "tog-hidepatrolled": "Cuddio golygiadau sydd wedi derbyn ymweliad patrôl rhag y rhestr newidiadau diweddar",
        "tog-newpageshidepatrolled": "Cuddio tudalennau a batroliwyd o'r rhestr y tudalennau newydd",
-       "tog-hidecategorization": "Cuddiwych y categoriau",
+       "tog-hidecategorization": "Cuddiwch y categoriau",
        "tog-extendwatchlist": "Ehangu'r rhestr wylio i ddangos pob golygiad yn hytrach na'r diweddaraf yn unig",
        "tog-usenewrc": "Grwpio'r newidiadau bob yn ddalen yn y 'newidiadau diweddar' a'r 'rhestr wylio'",
        "tog-numberheadings": "Rhifo penawdau'n awtomatig",
@@ -55,7 +55,7 @@
        "tog-watchlistreloadautomatically": "Ail-lwyther y Rhestr wylio yn otomatigpan newider ffiltr (angen JavaScript)",
        "tog-watchlisthideanons": "Cuddio golygiadau gan ddefnyddwyr anhysbys rhag y rhestr wylio",
        "tog-watchlisthidepatrolled": "Cuddio golygiadau sydd wedi derbyn ymweliad patrôl rhag y rhestr wylio",
-       "tog-watchlisthidecategorization": "Cuddiwych y categoriau",
+       "tog-watchlisthidecategorization": "Cuddiwch y categoriau",
        "tog-ccmeonemails": "Anfon copi ataf pan anfonaf e-bost at ddefnyddiwr arall",
        "tog-diffonly": "Peidio â dangos cynnwys y dudalen islaw'r gymhariaeth ar dudalennau cymharu",
        "tog-showhiddencats": "Dangos categorïau cuddiedig",
        "rcshowhidemine": "$1 fy ngolygiadau",
        "rcshowhidemine-show": "Dangoser",
        "rcshowhidemine-hide": "Cuddier",
-       "rcshowhidecategorization": "Categorieiddio tudalen $1",
+       "rcshowhidecategorization": "Categoreiddio tudalen $1",
        "rcshowhidecategorization-show": "Dangos",
        "rcshowhidecategorization-hide": "Cuddio",
        "rclinks": "Dangos y $1 newid diweddaraf yn ystod y(r) $2 diwrnod diwethaf<br />$3",
index 8c40276..05c4418 100644 (file)
        "revdelete-restricted": "tilføjede begrænsninger for administratorer",
        "revdelete-unrestricted": "fjernede begrænsninger for administratorer",
        "logentry-block-block": "$1 {{GENDER:$2|blokerede}} {{GENDER:$4|$3}} med en udløbstid på $5 $6",
+       "logentry-block-unblock": "$1 {{GENDER:$2|ophævede blokering af}} {{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1 {{GENDER:$2|ændrede}} blokeringsindstillinger for {{GENDER:$4|$3}} med en udløbstid på $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|ændrede}} blokeringsindstillinger for {{GENDER:$4|$3}} med en udløbstid på $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|flyttede}} siden $3 til $4",
index c5bef5b..be7ed04 100644 (file)
        "blankarticle": "<strong>Warnung:</strong> Die Seite, die du erstellst, ist leer.\nWenn du erneut auf „{{int:savearticle}}“ klickst, wird die Seite ohne Inhalt erstellt.",
        "anoneditwarning": "<strong>Warnung:</strong> Du bist nicht angemeldet. Deine IP-Adresse wird öffentlich sichtbar, falls du Bearbeitungen durchführst. Sofern du dich <strong>[$1 anmeldest]</strong> oder <strong>[$2 ein Benutzerkonto erstellst]</strong>, werden deine Bearbeitungen zusammen mit anderen Beiträgen deinem Benutzernamen zugeordnet.",
        "anonpreviewwarning": "''Du bist nicht angemeldet. Beim Speichern wird deine IP-Adresse in der Versionsgeschichte aufgezeichnet.''",
-       "missingsummary": "'''Hinweis:''' Du hast keine Zusammenfassung angegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Änderung ohne Zusammenfassung übernommen.",
+       "missingsummary": "<strong>Hinweis:</strong> Du hast keine Zusammenfassung angegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Änderung ohne Zusammenfassung übernommen.",
        "selfredirect": "<strong>Warnung:</strong> Du leitest auf diese Seite selbst weiter.\nDu hast vermutlich das falsche Weiterleitungsziel angegeben oder du bearbeitest die falsche Seite.\nWenn du erneut auf „{{int:savearticle}}“ klickst, wird die Weiterleitung dennoch erstellt.",
        "missingcommenttext": "Bitte gib unten einen Kommentar ein.",
        "missingcommentheader": "<strong>Achtung:</strong> Du hast keinen Betreff eingegeben. Wenn du erneut auf „{{int:savearticle}}“ klickst, wird deine Bearbeitung ohne Überschrift gespeichert.",
        "editpage-cannot-use-custom-model": "Das Inhaltsmodell dieser Seite kann nicht geändert werden.",
        "longpageerror": "'''Fehler: Der Text, den du zu speichern versuchst, ist {{PLURAL:$1|ein Kilobyte|$1 Kilobyte}} groß. Dies ist größer als das erlaubte Maximum von {{PLURAL:$2|ein Kilobyte|$2 Kilobyte}}.'''\nEr kann nicht gespeichert werden.",
        "readonlywarning": "<strong>Achtung: Die Datenbank wurde für Wartungsarbeiten gesperrt, so dass deine Änderungen derzeit nicht gespeichert werden können.\nSichere den Text bitte lokal auf deinem Computer und versuche zu einem späteren Zeitpunkt, die Änderungen zu übertragen.</strong>\n\nGrund für die Sperre: $1",
-       "protectedpagewarning": "'''Achtung: Diese Seite wurde geschützt. Nur Benutzer mit Administratorrechten können die Seite bearbeiten.'''\nZur Information folgt der aktuelle Logbucheintrag:",
+       "protectedpagewarning": "<strong>Achtung: Diese Seite wurde geschützt. Nur Benutzer mit Administratorrechten können die Seite bearbeiten.</strong>\nZur Information folgt der aktuelle Logbucheintrag:",
        "semiprotectedpagewarning": "'''Halbsperrung:''' Die Seite wurde so geschützt, dass nur registrierte Benutzer diese ändern können.\nZur Information folgt der aktuelle Logbucheintrag:",
        "cascadeprotectedwarning": "<strong>Achtung:</strong> Diese Seite wurde so geschützt, dass sie nur durch Benutzer mit Administratorrechten bearbeitet werden kann. Sie ist in die {{PLURAL:$1|folgende Seite|folgenden Seiten}} eingebunden, die mittels der Kaskadensperroption geschützt {{PLURAL:$1|ist|sind}}:",
        "titleprotectedwarning": "'''Achtung: Die Seitenerstellung wurde so geschützt, dass nur Benutzer mit [[Special:ListGroupRights|speziellen Rechten]] diese Seite erstellen können.'''\nZur Information folgt der aktuelle Logbucheintrag:",
        "permissionserrorstext": "Du bist nicht berechtigt, die Aktion auszuführen. {{PLURAL:$1|Grund|Gründe}}:",
        "permissionserrorstext-withaction": "Du bist aus {{PLURAL:$1|dem folgenden Grund|den folgenden Gründen}} nicht berechtigt, $2:",
        "contentmodelediterror": "Du kannst diese Version nicht bearbeiten, da das Inhaltsmodell <code>$1</code> vom aktuellen Inhaltsmodell der Seite <code>$2</code> abweicht.",
-       "recreate-moveddeleted-warn": "'''Achtung: Du erstellst eine Seite, die bereits früher gelöscht wurde.'''\n\nBitte prüfe sorgfältig, ob die erneute Seitenerstellung den Richtlinien entspricht.\nZu deiner Information folgt das Lösch- und Verschiebungs-Logbuch mit der Begründung für die vorhergehende Löschung:",
+       "recreate-moveddeleted-warn": "<strong>Achtung: Du erstellst eine Seite, die bereits früher gelöscht wurde.</strong>\n\nBitte prüfe sorgfältig, ob die erneute Seitenerstellung den Richtlinien entspricht.\nZu deiner Information folgt das Lösch- und Verschiebungs-Logbuch mit der Begründung für die vorhergehende Löschung:",
        "moveddeleted-notice": "Diese Seite wurde gelöscht. Zur Information folgt das Lösch- und Verschiebungs-Logbuch dieser Seite.",
        "moveddeleted-notice-recent": "Leider wurde diese Seite kürzlich gelöscht (innerhalb der letzten 24 Stunden).\nZur Information wird das Lösch- und Verschiebungs-Logbuch für die Seite unten angezeigt.",
        "log-fulllog": "Alle Logbucheinträge ansehen",
        "whatlinkshere-prev": "{{PLURAL:$1|vorheriger|vorherige $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|nächster|nächste $1}}",
        "whatlinkshere-links": "← Links",
-       "whatlinkshere-hideredirs": "Weiterleitungen $1",
-       "whatlinkshere-hidetrans": "Vorlageneinbindungen $1",
-       "whatlinkshere-hidelinks": "Links $1",
-       "whatlinkshere-hideimages": "Dateilinks $1",
+       "whatlinkshere-hideredirs": "Weiterleitungen ausblenden",
+       "whatlinkshere-hidetrans": "Vorlageneinbindungen ausblenden",
+       "whatlinkshere-hidelinks": "Links ausblenden",
+       "whatlinkshere-hideimages": "Dateilinks ausblenden",
        "whatlinkshere-filters": "Filter",
        "whatlinkshere-submit": "Los",
        "autoblockid": "Automatische Sperrung #$1",
        "movepagetext": "Mit untenstehendem Formular kannst du eine Seite umbenennen, indem du sie mitsamt allen Versionen auf einen neuen Titel verschiebst.\nDer alte Titel wird danach zum neuen weiterleiten.\nDu kannst Weiterleitungen, die auf den Originaltitel verlinken, automatisch korrigieren lassen.\nStelle sicher, dass du im Anschluss alle [[Special:DoubleRedirects|doppelten]] oder [[Special:BrokenRedirects|defekten Weiterleitungen]] überprüfst.\nDu bist dafür verantwortlich, dass Links weiterhin auf das korrekte Ziel verweisen.\n\nDie Seite wird <strong>nicht</strong> verschoben, sofern es bereits eine Seite mit dem vorgesehenen Titel gibt, es sei denn, letztere ist eine Weiterleitung ohne Versionsgeschichte.\nDies bedeutet, dass du die Umbenennung rückgängig machen kannst, sofern du einen Fehler gemacht hast. Du kannst hingegen keine existierende Seite überschreiben.\n\n<strong>Hinweis:</strong>\nDie Verschiebung kann weitreichende und unerwartete Folgen für häufig besuchte Seiten haben.\nDu solltest daher die Konsequenzen verstanden haben, bevor du jetzt fortfährst.",
        "movepagetext-noredirectfixer": "Mit untenstehendem Formular kannst du eine Seite umbenennen, indem du sie mitsamt allen Versionen auf einen neuen Titel verschiebst.\nDer alte Titel wird danach zum neuen weiterleiten.\nStelle sicher, dass du im Anschluss alle [[Special:DoubleRedirects|doppelten]] oder [[Special:BrokenRedirects|defekten Weiterleitungen]] überprüfst.\nDu bist dafür verantwortlich, dass Links weiterhin auf das korrekte Ziel verweisen.\n\nDie Seite wird <strong>nicht</strong> verschoben, sofern es bereits eine Seite mit dem vorgesehenen Titel gibt, es sei denn, diese ist eine Weiterleitung ohne Versionsgeschichte.\nDies bedeutet, dass du die Umbenennung rückgängig machen kannst, sofern du einen Fehler gemacht hast. Du kannst hingegen keine existierende Seite überschreiben.\n\n<strong>Hinweis:</strong>\nDie Verschiebung kann weitreichende und unerwartete Folgen für häufig besuchte Seiten haben.\nDu solltest daher die Konsequenzen verstanden haben, bevor du jetzt fortfährst.",
        "movepagetalktext": "Falls du dieses Kästchen aktivierst, wird die dazugehörige Diskussionsseite automatisch auf den neuen Titel verschoben, sofern nicht bereits eine nicht-leere Diskussionsseite dort vorhanden ist.\n\nIn diesem Fall musst du die Seite manuell verschieben oder zusammenführen, falls erforderlich.",
-       "moveuserpage-warning": "'''Warnung:''' Du bist dabei, eine Benutzerseite zu verschieben. Bitte bedenke, dass dadurch nur die Benutzerseite verschoben, '''nicht''' aber der Benutzer umbenannt wird.",
+       "moveuserpage-warning": "<strong>Warnung:</strong> Du bist dabei, eine Benutzerseite zu verschieben. Bitte bedenke, dass dadurch nur die Benutzerseite verschoben, <em>nicht</em> aber der Benutzer umbenannt wird.",
        "movecategorypage-warning": "<strong>Warnung:</strong> Du bist gerade dabei, eine Kategorieseite zu verschieben. Bitte sei dir bewusst, dass nur die Seite verschoben wird. Alle der alten Kategorie zugeordneten Seiten werden <em>nicht</em> neu kategorisiert.",
        "movenologintext": "Du musst ein registrierter Benutzer und [[Special:UserLogin|angemeldet]] sein, um eine Seite zu verschieben.",
        "movenotallowed": "Du hast nicht die erforderliche Berechtigung, um Seiten verschieben zu können.",
        "imageinvalidfilename": "Der Ziel-Dateiname ist ungültig",
        "fix-double-redirects": "Nach dem Verschieben alle Weiterleitungen auf die Ursprungsseite bereinigen",
        "move-leave-redirect": "Weiterleitung erstellen",
-       "protectedpagemovewarning": "'''Warnung:''' Diese Seite wurde so geschützt, dass sie nur von Benutzern mit Administratorenrechten verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
+       "protectedpagemovewarning": "<strong>Warnung:</strong> Diese Seite wurde so geschützt, dass sie nur von Benutzern mit Administratorenrechten verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
        "semiprotectedpagemovewarning": "'''Hinweis:''' Diese Seite wurde so geschützt, dass sie nur von angemeldeten Benutzern verschoben werden kann.\nZur Information folgt der aktuelle Logbucheintrag:",
        "move-over-sharedrepo": "[[:$1]] existiert in einem gemeinsam genutzten Repositorium. Das Verschieben einer Datei zu diesem Titel überschreibt die gemeinsam genutzte Datei.",
        "file-exists-sharedrepo": "Der gewählte Dateiname wird bereits in einem gemeinsam genutzten Repositorium verwendet.\nBitte wähle einen anderen Namen.",
        "filedelete-archive-read-only": "Das Archiv-Verzeichnis „$1“ ist für den Webserver nicht beschreibbar.",
        "previousdiff": "← Zum vorherigen Versionsunterschied",
        "nextdiff": "Zum nächsten Versionsunterschied →",
-       "mediawarning": "'''Warnung:''' Dieser Dateityp kann böswilligen Programmcode enthalten.\nDurch das Herunterladen und Öffnen der Datei kann dein Computer beschädigt werden.",
+       "mediawarning": "<strong>Warnung:</strong> Dieser Dateityp kann böswilligen Programmcode enthalten.\nDurch das Herunterladen und Öffnen der Datei kann dein Computer beschädigt werden.",
        "imagemaxsize": "Maximale Bildgröße:<br />''(für Dateibeschreibungsseiten)''",
        "thumbsize": "Standardgröße der Vorschaubilder:",
        "widthheightpage": "$1 × $2, {{PLURAL:$3|1 Seite|$3 Seiten}}",
index 9af2bc5..3578658 100644 (file)
        "hidden-category-category": "Kategoriyê nımıtey",
        "category-subcat-count": "{{PLURAL:$2|Na kategoriya de $1 bınkategoriyay estê.|$2 kategoriyan ra $1 bınkategoriyay asenê.}}",
        "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|ena kategoriya bınêne esta|enê $1 kategoriyê bınêni estê}}.",
-       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategori de ra|$1 enê peli na kategori de rê.}}}}",
+       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategoriye dera|$1 enê peli na kategoriye derê.}}",
        "category-article-count-limited": "{{PLURAL:$1|Pela cêrêne|$1 Pelê cêrêni}} na kategoriye derê.",
        "category-file-count": "<noinclude>{{PLURAL:$2|Na kategoriye tenya dosyayanê cêrênan muhtewa kena.}}</noinclude>\n*Na kategoriye de $2 dosyayan ra {{PLURAL:$1|yew dosya tenêka esta| $1 dosyey asenê}}.",
        "category-file-count-limited": "{{PLURAL:$1|Dosya cêrêne|$1 Dosyê cêrêni}} na kategoriye derê.",
        "whatlinkshere-prev": "{{PLURAL:$1|veror|veror $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|verni|verni $1}}",
        "whatlinkshere-links": "← gırey",
-       "whatlinkshere-hideredirs": "Hetenayışê $1",
-       "whatlinkshere-hidetrans": "Açarnayışê $1",
-       "whatlinkshere-hidelinks": "Greyê $1",
+       "whatlinkshere-hideredirs": "Hetenayışan bınımne",
+       "whatlinkshere-hidetrans": "Gırêyanê açarnayışan bınımne",
+       "whatlinkshere-hidelinks": "Gırêyan bınımne",
        "whatlinkshere-hideimages": "Gıreyê dosya $1",
        "whatlinkshere-filters": "Avrêci",
        "autoblockid": "Otomatik vındarnayış #$1",
index 379bbec..6d198c2 100644 (file)
        "apisandbox-dynamic-parameters-add-placeholder": "Ονομασία παραμέτρου",
        "apisandbox-dynamic-error-exists": "Η παράμετρος με την ονομασία \"$1\" υπάρχει ήδη",
        "apisandbox-submit-invalid-fields-title": "Κάποια από τα πεδία δεν είναι έγκυρα",
-       "apisandbox-submit-invalid-fields-message": "ΠαÏ\81ακαλÏ\8e Î´Î¹Î¿Ï\81θÏ\8eÏ\83Ï\84ε Ï\84α Ï\83ημειÏ\89μένα Ï\80εδία ÎºÎ±Î¹ Ï\80Ï\81οÏ\83Ï\80αθείστε ξανά.",
+       "apisandbox-submit-invalid-fields-message": "ΠαÏ\81ακαλÏ\8e Î´Î¹Î¿Ï\81θÏ\8eÏ\83Ï\84ε Ï\84α Ï\83ημειÏ\89μένα Ï\80εδία ÎºÎ±Î¹ Ï\80Ï\81οÏ\83Ï\80αθήστε ξανά.",
        "apisandbox-results": "Αποτελέσματα",
        "apisandbox-sending-request": "Αποστολή αιτήματος API...",
        "apisandbox-loading-results": "Λήψη αποτελεσμάτων API...",
index 10072b9..e7fa4c7 100644 (file)
        "whatlinkshere-prev": "{{PLURAL:$1|previous|previous $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|next|next $1}}",
        "whatlinkshere-links": "← links",
-       "whatlinkshere-hideredirs": "$1 redirects",
-       "whatlinkshere-hidetrans": "$1 transclusions",
-       "whatlinkshere-hidelinks": "$1 links",
-       "whatlinkshere-hideimages": "$1 file links",
+       "whatlinkshere-hideredirs": "Hide redirects",
+       "whatlinkshere-hidetrans": "Hide transclusions",
+       "whatlinkshere-hidelinks": "Hide links",
+       "whatlinkshere-hideimages": "Hide file links",
        "whatlinkshere-filters": "Filters",
        "whatlinkshere-submit": "Go",
        "autoblockid": "Autoblock #$1",
index 8e55b82..52e1cd1 100644 (file)
@@ -89,7 +89,7 @@
        "tog-ccmeonemails": "Sendi al mi kopiojn de retpoŝtaĵoj, kiujn mi sendis al aliaj uzantoj.",
        "tog-diffonly": "Ne montri paĝan enhavon sub la ŝanĝmontrilo",
        "tog-showhiddencats": "Montri kaŝitajn kategoriojn",
-       "tog-norollbackdiff": "Preterlasi ŝanĝoelmontron post malfaro",
+       "tog-norollbackdiff": "Nemontri diferencon post plenumado de ŝanĝomalfaro",
        "tog-useeditwarning": "Averti min kiam mi forlasas redaktan paĝon kun nekonservitaj ŝanĝoj",
        "tog-prefershttps": "Ĉiam uzu sekuran konekton ensalutite",
        "underline-always": "Ĉiam",
        "missingarticle-rev": "(versio#: $1)",
        "missingarticle-diff": "(Diferenco inter versioj: $1, $2)",
        "readonly_lag": "La datumbazo estis aŭtomate ŝlosita dum la subdatumbazo atingas la ĉefan datumbazon.",
+       "nonwrite-api-promise-error": "La 'Promeso-Ne-Skribi-API-Ago' HTTPa titolo estis sendita sed la peto estis al API skriba modulo.",
        "internalerror": "Interna eraro",
        "internalerror_info": "Interna eraro: $1",
        "internalerror-fatal-exception": "Neriparebla escepto de la tipo \"$1\"",
        "upload-form-label-not-own-work-message-local": "Se vi ne eblas alŝuti tiun dosieron respektante de politikoj de {{SITENAME}}, bonvolu fermi tiun dialogon kaj provi denove kun alia metodo.",
        "upload-form-label-not-own-work-local-local": "Vi eble ŝatu egale pravi [[Special:Upload|la defaŭltan paĝon]].",
        "upload-form-label-own-work-message-default": "Mi komprenas ke mi alŝutas tiun dosieron al komunigita deponejo. Mi konfirmas ke mi faras tiun respektante de la uzadtermoj kaj de la permisilopolitikoj tie.",
+       "upload-form-label-not-own-work-message-default": "Se vi ne eblas alŝuti tiun dosieron respektante de politikoj de komuna deponejo, bonvolu fermi tiun dialogon kaj provi denove kun alia metodo.",
+       "upload-form-label-not-own-work-local-default": "Vi ankaŭ eble dezirus provi per uzi [[Special:Upload|la alŝutan paĝon sur {{SITENAME}}]], se ĉi tiu dosiero povas esti alŝutita tie respektante de iliaj politikoj.",
+       "upload-form-label-own-work-message-shared": "Mi atestas ke mi posedas la kopirajton sur ĉi tiu dosiero kaj konsenti al neeksvalidiĝebla liberigo de ĉi tiu dosiero al Vikimedia Komunejo sub la [https://creativecommons.Org/licencoj/de-sa/4.0/ Krea Komunaĵo Atribuite Samkondiĉe 4.0] permisilo kaj mi konsentas al la [https://wikimediafoundation.Org/vikiaj/Terminoj_de_Uzaj kondiĉoj de uzo].",
+       "upload-form-label-not-own-work-message-shared": "Se vi ne posedas la kopirajton sur ĉi tiu dosiero aŭ vi deziras liberigi ĝin sub malsama licenco, konsideru uzi la [https://commons.Wikimedia.Org/vikia/Specialaĵo:UploadWizard asistanto de  alŝutado al komunejo].",
+       "upload-form-label-not-own-work-local-shared": "Vi ankaŭ eble dezirus provi per uzi [[Special:Upload|la paĝon de alŝutado sur {{SITENAME}}]], se ĉi tiu dosiero povas esti alŝutita tie respektante de iliaj politikoj.",
        "backend-fail-stream": "Ne povis fluigi dosieron $1.",
        "backend-fail-backup": "Ne povis enarkivigi dosieron $1.",
        "backend-fail-notexists": "La dosiero $1 ne ekzistas.",
        "apihelp": "Helpo pri API",
        "apihelp-no-such-module": "Modulo \"$1\" ne estis trovita.",
        "apisandbox": "API testejo",
+       "apisandbox-jsonly": "JavaScript estas postulita por uzi la API provejon.",
        "apisandbox-api-disabled": "API estas malŝalta en ĉi tiu retejo.",
        "apisandbox-intro": "Uzu tiun ĉi paĝon por eksperimenti kun '''MediaWiki API'''.\nVidu [//www.mediawiki.org/wiki/API:Main_page la API-dokumentadon] por pli da detaloj pri la uzo de API. Ekz-e: [//www.mediawiki.org/wiki/API#A_simple_example atingi la enhavon de la Ĉefpaĝo]. Elektu agon por vidi pliajn ekzemplojn.\n\nNotu ke, kvankam ĉi tiu estas provejo, agoj kiun vi faros en ĉi tiu paĝo povas modifi la vikion.",
        "apisandbox-unfullscreen": "Montri paĝon",
index 8173ca9..0337736 100644 (file)
                        "Transonlohk",
                        "Eloy",
                        "Lemondoge",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Indiralena",
+                       "Rubentl134"
                ]
        },
        "tog-underline": "Subrayar los enlaces:",
        "changecontentmodel-success-text": "Se ha cambiado el tipo de contenido de [[:$1]].",
        "changecontentmodel-cannot-convert": "El contenido de [[:$1]] no se puede convertir a un tipo de $2.",
        "changecontentmodel-nodirectediting": "El modelo de contenido $1 no admite la edición directa",
+       "changecontentmodel-emptymodels-title": "No hay modelos de contenido disponibles",
        "log-name-contentmodel": "Registro de cambios del modelo de contenido",
        "log-description-contentmodel": "Eventos relacionados con los modelos de contenido de una página",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|creó}} la página $3 usando un modelo de contenido no predeterminado \"$5\"",
        "whatlinkshere-hideredirs": "$1 redirecciones",
        "whatlinkshere-hidetrans": "$1 inclusiones",
        "whatlinkshere-hidelinks": "$1 enlaces",
-       "whatlinkshere-hideimages": "$1 enlaces a archivos",
+       "whatlinkshere-hideimages": "Ocultar los vínculos de archivo",
        "whatlinkshere-filters": "Filtros",
        "whatlinkshere-submit": "Ir",
        "autoblockid": "Bloqueo automático #$1",
        "lockdbsuccesstext": "La base de datos de {{SITENAME}} ha sido bloqueada.\n<br />Recuerde retirar el bloqueo después de completar las tareas de mantenimiento.",
        "unlockdbsuccesstext": "La base de datos de {{SITENAME}} ha sido desbloqueada.",
        "lockfilenotwritable": "El archivo-cerrojo de la base de datos no tiene permiso de escritura. Para bloquear o desbloquear la base de datos, este archivo tiene que ser escribible por el servidor web.",
+       "databaselocked": "La base de datos ya está bloqueada.",
        "databasenotlocked": "La base de datos no está bloqueada.",
        "lockedbyandtime": "(por {{GENDER:$1|$1}} el $2 a las $3)",
        "move-page": "Trasladar $1",
index 8f30db3..672b071 100644 (file)
                        "Gnangbade",
                        "Frigory",
                        "Lemondoge",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Yasten"
                ]
        },
        "tog-underline": "Soulignement des liens :",
        "tog-editsectiononrightclick": "Activer la modification des sections par un clic droit sur les titres de section",
        "tog-watchcreations": "Ajouter à ma liste de suivi les pages que je crée et les fichiers que j’importe",
        "tog-watchdefault": "Ajouter à ma liste de suivi les pages et les fichiers que je modifie",
-       "tog-watchmoves": "Ajouter les pages et les fichiers que je place dans ma liste de suivi",
+       "tog-watchmoves": "Ajouter les pages et les fichiers que je place dans ma liste de suivi",
        "tog-watchdeletion": "Ajouter à ma liste de suivi les pages et les fichiers que je supprime",
        "tog-watchuploads": "Ajouter les nouveaux fichiers que j’importe à ma liste de suivi",
        "tog-watchrollback": "Ajouter à ma liste de suivi les pages sur lesquelles j’ai effectué une révocation",
        "mainpage": "Accueil",
        "mainpage-description": "Accueil",
        "policy-url": "Project:Règles",
-       "portal": "Portail de la communauté",
+       "portal": "Communauté",
        "portal-url": "Project:Accueil",
        "privacy": "Politique de confidentialité",
        "privacypage": "Project:Confidentialité",
        "whatlinkshere-prev": "{{PLURAL:$1|précédente|$1 précédentes}}",
        "whatlinkshere-next": "{{PLURAL:$1|suivante|$1 suivantes}}",
        "whatlinkshere-links": "← liens",
-       "whatlinkshere-hideredirs": "$1 les redirections",
-       "whatlinkshere-hidetrans": "$1 les inclusions",
-       "whatlinkshere-hidelinks": "$1 les liens",
-       "whatlinkshere-hideimages": "$1 les liens vers le fichier",
+       "whatlinkshere-hideredirs": "Masquer les redirections",
+       "whatlinkshere-hidetrans": "Masquer les inclusions",
+       "whatlinkshere-hidelinks": "Masquer les liens",
+       "whatlinkshere-hideimages": "Masquer les liens vers le fichier",
        "whatlinkshere-filters": "Filtres",
        "whatlinkshere-submit": "Lister",
        "autoblockid": "Blocage automatique #$1",
        "invalidateemail": "Annuler la confirmation de l'adresse de courriel",
        "notificationemail_subject_changed": "L’adresse courriel enregistrée sur {{SITENAME}} a été changée",
        "notificationemail_subject_removed": "L’adresse courriel enregistrée sur {{SITENAME}} a été supprimée",
-       "notificationemail_body_changed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1,\na changé l’adresse courriel associée au compte « $2 » sur {{SITENAME}}\nen « $3 ».\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
+       "notificationemail_body_changed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1, a changé l’adresse courriel\nassociée au compte « $2 » sur {{SITENAME}} en « $3 ».\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
        "notificationemail_body_removed": "Quelqu’un, probablement vous, connecté depuis l’adresse IP $1,\na suprrimé l’adresse courriel associée au compte « $2 » sur {{SITENAME}}.\n\nSi ce n’était pas vous, contactez un administrateur du site immédiatement.",
        "scarytranscludedisabled": "[La transclusion interwiki est désactivée]",
        "scarytranscludefailed": "[La récupération de modèle a échoué pour $1]",
        "api-error-nomodule": "Erreur interne : aucun module de versement défini.",
        "api-error-ok-but-empty": "Erreur interne : Le serveur n'a pas répondu.",
        "api-error-overwrite": "Écraser un fichier existant n'est pas autorisé.",
-       "api-error-ratelimited": "Vous essayez de télécharger plus de fichiers dans un court espace de temps que ce que ce wiki autorise.\nVeuillez réessayer dans quelques minutes.",
+       "api-error-ratelimited": "Vous essayez de téléverser plus de fichiers dans un court espace de temps que ce que ce wiki peut accepter.\nVeuillez réessayer dans quelques minutes.",
        "api-error-stashfailed": "Erreur interne : le serveur n'a pas pu enregistrer le fichier temporaire.",
        "api-error-publishfailed": "Erreur interne : Le serveur n'a pas pu publier le fichier temporaire.",
        "api-error-stasherror": "Une erreur s'est produite lors du téléchargement du fichier pour le dissimuler.",
index 6499e8e..ba7e524 100644 (file)
        "whatlinkshere-prev": "{{PLURAL:$1|anterior|$1 anteriores}}",
        "whatlinkshere-next": "{{PLURAL:$1|seguinte|$1 seguintes}}",
        "whatlinkshere-links": "← ligazóns",
-       "whatlinkshere-hideredirs": "$1 as redireccións",
-       "whatlinkshere-hidetrans": "$1 as inclusións",
-       "whatlinkshere-hidelinks": "$1 as ligazóns",
-       "whatlinkshere-hideimages": "$1 as ligazóns ao ficheiro",
+       "whatlinkshere-hideredirs": "Ocultar as redireccións",
+       "whatlinkshere-hidetrans": "Ocultar as inclusións",
+       "whatlinkshere-hidelinks": "Ocultar as ligazóns",
+       "whatlinkshere-hideimages": "Ocultar as ligazóns ao ficheiro",
        "whatlinkshere-filters": "Filtros",
        "whatlinkshere-submit": "Ir",
        "autoblockid": "Bloqueo automático nº$1",
index f13c5af..0d61e70 100644 (file)
        "category_header": "דפים בקטגוריה \"$1\"",
        "subcategories": "קטגוריות משנה",
        "category-media-header": "קובצי מדיה בקטגוריה \"$1\"",
-       "category-empty": "'''קטגוריה זו אינה כוללת דפים או קובצי מדיה.'''",
+       "category-empty": "<em>קטגוריה זו אינה כוללת דפים או קובצי מדיה.</em>",
        "hidden-categories": "{{PLURAL:$1|קטגוריה מוסתרת|קטגוריות מוסתרות}}",
        "hidden-category-category": "קטגוריות מוסתרות",
        "category-subcat-count": "{{PLURAL:$2|קטגוריה זו כוללת את קטגוריית המשנה הבאה בלבד.|קטגוריה זו כוללת את {{PLURAL:$1|קטגוריית המשנה המוצגת להלן|$1 קטגוריות המשנה המוצגות להלן}}, וכוללת בסך־הכול $2 קטגוריות משנה.}}",
        "redirectedfrom": "(הופנה מהדף $1)",
        "redirectpagesub": "דף הפניה",
        "redirectto": "הפניה ל:",
-       "lastmodifiedat": "×\93×£ ×\96×\94 ×©×\81×\95Ö¼× ×\94 ×\9c×\90×\97ר×\95× ×\94 ×\91Ö¾$1, ×\91שע×\94 $2.",
+       "lastmodifiedat": "דף זה שוּנה לאחרונה ב־$1, בשעה $2.",
        "viewcount": "דף זה נצפה {{PLURAL:$1|פעם אחת|פעמיים|$1 פעמים}}.",
        "protectedpage": "דף מוגן",
        "jumpto": "קפיצה אל:",
        "perfcachedts": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, שעודכן לאחרונה ב־$1. לכל היותר {{PLURAL:$4|תוצאה אחת נשמרת|$4 תוצאות נשמרות}} בזיכרון המטמון.",
        "querypage-no-updates": "העדכונים לדף זה כרגע מופסקים, והמידע לא יעודכן באופן שוטף.",
        "viewsource": "הצגת מקור",
-       "viewsource-title": "הצגת המקור של $1",
+       "viewsource-title": "הצגת המקור של הדף \"$1\"",
        "actionthrottled": "הפעולה הוגבלה",
        "actionthrottledtext": "כאמצעי נגד שימוש לרעה, קיימת מגבלה על ביצוע פעולה זו פעמים רבות מדי בזמן קצר, וחרגת מהמגבלה הזאת.\nנא לנסות שוב בעוד מספר דקות.",
        "protectedpagetext": "דף זה מוגן כדי למנוע עריכה ופעולות אחרות.",
        "subject": "נושא:",
        "minoredit": "זוהי עריכה משנית",
        "watchthis": "מעקב אחרי דף זה",
-       "savearticle": "ש×\9e×\99ר×\94",
+       "savearticle": "ש×\9e×\99רת ×\94×\93×£",
        "publishpage": "פרסום הדף",
        "preview": "תצוגה מקדימה",
        "showpreview": "תצוגה מקדימה",
        "edit-no-change": "המערכת התעלמה מעריכתך כיוון שלא נעשה שינוי בטקסט.",
        "postedit-confirmation-created": "הדף נוצר.",
        "postedit-confirmation-restored": "הדף שוחזר.",
-       "postedit-confirmation-saved": "ער×\99×\9bתך נשמרה.",
+       "postedit-confirmation-saved": "×\94ער×\99×\9b×\94 ×©×\9cך נשמרה.",
        "edit-already-exists": "לא ניתן ליצור דף חדש.\nהוא כבר קיים.",
        "defaultmessagetext": "טקסט ההודעה המקורי",
        "content-failed-to-parse": "פענוח $2 כתוכן מסוג $1 נכשל: $3",
        "revdelete-unsuppress": "הסרת הגבלות בגרסאות המשוחזרות",
        "revdelete-log": "סיבה:",
        "revdelete-submit": "ביצוע על {{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}}",
-       "revdelete-success": "×\9eצ×\91 ×\94תצ×\95×\92×\94 ×©×\9c ×\94×\92רס×\94 ×©×\81×\95Ö¼× ×\94.",
+       "revdelete-success": "מצב התצוגה של הגרסה שוּנה.",
        "revdelete-failure": "לא ניתן היה לשנות את מצב התצוגה של הגרסה:\n$1",
-       "logdelete-success": "×\9eצ×\91 ×\94תצ×\95×\92×\94 ×©×\9c ×¤×¢×\95×\9cת ×\94×\99×\95×\9e×\9f ×©×\81×\95Ö¼× ×\94.",
+       "logdelete-success": "מצב התצוגה של פעולת היומן שוּנה.",
        "logdelete-failure": "לא ניתן היה לשנות את מצב התצוגה של היומן:\n$1",
        "revdel-restore": "שינוי מצב התצוגה",
        "pagehist": "היסטוריית הדף",
        "recentchangeslinked": "שינויים בדפים המקושרים",
        "recentchangeslinked-feed": "שינויים בדפים המקושרים",
        "recentchangeslinked-toolbox": "שינויים בדפים המקושרים",
-       "recentchangeslinked-title": "שינויים בדפים המקושרים מהדף $1",
+       "recentchangeslinked-title": "שינויים בדפים המקושרים מהדף \"$1\"",
        "recentchangeslinked-summary": "בדף מיוחד זה רשומים השינויים האחרונים בדפים המקושרים מתוך הדף (או בדפים הכלולים בקטגוריה).\nדפים ב[[Special:Watchlist|רשימת המעקב שלכם]] מוצגים ב'''הדגשה'''.",
        "recentchangeslinked-page": "שם הדף:",
        "recentchangeslinked-to": "הצגת השינויים בדפים המקשרים לדף הנתון במקום זאת",
        "recentchanges-page-removed-from-category": "הדף [[:$1]] הוסר מקטגוריה",
        "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "autochange-username": "שינוי אוטומטי של מדיה־ויקי",
-       "upload": "העלאת קובץ לשרת",
-       "uploadbtn": "×\94×¢×\9c×\90×\94",
+       "upload": "העלאת קובץ",
+       "uploadbtn": "×\94×¢×\9c×\90ת ×\94ק×\95×\91×¥",
        "reuploaddesc": "ביטול ההעלאה וחזרה לטופס העלאת קבצים לשרת",
        "upload-tryagain": "שליחת התיאור החדש של הקובץ",
        "uploadnologin": "לא נכנסת לחשבון",
        "ntransclusions": "בשימוש {{PLURAL:$1|בדף אחד|ב־$1 דפים}}",
        "specialpage-empty": "אין תוצאות.",
        "lonelypages": "דפים יתומים",
-       "lonelypagestext": "×\94×\93פ×\99×\9d ×\94×\91×\90×\99×\9d ×\90×\99× ×\9d ×\9eק×\95שר×\99×\9d ×\9e×\93פ×\99×\9d ×\90×\97ר×\99×\9d ×\91×\90תר ×\96×\94 ×\95×\90×\99× ×\9d ×\9e×\95×\9b×\9c×\9c×\99×\9d ×\91×\94×\9d.",
+       "lonelypagestext": "×\94×\93פ×\99×\9d ×\94×\91×\90×\99×\9d ×\90×\99× ×\9d ×\9eק×\95שר×\99×\9d ×\95×\90×\99× ×\9d ×\9e×\95×\9b×\9c×\9c×\99×\9d ×\91×\93פ×\99×\9d ×\90×\97ר×\99×\9d ×\91×\90תר {{SITENAME}}.",
        "uncategorizedpages": "דפים חסרי קטגוריה",
        "uncategorizedcategories": "קטגוריות חסרות קטגוריה",
        "uncategorizedimages": "קבצים חסרי קטגוריה",
        "emailccsubject": "העתק של הודעתך למשתמש $1: $2",
        "emailsent": "הדואר נשלח",
        "emailsenttext": "הודעת הדואר האלקטרוני שלך נשלחה.",
-       "emailuserfooter": "$1 {{GENDER:$1|ש×\9c×\97|ש×\9c×\97×\94}} ×\90ת ×\94×\93×\95×\90\"×\9c ×\94×\96×\94 ×\9c{{GRAMMAR:ת×\97×\99×\9c×\99ת|$2}} ×\91×\90×\9eצע×\95ת ×¤×¢×\95×\9cת \"{{int:emailuser}}\" ב{{GRAMMAR:תחילית|{{SITENAME}}}}.",
+       "emailuserfooter": "$1 {{GENDER:$1|ש×\9c×\97|ש×\9c×\97×\94}} ×\90ת ×\94×\93×\95×\90\"×\9c ×\94×\96×\94 ×\9c{{GRAMMAR:ת×\97×\99×\9c×\99ת|$2}} ×\91×\90×\9eצע×\95ת ×\94ת×\9b×\95× ×\94 \"{{int:emailuser}}\" ב{{GRAMMAR:תחילית|{{SITENAME}}}}.",
        "usermessage-summary": "השארת הודעת מערכת.",
        "usermessage-editor": "שולח הודעות המערכת",
        "watchlist": "רשימת המעקב",
        "excontent": "התוכן היה: \"$1\"",
        "excontentauthor": "התוכן היה: \"$1\", {{GENDER:$2|והתורם היחיד היה|והתורמת היחידה הייתה}} \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|שיחה]])",
        "exbeforeblank": "התוכן לפני שרוקן היה: \"$1\"",
-       "delete-confirm": "מחיקת \"$1\"",
+       "delete-confirm": "מחיקת הדף \"$1\"",
        "delete-legend": "מחיקה",
        "historywarning": "<strong>אזהרה:</strong> לדף שאתם עומדים למחוק יש היסטוריית שינויים של {{PLURAL:$1|גרסה אחת|$1 גרסאות}}:",
        "historyaction-submit": "הצגה",
        "sp-contributions-newonly": "הצגת עריכות שהן יצירות של דפים בלבד",
        "sp-contributions-submit": "חיפוש",
        "whatlinkshere": "דפים המקושרים לכאן",
-       "whatlinkshere-title": "דפים המקשרים לדף $1",
+       "whatlinkshere-title": "דפים המקשרים לדף \"$1\"",
        "whatlinkshere-page": "דף:",
        "linkshere": "הדפים שלהלן מקושרים לדף '''[[:$1]]''':",
        "nolinkshere": "אין דפים המקושרים לדף '''[[:$1]]'''.",
        "whatlinkshere-prev": "{{PLURAL:$1|הקודם|$1 הקודמים}}",
        "whatlinkshere-next": "{{PLURAL:$1|הבא|$1 הבאים}}",
        "whatlinkshere-links": "→ קישורים",
-       "whatlinkshere-hideredirs": "$1 הפניות",
-       "whatlinkshere-hidetrans": "$1 הכללות",
-       "whatlinkshere-hidelinks": "$1 קישורים",
-       "whatlinkshere-hideimages": "$1 קישורים לקובץ",
+       "whatlinkshere-hideredirs": "הסתרת הפניות",
+       "whatlinkshere-hidetrans": "הסתרת הכללות",
+       "whatlinkshere-hidelinks": "הסתרת קישורים",
+       "whatlinkshere-hideimages": "הסתרת קישורי קבצים",
        "whatlinkshere-filters": "מסננים",
        "whatlinkshere-submit": "הצגה",
        "autoblockid": "חסימה אוטומטית #$1",
        "databaselocked": "בסיס הנתונים כבר נעול.",
        "databasenotlocked": "בסיס הנתונים אינו נעול.",
        "lockedbyandtime": "(על־ידי $1 ב־$3, $2)",
-       "move-page": "העברת $1",
+       "move-page": "העברת הדף \"$1\"",
        "move-page-legend": "העברת דף",
        "movepagetext": "שימוש בטופס שלהלן ישנה את שמו של דף, ויעביר את כל ההיסטוריה שלו לשם חדש.\nהשם הישן יהפוך לדף הפניה אל הדף עם השם החדש.\nבאפשרותכם לעדכן אוטומטית דפי הפניה לכותרת המקורית.\nאם תבחרו לא לעשות זאת, אנא ודאו שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|שבורות]].\nאתם אחראים לוודא שכל הקישורים ימשיכו להצביע למקום שאליו הם אמורים להצביע.\n\nשימו לב: הדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם הדף השני הוא הפניה ואין לו היסטוריית עריכות קודמות.\nפירוש הדבר שאפשר לשנות חזרה את שמו של דף לשם המקורי אם נעשתה טעות, ושלא ניתן לדרוס דף קיים.\n\n<strong>לתשומת לבכם:</strong>\nשינוי זה עשוי להיות שינוי דרסטי ובלתי צפוי לדף פופולרי;\nאנא ודאו שאתם מבינים את השלכות המעשה לפני שאתם ממשיכים.",
        "movepagetext-noredirectfixer": "שימוש בטופס שלהלן ישנה את שמו של דף, ויעביר את כל ההיסטוריה שלו לשם חדש.\nהשם הישן יהפוך לדף הפניה אל הדף עם השם החדש.\nאנא ודאו שאין [[Special:DoubleRedirects|הפניות כפולות]] או [[Special:BrokenRedirects|שבורות]].\nאתם אחראים לוודא שכל הקישורים ימשיכו להצביע למקום שאליו הם אמורים להצביע.\n\nשימו לב: הדף <strong>לא</strong> יועבר אם כבר יש דף תחת השם החדש, אלא אם הדף הזה הוא הפניה ואין לו היסטוריית עריכות קודמות.\nפירוש הדבר שאפשר לשנות חזרה את שמו של דף לשם המקורי אם נעשתה טעות, ושלא ניתן לדרוס דף קיים.\n\n<strong>לתשומת לבכם:</strong>\nשינוי זה עשוי להיות שינוי דרסטי ובלתי צפוי לדף פופולרי;\nאנא ודאו שאתם מבינים את השלכות המעשה לפני שאתם ממשיכים.",
        "tooltip-pt-mytalk": "דף השיחה שלך",
        "tooltip-pt-anontalk": "שיחה על תרומות המשתמש האנונימי",
        "tooltip-pt-preferences": "ההעדפות שלך",
-       "tooltip-pt-watchlist": "רשימת הדפים שאתם עוקבים אחרי השינויים בהם",
+       "tooltip-pt-watchlist": "רשימת הדפים ש{{GENDER:|אתה עוקב|את עוקבת}} אחרי השינויים בהם",
        "tooltip-pt-mycontris": "רשימת התרומות שלך",
        "tooltip-pt-anoncontribs": "רשימת העריכות שנעשו מכתובת ה־IP הזאת",
        "tooltip-pt-login": "מומלץ להיכנס לחשבון; עם זאת, אין חובה לעשות זאת",
        "tooltip-save": "שמירת השינויים שלך",
        "tooltip-publish": "פרסום השינויים שלך",
        "tooltip-preview": "תצוגה מקדימה של השינויים שלך. נא להשתמש באפשרות זו לפני השמירה.",
-       "tooltip-diff": "צפ×\99×\99×\94 ×\91ש×\99× ×\95×\99×\99×\9d ×©×¢×¨×\9bת×\9d בטקסט",
+       "tooltip-diff": "×\94צ×\92ת ×\94ש×\99× ×\95×\99×\99×\9d ×©×\91×\99צעת בטקסט",
        "tooltip-compareselectedversions": "צפייה בהשוואת שתי גרסאות של דף זה",
        "tooltip-watch": "הוספת דף זה לרשימת המעקב שלך",
        "tooltip-watchlistedit-normal-submit": "הסרת הדפים",
        "anonymous": "{{PLURAL:$1|משתמש אנונימי|משתמשים אנונימיים}} של {{SITENAME}}",
        "siteuser": "משתמש {{SITENAME}} $1",
        "anonuser": "משתמש אנונימי של {{SITENAME}} $1",
-       "lastmodifiedatby": "דף זה שונה לאחרונה ב־$2, $1 על־ידי $3.",
+       "lastmodifiedatby": "דף זה שוּנה לאחרונה ב־$2, $1 על־ידי $3.",
        "othercontribs": "מבוסס על העבודה של $1.",
        "others": "אחרים",
        "siteusers": "{{PLURAL:$2|{{GENDER:$1|משתמש}}|משתמשי}} {{SITENAME}} $1",
        "spam_blanking": "כל הגרסאות כוללות קישורים ל־$1, מרוקן את הדף",
        "spam_deleting": "כל הגרסאות כוללות קישורים ל־$1, מוחק את הדף",
        "simpleantispam-label": "בדיקת אנטי־ספאם.\n<strong>אל</strong> תמלאו שדה זה!",
-       "pageinfo-title": "מידע על \"$1\"",
+       "pageinfo-title": "מידע על הדף \"$1\"",
        "pageinfo-not-current": "מצטערים, לא ניתן להציג את המידע הזה לגרסאות ישנות.",
        "pageinfo-header-basic": "מידע בסיסי",
        "pageinfo-header-edits": "היסטוריית עריכות",
index c1ff526..ac588bf 100644 (file)
        "listingcontinuesabbrev": "(дIахо)",
        "index-category": "Индекс оттаеш оагIонаш",
        "noindex-category": "Индекс ца оттаеш оагIонаш",
-       "broken-file-category": "Ð\9fаÑ\8cла Ñ\85Ñ\8cожадеÑ\80гаÑ\88Ñ\86а Ð±Ð¾Ð»Ñ\85беÑ\88 Ð¹Ð¾Ð°Ñ\86а Ð¾Ð°Ð³|онаш",
-       "about": "Ð\9bоаÑ\86ам",
+       "broken-file-category": "Файла Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80гаÑ\88 Ð±Ð¾Ð»Ñ\85беÑ\88 Ð¹Ð¾Ð°Ñ\86а Ð¾Ð°Ð³Iонаш",
+       "about": "СÑ\83Ñ\80Ñ\82 Ð¾Ñ\82Ñ\82адаÑ\80",
        "article": "Йоазув",
-       "newwindow": "(кердача коре)",
+       "newwindow": "&nbsp;(керда кора чу)",
        "cancel": "Эшац",
-       "moredotdotdot": "Д|ахо",
-       "morenotlisted": "Ер |ояздар хьалдиззанз да.",
-       "mypage": "Oаг|ув",
-       "mytalk": "Дувцам",
-       "anontalk": "Дувцар",
+       "moredotdotdot": "ДIахо...",
+       "morenotlisted": "Ер список хьалйиза яц.",
+       "mypage": "ОагIув",
+       "mytalk": "Дувца оттадар",
+       "anontalk": "Дувца оттадар",
        "navigation": "Навигаци",
        "and": "&#32;а",
        "qbfind": "Лахар",
-       "qbbrowse": "Б|аргтасса",
-       "qbedit": "Ð¥Ñ\83вÑ\86а",
-       "qbpageoptions": "Оаг|он оттамаш",
+       "qbbrowse": "БIаргтохар",
+       "qbedit": "Ð\9dиÑ\81Ñ\8aе",
+       "qbpageoptions": "ОагIон оттамаш",
        "qbmyoptions": "Са оттамаш",
        "faq": "КТХ",
        "faqpage": "Project:КТХ",
-       "actions": "Ð¥|амдаÑ\80аш",
+       "actions": "Ð\90Ñ\80дамаш",
        "namespaces": "ЦIерий мотташ",
        "variants": "Варианташ",
        "navigation-heading": "Навигацен меню",
-       "errorpagetitle": "Г|алат",
-       "returnto": "цу $1 оаг|он т|а юхаг|о",
+       "errorpagetitle": "ГӀалат",
+       "returnto": "Укх $1 оагIона тIа юхагӀо.",
        "tagline": "Кечал укхазара: {{grammar:genitive|{{SITENAME}}}}",
-       "help": "Ð\93Ó\80о",
+       "help": "Ð\9dовкÑ\8a\81Ñ\82ал",
        "search": "Лахаp",
        "searchbutton": "Хьалáха",
-       "go": "Дехьа г|о",
+       "go": "Дехьавала",
        "searcharticle": "Дехьавала",
        "history": "Истори",
        "history_short": "Истори",
-       "updatedmarker": "Со Ñ\85анаÑ\87а Ð´ÐµÐ½Ñ\86а Ñ\85Ñ\83вÑ\86амаÑ\88 Ñ\85иннaд",
+       "updatedmarker": "Со Ñ\82IеÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\83кÑ\85аз Ñ\85иннаÑ\87Ñ\83л Ñ\82IеÑ\85Ñ\8cагIа ÐºÐµÑ\80дадаÑ\8cккÑ\85ад",
        "printableversion": "Зарба тохара верси",
-       "permalink": "Даиман латташ йола хьожаярг",
-       "print": "Ð\9aепаÑ\82оÑ\85аÑ\80",
+       "permalink": "Ð\94аиман Ð»Ð°Ñ\82Ñ\82аÑ\88 Ð¹Ð¾Ð»Ð° Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80г",
+       "print": "Ð\97аÑ\80ба Ñ\82оÑ\85а",
        "view": "Хьажар",
-       "view-foreign": "УкÑ\85 $1 Ñ\8fÑ\85 Ñ\81айÑ\82а Ñ\87Ñ\83 Ñ\85Ñ\8cажа",
+       "view-foreign": "Укх $1 сайта чу хьажа",
        "edit": "Нийсде",
        "edit-local": "Хувца локальни йоазонца сурт оттадар",
-       "create": "Ð¥Ñ\8cаде",
+       "create": "Ð¥Ñ\8cакÑ\85олла",
        "create-local": "ТIатоха локальни йоазонца сурт оттадар",
-       "editthispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ñ\85Ñ\83вÑ\86а",
-       "create-this-page": "Ep oаг|ув хьае",
-       "delete": "Д|аяккха",
-       "deletethispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ð´|аÑ\8fÑ\8cккÑ\85а",
-       "undeletethispage": "Ð\95Ñ\80 Ð¾Ð°Ð³|Ñ\83в Ð´|аÑ\8fккÑ\85анз Ð¹Ð¸Ñ\82а",
-       "undelete_short": "Ð\9cеÑ\82Ñ\82аоÑ\82Ñ\82ае {{PLURAL:$1|1=Ñ\85Ñ\83вÑ\86ам|$1 Ñ\85Ñ\83вÑ\86амаÑ\88}}",
-       "viewdeleted_short": "Б|аргтасса {{PLURAL:$1|1=д|адаьккха хувцам|$1 д|адаьккха хувцамаш}}",
-       "protect": "Ð\9bоÑ\80аде",
+       "editthispage": "Ð\9dийÑ\81Ñ\8aе ÐµÑ\80 Ð¾Ð°Ð³IÑ\83в",
+       "create-this-page": "Хьакхолла ер оагӀув",
+       "delete": "ДӀаяккха",
+       "deletethispage": "Ð\94Ó\80аÑ\8fккÑ\85а ÐµÑ\80 Ð¾Ð°Ð³Ó\80Ñ\83в",
+       "undeletethispage": "ЮÑ\85амеÑ\82Ñ\82аоÑ\82Ñ\82ае ÐµÑ\80 Ð¾Ð°Ð³Ó\80Ñ\83в",
+       "undelete_short": "ЮÑ\85амеÑ\82Ñ\82аоÑ\82Ñ\82ае {{PLURAL:$1|$1 Ð½Ð¸Ð¹Ñ\81даÑ\80|$1 Ð½Ð¸Ð¹Ñ\81даÑ\80аÑ\88|1=нийÑ\81даÑ\80}}",
+       "viewdeleted_short": "{{PLURAL:$1|$1 дIадаьккха нийсдарга|дIадаьккха нийсдарга|$1 дIадаьккха нийсдарашга}} хьажар",
+       "protect": "Ð\93Iо Ð´Ð°Ñ\80",
        "protect_change": "хувца",
-       "protectthispage": "Ð\9bоÑ\80ае ÐµÑ\80 Ð¾Ð°Ð³|Ñ\83в",
-       "unprotect": "Ð\9bоÑ\80ам хувца",
-       "unprotectthispage": "Ð\9bоÑ\80ам хувца",
+       "protectthispage": "Ð\93Iо Ð´Ðµ Ñ\83кÑ\85 Ð¾Ð°Ð³Iон",
+       "unprotect": "Ð\93Iо хувца",
+       "unprotectthispage": "УкÑ\85 Ð¾Ð°Ð³Iон Ð³Iо хувца",
        "newpage": "Керда оагӀув",
-       "talkpage": "УкÑ\85 Ð¾Ð°Ð³|он Ñ\82|а Ð´Ñ\83вÑ\86ам Ð±Ðµ",
+       "talkpage": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\8eвÑ\86а",
        "talkpagelinktext": "дувца оттадар",
-       "specialpage": "Ð\93\83лакÑ\85адаÑ\80а Ð¾Ð°Ð³|ув",
+       "specialpage": "Ð\91алÑ\85а Ð¾Ð°Ð³Ó\80ув",
        "personaltools": "Доакъашхочун гӀирсаш",
        "articlepage": "Йоазон т|а б|аргтасса",
        "talk": "Дувца оттадар",
        "nstab-main": "Йоазув",
        "nstab-user": "Дакъалаьцархо",
        "nstab-media": "Медифаг",
-       "nstab-special": "Ð\93Ó\80Ñ\83лакха оагӀув",
+       "nstab-special": "Ð\91алха оагӀув",
        "nstab-project": "Проектах лаьца",
        "nstab-image": "Файл",
        "nstab-mediawiki": "Хоам",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|дувца оттадар]])",
        "duplicate-defaultsort": "Зем бе. Сатийна дIа-хьа хьоржама доагI \"$2\" хьалхара сатийна дIа-хьа хьоржама доагI \"$1\" хьахьоржа.",
        "version": "Доржам",
-       "version-specialpages": "Ð\93\83лакÑ\85ий Ð¾Ð°Ð³IÑ\83внаш",
+       "version-specialpages": "Ð\91алÑ\85а Ð¾Ð°Ð³Ó\80онаш",
        "version-version": "($1)",
        "version-software-version": "Доржам",
        "fileduplicatesearch-filename": "ПаьлацIи:",
index cb236f5..14da229 100644 (file)
        "changecontentmodel-success-text": "Il tipo di contenuto di [[:$1]] è stato modificato.",
        "changecontentmodel-cannot-convert": "Il contenuto di [[:$1]] non può essere convertito in tipo $2.",
        "changecontentmodel-nodirectediting": "Il modello di contenuto $1 non supporta la modifica diretta",
+       "changecontentmodel-emptymodels-title": "Nessun modello di contenuto disponibile",
        "changecontentmodel-emptymodels-text": "Il contenuto di [[:$1]] non può essere convertito in alcun tipo.",
        "log-name-contentmodel": "Modifiche del modello contenuti",
        "log-description-contentmodel": "Eventi relativi al modello di contenuto di una pagina",
        "whatlinkshere-prev": "{{PLURAL:$1|precedente|precedenti $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|successivo|successivi $1}}",
        "whatlinkshere-links": "← collegamenti",
-       "whatlinkshere-hideredirs": "$1 redirect",
-       "whatlinkshere-hidetrans": "$1 inclusioni",
-       "whatlinkshere-hidelinks": "$1 collegamenti",
-       "whatlinkshere-hideimages": "$1 link da file",
+       "whatlinkshere-hideredirs": "Nascondi redirect",
+       "whatlinkshere-hidetrans": "Nascondi inclusioni",
+       "whatlinkshere-hidelinks": "Nascondi collegamenti",
+       "whatlinkshere-hideimages": "Nascondi collegamenti da file",
        "whatlinkshere-filters": "Filtri",
        "whatlinkshere-submit": "Vai",
        "autoblockid": "Autoblocco #$1",
        "autoredircomment": "Redirect alla pagina [[$1]]",
        "autosumm-new": "Creata pagina con \"$1\"",
        "autosumm-newblank": "Creata pagina vuota",
-       "size-bytes": "$1 byte",
+       "size-bytes": "$1 {{PLURAL:$1|byte}}",
        "lag-warn-normal": "Le modifiche apportate {{PLURAL:$1|nell'ultimo secondo|negli ultimi $1 secondi}} potrebbero non apparire in questa lista.",
        "lag-warn-high": "A causa di un eccessivo ritardo nell'aggiornamento del server di database, le modifiche apportate {{PLURAL:$1|nell'ultimo secondo|negli ultimi $1 secondi}} potrebbero non apparire in questa lista.",
        "watchlistedit-normal-title": "Modifica osservati speciali",
index 0cdd350..a20bb2f 100644 (file)
@@ -20,8 +20,8 @@
                ]
        },
        "tog-underline": "Nggaris ngisori pranala:",
-       "tog-hideminor": "Dhelikaké besutan cilik ing owah-owahan pungkasan",
-       "tog-hidepatrolled": "Dhelikaké besutan ingawasan ing owah-owahan pungkasan",
+       "tog-hideminor": "Dhelikaké besutan cilik saka owah-owahan pungkasan",
+       "tog-hidepatrolled": "Dhelikaké besutan ingawasan saka owah-owahan pungkasan",
        "tog-newpageshidepatrolled": "Dhelikaké kaca ingawasan saka pratélaning kaca anyar",
        "tog-hidecategorization": "Dhelikaké gegebengan kaca",
        "tog-extendwatchlist": "Ambakaké pawawangan nedya nuduhaké kabèh owahan, ora mung sing paling anyar",
        "tog-minordefault": "Tengeri kabèh besutan minangka besutan cilik sacara baku",
        "tog-previewontop": "Deleng prawuryan sadurungé besut kothak",
        "tog-previewonfirst": "Tuduhaké prawuryan nalika mbesut pisanan",
-       "tog-enotifwatchlistpages": "Kirimi aku layang èlèktronik yèn ana kaca utawa barkas ing pawawanganku sing diowah",
-       "tog-enotifusertalkpages": "Kirimi aku layang èlèktronik yèn kaca gegunemanku diowah",
-       "tog-enotifminoredits": "Uga kirimi aku layang èlèktronik yèn ana besutan cilik ing kaca lan barkas",
-       "tog-enotifrevealaddr": "Singkab alamat layang èlèktronikku ing layang pawarta",
-       "tog-shownumberswatching": "Tuduhaké cacah sing ngawasi",
+       "tog-enotifwatchlistpages": "Kirimi aku layangtronik yèn ana kaca utawa barkas ing pawawanganku sing diowah",
+       "tog-enotifusertalkpages": "Kirimi aku layangtronik yèn kaca gegunemanku diowah",
+       "tog-enotifminoredits": "Uga kirimi aku layangtronik yèn ana besutan cilik ing kaca lan barkas",
+       "tog-enotifrevealaddr": "Singkab alamat layangtronikku ing layang pawarta",
+       "tog-shownumberswatching": "Tuduhaké cacah wong sing ngawasi",
        "tog-oldsig": "Tandha tangan sing ana:",
-       "tog-fancysig": "Anggepen tapak asta minangka teks wiki (tanpa pranala otomatis)",
+       "tog-fancysig": "Anggep tandha tangan minangka tulisan wiki (tanpa pranala otomatis)",
        "tog-uselivepreview": "Trapaké prawuryan langsung",
        "tog-forceeditsummary": "Élingna aku menawa kothak ringkesan suntingan isih kosong",
-       "tog-watchlisthideown": "Delikna suntinganku ing daftar pangawasan",
+       "tog-watchlisthideown": "Dhelikaké besutanku saka pawawangan",
        "tog-watchlisthidebots": "Dhelikaké besutan bot saka pangawasan",
-       "tog-watchlisthideminor": "Delikna suntingan kecil di daftar pangawasan",
-       "tog-watchlisthideliu": "Ngumpetaké suntingan panganggo sing mlebu log seka daftar pangawasan",
+       "tog-watchlisthideminor": "Dhelikaké besutan cilik saka pawawangan",
+       "tog-watchlisthideliu": "Dhelikaké saka pawawangan besutaning wong sing mlebu",
        "tog-watchlistreloadautomatically": "Mot manèh pawawangan kanthi otomanis samangsa panyaring diowah (butuh JavaScript)",
-       "tog-watchlisthideanons": "Ngumpetaké suntingan panganggo anonim seka daftar pangawasan",
-       "tog-watchlisthidepatrolled": "Delikna suntingan sing wis dipatroli saka daftar pangawasan",
+       "tog-watchlisthideanons": "Dhelikaké saka pawawangan besutaning para anonim",
+       "tog-watchlisthidepatrolled": "Dhelikaké besutan ingawasan saka pawawangan",
        "tog-watchlisthidecategorization": "Dhelikaké kategorisasi kaca",
-       "tog-ccmeonemails": "Kirimana aku salinan layang e-mail sing tak-kirimaké menyang wong liya",
-       "tog-diffonly": "Aja dituduhaké isi kaca ing ngisor bédané suntingan",
-       "tog-showhiddencats": "Tuduhna kategori sing didelikaké",
-       "tog-norollbackdiff": "Aja nuduhaké prabédan sawisé mbalèkaké.",
-       "tog-useeditwarning": "Ã\88lingaké kula yèn kula ninggalaké suntingan sing durung kasimpen",
-       "tog-prefershttps": "Panggah sarana sambungan aman nalika mlebu",
-       "underline-always": "Mesthi",
+       "tog-ccmeonemails": "Kirimi aku salinan layangtronik sing tak kirim nyang wong liya",
+       "tog-diffonly": "Aja dituduhaké isining kaca ing ngisor bédané suntingan",
+       "tog-showhiddencats": "Tuduhaké kategori sing didhelikaké",
+       "tog-norollbackdiff": "Aja tuduhaké prabédan sawisé mbalèkaké.",
+       "tog-useeditwarning": "Ã\89lingaké kula yèn kula ninggalaké suntingan sing durung kasimpen",
+       "tog-prefershttps": "Tansah nganggo sambungan aman nalika mlebu",
+       "underline-always": "Tansah",
        "underline-never": "Ora tau",
-       "underline-default": "Kulit atau penjelajah bawaan",
-       "editfont-style": "Modhèl aksara (font) ing kotak suntingan:",
-       "editfont-default": "Standar panjelajah wèb",
+       "underline-default": "Baku kulit utawa pangluron",
+       "editfont-style": "Gagrag fon ing pambesutan:",
+       "editfont-default": "Baku pangluron",
        "editfont-monospace": "Fon monospasi",
        "editfont-sansserif": "Fon tansèrif",
        "editfont-serif": "Fon sèrif",
        "august-date": "Agustus $1",
        "september-date": "$1 Sèptèmber",
        "october-date": "Oktober $1",
-       "november-date": "$1 Novèmber",
-       "december-date": "$1 Dèsèmber",
+       "november-date": "$1 Nopèmber",
+       "december-date": "$1 Dsèmber",
        "period-am": "Isuk-Awan",
        "period-pm": "Soré-Wengi",
        "pagecategories": "{{PLURAL:$1|Kategori|Kategori}}",
        "category_header": "Kaca sajeroning kategori \"$1\"",
-       "subcategories": "Subkategori",
+       "subcategories": "Anak kategori",
        "category-media-header": "Médhia sajeroning kategori \"$1\"",
-       "category-empty": "''Kategori iki saiki ora ngandhut artikel utawa média.''",
-       "hidden-categories": "{{PLURAL:$1|Kategori kadhelikaké|Kategori kadhelikaké}}",
+       "category-empty": "<em>Kategori iki lagi ora ngandhut artikel utawa médhia.</em>",
+       "hidden-categories": "{{PLURAL:$1|Kategori kadhelikan}}",
        "hidden-category-category": "Kategori kadhelikan",
-       "category-subcat-count": "{{PLURAL:$2|Kategori iki mung ngandhut subkategori ngisor iki.|Kategori iki ngandhut {{PLURAL:$1|subkategori|$1 subkategori}} ngisor iki saka gunggung $2 subkategori.}}",
-       "category-subcat-count-limited": "Kategori iki ora duwé {{PLURAL:$1|subkategori|$1 subkategori}} ''berikut''.",
+       "category-subcat-count": "{{PLURAL:$2|Kategori iki mung ngandhut saanak kategori ngisor iki.|Kategori iki ngandhut {{PLURAL:$1|anak kategori|$1 anak kategori}} ngisor iki saka gunggung $2 anak kategori.}}",
+       "category-subcat-count-limited": "Kategori iki duwé {{PLURAL:$1|anak kategori|$1 anak kategori}} kaya ngisor iki.",
        "category-article-count": "{{PLURAL:$2|Kategori iki mung ngandhut kaca ngisor iki.|{{PLURAL:$1|Kaca|$1 kaca}} ngisor iki ana ing kategori iki saka gunggung $2 kaca.}}",
        "category-article-count-limited": "Kategori iki ngandhut {{PLURAL:$1|kaca|$1 kaca-kaca}} sing kapacak ing ngisor iki.",
        "category-file-count": "{{PLURAL:$2|Kategori iki mung isi barkas iki.|{{PLURAL:$1|Barkas|$1 barkas}} iki ana sajeroning kategori iki saka $2 gunggungé.}}",
        "deletethispage": "Busak kaca iki",
        "undeletethispage": "Wurungaké pambusaking kaca iki",
        "undelete_short": "Batal busak {{PLURAL:$1|sabesutan|$1 besutan}}",
-       "viewdeleted_short": "Pirsani {{PLURAL:$1|suntingan|suntingan}} ingkang sampun kabusak",
+       "viewdeleted_short": "Deleng {{PLURAL:$1|sabesutan sing kabusak|$1 besutan sing kabusak}}",
        "protect": "Reksa",
        "protect_change": "owah",
        "protectthispage": "Reksa kaca iki",
        "redirectpagesub": "Alih kaca",
        "redirectto": "Ngalih menyang:",
        "lastmodifiedat": "Kaca iki pungkasan diowah kala $1, tabuh $2.",
-       "viewcount": "Kaca iki wis tau diaksès cacahé ping {{PLURAL:$1|siji|$1}}.",
+       "viewcount": "Kaca iki wis diaksès ping {{PLURAL:$1|siji|$1}}.",
        "protectedpage": "Kaca kareksa",
        "jumpto": "Jujug:",
        "jumptonavigation": "napigasi",
        "aboutpage": "Project:Bab",
        "copyright": "Kabèh isi kasedyakaké miturut $1.",
        "copyrightpage": "{{ns:project}}:Hak cipta",
-       "currentevents": "Kadadéan saiki",
-       "currentevents-url": "Project:Kadadéan saiki",
+       "currentevents": "Kadadian saiki",
+       "currentevents-url": "Project:Kadadian saiki",
        "disclaimers": "Sélakan",
        "disclaimerpage": "Project:Sélakan umum",
        "edithelp": "Pitulung besut",
        "logdelete-failure": "'''Aturan pandhelikan ora bisa disèt:'''\n$1",
        "revdel-restore": "Ngowahi visiblitas (pangatonan)",
        "pagehist": "Babading kaca",
-       "deletedhist": "Babad kabusakan",
+       "deletedhist": "Babad kabusak",
        "revdelete-hide-current": "Gagal ndhelikaké révisi tanggal $2, $1: iki arupa révisi paling anyar.\nRévisi iki ora bisa didhelikaké.",
        "revdelete-show-no-access": "Gagal nampilaké révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "revdelete-modify-no-access": "Gagal ngowahi révisi tanggal $1, jam $2: révisi iki wis ditandhani \"kawates\".\nPanjenengan ora nduwèni aksès menyang révisi iki.",
        "search-external": "Panggolèkan èkstèrnal",
        "searchdisabled": "Sawetara wektu iki panjenengan ora bisa nggolèk mawa fungsi golèk {{SITENAME}}. Kanggo saiki mangga panjenengan bisa golèk nganggo Google. Nanging isi indèks Google kanggo {{SITENAME}} bisa waé lawas lan durung dianyari.",
        "search-error": "Ana kasalahan wektu nggoleki: $1",
-       "preferences": "Preferensi (pilihan)",
+       "preferences": "Pilihan",
        "mypreferences": "Pilihan",
        "prefs-edits": "Gunggung besutan:",
        "prefsnologintext2": "Tulung $1 kanggo ngganti preferensi sampeyan.",
        "prefs-skin": "Kulit",
-       "skin-preview": "Pratilik",
-       "datedefault": "Ora ana préferènsi",
+       "skin-preview": "Prawuryan",
+       "datedefault": "Ora ana pilihan",
        "prefs-labs": "Piranti lab",
-       "prefs-user-pages": "Kaca panganggo",
-       "prefs-personal": "Profil panganggo",
+       "prefs-user-pages": "Kacaning sing nganggo",
+       "prefs-personal": "Panjèrènging sing nganggo",
        "prefs-rc": "Owah-owahan pungkasan",
-       "prefs-watchlist": "Dhaftar pangawasan",
+       "prefs-watchlist": "Pawawangan",
        "prefs-editwatchlist": "Besut pawawangan",
        "prefs-editwatchlist-label": "Besut isining pawawanganing sampéyan",
        "prefs-editwatchlist-edit": "Deleng lan busak sesirah ing pawawanganing sampéyan",
        "prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|dina|dina}}",
        "prefs-watchlist-edits": "Cacahé suntingan maksimum sing dituduhaké ing dhaftar pangawasan sing luwih jangkep:",
        "prefs-watchlist-edits-max": "Gunggung maksimum: 1000",
-       "prefs-watchlist-token": "Token pantauan:",
+       "prefs-watchlist-token": "Tokening pawawangan:",
        "prefs-misc": "Liya-liya",
        "prefs-resetpass": "Ganti tembung sandi",
        "prefs-changeemail": "Owah utawa busak alamat layangtronik",
        "prefs-help-signature": "Komentar ing kaca wicara kudu ditapak astani nganggo \"<nowiki>~~~~</nowiki>\" sing bakal dikonvèrsi dadi tapak asta panjenengan lan tanggal wektu.",
        "badsig": "Tapak astanipun klèntu; cèk rambu HTML.",
        "badsiglength": "Tapak asta panjenengan kedawan.\nAja luwih saka {{PLURAL:$1|karakter|karakter}}.",
-       "yourgender": "Jinis kelamin:",
+       "yourgender": "Kepiyé sampéyan medhar priangganing sampéyan?",
        "gender-unknown": "Nalika nyebut sampéyan, piranti alus iku bakal nganggo tembung sing nétral jèndher sabisané",
-       "gender-male": "Lanang",
-       "gender-female": "Wadon",
+       "gender-male": "Dhèwèké mbesut kaca wiki",
+       "gender-female": "Dhèwèké mbesut kaca wiki",
        "prefs-help-gender": "Opsional: Dipigunakaké kanggo panyebutan jinis kelamin sing bener déning piranti alus.\nInformasi iki bakal kabuka kanggo publik.",
-       "email": "Layang élèktronik (E-mail)",
+       "email": "Layangtronik",
        "prefs-help-realname": "Jeneng asli manasuka.\nMenawa diisi, iku bakal kanggo ngatribusi sampéyan awit karyaning sampéyan.",
        "prefs-help-email": "Alamat layang èlèktronik sipaté mung pilihan, nanging dibutuhaké kanggo nyetèl ulang tembung sandhi yèn Sampéyan lali.",
        "prefs-help-email-others": "Sampéyan uga bisa milih kanggo ngidinaké wong liya ngubungi Sampéyan liwat layang èlèktronik sing ana ing kaca panganggo utawa kaca guneman.\nAlamat layang èlèktronik Sampéyan ora dituduhaké nalika wong liya ngubungi Sampéyan.",
        "prefs-help-email-required": "Alamat layang-e dibutuhaké.",
-       "prefs-info": "Informasi dhasar",
+       "prefs-info": "Katerangan pokok",
        "prefs-i18n": "Internasionalisasi",
        "prefs-signature": "Tapak asma",
        "prefs-dateformat": "Format tanggal",
index 5bce2c6..e9a314c 100644 (file)
        "botpasswords-invalid-name": "지정된 사용자 이름은 봇 비밀번호 구분자(\"$1\")를 포함하고 있지 않습니다.",
        "botpasswords-not-exist": "\"$1\" 사용자가 이름이 \"$2\"인 봇의 비밀번호를 가지고 있지 않습니다.",
        "resetpass_forbidden": "비밀번호를 바꿀 수 없습니다",
-       "resetpass-no-info": "ì\9d´ í\8a¹ì\88\98 ë¬¸ì\84\9cì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 ë°\98ë\93\9cì\8b\9c 로그인해야 합니다.",
+       "resetpass-no-info": "ì\9d´ í\8e\98ì\9d´ì§\80ì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 로그인해야 합니다.",
        "resetpass-submit-loggedin": "비밀번호 바꾸기",
        "resetpass-submit-cancel": "취소",
        "resetpass-wrong-oldpass": "비밀번호가 잘못되었거나 현재의 비밀번호와 같습니다.\n이미 비밀번호를 바꾸었거나 새 임시 비밀번호를 요청했을 수 있습니다.",
        "changeemail": "이메일 주소를 바꾸거나 제거하기",
        "changeemail-header": "이메일 주소를 바꾸려면 이 양식을 채우세요. 계정에서 이메일 연동을 취소하고 싶다면 양식을 제출할 때 새 이메일 주소를 공란으로 두세요.",
        "changeemail-passwordrequired": "변경을 적용하려면 비밀번호를 입력해야 합니다.",
-       "changeemail-no-info": "ì\9d´ í\8a¹ì\88\98 ë¬¸ì\84\9cì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 ë°\98ë\93\9cì\8b\9c 로그인해야 합니다.",
+       "changeemail-no-info": "ì\9d´ í\8e\98ì\9d´ì§\80ì\97\90 ì§\81ì \91 ì \91ê·¼í\95\98려면 로그인해야 합니다.",
        "changeemail-oldemail": "현재 이메일 주소:",
        "changeemail-newemail": "새 이메일 주소:",
        "changeemail-newemail-help": "이메일 주소를 삭제하고자 한다면 이 칸을 빈칸으로 두세요. 비밀번호 재설정이 불가능해지며, 이메일 주소가 없다면 이메일을 받을 수 없습니다.",
index 7e4cc1a..ca96110 100644 (file)
        "whatlinkshere-prev": "de vörijje {{PLURAL:$1||$1|noll}} zeije",
        "whatlinkshere-next": "de nächste {{PLURAL:$1||$1|noll}} zeije",
        "whatlinkshere-links": "← Links",
-       "whatlinkshere-hideredirs": "de Ömleijdonge $1",
+       "whatlinkshere-hideredirs": "De Ömleijdonge verschteijsche",
        "whatlinkshere-hidetrans": "de Oproofe $1",
-       "whatlinkshere-hidelinks": "de nommahle Lengks $1",
+       "whatlinkshere-hidelinks": "De nommahle Lengks verschteijsche",
        "whatlinkshere-hideimages": "$1 de Lengks op Datteihje",
        "whatlinkshere-filters": "Ußsööke",
        "whatlinkshere-submit": "Lohß jonn!",
index 8511b83..7caf232 100644 (file)
        "remembermypassword": "Prisiminti prisijungimo duomenis šiame kompiuteryje (daugiausiai $1 {{PLURAL:$1|dieną|dienas|dienų}})",
        "userlogin-remembermypassword": "Įsiminti mane",
        "userlogin-signwithsecure": "Naudoti saugią jungtį",
-       "cannotloginnow-title": "Negalima prisijungti dabar",
+       "cannotloginnow-title": "Dabar negalima prisijungti",
        "cannotloginnow-text": "Prisijungimas negalimas, kai naudojama $1.",
        "yourdomainname": "Jūsų domenas:",
        "password-change-forbidden": "Jus negalite keisti slaptažodžių šioje wiki.",
index 97f08d2..bee6f50 100644 (file)
        "right-userrights": "Alle gebrukersrechten bewarken",
        "right-userrights-interwiki": "Gebrukersrechten van gebrukers in aandere wiki's wiezigen",
        "right-siteadmin": "De databanke blokkeren en weer vriegeven",
-       "right-override-export-depth": "Ziejen uutvoeren, oek de ziejen waor naor verwezen wörden, tot n diepte van 5",
+       "right-override-export-depth": "Ziejen exporteren, oek de ziejen waor naor verwezen wördt, tot n diepte van 5",
        "right-sendemail": "Bericht versturen naor aandere gebrukers",
        "right-passwordreset": "Bekiek netpostberichten veur t opniej instellen van joew wachtwoord",
        "newuserlogpage": "Logboek mit anwas",
        "semiprotectedpagemovewarning": "'''Waorschuwing:''' disse zied kan allinnig deur eregistreerden gebrukers herneumd wörden.\nDe leste logboekregel steet hieronder:",
        "move-over-sharedrepo": "== t Bestaand besteet al ==\n[[:$1]] besteet al in de edeelden mediadatabanke. A'j n bestaand naor disse titel herneumen, dan ku'j  t edeelden bestaand niet gebruken.",
        "file-exists-sharedrepo": "Disse bestaandsnaam besteet al in de edeelden mediadatabanke.\nKies n aandere bestaandsnaam.",
-       "export": "Ziejen uutvoeren",
-       "exporttext": "De tekste en geschiedenisse van n zied of n koppel ziejen kunnen in XML-formaot uutevoerd wörden. Dit bestaand ku'j daornao uutvoeren naor n aandere MediaWiki deur de [[Special:Import|invoerzied]] te gebruken.\n\nZet in t onderstaonde veld de namen van de ziejen die'j uutvoeren willen, één zied per regel, en gif an o'j alle versies mit de bewarkingssamenvatting uutvoeren willen of allinnig de leste versies mit de bewarkingssamenvatting.\n\nA'j dat leste doon willen dan ku'j oek n verwiezing gebruken, bieveurbeeld [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] veur de zied \"[[{{MediaWiki:Mainpage}}]]\".",
-       "exportall": "Alle ziejen uutvoeren",
+       "export": "Ziejen exporteren",
+       "exporttext": "De tekste en geschiedenisse van n zied of n koppel ziejen kunnen in XML-formaot uutevoerd wörden. Dit bestaand ku'j daornao exporteren naor n aandere MediaWiki deur de [[Special:Import|invoerzied]] te gebruken.\n\nZet in t onderstaonde veld de namen van de ziejen die'j exporteren willen, één zied per regel, en geef an o'j alle versies mit de bewarkingssamenvatting exporteren willen of allinnig de leste versies mit de bewarkingssamenvatting.\n\nA'j dat leste doon willen dan ku'j oek n verwiezing gebruken, bieveurbeeld [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] veur de zied \"[[{{MediaWiki:Mainpage}}]]\".",
+       "exportall": "Alle ziejen exporteren",
        "exportcuronly": "Allinnig de actuele versie, niet de veurgeschiedenisse",
-       "exportnohistory": "----\n'''NB:''' t uutvoeren van de hele geschiedenisse is uutezet vanwegen prestasieredens.",
+       "exportnohistory": "----\n'''NB:''' t exporteren van de hele geschiedenisse is uutezet vanwegen prestasieredens.",
        "exportlistauthors": "De hele auteurslieste opnemen veur elke zied",
-       "export-submit": "Uutvoeren",
+       "export-submit": "Exporteren",
        "export-addcattext": "Ziejen derbie doon uut de kategorie:",
        "export-addcat": "Derbie doon",
        "export-addnstext": "Ziejen uut de volgende naamruumte derbie doon:",
index d711ecd..1449441 100644 (file)
        "preferences": "ପସନ୍ଦ",
        "mypreferences": "ପସନ୍ଦ",
        "prefs-edits": "ସମ୍ପାଦନା ସଂଖ୍ୟା:",
-       "prefsnologintext2": "ନିà¬\9cର à¬ªà¬¸à¬¨à­\8dଦ à¬¬à¬¦à¬²ାଇବା ପାଇଁ ଲଗ ଇନ କରନ୍ତୁ ।",
+       "prefsnologintext2": "ନିà¬\9cର à¬ªà¬¸à¬¨à­\8dଦ à¬¬à¬¦à¬³ାଇବା ପାଇଁ ଲଗ ଇନ କରନ୍ତୁ ।",
        "prefs-skin": "ବହିରାବରଣ",
        "skin-preview": "ସାଇତା ଆଗରୁ ଦେଖଣା",
        "datedefault": "କୌଣସି ପସନ୍ଦ ନାହିଁ",
index 099edc1..43656ec 100644 (file)
        "minoredit": "To jest drobna zmiana",
        "watchthis": "Obserwuj",
        "savearticle": "Zapisz",
+       "publishpage": "Opublikuj stronę",
        "preview": "Podgląd",
        "showpreview": "Pokaż podgląd",
        "showdiff": "Podgląd zmian",
        "whatlinkshere-prev": "{{PLURAL:$1|poprzednie|poprzednie $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|następne|następne $1}}",
        "whatlinkshere-links": "← linkujące",
-       "whatlinkshere-hideredirs": "$1 przekierowania",
-       "whatlinkshere-hidetrans": "$1 dołączenia",
-       "whatlinkshere-hidelinks": "$1 linki",
-       "whatlinkshere-hideimages": "$1 linki z plików",
+       "whatlinkshere-hideredirs": "Ukryj przekierowania",
+       "whatlinkshere-hidetrans": "Ukryj dołączenia",
+       "whatlinkshere-hidelinks": "Ukryj linki",
+       "whatlinkshere-hideimages": "Ukryj linki z plików",
        "whatlinkshere-filters": "Filtry",
        "whatlinkshere-submit": "Dalej",
        "autoblockid": "automatyczna blokada nr $1",
index 2faea55..751bb82 100644 (file)
        "whatlinkshere-prev": "This is part of the navigation message on the top and bottom of Whatlinkshere pages, where it is used as the first argument of {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - the number of items shown per page. It is not used when $1 is zero; not sure what happens when $1 is one.\nSpecial pages use {{msg-mw|Prevn}} instead (still as an argument to {{msg-mw|Viewprevnext}}).\n\nSee also:\n* {{msg-mw|Whatlinkshere-next}}\n{{Identical|Previous}}",
        "whatlinkshere-next": "This is part of the navigation message on the top and bottom of Whatlinkshere pages, where it is used as the second argument of {{msg-mw|Viewprevnext}}.\n\nParameters:\n* $1 - the number of items shown per page. It is not used when $1 is zero; not sure what happens when $1 is one.\nSpecial pages use {{msg-mw|Nextn}} instead (still as an argument to {{msg-mw|Viewprevnext}}).\n\nSee also:\n* {{msg-mw|Whatlinkshere-prev}}\n{{Identical|Next}}",
        "whatlinkshere-links": "Used on [[Special:WhatLinksHere]]. It is a link to the WhatLinksHere page of that page.\n\nExample line:\n* [[Main Page]] ([[Special:WhatLinksHere/Main Page|{{int:whatlinkshere-links}}]])\n{{Identical|Link}}",
-       "whatlinkshere-hideredirs": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Redirect}}",
-       "whatlinkshere-hidetrans": "First filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Transclusion}}",
-       "whatlinkshere-hidelinks": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}\n{{Identical|Link}}",
+       "whatlinkshere-hideredirs": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
+       "whatlinkshere-hidetrans": "First filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
+       "whatlinkshere-hidelinks": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 is the {{msg-mw|hide}} or {{msg-mw|show}}",
        "whatlinkshere-hideimages": "Filter option in [[Special:WhatLinksHere]]. Parameters:\n* $1 - the {{msg-mw|Hide}} or {{msg-mw|Show}}\n\nSee also:\n*{{msg-mw|Isimage}}\n*{{msg-mw|Media tip}}",
        "whatlinkshere-filters": "{{Identical|Filter}}",
        "whatlinkshere-submit": "Label for submit button in [[Special:WhatLinksHere]]\n{{Identical|Go}}",
index 7af459f..bfd77ff 100644 (file)
        "lockdbsuccesstext": "База данных проекта была заблокирована.<br />\nНе забудьте [[Special:UnlockDB|убрать блокировку]] после завершения процедуры обслуживания.",
        "unlockdbsuccesstext": "База данных проекта была разблокирована.",
        "lockfilenotwritable": "Нет права на запись в файл блокировки базы данных. Чтобы заблокировать или разблокировать базу данных, веб-сервер должен иметь разрешение на запись в этот файл.",
+       "databaselocked": "База данных уже заблокирована.",
        "databasenotlocked": "База данных не была заблокирована.",
        "lockedbyandtime": "($1 $2 $3)",
        "move-page": "$1 — переименование",
index b57f29d..df323fe 100644 (file)
        "lockdbsuccesstext": "Databasen är nu låst.\n<br />Kom ihåg att [[Special:UnlockDB|ta bort låsningen]] när du är färdig med ditt underhåll.",
        "unlockdbsuccesstext": "Databasen är upplåst.",
        "lockfilenotwritable": "Det går inte att skriva till databasens låsfil. För att låsa eller låsa upp databasen, så måste webbservern kunna skriva till den filen.",
+       "databaselocked": "Databasen är redan låst.",
        "databasenotlocked": "Databasen är inte låst.",
        "lockedbyandtime": "(av $1 den $2 kl. $3)",
        "move-page": "Flytta $1",
index 173d31f..24f55bf 100644 (file)
        "changecontentmodel-success-text": "Тип вмісту сторінки [[:$1]] було змінено.",
        "changecontentmodel-cannot-convert": "Вміст сторінки [[:$1]] не можна перетворити на вміст типу $2.",
        "changecontentmodel-nodirectediting": "Модель вмісту $1 не підтримує пряме редагування",
+       "changecontentmodel-emptymodels-title": "Немає доступних моделей коментарів",
+       "changecontentmodel-emptymodels-text": "Вміст сторінки [[:$1]] не може бути перетворений до будь якого типу.",
        "log-name-contentmodel": "Журнал змін моделі вмісту",
        "log-description-contentmodel": "Події, пов'язані з моделями вмісту сторінки",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|створив|створила}} сторінку $3, використовуючи нестандартну модель вмісту «$5»",
        "lockdbsuccesstext": "Базу даних проекту заблоковано.<br />\nНе забудьте її [[Special:UnlockDB|розблокувати]] після завершення обслуговування.",
        "unlockdbsuccesstext": "Базу даних проекту розблоковано.",
        "lockfilenotwritable": "Немає права на запис в файл блокування бази даних. Щоб заблокувати чи розблокувати БД, веб-сервер повинен мати дозвіл на запис в цей файл.",
+       "databaselocked": "База даних вже заблокована.",
        "databasenotlocked": "База даних не заблокована.",
        "lockedbyandtime": "($1 $2 $3)",
        "move-page": "Перейменування сторінки «$1»",
index 2589d77..66a40c6 100644 (file)
        "whatlinkshere-prev": "{{PLURAL:$1|kết quả trước|$1 kết quả trước}}",
        "whatlinkshere-next": "{{PLURAL:$1|kết quả sau|$1 kết quả sau}}",
        "whatlinkshere-links": "← liên kết",
-       "whatlinkshere-hideredirs": "$1 trang đổi hướng",
+       "whatlinkshere-hideredirs": "Ẩn trang đổi hướng",
        "whatlinkshere-hidetrans": "$1 trang nhúng",
-       "whatlinkshere-hidelinks": "$1 liên kết",
-       "whatlinkshere-hideimages": "$1 liên kết tập tin",
+       "whatlinkshere-hidelinks": "Ẩn liên kết",
+       "whatlinkshere-hideimages": "Ẩn liên kết tập tin",
        "whatlinkshere-filters": "Bộ lọc",
        "whatlinkshere-submit": "Xem",
        "autoblockid": "Cấm tự động #$1",
index e0e6ce8..d63feb5 100644 (file)
@@ -53,7 +53,7 @@
        "tog-ccmeonemails": "Padad-i ak hin mga kopya hin mga email nga akon ginpapadara ha iba nga mga gumaramit",
        "tog-diffonly": "Ayaw igpakita an sulod han pakli ha ilarom han pagkakaiba",
        "tog-showhiddencats": "Igpakita an mga tinago nga mga kaarangay",
-       "tog-norollbackdiff": "Iglat-ang an kaiban kahuman himoa an libot-pabalik",
+       "tog-norollbackdiff": "Ayaw igpakita an kaiban kahuman himoa an rollback",
        "tog-useeditwarning": "Pasabti ako kun nabaya ako hin ginliwat ng pakli nga waray katipig an mga pagbag-o",
        "tog-prefershttps": "Pirmihi paggamit hin segurado nga koneksyon kun nakalog-in",
        "underline-always": "Pirme",
        "createaccount-title": "Paghimo hin akawnt para han {{SITENAME}}",
        "createaccount-text": "Mayda tawo nga naghimo hin akawnt nga gingamit an imo email address ha {{SITENAME}} ($4) nga ginngaranan nga \"$2\", nga may-ada tigaman-panakob nga \"$3\".\n\nKinahanglan ka maglog-in ngan igbal-iw an imo tigaman-panakob yana dayon.\n\nPuydi nimo pabay-on ini nga mensahe, kun ini nga paghimo hin akwant in nagsayop la.",
        "login-throttled": "Damo na an imo login attempts ha pagkayana.\nAlayon paghulat hin $1 san-o ka umutro.",
-       "login-abort-generic": "An imo paglog-in in diri malinamposon - Naundang",
+       "login-abort-generic": "An imo paglog-in in pakyas - Gin-undang",
        "login-migrated-generic": "An imo account in nabalhin, ngan an imo agnay-gumaramit in waray na hinin nga wiki.",
        "loginlanguagelabel": "Pinulongan: $1",
        "suspicious-userlogout": "Waray ka tugoti pag-logout tungod nga baga ini ginpadangat hin usa nga broken browser o caching proxy.",
        "newpassword": "Bag-o nga tigaman-pagsulod:",
        "retypenew": "Utroha pagbutang an bag-o nga tigaman-pagsulod:",
        "resetpass_submit": "Igbutang an password ngan log in",
-       "changepassword-success": "Malinamposon an pagbal-iw hit imo tigaman-panakob!",
+       "changepassword-success": "Malinamposon an pagliwat hit imo password!",
        "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.",
        "rev-delundel": "igpakita/igtago",
        "rev-showdeleted": "igpakita",
        "revisiondelete": "Pagpara/pagtanggal han pagpara nga mga rebisyon",
+       "revdelete-nooldid-title": "Diri ginkakarawat an target revision",
        "revdelete-show-file-confirm": "Sigurado ka nga gusto mo makita an ginpara nga pagliwat han file \"<nowiki>$1</nowiki>\" tikang $2 ha $3?",
        "revdelete-show-file-submit": "Oo",
        "revdelete-hide-text": "Rebisyon nga sinurat",
        "right-reupload": "Sapawa an mga aada nga mga paypay",
        "right-reupload-own": "Igsapaw an aada yana nga mga paypay nga ginkarga-pasaka nimo mismo",
        "right-upload_by_url": "Igkarga paigbaw an mga paypay tikang ha uska URL",
+       "right-purge": "Igpurge an site cache han pakli bisan waray kompirmasyon",
        "right-autoconfirmed": "Diri malalalbtan hin IP-nga-nahibasi nga mga rate hin paglimit",
        "right-bot": "Igtrato komo uska naglulugaring nga proseso",
        "right-writeapi": "Paggamit han write API",
        "listgrouprights-removegroup-self": "Igtanggal an {{PLURAL:$2|grupo|mga grupo}} tikang ha kalugaringon nga akawnt: $1",
        "listgrouprights-addgroup-self-all": "Igdugang an ngatanan nga mga grupo ha kalugaringon nga akawnt",
        "listgrouprights-removegroup-self-all": "Igtanggal an ngatanan nga mga grupo tikang ha kalugaringon nga akawnt",
+       "listgrants-rights": "Mga katungod",
+       "trackingcategories": "Ginsusubay an mga kategorya",
+       "trackingcategories-name": "Ngaran han mensahe",
        "mailnologin": "Waray kakadtoan nga address",
        "mailnologintext": "Kinahanglan nimo nga [[Special:UserLogin|nakalog-in]] ngan may-ada balido nga email address ha imo[[Special:Preferences|mga preperensya]] para makapadangat hin email ngadto ha iba nga mga gumaramit.",
        "emailuser": "Ig-e-mail ini nga gumaramit",
        "protectcomment": "Katadongan:",
        "protect-default": "Togota an ngatanan nga mga gumaramit",
        "protect-level-sysop": "Tuguti la an mga magdudumara",
+       "protect-expiring": "mawawaray gamit pag-$1 (UTC)",
+       "protect-expiring-local": "mawawaray gamit $1",
+       "protect-expiry-indefinite": "waray kataposan",
        "protect-othertime": "Lain nga oras:",
        "protect-othertime-op": "lain nga oras",
        "protect-otherreason": "Lain/dugang nga katadongan:",
index 36b1f8d..fe5623f 100644 (file)
@@ -17,7 +17,8 @@
                        "පසිඳු කාවින්ද",
                        "Matma Rex",
                        "Macofe",
-                       "Nemo bis"
+                       "Nemo bis",
+                       "Alefbeis"
                ]
        },
        "tog-underline": "שטרייכט אונטער לינקען",
@@ -35,6 +36,7 @@
        "tog-watchdefault": "צולייגן בלעטער וואס איך רעדאקטיר צו מיין אכטונג ליסטע",
        "tog-watchmoves": "צולייגן בלעטער וואס איך באוועג און טעקעס וואס איך לאד ארויף צו מיין אכטונג ליסטע",
        "tog-watchdeletion": "צולייגן בלעטער וואס איך מעק אויס צו מיין אויפפאסונג ליסטע",
+       "tog-watchuploads": "לייגט צו נייע פיילס וואס איך טוה ארויפלאדירן צו מיין וואטש ליסטע",
        "tog-watchrollback": "צולייגן צו מיין אויפפאסן ליסטע בלעטער אויף וואס איך האב אדורכגעפירט א צוריקשטעלן.",
        "tog-minordefault": "באגרענעצן אלע רעדאַקטירונגען גרונטלעך אלס מינערדיק",
        "tog-previewontop": "צײַג די \"פֿאָרויסיגע װײַזונג\" גלײַך בײַ דער ערשטער באַאַרבעטונג",
@@ -58,7 +60,7 @@
        "tog-ccmeonemails": "שיק מיר קאפיעס פון בליצבריוו וואס איך שיק צו אנדערע באַניצער",
        "tog-diffonly": "ווייז נישט אינהאלט אונטער די דיפערענץ",
        "tog-showhiddencats": "ווײַזן באהאלטענע קאטעגאריעס",
-       "tog-norollbackdiff": "×\94×\99פ×\98 ×\90×\99×\91ער ווײַזן אונטערשייד נאכן אויספֿירן א צוריקדריי",
+       "tog-norollbackdiff": "× ×\99ש×\98 ווײַזן אונטערשייד נאכן אויספֿירן א צוריקדריי",
        "tog-useeditwarning": "שטעלן א ווארענונג ווען איך לאז איבער א רעדאקטירונג בלאט מיט נישט אויפגעהיטענע ענדערונגען",
        "tog-prefershttps": "ניצט שטענדיק א זיכערע פארבינדונג ווען ארײַנגלאגירט",
        "underline-always": "אייביג",
        "february-gen": "פעברואר",
        "march-gen": "מערץ",
        "april-gen": "אַפּריל",
-       "may-gen": "×\9e×\99י",
+       "may-gen": "×\9e×\90Ö·י",
        "june-gen": "יוני",
        "july-gen": "יולי",
        "august-gen": "אויגוסט",
        "returnto": "צוריקקערן צו $1.",
        "tagline": "פֿון {{SITENAME}}",
        "help": "הילף",
-       "search": "×\96×\95×\9b×\9f",
-       "searchbutton": "×\96×\95×\9b×\9f",
+       "search": "×\96×\95×\9a",
+       "searchbutton": "×\96×\95×\9a",
        "go": "גיין",
        "searcharticle": "גיין",
        "history": "בלאט היסטאריע",
        "protectedpage": "באשיצטער בלאט",
        "jumpto": "שפּרינג צו:",
        "jumptonavigation": "נאַוויגאַציע",
-       "jumptosearch": "×\96×\95×\9b×\9f",
+       "jumptosearch": "×\96×\95×\9a",
        "view-pool-error": "אנטשולדיגט, די סערווערס זענען איבערגעפילט איצט.\nצופיל באניצער פרובירן צו ליינען דעם בלאט.\nביטע ווארטן א ביסל צייט בעפאר איר פרובירט ווידער אריינגיין אינעם בלאט.\n\n$1",
        "generic-pool-error": "אנטשולדיגט, די סערווערס זענען איבערגעפילט איצט.\nצופיל באניצער פרובירן צו באקוקן דעם רעסורס.\nביטע ווארטן א ביסל צייט בעפאר איר פרובירט ווידער אריינטרעטן אין דעם רעסורס.",
        "pool-timeout": "אַריבער דער צײַט וואַרטן פֿאר דער שליסונג",
        "newmessageslinkplural": "{{PLURAL:$1|א נייע מעלדונג|999=נייע מעלדונגען}}",
        "newmessagesdifflinkplural": "לעצטע {{PLURAL:$1|ענדערונג|999=ענדערונגען}}",
        "youhavenewmessagesmulti": "איר האט נייע מעלדונגען אין $1",
-       "editsection": "×\91×\90Ö·×\90ַר×\91×¢×\98ן",
+       "editsection": "רע×\93×\90ַק×\98×\99רן",
        "editold": "רעדאַקטירן",
        "viewsourceold": "ווײַזן מקור",
        "editlink": "רעדאַקטירן",
        "red-link-title": "$1 (בלאט טוט נאָך נישט עקזיסטירן)",
        "sort-descending": "סארטירן אַראָפ",
        "sort-ascending": "סארטירן אַרויף",
-       "nstab-main": "×\90ַר×\98×\99ק×\9c",
+       "nstab-main": "×\91×\9c×\90Ö·×\98",
        "nstab-user": "באַניצער בלאט",
        "nstab-media": "מעדיע בלאט",
        "nstab-special": "ספעציעלער בלאט",
        "virus-unknownscanner": "אומבאוואוסטער אנטי־ווירוס:",
        "logouttext": "'''איר האָט זיך ארויסלאָגירט.'''\n\nבאמערקט אז געוויסע בלעטער קענען זיך ווייטער ארויסשטעלן אזוי ווי ווען איר זענט אריינלאגירט, ביז איר וועט אויסליידיגן דעם בלעטערער זאפאס.",
        "cannotlogoutnow-title": "קען נישט ארויסלאגירן אצינד",
+       "cannotlogoutnow-text": "ארויסלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "welcomeuser": "ברוך הבא, $1!",
        "welcomecreation-msg": "מ'האט געשאפן אייער קאנטע.\nפארגעסט נישט צו ענדערן אייערע [[Special:Preferences|{{SITENAME}} פרעפערענצן]].",
        "yourname": "באַניצער נאָמען:",
        "userlogin-remembermypassword": "לאז מיך בלײַבן ארײַנלאגירט",
        "userlogin-signwithsecure": "ניצן זיכערן סארווער",
        "cannotloginnow-title": "קען נישט אריינלאגירן אצינד",
+       "cannotloginnow-text": "אריינלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "yourdomainname": "אײַער געביט:",
        "password-change-forbidden": "איר קען נישט ענדערן פאסווערטער אויף דער וויקי.",
        "externaldberror": "עס איז אדער פארגעקומען אן אויטענטיקאציע דאטנבאזע פֿעלער אדער איר זענט נישט ערמעגליכט צו דערהיינטיגן אייער דרויסנדיגע קאנטע.",
        "nocookieslogin": "{{SITENAME}} נוצט קיכלעך כדי אַרײַנלאגירן באַניצער.\nבײַ אײַך זענען קיכלעך אומדערמעגלעכט.\nביטע אַקטיווירט זיי און פרובירט נאכאַמאָל.",
        "nocookiesfornew": "מען האט נישט געשאַפֿן די באַניצער קאנטע, ווײַל מען האט נישט געקענט באַשטעטיקן איר מקור.\nטוט פֿעסטשטעלן אָז קיכלעך זענען אַקטיווירט, לאָדט אָן נאכאַמאל דעם בלאַט און פרואווט ווידער.",
        "noname": "איר האט נישט אַרײַנגעשטעלט קײַן גילטיקן באַניצער נאָמען.",
-       "loginsuccesstitle": "אריינלאגירט מיט הצלחה",
+       "loginsuccesstitle": "אריינלאגירט",
        "loginsuccess": "'''איר זענט אַצינד אַרײַנלאָגירט אין {{SITENAME}} ווי \"$1\".'''",
        "nosuchuser": "נישטא קיין באניצער מיטן נאמען  \"$1\".\n\nקוקט איבער אײַער אויסלייג, אדער [[Special:UserLogin/signup|שאַפֿט א נײַע קאנטע]].",
        "nosuchusershort": "נישטאָ קיין באַניצער מיטן נאָמען \"$1\".\nביטע באַשטעטיקט דעם אויסלייג.",
        "createaccount-title": "קאנטע באשאפֿן אין {{SITENAME}}",
        "createaccount-text": "עמעצער האט באשאפֿן א קאנטע פֿאר אייער ע-פאסט אדרעס אין {{SITENAME}} ($4) מיטן נאמען \"$2\" און  פאסווארט \"$3\". איר דארפט אצינד איינלאגירן און ענדערן דאס פאסווארט.\n\nאיר קענט איגנארירן די מעלדונג, ווען די קאנטע איז באשאפֿן בטעות.",
        "login-throttled": "איר האט געפרוווט צופֿיל מאל אריינלאגירן.\nזייט אזוי גוט און וואַרט $1 איידער איר פרוווט נאכאמאל.",
-       "login-abort-generic": "×\90ײַער ×\90רײַנ×\9c×\90×\92×\99ר×\95× ×\92 ×\90×\99×\96 × ×\99ש×\98 ×\92×¢×\95×\95×¢×\9f ×\93ערפֿ×\90×\9c×\92ר×\99×\99×\9a—אָפגעשטעלט",
+       "login-abort-generic": "×\90ײַער ×\90רײַנ×\9c×\90×\92×\99ר×\95× ×\92 ×\90×\99×\96 ×\93×\95ר×\9b×\92עפ×\90×\9c×\9f—אָפגעשטעלט",
        "login-migrated-generic": "אײַער קאנטע איז געווארן מיגרירט, און אײַער באניצער־נאמען איז מער נישט פאראן אויף דער וויקי.",
        "loginlanguagelabel": "שפראך: $1",
        "suspicious-userlogout": " אײַער בקשה אַרויסלאָגירן זיך איז אפגעלייגט געווארן ווייַל אייגנטלעך איז זי געשיקט דורך אַ צעבראכענעם בלעטערער אָדער א פראקסי מיט א זאפאס.",
        "createacct-another-realname-tip": "עכטער נאמען איז אפציאנאל.\nאויב איר וויילט אויס צוצושטעלן אים, וועט דאס גענוצט ווערן צו געבן אטריבוציע פאר זייער ארבעט.",
-       "pt-login": "אַרײַנלאגירן",
+       "pt-login": "אריינלאגירן",
        "pt-login-button": "אַרײַנלאָגירן",
        "pt-createaccount": "שאַפֿן אַ קאנטע",
        "pt-userlogout": "אַרויסלאָגירן",
        "newpassword": "ניי פּאסוואָרט:",
        "retypenew": "ווידער שרײַבן פאַסווארט:",
        "resetpass_submit": "שטעלן פאסווארט און אריינלאגירן",
-       "changepassword-success": "אייער פאַסווארט איז געטוישט געווארן מיט דערפֿאלג!",
+       "changepassword-success": "אייער פאַסווארט איז געטוישט געווארן!",
        "changepassword-throttled": "איר האט געפרוווט צופֿיל מאל אריינלאגירן.\nזייט אזוי גוט און וואַרט $1 איידער איר פרוווט נאכאמאל.",
+       "botpasswords": "באט פאסווערטער",
+       "botpasswords-label-appid": "באט נאמען:",
        "botpasswords-label-create": "שאַפֿן",
        "botpasswords-label-update": "דערהײַנטיקן",
        "botpasswords-label-cancel": "אַנולירן",
        "botpasswords-label-delete": "אויסמעקן",
        "botpasswords-label-resetpassword": "ווידערשטעלן פאַסווארט",
+       "botpasswords-label-grants-column": "נאכגעגעבן",
+       "botpasswords-created-title": "באט פאסווארט געשאפן",
+       "botpasswords-created-body": "דאס באט פאסווארט פאר באט־נאמען \"$1\" פון באניצער \"$2\" איז געשאפן געווארן.",
+       "botpasswords-updated-title": "באט פאסווארט דערהיינטיקט",
+       "botpasswords-updated-body": "דאס באט פאסווארט פאר באט־נאמען \"$1\" פון באניצער \"$2\" איז דערהיינטיקט געווארן.",
+       "botpasswords-deleted-title": "באט פאסווארט אויסגעמעקט",
        "resetpass_forbidden": "פאסווערטער קענען נישט ווערן געטוישט",
        "resetpass-no-info": "איר דארפֿט זיין אריינלאגירט צוצוקומען גלייך צו דעם דאזיגן בלאט.",
        "resetpass-submit-loggedin": "טוישן פאסווארט",
        "resetpass-submit-cancel": "אַנולירן",
-       "resetpass-wrong-oldpass": "×\90×\95×\9e×\92×\99×\9c×\98×\99×\92 ×¦×²Ö·×\98×\95×\95ײַ×\9c×\99ק ×\90×\93ער ×\9c×\95×\99פֿ×\99ק ×¤×\90ַס×\95×\95×\90ר×\98.\n×\90×\99ר ×\94×\90×\98 ×\9e×¢×\92×\9c×¢×\9a ×©×\95×\99×\9f ×\92×¢×\98×\95×\99ש×\98 ×\90×\99×\99ער ×¤×\90ַס×\95×\95×\90ר×\98 ×\9e×\99×\98 ×\94צ×\9c×\97×\94 ×\90×\93ער ×\92×¢×\91×¢×\98×\9f ×\90 × ×²Ö·  ×¦×²Ö·×\98×\95×\95ײַ×\9c×\99ק ×¤×\90ַס×\95×\95×\90ר×\98.",
+       "resetpass-wrong-oldpass": "אומגילטיג צײַטווײַליק אדער לויפֿיק פאַסווארט.\nאיר האט מעגלעך שוין געטוישט אייער פאַסווארט אדער געבעטן א נײַ  צײַטווײַליק פאַסווארט.",
        "resetpass-recycled": "זײַט אזוי גוט שטעטל אירע פאסווארט צו עפעס אנדערש פונעם לויפיקן פאסווארט.",
        "resetpass-temp-emailed": "איר האט זיך ארי לאגירת מיט א פראוויזארישן קאד געשיקט דורכן ע־פאסט. כדי שליסן דאס ארײַנלאגירן, דארט איר שטעלן א נײַ פאסווארט דא.",
        "resetpass-temp-password": "צײַטווייליק פאַסווארט:",
        "passwordreset-emailtext-user": "באניצער $1 אויף  {{SITENAME}} האט געבעטן צוריקצושטעלן אייער פאסווארט פאר {{SITENAME}} ($4).\nדי פאלגנדע באניצער {{PLURAL:$3|קאנטע איז|קאנטעס זענען}} פארבונדן מיט דעם ע־פאסט אדרעס:\n\n$2\n\n{{PLURAL:$3|דאס פראוויזארישע פאסווארט|די פראוויזארישע פאסווערטער}} וועלן אויסגיין נאך {{PLURAL:$5|איין טאג|$5 טעג}}.\nאיר זאלט אריינלאגירן און קלויבן א נייע פאסווארט אצינד. טאמער א צווייטער האט געשיקט די בקשה,\nאדער ווען איר געדענקט יא אייער פריעריקע פאסווארט, און וויל עס נישט ענדערן,\n קענט איר איגנארירן דעם אנזאג און ניצן ווייטער דאס אלטע פאסווארט.",
        "passwordreset-emailelement": "באַניצער נאָמען: \n$1\n\nפראוויזארישער פּאַראָל: \n$2",
        "passwordreset-emailsentemail": "טאמער איז דער ע־פאסט אדרעס פארקניפט מיט אייער קאנטע, וועט מען שיקן א פאסווארט צוריקשטעלן ע-פּאָסט.",
+       "passwordreset-emailsentusername": "טאמער איז פאראן אן ע־פאסט אדרעס פארקניפט מיט דעם באניצער־נאמען, וועט מען שיקן א פאסווארט צוריקשטעלן ע-פּאָסט.",
        "passwordreset-emailsent-capture": "מען האט געשיקט א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן.",
        "passwordreset-emailerror-capture": "מען האט געשאפן א פאסווארט צוריקשטעלן בליצבריוו, וואס ווערט געוויזן אונטן, אבער שיקן צום {{GENDER:$2|באניצער}}איז דורכגעפאלן: $1",
        "changeemail": "ענדערן אדער אראפנעמען ע-פּאָסט אַדרעס",
        "minoredit": "דאס איז א מינערדיגע ענדערונג",
        "watchthis": "טוט אױפֿפּאַסן דעם בלאט",
        "savearticle": "אויפהיטן בלאַט",
+       "publishpage": "פובליצירן בלאט",
        "preview": "פֿאראויסקוק",
        "showpreview": "ווייזן פאָרױסקוק",
        "showdiff": "ווײַז די ענדערונגען",
        "userpage-userdoesnotexist": "באניצער קאנטע \"$1\" איז נישט אײַנגעשריבן.\nקוקט איבער צי איר ווילט שאפֿן/רעדאקטירן דעם בלאט.",
        "userpage-userdoesnotexist-view": "באניצער קאנטע \"$1\" איז נישט איינגעשריבן.",
        "blocked-notice-logextract": "דער באַניצער איז דערווייַל פֿאַרשפאַרט.\nדי לעצטע בלאָקירן לאג אַקציע איז צוגעשטעלט אונטן:",
-       "clearyourcache": "'''אַכטונג:''' נאכן אויפֿהיטן, ברויכט איר אפשר נאך אַריבערגיין דעם בלעטערערס זאַפאַס צו זען די ענדערונגען.\n\n* '''פֿייערפוקס/סאפֿארי:''' האלט אראפ ''שיפֿט'' בשעתן דרוקן ''Reload'', אדער דרוקט ''Ctrl-F5'' אדער ''Ctrl-R'' (אויף א מאקינטאש ''⌘-R'')\n\n* '''גוגל כראם:''' דרוקט ''Ctrl-Shift-R'' (אויף א מאקינטאש ''⌘-Shift-R'')\n\n* '''אינטערנעט עקספלארער:''' האלט אראפ ''Ctrl'' בשעתן קליקן ''Refresh'', אדער  דרוקט ''Ctrl-F5''\n\n* '''אפערע:''' ליידיגט אויס דעם זאַפאַס אין ''Tools → Preferences'' (''העדפות'' > ''כלים'')",
+       "clearyourcache": "'''אַכטונג:''' נאכן אויפֿהיטן, ברויכט איר אפשר נאך אַריבערגיין דעם בלעטערערס זאַפאַס צו זען די ענדערונגען.\n\n* '''פֿייערפוקס/סאפֿארי:''' האלט אראפ ''שיפֿט'' בשעתן דרוקן ''Reload'', אדער דרוקט ''Ctrl-F5'' אדער ''Ctrl-R'' (אויף א מאקינטאש ''⌘-R'')\n\n* '''גוגל כראם:''' דרוקט ''Ctrl-Shift-R'' (אויף א מאקינטאש ''⌘-Shift-R'')\n\n* '''אינטערנעט עקספלארער:''' האלט אראפ ''Ctrl'' בשעתן קליקן ''Refresh'', אדער  דרוקט ''Ctrl-F5''\n\n* <strong>אפערע:</strong> גייט צו <em>מעניו → שטעלונגען</em> (<em> אפערע → פרעפערנצן</em> אויף א מעק) און דערנאך צו <em>פריוואטקייט & און זיכערהייט → אראפראמען בלעטערן דאטן → בילדער און טעקעס אין זאפאס</em>",
        "usercssyoucanpreview": "'''טיפ:''' נוצט דאס {{int:showpreview}} קנעפל אויספרובירן אייער CSS בעפארן אויפהיטן.",
        "userjsyoucanpreview": "'''טיפ:''' נוצט דאס {{int:showpreview}} קנעפל אויספרובירן אייער  JavaScript בעפארן אויפהיטן.",
        "usercsspreview": "'''געדענקט אז איר טוט בלויז פאראויס זען אייער באניצער CSS.'''\n'''ער איז דערווייל נאכנישט אויפֿגעהיטן!'''",
        "revdelete-unsuppress": "טוה אפ באגרענעצונגן אין גענדערטע רעוויזיעס",
        "revdelete-log": "אורזאַך:",
        "revdelete-submit": "צושטעלן צו {{PLURAL:$1|סעלעקטירטער רעוויזיע| סעלעקטירטע רעוויזיעס}}",
-       "revdelete-success": "'''רע×\95×\95×\99×\96×\99×¢ ×\96×¢×\91×\90ַרק×\99×\99×\98 ×\93ערפֿ×\90×\9c×\92ר×\99×\99×\9a ×\93ער×\94ײַנ×\98×\99ק×\98.'''",
+       "revdelete-success": "'''רעוויזיע זעבאַרקייט דערהײַנטיקט.'''",
        "revdelete-failure": "'''נישט מעגלעך צו דערהײַנטיקן רעוויזיע זעבאַרקייט:'''\n$1",
-       "logdelete-success": "'''לאג באהאלטן איז סוקסעספול איינגעשטעלט.'''",
+       "logdelete-success": "'''לאגבוך זעבארקייט איינגעשטעלט.'''",
        "logdelete-failure": "'''נישט מעגלעך צו שטעלן לאג זעבאַרקייט:'''\n$1",
        "revdel-restore": "טויש די זעבארקייט",
        "pagehist": "בלאט היסטאריע",
        "userrights-changeable-col": "גרופעס איר קענט ענדערן",
        "userrights-unchangeable-col": "גרופעס איר קענט נישט ענדערן",
        "userrights-conflict": "קאנפֿליקט פון באניצער־רעכטן ענדערונגען! זייט אזוי גוט רעצענזירן און באשטעטיקן אײַערע ענדערונגען.",
-       "userrights-removed-self": "×\90×\99ר ×\94×\90×\98 ×\93ערפ×\90×\9c×\92ר×\99×\99×\9a ×\90ר×\90פ×\92×¢× ×\95×\9e×¢×\9f ×\90×\99×\99ערע ×\90×\99×\99×\92×¢× ×¢ ×¨×¢×\9b×\98×¢. אזוי קענט איר מער נישט דערגרייכן דעם בלאט.",
+       "userrights-removed-self": "×\90×\99ר ×\94×\90×\98 ×\90ר×\90פ×\92×¢× ×\95×\9e×¢×\9f ×\90×\99×\99ערע ×\90×\99×\99×\92×¢× ×¢ ×¨×¢×\9b×\98×\9f. אזוי קענט איר מער נישט דערגרייכן דעם בלאט.",
        "group": "גרופע:",
        "group-user": "באניצערס",
        "group-autoconfirmed": "באַשטעטיקטע באַניצער",
        "grant-group-file-interaction": "אינטעראגירן מיט מעדיע",
        "grant-group-email": "שיקן ע־פאסט",
        "grant-createaccount": "שאַפֿן קאנטעס",
+       "grant-uploadfile": "אַרויפֿלאָדן נייע טעקעס",
+       "grant-basic": "בעיסיק רעכטן",
+       "grant-viewmywatchlist": "קוקט אייער אויפפאסונג ליסטע",
        "newuserlogpage": "נייע באַניצערס לאָג-בוך",
        "newuserlogpagetext": "דאס איז א לאג פון באַניצערס אײַנשרײַבונגען.",
        "rightslog": "באַניצער רעכטן לאג",
        "imagelinks": "טעקע באַניץ",
        "linkstoimage": "{{PLURAL:$1|דער פאלגנדער בלאט ניצט|די פאלגנדע בלעטער ניצן}} דאס דאזיגע בילד:",
        "linkstoimage-more": "מער ווי $1 {{PLURAL:$1|בלאַט פֿאַרבינדט|בלעטער פֿאַרבינדן}} צו דער דאזיגער טעקע.\nדי פֿאלגנדע ליסטע ווײַזט  {{PLURAL:$1|דעם ערשטן בלאַט לינק|די ערשטע $1 בלאַט לינקען}} צו דער טעקע.\nס'איז פֿאַראַן[[Special:WhatLinksHere/$2|פֿולע רשימה]].",
-       "nolinkstoimage": "× ×\99ש×\98×\90 ×§×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס × ×\99צ×\9f ×\93×\90ס ×\93×\90×\96×\99×\92×¢ ×\91×\99×\9c×\93.",
+       "nolinkstoimage": "× ×\99ש×\98×\90 ×§×\99×\99×\9f ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×\9f ×¦×\95 ×\93×\99 ×\98עקע.",
        "morelinkstoimage": "באַקוקן  [[Special:WhatLinksHere/$1|מער לינקען]] צו דער טעקע.",
        "linkstoimage-redirect": "$1 (טעקע ווײַטערפֿירונג) $2",
        "duplicatesoffile": "די פֿאלגנדע {{PLURAL:$1|טעקע דופליקירט|$1 טעקעס דופליקירן}} די דאזיגע טעקע ([[Special:FileDuplicateSearch/$2|נאך פרטים]]):",
        "unusedtemplates": "נישט באניצטע מוסטערן",
        "unusedtemplatestext": "דער בלאט ווײַזט אלע בלעטער אינעם {{ns:template}} נאמענטייל וואס זענען נישט אײַנגעשלאסן אין אן אנדער בלאט. געדענקט צו באקוקן אנדערע בלעטער פאר לינקען צו די מוסטערן איידער איר מעקט זיי אויס.",
        "unusedtemplateswlh": "אנדערע פֿאַרבינדונגען",
-       "randompage": "צ×\95פֿע×\9c×\99×\92ער ×\90ַר×\98×\99ק×\9c",
+       "randompage": "צ×\95פֿע×\9c×\99×\92ער ×\91×\9c×\90×\98",
        "randompage-nopages": "נישטא קיין בלעטער אין {{PLURAL:$2|דעם פאלגנדן נאמענטייל |די פאלגנדע נאמענטיילן}} \"$1\".",
        "randomincategory": "צופעליקער בלאט אין קאטעגאריע",
        "randomincategory-invalidcategory": "\"$1\" איז נישט קיין גילטיקער קאטעגאריע נאמען.",
        "querypage-disabled": "דער באַזונדער־בלאַט איז אומאַקטיווירט צוליב אויספֿירונג סיבות.",
        "apihelp": "API־הילף",
        "apihelp-no-such-module": "מאָדול \"$1\" נישט געפונען.",
+       "apisandbox-unfullscreen": "ווייזן בלאט",
        "apisandbox-reset": "רייניקן",
+       "apisandbox-retry": "פרובירן נאכאמאל",
+       "apisandbox-helpurls": "הילף לינקען",
+       "apisandbox-examples": "ביישפילן",
        "apisandbox-results": "רעזולטאטן",
        "booksources": "דרויסנדיגע ליטעראַטור ISBN",
        "booksources-search-legend": "זוכן פאר דרויסנדע ביכער מקורות",
        "logempty": "נישטא קיין פאַסנדיקע זאכן אין לאג.",
        "log-title-wildcard": "זוכן טיטלען וואס הייבן אָן מיט דעם טעקסט",
        "showhideselectedlogentries": "ווײַזן/באַהאַלטן געקליבענע לאגבוך אקציעס",
+       "checkbox-all": "אַלע",
+       "checkbox-none": "קיינע",
+       "checkbox-invert": "אומקערן",
        "allpages": "אַלע בלעטער",
        "nextpage": "קומענדיקער בלאַט ($1)",
        "prevpage": "פֿריִערדיקער בלאַט ($1)",
        "listgrouprights-namespaceprotection-header": "נאמענטייל באשרענקונגען",
        "listgrouprights-namespaceprotection-namespace": "נאָמענטייל",
        "listgrouprights-namespaceprotection-restrictedto": "רעכט(ן) וואס דערלויבט באניצער צו רעדאקטירן",
+       "listgrants-grant": "צולאזן",
+       "listgrants-rights": "רעכטן",
        "trackingcategories": "אויפפאסן־קאטעגאריעס",
        "trackingcategories-msg": "אויפפאסן־קאטעגאריע",
        "trackingcategories-name": "מעלדונג נאמען",
        "wlnote": "אונטן {{PLURAL:$1|איז די לעצטע ענדערונג|זענען די לעצטע <strong>$1</strong> ענדערונגען}} אין {{PLURAL:$2|דער לעצטער שעה|די לעצטע <strong>$2</strong> שעה'ן}} ביז $3, $4.",
        "wlshowlast": "ווײַזן די לעצטע $1 שעה'ן  $2 טעג",
        "watchlist-hide": "באַהאַלטן",
+       "watchlist-submit": "ווײַזן",
        "wlshowtime": "צייט־פעריאד צו ווייזן:",
        "wlshowhideminor": "מינערדיקער רעדאקטירונגען",
        "wlshowhidebots": "באטן",
        "wlshowhideanons": "אַנאָנימע באַניצער",
        "wlshowhidepatr": "פאטראלירטע רעדאקטירונגען",
        "wlshowhidemine": "מיינע רעקאקטירונגען",
+       "wlshowhidecategorization": "בלאט קאטעגאריזירונג",
        "watchlist-options": "אויפֿפאַסן ליסטע ברירות",
        "watching": "אויפפאסענדונג…",
        "unwatching": "נעמט אראפ פון אויפפאסונג ליסטע…",
        "delete-toobig": "דער בלאַט האט א גרויסע רעדאקטירונג היסטאריע, מער ווי $1 {{PLURAL:$1|רעוויזיע|רעוויזיעס}}. אויסמעקן אזעלכע בלעטער איז באַגרענעצט געווארן בכדי צו פֿאַרמײַדן א צופֿעליגע פֿאַרשטערונג פֿון  {{SITENAME}}.",
        "delete-warning-toobig": "דער בלאַט האט א גרויסע רעדאקטירונג היסטאריע, מער ווי $1 {{PLURAL:$1|רעוויזיע|רעוויזיעס}}. אויסמעקן אים קען פֿאַרשטערן דאַטנבאַזע אפעראַציעס פֿון {{SITENAME}}; זײַט פֿארזיכטיג איידער איר מעקט אויס.",
        "deleteprotected": "איר קענט נישט אויסמעקן דעם בלאט ווײַל ער איז געשיצט.",
-       "deleting-backlinks-warning": "'''ווארענוג:'''\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|אנדערע בלעטער]]  פארבינדן צום בלאט אדער אריבערשליסן דעם בלאט איר האלט ביי אויסמעקן.",
+       "deleting-backlinks-warning": "</strong>ווארענוג:<strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|אנדערע בלעטער]]  פארבינדן צום בלאט אדער אריבערשליסן דעם בלאט איר האלט ביי אויסמעקן.",
        "rollback": "צוריקדרייען רעדאַקטירונגען",
        "rollbacklink": "צוריקדרייען",
        "rollbacklinkcount": "צוריקדרייען $1 {{PLURAL:$1|רעדאקטירונג|רעדאקטירונגען}}",
        "rollbackfailed": "צוריקדרייען דורכגעפֿאַלן",
        "cantrollback": "מען קען נישט צוריקדרייען די ענדערונג – דער לעצטער בײַשטייערער איז דער איינציגסטער שרײַבער אין דעם בלאַט.",
        "alreadyrolled": "מען קען נישט צוריקדרייען די לעצטע ענדערונג פון בלאט [[:$1]] פֿון\n[[User:$2|$2]] ([[User talk:$2|רעדן]]{{int:pipe-separator}} [[Special:Contributions/$2|{{int:contribslink}}]]);\nאן אנדערער האט שוין געענדערט אדער צוריקגעדרייט דעם בלאט.\n\nדי לעצטע ענדערונג צום בלאַט איז געווען פון [[User:$3|$3]] ([[User talk:$3|רעדן]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
-       "editcomment": "קורץ ווארט איז געווען: \"'''$1'''\".",
+       "editcomment": "קורץ ווארט איז געווען: <em>$1</em>.",
        "revertpage": "רעדאַקטירונגען פֿון  [[Special:Contributions/$2|$2]] צוריקגענומען ([[User talk:$2|רעדן]])  צו דער לעצטער ווערסיע פֿון [[User:$1|$1]]",
        "revertpage-nouser": "צוריקגעשטעלט רעדאַקטירונגען פֿון א באהאלטענעם באַניצער צו לעצטער רעוויזיע פֿון {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "צוריקגעדרייט רעדאַקטירונגען פֿון $1 צו דער לעצטע ווערסיע פֿון $2",
        "changecontentmodel-title-label": "בלאט־טיטל",
        "changecontentmodel-model-label": "נייער אינהאלט מאדעל",
        "changecontentmodel-reason-label": "אורזאַך:",
+       "changecontentmodel-submit": "טוישן",
        "logentry-contentmodel-change-revertlink": "צוריקשטעלן",
        "logentry-contentmodel-change-revert": "צוריקשטעלן",
        "protectlogpage": "באשיצונג לאָג-בוך",
        "ipb-unblock": "אויפֿבלאקירן א באַניצער נאמען אדער IP אדרעס",
        "ipb-blocklist": "זעט עקזיסטירנדע בלאקירונגען",
        "ipb-blocklist-contribs": "בײַשטײַערונגען פֿון {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "נאך $1",
        "unblockip": "אויפֿבלאקירן באניצער",
        "unblockiptext": "מיט דעם פארמולאר קענט איר צוריקשטעלן שרייבן ערלויבניש צו אן IP אדרעס אדער באניצער נאמען וואס איז געווען בלאקירט.",
        "ipusubmit": "אוועקנעמען דעם בלאק",
        "blocklink": "ארויסטרייבן",
        "unblocklink": "באַפֿרײַען",
        "change-blocklink": "ענדערן בלאק",
-       "contribslink": "×\91×\90Ö·× ×\99צערס ×\91ײַש×\98ײַער×\95× ×\92×¢×\9f",
+       "contribslink": "בײַשטײַערונגען",
        "emaillink": "שיקן ע־פאסט",
        "autoblocker": "אויטאמאטיש בלאקירט ווייל אײַער IP אדרעס איז לעצטנס געניצט געווארן דורך [[User:$1|$1]]. \nדער סיבה וואס איז אנגעבען געווארן איז: \"$2\".",
        "blocklogpage": "בלאקירן לאג",
        "import-nonewrevisions": "קיין רעוויזיעס נישט אימפארטירט (אדער אלע שוין דא, אדער איבערגעהיפט צוליב גרײַזן).",
        "xml-error-string": "$1 בײַ שורה $2, זייל $3 (בייט $4): $5",
        "import-upload": "אַרויפֿלאָדן XML דאַטן",
-       "import-token-mismatch": "×\90ָנ×\95×\95ער ×¤×\95×\9f ×¡×¢×¡×\99×¢ ×\93×\90Ö·×\98×\9f.\n ×\91×\99×\98×¢ ×¤×¨×\95×\91×\99ר×\98 × ×\90×\9b×\90×\9e×\90×\9c.",
+       "import-token-mismatch": "פ×\90ר×\9c×\95ס×\98 ×¤×\95×\9f ×¡×¢×¡×\99×¢ ×\93×\90×\98×\9f. \n\nקע×\9f ×\96×\99×\99×\9f ×\90×\96 ×\90×\99ר ×\96×¢× ×¢×\9f ×\92×¢×\95×\95×\90ר×\9f ×\90ר×\95×\99ס×\9c×\90×\92×\99ר×\98\n<strong>×\91×\99×\98×¢ ×¤×¨×\95×\91×\99ר×\98 × ×\90×\9b×\90×\9e×\90×\9c</strong>. \n\n×\90×\95×\99×\91 ×¡'×\90ר×\91×¢×\98 × ×\90×\9a ×\90×\9cס × ×\99×\98, ×¤×¨×\95×\91×\99ר×\98 [[Special:UserLogout|×\90ר×\95×\99ס×\9c×\90×\92×\99ר×\9f]] ×\90×\95×\9f ×\96×\99×\9a ×¦×\95ר×\99ק ×\90ר×\99×\99× ×\9c×\90×\92×\99ר×\9f.",
        "import-invalid-interwiki": "נישט מעגלעך צו אימפארטירן פון ספעציפֿירטער וויקי.",
        "import-error-edit": "דעם בלאט \"$1\" קען מען נישט אימפארטירן ווייל איר האט נישט די רעכט אים צו רעדאקטירן.",
        "import-error-create": "דעם  בלאט \"$1\" האט מען נישט אימפארטירט ווייל איר האט נישט די רעכט צו שאפן אים.",
        "tooltip-pt-mycontris": "ליסטע פון {{GENDER:|אייערע}} ביישטייערונגען",
        "tooltip-pt-login": "עס איז רעקאָמענדירט זיך אײַנשרײַבן; ס'איז אבער נישט קיין פֿליכט",
        "tooltip-pt-logout": "ארויסלאגירן",
-       "tooltip-pt-createaccount": "איר ווערט דערמוטיגט צו שאפן א קאנטע און אריינלאגירן; ס'איז אביר נישט אבליגאטאריש",
+       "tooltip-pt-createaccount": "איר ווערט דערמוטיגט צו שאַפֿן א קאנטע און אריינלאגירן; ס׳איז אבער נישט פארפליכטעט",
        "tooltip-ca-talk": "שמועס וועגן דעם אינהאַלט בלאַט",
-       "tooltip-ca-edit": "רעדאקטירן דעם בלאַט",
+       "tooltip-ca-edit": "רעדאַקטירן דעם בלאַט",
        "tooltip-ca-addsection": "אָנהייבן א נײַע אָפטיילונג",
        "tooltip-ca-viewsource": "דאס איז א פֿארשלאסענער בלאט, איר קענט נאר באַקוקן זיין מקור",
        "tooltip-ca-history": "פריערדיגע ווערסיעס פון דעם בלאט.",
        "tooltip-ca-move": "באַוועגן דעם בלאַט",
        "tooltip-ca-watch": "לייגט צו דעם בלאט אויפצופאסן",
        "tooltip-ca-unwatch": "נעמט אַראָפּ דעם בלאַט פון נאָכפאָלג־ליסטע",
-       "tooltip-search": "×\96×\95×\9b×\98 ×\90×\99× ×¢×\9d ×¡×\99×\99×\98",
+       "tooltip-search": "×\96×\95×\9a {{SITENAME}}",
        "tooltip-search-go": "גייט צו א בלאט מיט אט דעם נאמען, אויב ער עקסיסטירט",
        "tooltip-search-fulltext": "זוכט דעם טעקסט אין די בלעטער",
-       "tooltip-p-logo": "×\94×\95×\99פ×\98 ×\96×\99×\99ט",
+       "tooltip-p-logo": "×\91×\90Ö·×\96×\95×\9b×\9f ×\93×¢×\9d ×\94×\95×\99פ×\98 ×\91×\9c×\90Ö·ט",
        "tooltip-n-mainpage": "באַזוכט דעם הויפּט־זײַט",
        "tooltip-n-mainpage-description": "באַזוכן דעם הויפט בלאַט",
        "tooltip-n-portal": "גייט אריין אין די געמיינדע צו שמועסן",
        "tooltip-n-currentevents": "מער אינפארמאציע איבער אקטועלע געשענישען",
-       "tooltip-n-recentchanges": "ליסטע פון לעצטע ענדערונגען",
+       "tooltip-n-recentchanges": "ליסטע פון לעצטע ענדערונגען אין דעם וויקי",
        "tooltip-n-randompage": "וועלט אויס א צופעליגער בלאט",
-       "tooltip-n-help": "×\94×\99×\9c×£",
-       "tooltip-t-whatlinkshere": "×\90×\9c×¢ ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×¢×\9f ×¦×\95 ×\93×¢×\9d ×\91×\9c×\90×\98",
+       "tooltip-n-help": "×\93ער ×¤×\9c×\90×¥ ×\90×\95×\99סצ×\95×\92עפ×\99× ×¢×\9f",
+       "tooltip-t-whatlinkshere": "×\90×\9c×¢ ×\9c×\99ס×\98×¢ ×¤×\95×\9f ×\90×\9c×¢ ×\91×\9c×¢×\98ער ×\95×\95×\90ס ×¤×\90ר×\91×\99× ×\93×\98 ×\90×\94ער",
        "tooltip-t-recentchangeslinked": "אלע ענדערונגען פון בלעטער וואס זענען אהער פארבינדען",
        "tooltip-feed-rss": "דערהײַנטיגט אויטאמאטיש פון אר.עס.עס. RSS",
        "tooltip-feed-atom": "לייג צו אן אטאמאטישער אפדעיט דורך אטאם Atom",
        "lastmodifiedatby": "די לעצטע ענדערונג פֿון דעם בלאַט איז געווען $2, $1 דורך $3.",
        "othercontribs": "באזירט אויף ארבעט פון $1.",
        "others": "אנדערע",
-       "siteusers": "{{PLURAL:$2|באַניצער| באַניצערס}} {{SITENAME}} $1",
+       "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1| באַניצער}}| באַניצערס}} $1",
        "anonusers": "{{SITENAME}} {{PLURAL:$2| אַנאנימער באַניצער|אַנאנימע באַניצער}} $1",
        "creditspage": "בלאט קרעדיטס",
        "nocredits": "נישט פאראן קיין אינפארמאציע פאר דעם בלאט.",
        "scarytranscludedisabled": "[אינטערוויקי אריבערשליסן איז אַנולירט]",
        "scarytranscludetoolong": "[URL צו לאנג]",
        "deletedwhileediting": "ווארענונג: דער בלאט איז געווארן אויסגעמעקט נאכדעם וואס איר האט אנגעהויבן רעדאקטירן!",
-       "confirmrecreate": "באַניצער [[User:$1|$1]] ([[User talk:$1|רעדן]]) האט אויסגעמעקט דעם בלאט נאכדעם וואס איר האט אנגעהויבן דאס צו ענדערן, אלס אָנגעבליכער סיבה:\n:'''$2'''\nביטע באשטעטיגט אז איר ווילט טאקע צוריקשטעלן דעם בלאט.",
+       "confirmrecreate": "באַניצער [[User:$1|$1]] ([[User talk:$1|רעדן]]) {{GENDER:$1|האט אויסגעמעקט}} דעם בלאט נאכדעם וואס איר האט אנגעהויבן דאס צו ענדערן, אלס אָנגעבליכער סיבה:\n: <em>$2</em>\nביטע באשטעטיגט אז איר ווילט טאקע צוריקשטעלן דעם בלאט.",
        "recreate": "שאַפֿן פֿונדאסניי",
        "confirm_purge_button": "אויספֿירן",
        "confirm-purge-top": "אויסקלארן די קאשעי פון דעם בלאט?",
        "fileduplicatesearch-submit": "זוכן",
        "fileduplicatesearch-info": "$1 × $2 פיקסעל<br />טעקע גרייס: $3<br /> טיפ MIME: $4",
        "fileduplicatesearch-noresults": "קיין טעקע מיטן נאמען \"$1\" נישט געטראפֿן.",
-       "specialpages": "ספּעציעלע זײַטן",
+       "specialpages": "ספעציעלע בלעטער",
        "specialpages-note-top": "לעגענדע",
        "specialpages-note": "* נארמאַלע באַזונדערע בלעטער.\n* <span class=\"mw-specialpagerestricted\">באַגרענעצטע באַזונדערע בלעטער.</span>",
        "specialpages-group-maintenance": "אויפֿהאַלטונג באַריכטן",
        "logentry-newusers-create2": "באניצער קאנטע $1 איז {{GENDER:$2|געשאפן געווארן}} דורך $3",
        "logentry-newusers-byemail": "באניצער קאנטע $3 איז {{GENDER:$2|געשאפן געווארן}} דורך $1 און דאס פאסווארט איז געשיקט געווארט דורך ע־פאסט",
        "logentry-newusers-autocreate": "באַניצער קאנטע $1 {{GENDER:$2|געשאפן}} אויטאמאטיש",
-       "logentry-rights-rights": "$1 האָט {{GENDER:$2|געביטן}} גרופּע מיטגלידערשאַפט פאַר $3 פון $4 אויף $5",
+       "logentry-rights-rights": "$1 האָט {{GENDER:$2|געביטן}} גרופּע מיטגלידערשאַפט פאַר {{GENDER:$6|$3}} פון $4 אויף $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|האט געביטן}} גרופע מיטגלידערשאפט פאר $3",
        "logentry-rights-autopromote": "$1 אויטאמאטיש  {{GENDER:$2|פראמאווירט}} פון $4 צו $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|האט ארויפגעלאדן}} $3",
        "feedback-subject": "טעמע:",
        "feedback-submit": "אײַנגעבן",
        "feedback-thanks": "ייש\"כ! אײַער פֿידבעק איז געווארן ארויפגעלעגט צום בלאט \"[$2 $1]\".",
-       "searchsuggest-search": "×\96×\95×\9b×\9f",
+       "searchsuggest-search": "×\96×\95×\9a",
        "searchsuggest-containing": "כולל…",
        "api-error-badaccess-groups": "איר האט נישט קיין רעכטן אַרויפֿלאָדן טעקעס אויף דער וויקי.",
        "api-error-badtoken": "אינערלעכער גרײַז: סימן טויג נישט.",
index 68b3945..f261a47 100644 (file)
        "whatlinkshere-prev": "{{PLURAL:$1|前|前$1个}}",
        "whatlinkshere-next": "{{PLURAL:$1|后|后$1个}}",
        "whatlinkshere-links": "←链接",
-       "whatlinkshere-hideredirs": "$1重定向",
-       "whatlinkshere-hidetrans": "$1嵌入",
-       "whatlinkshere-hidelinks": "$1链接",
-       "whatlinkshere-hideimages": "$1文件链接",
+       "whatlinkshere-hideredirs": "隐藏重定向",
+       "whatlinkshere-hidetrans": "隐藏嵌入",
+       "whatlinkshere-hidelinks": "隐藏链接",
+       "whatlinkshere-hideimages": "隐藏文件链接",
        "whatlinkshere-filters": "过滤器",
        "whatlinkshere-submit": "提交",
        "autoblockid": "自动封禁#$1",
index 55b554f..2ec731c 100644 (file)
@@ -70,7 +70,8 @@
                        "Bowleerin",
                        "飞舞回堂前",
                        "Bbslam",
-                       "Zerng07"
+                       "Zerng07",
+                       "Reke"
                ]
        },
        "tog-underline": "底線標示連結:",
        "minoredit": "這是一個小修訂",
        "watchthis": "監視此頁面",
        "savearticle": "儲存頁面",
+       "publishpage": "發布頁面",
        "preview": "預覽",
        "showpreview": "顯示預覽",
        "showdiff": "顯示變更",
        "changecontentmodel-success-text": "已變更 [[:$1]] 的內容類型。",
        "changecontentmodel-cannot-convert": "[[:$1]] 的內容無法轉換為 $2 類型。",
        "changecontentmodel-nodirectediting": "$1 的內容模型不支援直接編輯",
+       "changecontentmodel-emptymodels-title": "沒有內容模型可用",
+       "changecontentmodel-emptymodels-text": "[[:$1]]上的內容不能轉換為任何類型。",
        "log-name-contentmodel": "內容模型變更日誌",
        "log-description-contentmodel": "與頁面內容模型相關的事件",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|已使用}}非預設的內容模型 \"$5\" 建立頁面 $3",
        "lockdbsuccesstext": "已鎖定資料庫。<br />\n請記得在維護完成後 [[Special:UnlockDB|解除鎖定]] 資料庫。",
        "unlockdbsuccesstext": "已解除鎖定資料庫。",
        "lockfilenotwritable": "沒有權限寫入資料庫鎖定檔案。\n網頁伺服器需要該檔案的寫入權限以鎖定和解除鎖定資料庫。",
+       "databaselocked": "資料庫已被鎖定。",
        "databasenotlocked": "資料庫尚未鎖定。",
        "lockedbyandtime": "(由 {{GENDER:$1|$1}} 於 $2 的 $3)",
        "move-page": "移動 $1",
        "tooltip-ca-nstab-category": "檢視分類頁面",
        "tooltip-minoredit": "標記為小修訂",
        "tooltip-save": "儲存您的變更",
+       "tooltip-publish": "發佈您的更改",
        "tooltip-preview": "請在儲存前預覽您的變更!",
        "tooltip-diff": "顯示您對內容所做的變更",
        "tooltip-compareselectedversions": "查閱此頁面兩個已選擇的修訂間的差異",
        "invalidateemail": "取消電子郵件確認",
        "notificationemail_subject_changed": "{{SITENAME}} 註冊的電子郵件位址已變更",
        "notificationemail_subject_removed": "{{SITENAME}} 註冊的電子郵件位址已移除",
+       "notificationemail_body_changed": "來自IP位址$1的某個人(可能是您),在{{SITENAME}}上將帳號\"$2\"的電子郵件位址改成\"$3\"。\n\n如果非您本人所為,請立即跟網站管理員聯繫。",
+       "notificationemail_body_removed": "來自IP位址$1的某人(可能是您),在{{SITENAME}}上移除了帳號$2的電子郵件位址。\n\n如果非您本人所為,請立即跟網站管理員聯繫。",
        "scarytranscludedisabled": "[Interwiki 轉換代碼不可用]",
        "scarytranscludefailed": "[模板 $1 讀取失敗]",
        "scarytranscludefailed-httpstatus": "[模板 $1 讀取失敗:HTTP $2]",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|已保護}} $3 $4 [連鎖]",
        "logentry-protect-modify": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4 [連鎖]",
-       "logentry-rights-rights": "$1 {{GENDER:$2|已更改}} $3 的群組成員資格由 $4 成為 $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|已更改}} {{GENDER:$6|$3}} 的群組成員資格由 $4 成為 $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|已更改}} $3 的群組成員資格",
        "logentry-rights-autopromote": "$1 已自動{{GENDER:$2|提升}}從 $4 成為 $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|已上傳}} $3",
        "feedback-useragent": "使用者代理:",
        "searchsuggest-search": "搜尋",
        "searchsuggest-containing": "包含...",
+       "api-error-autoblocked": "您的IP位址已經被自動封禁,因為它曾經被一名已封禁的使用者使用過。",
        "api-error-badaccess-groups": "您沒有權限在此 Wiki 上傳檔案。",
        "api-error-badtoken": "內部錯誤:密鑰錯誤。",
+       "api-error-blocked": "您已被封禁,無法編輯。",
        "api-error-copyuploaddisabled": "此伺服器已停用使用 URL 上傳檔案的功能。",
        "api-error-duplicate": "在網站上已有相同內容的{{PLURAL:$1|其他檔案|其他檔案}}。",
        "api-error-duplicate-archive": "在網站上曾有相同內容的{{PLURAL:$1|其他檔案|其他檔案}},但已被刪除。",
        "api-error-nomodule": "內部錯誤:缺少上傳模組集。",
        "api-error-ok-but-empty": "內部錯誤:伺服器沒有回應。",
        "api-error-overwrite": "不允許覆蓋已存在的檔案。",
+       "api-error-ratelimited": "您正在嘗試在比本wiki所允許時間更短的時間內,上傳更多的檔案。請稍待幾分鐘之後再試一次。",
        "api-error-stashfailed": "內部錯誤:伺服器儲存暫存檔案失敗。",
        "api-error-publishfailed": "內部錯誤:伺服器發佈暫存檔案失敗。",
        "api-error-stasherror": "上傳檔案至儲藏庫時發生錯誤。",
        "api-error-unknownerror": "不明錯誤:\"$1\"。",
        "api-error-uploaddisabled": "此 Wiki 的上傳功能已停用。",
        "api-error-verification-error": "此檔案可能已損壞,或副檔名錯誤。",
+       "api-error-was-deleted": "與此名稱相同的檔案曾被上傳過,隨後遭到刪除。",
        "duration-seconds": "$1 秒",
        "duration-minutes": "$1 分鐘",
        "duration-hours": "$1 小時",
        "special-characters-group-ipa": "國際音標",
        "special-characters-group-symbols": "符號",
        "special-characters-group-greek": "希臘文",
+       "special-characters-group-greekextended": "希臘字母擴展",
        "special-characters-group-cyrillic": "斯拉夫文",
        "special-characters-group-arabic": "阿拉伯文",
        "special-characters-group-arabicextended": "阿拉伯文擴充",
index e54c4a7..2c577ed 100644 (file)
@@ -48,13 +48,19 @@ $namespaceGenderAliases = [
 $specialPageAliases = [
        'Activeusers'               => [ 'Aktivní_uživatelé', 'Aktivni_uzivatele' ],
        'Allmessages'               => [ 'Všechna_hlášení', 'Všechny_zprávy', 'Vsechna_hlaseni', 'Vsechny_zpravy' ],
+       'AllMyUploads'              => [ 'Všechny_moje_soubory', 'Všechny_mé_soubory' ],
        'Allpages'                  => [ 'Všechny_stránky', 'Vsechny_stranky' ],
        'Ancientpages'              => [ 'Nejstarší_stránky', 'Staré_stránky', 'Stare_stranky' ],
+       'ApiHelp'                   => [ 'Nápověda_k_API' ],
+       'ApiSandbox'                => [ 'API_pískoviště' ],
+       'Badtitle'                  => [ 'Neplatný_název' ],
        'Blankpage'                 => [ 'Prázdná_stránka' ],
        'Block'                     => [ 'Blokování', 'Blokovani', 'Blokovat_uživatele', 'Blokovat_IP', 'Blokovat_uzivatele' ],
        'Booksources'               => [ 'Zdroje_knih' ],
+       'BotPasswords'              => [ 'Hesla_pro_boty' ],
        'BrokenRedirects'           => [ 'Přerušená_přesměrování', 'Prerusena_presmerovani' ],
        'Categories'                => [ 'Kategorie' ],
+       'ChangeContentModel'        => [ 'Změnit_model_obsahu_stránky' ],
        'ChangeEmail'               => [ 'Změna_emailu', 'Zmena_emailu' ],
        'ChangePassword'            => [ 'Změna_hesla', 'Zmena_hesla' ],
        'ComparePages'              => [ 'Porovnání_stránek', 'PorovnáníStránek', 'Porovnani_stranek', 'PorovnaniStranek' ],
@@ -63,7 +69,10 @@ $specialPageAliases = [
        'CreateAccount'             => [ 'Vytvořit_účet', 'Vytvorit_ucet' ],
        'Deadendpages'              => [ 'Slepé_stránky', 'Slepe_stranky' ],
        'DeletedContributions'      => [ 'Smazané_příspěvky', 'Smazane_prispevky' ],
+       'Diff'                      => [ 'Rozdíl' ],
        'DoubleRedirects'           => [ 'Dvojitá_přesměrování', 'Dvojita_presmerovani' ],
+       'EditTags'                  => [ 'Upravit_značky' ],
+       'EditWatchlist'             => [ 'Upravit_seznam_sledovaných_stránek' ],
        'Emailuser'                 => [ 'E-mail' ],
        'ExpandTemplates'           => [ 'Testy_šablon' ],
        'Export'                    => [ 'Exportovat_stránky' ],
@@ -76,7 +85,9 @@ $specialPageAliases = [
        'LinkSearch'                => [ 'Hledání_odkazů', 'Hledani_odkazu' ],
        'Listadmins'                => [ 'Seznam_správců', 'Seznam_spravcu' ],
        'Listbots'                  => [ 'Seznam_botů', 'Seznam_botu' ],
+       'ListDuplicatedFiles'       => [ 'Seznam_duplicitních_souborů' ],
        'Listfiles'                 => [ 'Seznam_souborů', 'Seznam_souboru' ],
+       'Listgrants'                => [ 'Seznam_grantů' ],
        'Listgrouprights'           => [ 'Práva_uživatelských_skupin', 'Seznam_uživatelských_práv', 'Seznam_uzivatelskych_prav' ],
        'Listredirects'             => [ 'Seznam_přesměrování', 'Seznam_presmerovani' ],
        'Listusers'                 => [ 'Uživatelé', 'Uzivatele', 'Seznam_uživatelů', 'Seznam_uzivatelu' ],
@@ -84,6 +95,7 @@ $specialPageAliases = [
        'Log'                       => [ 'Protokolovací_záznamy', 'Protokoly', 'Protokol', 'Protokolovaci_zaznamy' ],
        'Lonelypages'               => [ 'Sirotčí_stránky', 'Sirotci_stranky' ],
        'Longpages'                 => [ 'Nejdelší_stránky', 'Nejdelsi_stranky' ],
+       'MediaStatistics'           => [ 'Statistika_souborů', 'Statistiky_souborů' ],
        'MergeHistory'              => [ 'Sloučení_historie', 'Slouceni_historie', 'Sloučit_historii' ],
        'MIMEsearch'                => [ 'Hledání_podle_MIME', 'Hledani_podle_MIME', 'Hledat_podle_MIME_typu' ],
        'Mostcategories'            => [ 'Stránky_s_nejvíce_kategoriemi', 'Stranky_s_nejvice_kategoriemi', 'Stránky_s_nejvyšším_počtem_kategorií' ],
@@ -94,18 +106,27 @@ $specialPageAliases = [
        'Mostrevisions'             => [ 'Stránky_s_nejvíce_editacemi', 'Stranky_s_nejvice_editacemi', 'Stránky_s_nejvyšším_počtem_editací' ],
        'Movepage'                  => [ 'Přesunout_stránku', 'Přejmenovat_stránku' ],
        'Mycontributions'           => [ 'Mé_příspěvky', 'Me_prispevky' ],
+       'MyLanguage'                => [ 'V_mém_jazyce', 'Můj_jazyk' ],
        'Mypage'                    => [ 'Moje_stránka', 'Moje_stranka' ],
        'Mytalk'                    => [ 'Moje_diskuse' ],
+       'Myuploads'                 => [ 'Moje_soubory', 'Mé_soubory' ],
        'Newimages'                 => [ 'Nové_obrázky', 'Galerie_nových_obrázků', 'Nove_obrazky' ],
        'Newpages'                  => [ 'Nové_stránky', 'Nove_stranky', 'Nejnovější_stránky', 'Nejnovejsi_stranky' ],
        'PasswordReset'             => [ 'Reset_hesla', 'Resetovat_heslo' ],
+       'PagesWithProp'             => [ 'Stránky_s_vlastností', 'Stránky_s_vlastnostmi' ],
+       'PasswordReset'             => [ 'Reset_hesla', 'Resetovat_heslo', 'Obnova_hesla', 'Obnovit_heslo' ],
+       'PermanentLink'             => [ 'Trvalý_odkaz' ],
        'Preferences'               => [ 'Nastavení', 'Nastaveni' ],
+       'Prefixindex'               => [ 'Stránky_podle_začátku' ],
        'Protectedpages'            => [ 'Zamčené_stránky', 'Zamcene_stranky' ],
        'Protectedtitles'           => [ 'Zamčené_názvy', 'Zamcene_nazvy', 'Stránky_které_nelze_vytvořit' ],
        'Randompage'                => [ 'Náhodná_stránka', 'Nahodna_stranka' ],
        'Randomredirect'            => [ 'Náhodné_přesměrování', 'Nahodne_presmerovani' ],
+       'RandomInCategory'          => [ 'Náhodná_stránka_v_kategorii' ],
+       'Randomrootpage'            => [ 'Náhodná_kořenová_stránka' ],
        'Recentchanges'             => [ 'Poslední_změny', 'Posledni_zmeny' ],
        'Recentchangeslinked'       => [ 'Související_změny', 'Souvisejici_zmeny' ],
+       'Redirect'                  => [ 'Přesměrování', 'Přesměrovat' ],
        'Revisiondelete'            => [ 'Smazat_revizi' ],
        'Search'                    => [ 'Hledání', 'Hledani' ],
        'Shortpages'                => [ 'Nejkratší_stránky', 'Nejkratsi_stranky' ],
index 6d9a616..2db0139 100644 (file)
@@ -70,6 +70,7 @@ TEXT
                global $wgCategoryCollation;
 
                $dbw = $this->getDB( DB_MASTER );
+               $dbr = $this->getDB( DB_SLAVE );
                $force = $this->getOption( 'force' );
                $dryRun = $this->getOption( 'dry-run' );
                $verboseStats = $this->getOption( 'verbose-stats' );
@@ -97,6 +98,7 @@ TEXT
                $options = [
                        'LIMIT' => self::BATCH_SIZE,
                        'ORDER BY' => $orderBy,
+                       'STRAIGHT_JOIN' // per T58041
                ];
 
                if ( $force || $dryRun ) {
@@ -110,7 +112,7 @@ TEXT
                                ];
                        }
 
-                       $count = $dbw->estimateRowCount(
+                       $count = $dbr->estimateRowCount(
                                'categorylinks',
                                '*',
                                $collationConds,
@@ -118,7 +120,7 @@ TEXT
                        );
                        // Improve estimate if feasible
                        if ( $count < 1000000 ) {
-                               $count = $dbw->selectField(
+                               $count = $dbr->selectField(
                                        'categorylinks',
                                        'COUNT(*)',
                                        $collationConds,
@@ -131,6 +133,7 @@ TEXT
                                return;
                        }
                        $this->output( "Fixing collation for $count rows.\n" );
+                       wfWaitForSlaves();
                }
                $count = 0;
                $batchCount = 0;
index 625c02e..9a8b058 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index eeb4b28..9ec7278 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-element-hidden {
        display: none !important;
        display: none;
 }
 .oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-decoration {
+.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
index 268a680..ba293e4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -62,9 +62,6 @@
 .oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
        margin-left: 0.46875em;
 }
-.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
-}
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
        margin-right: 0;
 }
@@ -77,6 +74,9 @@
        padding-right: 0.25em;
        color: #333333;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #555555;
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
        color: #cccccc;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus {
-       box-shadow: none;
-}
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
        opacity: 0.2;
        display: none;
 }
 .oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-button,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-results-decoration {
+.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
        outline: 0;
        border-color: #347bff;
-       box-shadow: inset 0 0 0 0.1em #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
index ddfee91..cbc02eb 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
@@ -2001,10 +2001,13 @@ OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
 /**
  * Set the button's active state.
  *
- * The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
- * a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
- * for other button types.
+ * The active state can be set on:
  *
+ *  - {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} when it is selected
+ *  - {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} when it is toggle on
+ *  - {@link OO.ui.ButtonWidget ButtonWidget} when clicking the button would only refresh the page
+ *
+ * @protected
  * @param {boolean} value Make button active
  * @chainable
  */
@@ -2017,6 +2020,7 @@ OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
 /**
  * Check if the button is active
  *
+ * @protected
  * @return {boolean} The button is active
  */
 OO.ui.mixin.ButtonElement.prototype.isActive = function () {
@@ -3332,6 +3336,7 @@ OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [active=false] Whether button should be shown as active
  * @cfg {string} [href] Hyperlink to visit when the button is clicked.
  * @cfg {string} [target] The frame or window in which to open the hyperlink.
  * @cfg {boolean} [noFollow] Search engine traversal hint (default: true)
@@ -3366,6 +3371,7 @@ OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
        this.$element
                .addClass( 'oo-ui-buttonWidget' )
                .append( this.$button );
+       this.setActive( config.active );
        this.setHref( config.href );
        this.setTarget( config.target );
        this.setNoFollow( config.noFollow );
@@ -3522,6 +3528,14 @@ OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
        return this;
 };
 
+// Override method visibility hints from ButtonElement
+/**
+ * @method setActive
+ */
+/**
+ * @method isActive
+ */
+
 /**
  * A ButtonGroupWidget groups related buttons and is used together with OO.ui.ButtonWidget and
  * its subclasses. Each button in a group is addressed by a unique reference. Buttons can be added,
index c1150d0..3a99fba 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index 957dcfc..d757813 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 1faf7e5..82335a4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 49b9674..d976448 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index 4de8cea..7a45a25 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
index 5c48ea5..a530235 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus {
        border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
        outline: 0;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus.oo-ui-toggleWidget-on {
 .oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
        width: 2.5em;
 }
-.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
        border-right-width: 0;
 }
-.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-plusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        border-top-left-radius: 0;
        border-bottom-left-radius: 0;
        border-left-width: 0;
index 366aa38..e3c2bd5 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index 764f40c..6dfe142 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
index deda1d0..9a544d6 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:06Z
+ * Date: 2016-05-10T22:58:31Z
  */
 .oo-ui-window {
        background: transparent;
index 93a870c..37b7d90 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.1
+ * OOjs UI v0.17.2
  * 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-05-03T22:58:02Z
+ * Date: 2016-05-10T22:58:27Z
  */
 ( function ( OO ) {
 
index b1a63b0..4c75e33 100644 (file)
@@ -14,7 +14,7 @@
                        $table = $( '#mw_metadata' ),
                        $tbody = $table.find( 'tbody' );
 
-               if ( !$tbody.length || !$tbody.find( '.collapsable' ).length ) {
+               if ( !$tbody.find( '.collapsable' ).length ) {
                        return;
                }
 
index 58115c3..2eb84e6 100644 (file)
         * @constructor
         * @inheritdoc
         */
-       function ForeignTitle() {
-               ForeignTitle.parent.apply( this, arguments );
+       function ForeignTitle( title, namespace ) {
+               // We only need to handle categories here... but we don't know the target language.
+               // So assume that any namespace-like prefix is the 'Category' namespace...
+               title = title.replace( /^(.+?)_*:_*(.*)$/, 'Category:$2' ); // HACK
+               ForeignTitle.parent.call( this, title, namespace );
        }
        OO.inheritClass( ForeignTitle, mw.Title );
        ForeignTitle.prototype.getNamespacePrefix = function () {
index 78e5f6f..ccb86d0 100644 (file)
@@ -27,6 +27,7 @@
  * @file
  * @ingroup Testing
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * @ingroup Testing
@@ -331,6 +332,18 @@ class ParserTest {
                Hooks::clear( 'InterwikiLoadPrefix' );
        }
 
+       /**
+        * Reset the Title-related services that need resetting
+        * for each test
+        */
+       public static function resetTitleServices() {
+               $services = MediaWikiServices::getInstance();
+               $services->resetServiceForTesting( 'TitleFormatter' );
+               $services->resetServiceForTesting( 'TitleParser' );
+               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+
+       }
+
        public function setupRecorder( $options ) {
                if ( isset( $options['record'] ) ) {
                        $this->recorder = new DbTestRecorder( $this );
@@ -958,6 +971,8 @@ class ParserTest {
                MWTidy::destroySingleton();
                RepoGroup::destroySingleton();
 
+               self::resetTitleServices();
+
                return $context;
        }
 
index e50b4f1..d701a81 100644 (file)
@@ -309,4 +309,118 @@ class LinkerTest extends MediaWikiLangTestCase {
                ];
                // @codingStandardsIgnoreEnd
        }
+
+       public static function provideLinkBeginHook() {
+               // @codingStandardsIgnoreStart Generic.Files.LineLength
+               return [
+                       // Modify $html
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $html = 'foobar';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
+                       ],
+                       // Modify $attribs
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $attribs['bar'] = 'baz';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
+                       ],
+                       // Modify $query
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $query['bar'] = 'baz';
+                               },
+                               '<a href="/w/index.php?title=Special:BlankPage&amp;bar=baz" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Force HTTP $options
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $options = [ 'http' ];
+                               },
+                               '<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Force 'forcearticlepath' in $options
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $options = [ 'forcearticlepath' ];
+                                       $query['foo'] = 'bar';
+                               },
+                               '<a href="/wiki/Special:BlankPage?foo=bar" title="Special:BlankPage">Special:BlankPage</a>'
+                       ],
+                       // Abort early
+                       [
+                               function( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
+                                       $ret = 'foobar';
+                                       return false;
+                               },
+                               'foobar'
+                       ],
+               ];
+               // @codingStandardsIgnoreEnd
+       }
+
+       /**
+        * @dataProvider provideLinkBeginHook
+        */
+       public function testLinkBeginHook( $callback, $expected ) {
+               $this->setMwGlobals( [
+                       'wgArticlePath' => '/wiki/$1',
+                       'wgWellFormedXml' => true,
+                       'wgServer' => '//example.org',
+                       'wgCanonicalServer' => 'http://example.org',
+                       'wgScriptPath' => '/w',
+                       'wgScript' => '/w/index.php',
+               ] );
+
+               $this->setMwGlobals( 'wgHooks', [ 'LinkBegin' => [ $callback ] ] );
+               $title = SpecialPage::getTitleFor( 'Blankpage' );
+               $out = Linker::link( $title );
+               $this->assertEquals( $expected, $out );
+       }
+
+       public static function provideLinkEndHook() {
+               return [
+                       // Override $html
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $html = 'foobar';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
+                       ],
+                       // Modify $attribs
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $attribs['bar'] = 'baz';
+                               },
+                               '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
+                       ],
+                       // Fully override return value and abort hook
+                       [
+                               function( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
+                                       $ret = 'blahblahblah';
+                                       return false;
+                               },
+                               'blahblahblah'
+                       ],
+
+               ];
+       }
+
+       /**
+        * @dataProvider provideLinkEndHook
+        */
+       public function testLinkEndHook( $callback, $expected ) {
+               $this->setMwGlobals( [
+                       'wgArticlePath' => '/wiki/$1',
+                       'wgWellFormedXml' => true,
+               ] );
+
+               $this->setMwGlobals( 'wgHooks', [ 'LinkEnd' => [ $callback ] ] );
+
+               $title = SpecialPage::getTitleFor( 'Blankpage' );
+               $out = Linker::link( $title );
+               $this->assertEquals( $expected, $out );
+       }
 }
index 6c38d50..467a2ad 100644 (file)
@@ -200,6 +200,10 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
 
                // All getters should be named just like the service, with "get" added.
                foreach ( $getServiceCases as $name => $case ) {
+                       if ( $name[0] === '_' ) {
+                               // Internal service, no getter
+                               continue;
+                       }
                        list( $service, $class ) = $case;
                        $getterCases[$name] = [
                                'get' . $service,
@@ -238,6 +242,10 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                        'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', 'LBFactory' ],
                        'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
                        'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
+                       'GenderCache' => [ 'GenderCache', GenderCache::class ],
+                       '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
+                       'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
+                       'TitleParser' => [ 'TitleParser', TitleParser::class ],
                ];
        }
 
index 7d025d2..496f18f 100644 (file)
@@ -144,6 +144,13 @@ class TitleTest extends MediaWikiTestCase {
                                ]
                        ]
                ] );
+
+               // Reset TitleParser since we modified $wgLocalInterwikis
+               $this->setService( 'TitleParser', new MediaWikiTitleCodec(
+                               Language::factory( 'en' ),
+                               new GenderCache(),
+                               [ 'localtestiw' ]
+               ) );
        }
 
        /**
diff --git a/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php b/tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
new file mode 100644 (file)
index 0000000..f1f9295
--- /dev/null
@@ -0,0 +1,1592 @@
+<?php
+
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiQueryWatchlist
+ */
+class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
+
+       public function __construct( $name = null, array $data = [], $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+               $this->tablesUsed = array_unique(
+                       array_merge( $this->tablesUsed, [ 'watchlist', 'recentchanges', 'page' ] )
+               );
+       }
+
+       protected function setUp() {
+               parent::setUp();
+               self::$users['ApiQueryWatchlistIntegrationTestUser']
+                       = new TestUser( 'ApiQueryWatchlistIntegrationTestUser' );
+               self::$users['ApiQueryWatchlistIntegrationTestUser2']
+                       = new TestUser( 'ApiQueryWatchlistIntegrationTestUser2' );
+               $this->doLogin( 'ApiQueryWatchlistIntegrationTestUser' );
+       }
+
+       private function getTestUser() {
+               return self::$users['ApiQueryWatchlistIntegrationTestUser']->getUser();
+       }
+
+       private function getNonLoggedInTestUser() {
+               return self::$users['ApiQueryWatchlistIntegrationTestUser2']->getUser();
+       }
+
+       private function getSysopTestUser() {
+               return self::$users['sysop']->getUser();
+       }
+
+       private function doPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       $user
+               );
+       }
+
+       private function doMinorPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       EDIT_MINOR,
+                       false,
+                       $user
+               );
+       }
+
+       private function doBotPageEdit( User $user, LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       EDIT_FORCE_BOT,
+                       false,
+                       $user
+               );
+       }
+
+       private function doAnonPageEdit( LinkTarget $target, $content, $summary ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       User::newFromId( 0 )
+               );
+       }
+
+       private function doPatrolledPageEdit(
+               User $user,
+               LinkTarget $target,
+               $content,
+               $summary,
+               User $patrollingUser
+       ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $status = $page->doEditContent(
+                       ContentHandler::makeContent( $content, $title ),
+                       $summary,
+                       0,
+                       false,
+                       $user
+               );
+               /** @var Revision $rev */
+               $rev = $status->value['revision'];
+               $rc = $rev->getRecentChange();
+               $rc->doMarkPatrolled( $patrollingUser, false, [] );
+       }
+
+       private function deletePage( LinkTarget $target, $reason ) {
+               $title = Title::newFromLinkTarget( $target );
+               $page = WikiPage::factory( $title );
+               $page->doDeleteArticleReal( $reason );
+       }
+
+       /**
+        * Performs a batch of page edits as a specified user
+        * @param User $user
+        * @param array $editData associative array, keys:
+        *                        - target    => LinkTarget page to edit
+        *                        - content   => string new content
+        *                        - summary   => string edit summary
+        *                        - minorEdit => bool mark as minor edit if true (defaults to false)
+        *                        - botEdit   => bool mark as bot edit if true (defaults to false)
+        */
+       private function doPageEdits( User $user, array $editData ) {
+               foreach ( $editData as $singleEditData ) {
+                       if ( array_key_exists( 'minorEdit', $singleEditData ) && $singleEditData['minorEdit'] ) {
+                               $this->doMinorPageEdit(
+                                       $user,
+                                       $singleEditData['target'],
+                                       $singleEditData['content'],
+                                       $singleEditData['summary']
+                               );
+                               continue;
+                       }
+                       if ( array_key_exists( 'botEdit', $singleEditData ) && $singleEditData['botEdit'] ) {
+                               $this->doBotPageEdit(
+                                       $user,
+                                       $singleEditData['target'],
+                                       $singleEditData['content'],
+                                       $singleEditData['summary']
+                               );
+                               continue;
+                       }
+                       $this->doPageEdit(
+                               $user,
+                               $singleEditData['target'],
+                               $singleEditData['content'],
+                               $singleEditData['summary']
+                       );
+               }
+       }
+
+       private function getWatchedItemStore() {
+               return MediaWikiServices::getInstance()->getWatchedItemStore();
+       }
+
+       /**
+        * @param User $user
+        * @param LinkTarget[] $targets
+        */
+       private function watchPages( User $user, array $targets ) {
+               $store = $this->getWatchedItemStore();
+               $store->addWatchBatchForUser( $user, $targets );
+       }
+
+       private function doListWatchlistRequest( array $params = [], $user = null ) {
+               return $this->doApiRequest(
+                       array_merge(
+                               [ 'action' => 'query', 'list' => 'watchlist' ],
+                               $params
+                       ), null, false, $user
+               );
+       }
+
+       private function doGeneratorWatchlistRequest( array $params = [] ) {
+               return $this->doApiRequest(
+                       array_merge(
+                               [ 'action' => 'query', 'generator' => 'watchlist' ],
+                               $params
+                       )
+               );
+       }
+
+       private function getItemsFromApiResponse( array $response ) {
+               return $response[0]['query']['watchlist'];
+       }
+
+       /**
+        * Convenience method to assert that actual items array fetched from API is equal to the expected
+        * array, Unlike assertEquals this only checks if values of specified keys are equal in both
+        * arrays. This could be used e.g. not to compare IDs that could change between test run
+        * but only stable keys.
+        * Optionally this also checks that specified keys are present in the actual item without
+        * performing any checks on the related values.
+        *
+        * @param array $actualItems               array of actual items (associative arrays)
+        * @param array $expectedItems             array of expected items (associative arrays),
+        *                                         those items have less keys than actual items
+        * @param array $keysUsedInValueComparison list of keys of the actual item that will be used
+        *                                         in the comparison of values
+        * @param array $requiredKeys              optional, list of keys that must be present in the
+        *                                         actual items. Values of those keys are not checked.
+        */
+       private function assertArraySubsetsEqual(
+               array $actualItems,
+               array $expectedItems,
+               array $keysUsedInValueComparison,
+               array $requiredKeys = []
+       ) {
+               $this->assertCount( count( $expectedItems ), $actualItems );
+
+               // not checking values of all keys of the actual item, so removing unwanted keys from comparison
+               $actualItemsOnlyComparedValues = array_map(
+                       function( array $item ) use ( $keysUsedInValueComparison ) {
+                               return array_intersect_key( $item, array_flip( $keysUsedInValueComparison ) );
+                       },
+                       $actualItems
+               );
+
+               $this->assertEquals(
+                       $expectedItems,
+                       $actualItemsOnlyComparedValues
+               );
+
+               // Check that each item in $actualItems contains all of keys specified in $requiredKeys
+               $actualItemsKeysOnly = array_map( 'array_keys', $actualItems );
+               foreach ( $actualItemsKeysOnly as $keysOfTheItem ) {
+                       $this->assertEmpty( array_diff( $requiredKeys, $keysOfTheItem ) );
+               }
+       }
+
+       private function getTitleFormatter() {
+               return new MediaWikiTitleCodec( Language::factory( 'en' ), GenderCache::singleton() );
+       }
+
+       private function getPrefixedText( LinkTarget $target ) {
+               $formatter = $this->getTitleFormatter();
+               return $formatter->getPrefixedText( $target );
+       }
+
+       private function cleanTestUsersWatchlist() {
+               $user = $this->getTestUser();
+               $store = $this->getWatchedItemStore();
+               $items = $store->getWatchedItemsForUser( $user );
+               foreach ( $items as $item ) {
+                       $store->removeWatch( $user, $item->getLinkTarget() );
+               }
+       }
+
+       public function testListWatchlist_returnsWatchedItemsWithRCInfo() {
+               // Clean up after previous tests that might have added something to the watchlist of
+               // the user with the same user ID as user used here as the test user
+               $this->cleanTestUsersWatchlist();
+
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest();
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'watchlist', $result[0]['query'] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                                       'bot' => false,
+                                       'new' => true,
+                                       'minor' => false,
+                               ]
+                       ],
+                       [ 'type', 'ns', 'title', 'bot', 'new', 'minor' ],
+                       [ 'pageid', 'revid', 'old_revid' ]
+               );
+       }
+
+       public function testIdsPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'ids', ] );
+               $items = $this->getItemsFromApiResponse( $result );
+
+               $this->assertCount( 1, $items );
+               $this->assertArrayHasKey( 'pageid', $items[0] );
+               $this->assertArrayHasKey( 'revid', $items[0] );
+               $this->assertArrayHasKey( 'old_revid', $items[0] );
+               $this->assertEquals( 'new', $items[0]['type'] );
+       }
+
+       public function testTitlePropParameter() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testFlagsPropParameter() {
+               $user = $this->getTestUser();
+               $normalEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $minorEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageM' );
+               $botEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageB' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $normalEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $minorEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $minorEditTarget,
+                                       'content' => 'Slightly Better Content',
+                                       'summary' => 'Change content',
+                                       'minorEdit' => true,
+                               ],
+                               [
+                                       'target' => $botEditTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page with a bot',
+                                       'botEdit' => true,
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $normalEditTarget, $minorEditTarget, $botEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'flags', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'new' => true,
+                                       'minor' => false,
+                                       'bot' => true,
+                               ],
+                               [
+                                       'type' => 'edit',
+                                       'new' => false,
+                                       'minor' => true,
+                                       'bot' => false,
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'new' => true,
+                                       'minor' => false,
+                                       'bot' => false,
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testUserPropParameter() {
+               $user = $this->getTestUser();
+               $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' );
+               $this->doPageEdit(
+                       $user,
+                       $userEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doAnonPageEdit(
+                       $anonEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'user', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'anon' => true,
+                                       'user' => User::newFromId( 0 )->getName(),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'user' => $user->getName(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testUserIdPropParameter() {
+               $user = $this->getTestUser();
+               $userEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $anonEditTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPageA' );
+               $this->doPageEdit(
+                       $user,
+                       $userEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doAnonPageEdit(
+                       $anonEditTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $userEditTarget, $anonEditTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'userid', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'anon' => true,
+                                       'user' => 0,
+                                       'userid' => 0,
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'user' => $user->getId(),
+                                       'userid' => $user->getId(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testCommentPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the <b>page</b>'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'comment', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'comment' => 'Create the <b>page</b>',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testParsedCommentPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the <b>page</b>'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'parsedcomment', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'parsedcomment' => 'Create the &lt;b&gt;page&lt;/b&gt;',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testTimestampPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'timestamp', ] );
+               $items = $this->getItemsFromApiResponse( $result );
+
+               $this->assertCount( 1, $items );
+               $this->assertArrayHasKey( 'timestamp', $items[0] );
+               $this->assertInternalType( 'string', $items[0]['timestamp'] );
+       }
+
+       public function testSizesPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'sizes', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'oldlen' => 0,
+                                       'newlen' => 12,
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testNotificationTimestampPropParameter() {
+               $otherUser = $this->getNonLoggedInTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $otherUser,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $store = $this->getWatchedItemStore();
+               $store->addWatch( $this->getTestUser(), $target );
+               $store->updateNotificationTimestamp(
+                       $otherUser,
+                       $target,
+                       '20151212010101'
+               );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'notificationtimestamp', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '2015-12-12T01:01:01Z',
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function setupPatrolledSpecificFixtures( User $user ) {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+
+               $this->doPatrolledPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page (this gets patrolled)',
+                       $user
+               );
+
+               $this->watchPages( $user, [ $target ] );
+       }
+
+       public function testPatrolPropParameter() {
+               $user = $this->getSysopTestUser();
+               $this->setupPatrolledSpecificFixtures( $user );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'patrol', ], $user );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'patrolled' => true,
+                                       'unpatrolled' => false,
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function createPageAndDeleteIt( LinkTarget $target ) {
+               $this->doPageEdit(
+                       $this->getTestUser(),
+                       $target,
+                       'Some Content',
+                       'Create the page that will be deleted'
+               );
+               $this->deletePage( $target, 'Important Reason' );
+       }
+
+       public function testLoginfoPropParameter() {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->createPageAndDeleteIt( $target );
+
+               $this->watchPages( $this->getTestUser(), [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'loginfo', ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'type' => 'log',
+                                       'logtype' => 'delete',
+                                       'logaction' => 'delete',
+                                       'logparams' => [],
+                               ],
+                       ],
+                       [ 'type', 'logtype', 'logaction', 'logparams' ],
+                       [ 'logid' ]
+               );
+       }
+
+       public function testEmptyPropParameter() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => '', ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testNamespaceParam() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlnamespace' => '0', ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $result ),
+                       [
+                               [
+                                       'ns' => 0,
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       [ 'ns', 'title' ]
+               );
+       }
+
+       public function testUserParam() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'What is this page about?',
+                       'Create the talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user|title',
+                       'wluser' => $otherUser->getName(),
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                                       'user' => $otherUser->getName(),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testExcludeUserParam() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'What is this page about?',
+                       'Create the talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user|title',
+                       'wlexcludeuser' => $otherUser->getName(),
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                                       'user' => $user->getName(),
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testShowMinorParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Slightly Better Content',
+                                       'summary' => 'Change content',
+                                       'minorEdit' => true,
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultMinor = $this->doListWatchlistRequest( [ 'wlshow' => 'minor', 'wlprop' => 'flags' ] );
+               $resultNotMinor = $this->doListWatchlistRequest( [ 'wlshow' => '!minor', 'wlprop' => 'flags' ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultMinor ),
+                       [
+                               [ 'minor' => true, ]
+                       ],
+                       [ 'minor' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotMinor ) );
+       }
+
+       public function testShowBotParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doBotPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultBot = $this->doListWatchlistRequest( [ 'wlshow' => 'bot' ] );
+               $resultNotBot = $this->doListWatchlistRequest( [ 'wlshow' => '!bot' ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultBot ),
+                       [
+                               [ 'bot' => true ],
+                       ],
+                       [ 'bot' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotBot ) );
+       }
+
+       public function testShowAnonParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doAnonPageEdit(
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultAnon = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user',
+                       'wlshow' => 'anon'
+               ] );
+               $resultNotAnon = $this->doListWatchlistRequest( [
+                       'wlprop' => 'user',
+                       'wlshow' => '!anon'
+               ] );
+
+               $this->assertArraySubsetsEqual(
+                       $this->getItemsFromApiResponse( $resultAnon ),
+                       [
+                               [ 'anon' => true ],
+                       ],
+                       [ 'anon' ]
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotAnon ) );
+       }
+
+       public function testShowUnreadParams() {
+               $user = $this->getTestUser();
+               $otherUser = $this->getNonLoggedInTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $otherUser,
+                       $talkTarget,
+                       'Some Content',
+                       'Create the talk page'
+               );
+               $store = $this->getWatchedItemStore();
+               $store->addWatchBatchForUser( $user, [ $subjectTarget, $talkTarget ] );
+               $store->updateNotificationTimestamp(
+                       $otherUser,
+                       $talkTarget,
+                       '20151212010101'
+               );
+
+               $resultUnread = $this->doListWatchlistRequest( [
+                       'wlprop' => 'notificationtimestamp|title',
+                       'wlshow' => 'unread'
+               ] );
+               $resultNotUnread = $this->doListWatchlistRequest( [
+                       'wlprop' => 'notificationtimestamp|title',
+                       'wlshow' => '!unread'
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '2015-12-12T01:01:01Z',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultUnread )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'notificationtimestamp' => '',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultNotUnread )
+               );
+       }
+
+       public function testShowPatrolledParams() {
+               $user = $this->getSysopTestUser();
+               $this->setupPatrolledSpecificFixtures( $user );
+
+               $resultPatrolled = $this->doListWatchlistRequest( [
+                       'wlprop' => 'patrol',
+                       'wlshow' => 'patrolled'
+               ], $user );
+               $resultNotPatrolled = $this->doListWatchlistRequest( [
+                       'wlprop' => 'patrol',
+                       'wlshow' => '!patrolled'
+               ], $user );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'patrolled' => true,
+                                       'unpatrolled' => false,
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultPatrolled )
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultNotPatrolled ) );
+       }
+
+       public function testNewAndEditTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $resultNew = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'new' ] );
+               $resultEdit = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'edit' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultNew )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultEdit )
+               );
+       }
+
+       public function testLogTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->createPageAndDeleteIt( $subjectTarget );
+               $this->doPageEdit(
+                       $user,
+                       $talkTarget,
+                       'Some Talk Page Content',
+                       'Create Talk page'
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'log' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'log',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       private function getExternalRC( LinkTarget $target ) {
+               $title = Title::newFromLinkTarget( $target );
+
+               $rc = new RecentChange;
+               $rc->mTitle = $title;
+               $rc->mAttribs = [
+                       'rc_timestamp' => wfTimestamp( TS_MW ),
+                       'rc_namespace' => $title->getNamespace(),
+                       'rc_title' => $title->getDBkey(),
+                       'rc_type' => RC_EXTERNAL,
+                       'rc_source' => 'foo',
+                       'rc_minor' => 0,
+                       'rc_cur_id' => $title->getArticleID(),
+                       'rc_user' => 0,
+                       'rc_user_text' => 'External User',
+                       'rc_comment' => '',
+                       'rc_this_oldid' => $title->getLatestRevID(),
+                       'rc_last_oldid' => $title->getLatestRevID(),
+                       'rc_bot' => 0,
+                       'rc_ip' => '',
+                       'rc_patrolled' => 0,
+                       'rc_new' => 0,
+                       'rc_old_len' => $title->getLength(),
+                       'rc_new_len' => $title->getLength(),
+                       'rc_deleted' => 0,
+                       'rc_logid' => 0,
+                       'rc_log_type' => null,
+                       'rc_log_action' => '',
+                       'rc_params' => '',
+               ];
+               $rc->mExtra = [
+                       'prefixedDBkey' => $title->getPrefixedDBkey(),
+                       'lastTimestamp' => 0,
+                       'oldSize' => $title->getLength(),
+                       'newSize' => $title->getLength(),
+                       'pageStatus' => 'changed'
+               ];
+
+               return $rc;
+       }
+
+       public function testExternalTypeParameters() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $subjectTarget,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->doPageEdit(
+                       $user,
+                       $talkTarget,
+                       'Some Talk Page Content',
+                       'Create Talk page'
+               );
+
+               $rc = $this->getExternalRC( $subjectTarget );
+               $rc->save();
+
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'external' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'external',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testCategorizeTypeParameter() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $categoryTarget = new TitleValue( NS_CATEGORY, 'ApiQueryWatchlistIntegrationTestCategory' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $categoryTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the category',
+                               ],
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content [[Category:ApiQueryWatchlistIntegrationTestCategory]]t',
+                                       'summary' => 'Create the page and add it to the category',
+                               ],
+                       ]
+               );
+               $title = Title::newFromLinkTarget( $subjectTarget );
+               $revision = Revision::newFromTitle( $title );
+
+               $rc = RecentChange::newForCategorization(
+                       $revision->getTimestamp(),
+                       Title::newFromLinkTarget( $categoryTarget ),
+                       $user,
+                       $revision->getComment(),
+                       $title,
+                       0,
+                       $revision->getId(),
+                       null,
+                       false
+               );
+               $rc->save();
+
+               $this->watchPages( $user, [ $subjectTarget, $categoryTarget ] );
+
+               $result = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wltype' => 'categorize' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'categorize',
+                                       'ns' => $categoryTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $categoryTarget ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testLimitParam() {
+               $user = $this->getTestUser();
+               $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target1,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target2,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                               [
+                                       'target' => $target3,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Create the page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target1, $target2, $target3 ] );
+
+               $resultWithoutLimit = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] );
+               $resultWithLimit = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target1->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target1 )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultWithoutLimit )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultWithLimit )
+               );
+               $this->assertArrayHasKey( 'continue', $resultWithLimit[0] );
+               $this->assertArrayHasKey( 'wlcontinue', $resultWithLimit[0]['continue'] );
+       }
+
+       public function testAllRevParam() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title', 'wlallrev' => '', ] );
+               $resultNoAllRev = $this->doListWatchlistRequest( [ 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultNoAllRev )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'edit',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultAllRev )
+               );
+       }
+
+       public function testDirParams() {
+               $user = $this->getTestUser();
+               $subjectTarget = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $talkTarget = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $subjectTarget,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $talkTarget,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $subjectTarget, $talkTarget ] );
+
+               $resultDirOlder = $this->doListWatchlistRequest( [ 'wldir' => 'older', 'wlprop' => 'title' ] );
+               $resultDirNewer = $this->doListWatchlistRequest( [ 'wldir' => 'newer', 'wlprop' => 'title' ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultDirOlder )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $subjectTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $subjectTarget )
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $talkTarget->getNamespace(),
+                                       'title' => $this->getPrefixedText( $talkTarget )
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $resultDirNewer )
+               );
+       }
+
+       public function testStartEndParams() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $resultStart = $this->doListWatchlistRequest( [
+                       'wlstart' => '20010115000000',
+                       'wldir' => 'newer',
+                       'wlprop' => 'title',
+               ] );
+               $resultEnd = $this->doListWatchlistRequest( [
+                       'wlend' => '20010115000000',
+                       'wldir' => 'newer',
+                       'wlprop' => 'title',
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $resultStart )
+               );
+               $this->assertEmpty( $this->getItemsFromApiResponse( $resultEnd ) );
+       }
+
+       public function testContinueParam() {
+               $user = $this->getTestUser();
+               $target1 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target2 = new TitleValue( 1, 'ApiQueryWatchlistIntegrationTestPage' );
+               $target3 = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage2' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target1,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target2,
+                                       'content' => 'Some Talk Page Content',
+                                       'summary' => 'Create Talk page',
+                               ],
+                               [
+                                       'target' => $target3,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Create the page',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target1, $target2, $target3 ] );
+
+               $firstResult = $this->doListWatchlistRequest( [ 'wllimit' => 2, 'wlprop' => 'title' ] );
+               $this->assertArrayHasKey( 'continue', $firstResult[0] );
+               $this->assertArrayHasKey( 'wlcontinue', $firstResult[0]['continue'] );
+
+               $continuationParam = $firstResult[0]['continue']['wlcontinue'];
+
+               $continuedResult = $this->doListWatchlistRequest(
+                       [ 'wlcontinue' => $continuationParam, 'wlprop' => 'title' ]
+               );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target3->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target3 ),
+                               ],
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target2->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target2 ),
+                               ],
+                       ],
+                       $this->getItemsFromApiResponse( $firstResult )
+               );
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target1->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target1 )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $continuedResult )
+               );
+       }
+
+       public function testOwnerAndTokenParams() {
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $this->getTestUser(),
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+
+               $otherUser = $this->getNonLoggedInTestUser();
+               $otherUser->setOption( 'watchlisttoken', '1234567890' );
+               $otherUser->saveSettings();
+
+               $this->watchPages( $otherUser, [ $target ] );
+
+               $result = $this->doListWatchlistRequest( [
+                       'wlowner' => $otherUser->getName(),
+                       'wltoken' => '1234567890',
+                       'wlprop' => 'title',
+               ] );
+
+               $this->assertEquals(
+                       [
+                               [
+                                       'type' => 'new',
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target )
+                               ]
+                       ],
+                       $this->getItemsFromApiResponse( $result )
+               );
+       }
+
+       public function testOwnerAndTokenParams_wrongToken() {
+               $otherUser = $this->getNonLoggedInTestUser();
+               $otherUser->setOption( 'watchlisttoken', '1234567890' );
+               $otherUser->saveSettings();
+
+               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+
+               $this->doListWatchlistRequest( [
+                       'wlowner' => $otherUser->getName(),
+                       'wltoken' => 'wrong-token',
+               ] );
+       }
+
+       public function testOwnerAndTokenParams_noWatchlistTokenSet() {
+               $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
+
+               $this->doListWatchlistRequest( [
+                       'wlowner' => $this->getNonLoggedInTestUser()->getName(),
+                       'wltoken' => 'some-token',
+               ] );
+       }
+
+       public function testGeneratorWatchlistPropInfo_returnsWatchedPages() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdit(
+                       $user,
+                       $target,
+                       'Some Content',
+                       'Create the page'
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'info' ] );
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'pages', $result[0]['query'] );
+
+               // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them
+               $pages = array_values( $result[0]['query']['pages'] );
+
+               $this->assertArraySubsetsEqual(
+                       $pages,
+                       [
+                               [
+                                       'ns' => $target->getNamespace(),
+                                       'title' => $this->getPrefixedText( $target ),
+                                       'new' => true,
+                               ]
+                       ],
+                       [ 'ns', 'title', 'new' ]
+               );
+       }
+
+       public function testGeneratorWatchlistPropRevisions_returnsWatchedItemsRevisions() {
+               $user = $this->getTestUser();
+               $target = new TitleValue( 0, 'ApiQueryWatchlistIntegrationTestPage' );
+               $this->doPageEdits(
+                       $user,
+                       [
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Content',
+                                       'summary' => 'Create the page',
+                               ],
+                               [
+                                       'target' => $target,
+                                       'content' => 'Some Other Content',
+                                       'summary' => 'Change the content',
+                               ],
+                       ]
+               );
+               $this->watchPages( $user, [ $target ] );
+
+               $result = $this->doGeneratorWatchlistRequest( [ 'prop' => 'revisions', 'gwlallrev' => '' ] );
+
+               $this->assertArrayHasKey( 'query', $result[0] );
+               $this->assertArrayHasKey( 'pages', $result[0]['query'] );
+
+               // $result[0]['query']['pages'] uses page ids as keys. Page ids don't matter here, so drop them
+               $pages = array_values( $result[0]['query']['pages'] );
+
+               $this->assertCount( 1, $pages );
+               $this->assertEquals( 0, $pages[0]['ns'] );
+               $this->assertEquals( $this->getPrefixedText( $target ), $pages[0]['title'] );
+               $this->assertArraySubsetsEqual(
+                       $pages[0]['revisions'],
+                       [
+                               [
+                                       'comment' => 'Create the page',
+                                       'user' => $user->getName(),
+                                       'minor' => false,
+                               ],
+                               [
+                                       'comment' => 'Change the content',
+                                       'user' => $user->getName(),
+                                       'minor' => false,
+                               ],
+                       ],
+                       [ 'comment', 'user', 'minor' ]
+               );
+       }
+
+}
index 22bb237..4c973e5 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * Although marked as a stub, can work independently.
  *
@@ -179,6 +178,7 @@ class NewParserTest extends MediaWikiTestCase {
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
+               ParserTest::resetTitleServices();
        }
 
        protected function tearDown() {
index 15b47b4..d57ad04 100644 (file)
@@ -56,17 +56,26 @@ class PoolCounterTest extends MediaWikiTestCase {
 
                $keysWithTwoSlots = $keysWithFiveSlots = [];
                foreach ( range( 1, 100 ) as $i ) {
-                       $keysWithTwoSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 2 );
-                       $keysWithFiveSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'key ' . $i, 5 );
+                       $keysWithTwoSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'key ' . $i, 2 );
+                       $keysWithFiveSlots[] = $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'key ' . $i, 5 );
                }
 
-               $this->assertArrayEquals( range( 0, 1 ), array_unique( $keysWithTwoSlots ) );
-               $this->assertArrayEquals( range( 0, 4 ), array_unique( $keysWithFiveSlots ) );
+               $twoSlotKeys = [];
+               for ( $i = 0; $i <= 1; $i++ ) {
+                       $twoSlotKeys[] = "test:$i";
+               }
+               $fiveSlotKeys = [];
+               for ( $i = 0; $i <= 4; $i++ ) {
+                       $fiveSlotKeys[] = "test:$i";
+               }
+
+               $this->assertArrayEquals( $twoSlotKeys, array_unique( $keysWithTwoSlots ) );
+               $this->assertArrayEquals( $fiveSlotKeys, array_unique( $keysWithFiveSlots ) );
 
                // make sure it is deterministic
                $this->assertEquals(
-                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 ),
-                       $hashKeyIntoSlots->invoke( $poolCounter, 'asdfgh', 1000 )
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'asdfgh', 1000 ),
+                       $hashKeyIntoSlots->invoke( $poolCounter, 'test', 'asdfgh', 1000 )
                );
        }
 }
index ff22bfa..8f7b2a6 100644 (file)
@@ -103,6 +103,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $anonInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -118,6 +119,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -132,6 +134,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -150,6 +153,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $anonInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -165,6 +169,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -180,6 +185,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
@@ -231,6 +237,25 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( SessionInfo::MIN_PRIORITY + 5, $info->getPriority() );
                $this->assertTrue( $info->isIdSafe() );
 
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'id' => $id,
+                       'forceUse' => true,
+               ] );
+               $this->assertFalse( $info->forceUse(), 'no provider' );
+
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'provider' => $provider,
+                       'forceUse' => true,
+               ] );
+               $this->assertFalse( $info->forceUse(), 'no id' );
+
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY + 5, [
+                       'provider' => $provider,
+                       'id' => $id,
+                       'forceUse' => true,
+               ] );
+               $this->assertTrue( $info->forceUse(), 'correct use' );
+
                $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
                        'id' => $id,
                        'forceHTTPS' => 1,
@@ -242,6 +267,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                        'provider' => $provider,
                        'userInfo' => $userInfo,
                        'idIsSafe' => true,
+                       'forceUse' => true,
                        'persisted' => true,
                        'remembered' => true,
                        'forceHTTPS' => true,
@@ -255,6 +281,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( $provider, $info->getProvider() );
                $this->assertSame( $userInfo, $info->getUserInfo() );
                $this->assertTrue( $info->isIdSafe() );
+               $this->assertTrue( $info->forceUse() );
                $this->assertTrue( $info->wasPersisted() );
                $this->assertTrue( $info->wasRemembered() );
                $this->assertTrue( $info->forceHTTPS() );
@@ -265,6 +292,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                        'provider' => $provider2,
                        'userInfo' => $unverifiedUserInfo,
                        'idIsSafe' => false,
+                       'forceUse' => false,
                        'persisted' => false,
                        'remembered' => false,
                        'forceHTTPS' => false,
@@ -276,6 +304,7 @@ class SessionInfoTest extends MediaWikiTestCase {
                $this->assertSame( $provider2, $info->getProvider() );
                $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
                $this->assertFalse( $info->isIdSafe() );
+               $this->assertFalse( $info->forceUse() );
                $this->assertFalse( $info->wasPersisted() );
                $this->assertFalse( $info->wasRemembered() );
                $this->assertFalse( $info->forceHTTPS() );
index d04d7ec..5f387ea 100644 (file)
@@ -642,6 +642,35 @@ class SessionManagerTest extends MediaWikiTestCase {
                }
        }
 
+       public function testInvalidateSessionsForUser() {
+               $user = User::newFromName( 'UTSysop' );
+               $manager = $this->getManager();
+
+               $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
+                       ->setMethods( [ 'invalidateSessionsForUser', '__toString' ] );
+
+               $provider1 = $providerBuilder->getMock();
+               $provider1->expects( $this->once() )->method( 'invalidateSessionsForUser' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider1->expects( $this->any() )->method( '__toString' )
+                       ->will( $this->returnValue( 'MockProvider1' ) );
+
+               $provider2 = $providerBuilder->getMock();
+               $provider2->expects( $this->once() )->method( 'invalidateSessionsForUser' )
+                       ->with( $this->identicalTo( $user ) );
+               $provider2->expects( $this->any() )->method( '__toString' )
+                       ->will( $this->returnValue( 'MockProvider2' ) );
+
+               $this->config->set( 'SessionProviders', [
+                       $this->objectCacheDef( $provider1 ),
+                       $this->objectCacheDef( $provider2 ),
+               ] );
+
+               $oldToken = $user->getToken( true );
+               $manager->invalidateSessionsForUser( $user );
+               $this->assertNotEquals( $oldToken, $user->getToken() );
+       }
+
        public function testGetVaryHeaders() {
                $manager = $this->getManager();
 
@@ -1756,5 +1785,21 @@ class SessionManagerTest extends MediaWikiTestCase {
                        [ LogLevel::WARNING, 'Session "{session}": Hook aborted' ],
                ], $logger->getBuffer() );
                $logger->clearBuffer();
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionCheckInfo' => [] ] );
+
+               // forceUse deletes bad backend data
+               $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
+               $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
+                       'provider' => $provider,
+                       'id' => $id,
+                       'userInfo' => $userInfo,
+                       'forceUse' => true,
+               ] );
+               $this->assertTrue( $loadSessionInfoFromStore( $info ) );
+               $this->assertFalse( $this->store->getSession( $id ) );
+               $this->assertSame( [
+                       [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
+               ], $logger->getBuffer() );
+               $logger->clearBuffer();
        }
 }
index 18b1efd..f80baf2 100644 (file)
@@ -27,6 +27,8 @@ class SessionProviderTest extends MediaWikiTestCase {
                $this->assertSame( $manager, $priv->manager );
                $this->assertSame( $manager, $provider->getManager() );
 
+               $provider->invalidateSessionsForUser( new \User );
+
                $this->assertSame( [], $provider->getVaryHeaders() );
                $this->assertSame( [], $provider->getVaryCookies() );
                $this->assertSame( null, $provider->suggestLoginUsername( new \FauxRequest ) );
index e321bdb..40065f5 100644 (file)
@@ -179,6 +179,36 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $actual );
        }
 
+       public static function provideGetPrefixedDBkey() {
+               return [
+                       [ NS_MAIN, 'Foo_Bar', '', '', 'en', 'Foo_Bar' ],
+                       [ NS_USER, 'Hansi_Maier', 'stuff_and_so_on', '', 'en', 'User:Hansi_Maier' ],
+
+                       // No capitalization or normalization is applied while formatting!
+                       [ NS_USER_TALK, 'hansi__maier', '', '', 'en', 'User_talk:hansi__maier' ],
+
+                       // getGenderCache() provides a mock that considers first
+                       // names ending in "a" to be female.
+                       [ NS_USER, 'Lisa_Müller', '', '', 'de', 'Benutzerin:Lisa_Müller' ],
+
+                       [ NS_MAIN, 'Remote_page', '', 'remotetestiw', 'en', 'remotetestiw:Remote_page' ]
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetPrefixedDBkey
+        */
+       public function testGetPrefixedDBkey( $namespace, $dbkey, $fragment,
+               $interwiki, $lang, $expected
+       ) {
+               $codec = $this->makeCodec( $lang );
+               $title = new TitleValue( $namespace, $dbkey, $fragment, $interwiki );
+
+               $actual = $codec->getPrefixedDBkey( $title );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
        public static function provideGetFullText() {
                return [
                        [ NS_MAIN, 'Foo_Bar', '', 'en', 'Foo Bar' ],
index 7922553..4dbda74 100644 (file)
@@ -42,6 +42,7 @@ class TitleValueTest extends MediaWikiTestCase {
                $title = new TitleValue( $ns, $text, $fragment, $interwiki );
 
                $this->assertEquals( $ns, $title->getNamespace() );
+               $this->assertTrue( $title->inNamespace( $ns ) );
                $this->assertEquals( $text, $title->getText() );
                $this->assertEquals( $fragment, $title->getFragment() );
                $this->assertEquals( $hasFragment, $title->hasFragment() );