Merge "User namespace localisation update for Slovak"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 23 Aug 2016 12:49:43 +0000 (12:49 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 23 Aug 2016 12:49:43 +0000 (12:49 +0000)
99 files changed:
RELEASE-NOTES-1.28
autoload.php
docs/database.txt
includes/Block.php
includes/DefaultSettings.php
includes/FileDeleteForm.php
includes/Html.php
includes/MWTimestamp.php
includes/MovePage.php
includes/OutputPage.php
includes/Pingback.php
includes/Title.php
includes/api/ApiParse.php
includes/api/ApiQueryUsers.php
includes/api/ApiUpload.php
includes/api/i18n/ja.json
includes/api/i18n/lt.json
includes/db/Database.php
includes/db/DatabaseMysqlBase.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LoadBalancer.php
includes/debug/MWDebug.php
includes/deferred/DataUpdate.php
includes/deferred/EnqueueableDataUpdate.php [new file with mode: 0644]
includes/deferred/LinksUpdate.php
includes/deferred/SqlDataUpdate.php
includes/filebackend/lockmanager/DBLockManager.php
includes/filebackend/lockmanager/MySqlLockManager.php [new file with mode: 0644]
includes/filebackend/lockmanager/PostgreSqlLockManager.php [new file with mode: 0644]
includes/filerepo/file/LocalFile.php
includes/htmlform/HTMLFormElement.php [new file with mode: 0644]
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLComboboxField.php
includes/htmlform/fields/HTMLRadioField.php
includes/htmlform/fields/HTMLSelectField.php
includes/htmlform/fields/HTMLSelectNamespace.php
includes/htmlform/fields/HTMLTitleTextField.php
includes/htmlform/fields/HTMLUserTextField.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobRunner.php
includes/jobqueue/utils/PurgeJobUtils.php [new file with mode: 0644]
includes/libs/virtualrest/VirtualRESTServiceClient.php
includes/objectcache/SqlBagOStuff.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/specialpage/AuthManagerSpecialPage.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specials/SpecialChangeCredentials.php
includes/specials/SpecialExport.php
includes/specials/SpecialMovepage.php
includes/user/User.php
includes/utils/BatchRowWriter.php
languages/i18n/azb.json
languages/i18n/fr.json
languages/i18n/hr.json
languages/i18n/ilo.json
languages/i18n/jv.json
languages/i18n/lb.json
languages/i18n/lt.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/ru.json
languages/i18n/sah.json
languages/i18n/sk.json
languages/i18n/ur.json
languages/i18n/zh-hant.json
maintenance/namespaceDupes.php
resources/Resources.php
resources/src/jquery/jquery.makeCollapsible.js
resources/src/mediawiki.special/mediawiki.special.movePage.js
resources/src/mediawiki.ui/components/icons.less
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less
resources/src/mediawiki/htmlform/autocomplete.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/autoinfuse.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/checkmatrix.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/cloner.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/hide-if.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/htmlform.Element.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/htmlform.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/images/question.png [new file with mode: 0644]
resources/src/mediawiki/htmlform/images/question.svg [new file with mode: 0644]
resources/src/mediawiki/htmlform/multiselect.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/ooui.styles.css [new file with mode: 0644]
resources/src/mediawiki/htmlform/selectandother.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/selectorother.js [new file with mode: 0644]
resources/src/mediawiki/htmlform/styles.css [new file with mode: 0644]
resources/src/mediawiki/images/question.png [deleted file]
resources/src/mediawiki/images/question.svg [deleted file]
resources/src/mediawiki/mediawiki.htmlform.css [deleted file]
resources/src/mediawiki/mediawiki.htmlform.js [deleted file]
resources/src/mediawiki/mediawiki.htmlform.ooui.css [deleted file]
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/page/ready.js
tests/parser/parserTests.txt
tests/phpunit/includes/libs/objectcache/CachedBagOStuffTest.php
tests/phpunit/includes/libs/objectcache/HashBagOStuffTest.php
tests/phpunit/includes/libs/objectcache/MultiWriteBagOStuffTest.php
tests/phpunit/includes/user/UserTest.php

index eac4e2c..865e300 100644 (file)
@@ -52,6 +52,16 @@ production.
 ==== Removed and replaced external libraries ====
 
 === Bug fixes in 1.28 ===
+* (T137264) SECURITY: XSS in unclosed internal links
+* (T133147) SECURITY: Escape '<' and ']]>' in inline <style> blocks
+* (T133147) SECURITY: Require login to preview user CSS pages
+* (T132926) SECURITY: Do not allow undeleting a revision deleted file if it is
+  the top file
+* (T129738) SECURITY: Make $wgBlockDisablesLogin also restrict logged in
+  permissions
+* (T129738) SECURITY: Make blocks log users out if $wgBlockDisablesLogin is true
+* (T139670) Move 'UserGetRights' call before application of
+  Session::getAllowedUserRights()
 
 === Action API changes in 1.28 ===
 * Added 'maxarticlesize' property to action=query&meta=siteinfo which contains
@@ -72,6 +82,8 @@ production.
 === Action API internal changes in 1.28 ===
 * Added a new hook, 'ApiMakeParserOptions', to allow extensions to better
   interact with ApiParse and ApiExpandTemplates.
+* (T139565) SECURITY: API: Generate head items in the context of the given title
+* (T115333) SECURITY: Check read permission when loading page content in ApiParse
 
 === Languages updated in 1.28 ===
 
@@ -118,6 +130,9 @@ changes to languages because of Phabricator reports.
 * AuthenticationRequest::$required is now changed from REQUIRED to PRIMARY_REQUIRED
   on requests needed by primary providers even if all primaries need them.
   Primary providers are discouraged from returning multiple REQUIRED requests.
+* OOjs UI PHP widgets constructed with the `'infusable' => true` config option
+  will no longer be automatically infused. You should call `OO.ui.infuse()`
+  on them yourself from your JavaScript code.
 
 == Compatibility ==
 
index e37a011..0d19056 100644 (file)
@@ -408,7 +408,7 @@ $wgAutoloadLocalClasses = [
        'EnhancedChangesList' => __DIR__ . '/includes/changes/EnhancedChangesList.php',
        'EnotifNotifyJob' => __DIR__ . '/includes/jobqueue/jobs/EnotifNotifyJob.php',
        'EnqueueJob' => __DIR__ . '/includes/jobqueue/jobs/EnqueueJob.php',
-       'EnqueueableDataUpdate' => __DIR__ . '/includes/deferred/DataUpdate.php',
+       'EnqueueableDataUpdate' => __DIR__ . '/includes/deferred/EnqueueableDataUpdate.php',
        'EraseArchivedFile' => __DIR__ . '/maintenance/eraseArchivedFile.php',
        'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php',
        'EventRelayer' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php',
@@ -527,8 +527,11 @@ $wgAutoloadLocalClasses = [
        'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php',
        'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php',
        'HTMLForm' => __DIR__ . '/includes/htmlform/HTMLForm.php',
+       'HTMLFormActionFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
+       'HTMLFormElement' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
        'HTMLFormField' => __DIR__ . '/includes/htmlform/HTMLFormField.php',
        'HTMLFormFieldCloner' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldCloner.php',
+       'HTMLFormFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
        'HTMLFormFieldRequiredOptionsException' => __DIR__ . '/includes/htmlform/HTMLFormFieldRequiredOptionsException.php',
        'HTMLFormFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldWithButton.php',
        'HTMLHiddenField' => __DIR__ . '/includes/htmlform/fields/HTMLHiddenField.php',
@@ -950,7 +953,7 @@ $wgAutoloadLocalClasses = [
        'MwSql' => __DIR__ . '/maintenance/sql.php',
        'MySQLField' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
        'MySQLMasterPos' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
-       'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
+       'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
        'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
        'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
        'NaiveForeignTitleFactory' => __DIR__ . '/includes/title/NaiveForeignTitleFactory.php',
@@ -1058,7 +1061,7 @@ $wgAutoloadLocalClasses = [
        'PopulateRecentChangesSource' => __DIR__ . '/maintenance/populateRecentChangesSource.php',
        'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
        'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
-       'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
+       'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
        'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
        'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
        'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
@@ -1093,6 +1096,7 @@ $wgAutoloadLocalClasses = [
        'PurgeAction' => __DIR__ . '/includes/actions/PurgeAction.php',
        'PurgeChangedFiles' => __DIR__ . '/maintenance/purgeChangedFiles.php',
        'PurgeChangedPages' => __DIR__ . '/maintenance/purgeChangedPages.php',
+       'PurgeJobUtils' => __DIR__ . '/includes/jobqueue/utils/PurgeJobUtils.php',
        'PurgeList' => __DIR__ . '/maintenance/purgeList.php',
        'PurgeOldText' => __DIR__ . '/maintenance/purgeOldText.php',
        'PurgeParserCache' => __DIR__ . '/maintenance/purgeParserCache.php',
index ba3045e..44ec764 100644 (file)
@@ -8,7 +8,7 @@ By Tim Starling, January 2006.
 For information about the MediaWiki database layout, such as a 
 description of the tables and their contents, please see:
   https://www.mediawiki.org/wiki/Manual:Database_layout
-  https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/core.git;a=blob_plain;f=maintenance/tables.sql;hb=HEAD
+  https://phabricator.wikimedia.org/diffusion/MW/browse/master/maintenance/tables.sql
 
 
 ------------------------------------------------------------------------
index 93df004..79b31bb 100644 (file)
@@ -457,6 +457,7 @@ class Block {
         *      ('id' => block ID, 'autoIds' => array of autoblock IDs)
         */
        public function insert( $dbw = null ) {
+               global $wgBlockDisablesLogin;
                wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
 
                if ( $dbw === null ) {
@@ -499,6 +500,13 @@ class Block {
 
                if ( $affected ) {
                        $auto_ipd_ids = $this->doRetroactiveAutoblock();
+
+                       if ( $wgBlockDisablesLogin && $this->target instanceof User ) {
+                               // Change user login token to force them to be logged out.
+                               $this->target->setToken();
+                               $this->target->saveSettings();
+                       }
+
                        return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
                }
 
@@ -961,28 +969,40 @@ class Block {
 
        /**
         * Get/set whether the Block prevents a given action
-        * @param string $action
-        * @param bool|null $x
-        * @return bool
+        *
+        * @param string $action Action to check
+        * @param bool|null $x Value for set, or null to just get value
+        * @return bool|null Null for unrecognized rights.
         */
        public function prevents( $action, $x = null ) {
+               global $wgBlockDisablesLogin;
+               $res = null;
                switch ( $action ) {
                        case 'edit':
                                # For now... <evil laugh>
-                               return true;
-
+                               $res = true;
+                               break;
                        case 'createaccount':
-                               return wfSetVar( $this->mCreateAccount, $x );
-
+                               $res = wfSetVar( $this->mCreateAccount, $x );
+                               break;
                        case 'sendemail':
-                               return wfSetVar( $this->mBlockEmail, $x );
-
+                               $res = wfSetVar( $this->mBlockEmail, $x );
+                               break;
                        case 'editownusertalk':
-                               return wfSetVar( $this->mDisableUsertalk, $x );
-
-                       default:
-                               return null;
+                               $res = wfSetVar( $this->mDisableUsertalk, $x );
+                               break;
+                       case 'read':
+                               $res = false;
+                               break;
                }
+               if ( !$res && $wgBlockDisablesLogin ) {
+                       // If a block would disable login, then it should
+                       // prevent any action that all users cannot do
+                       $anon = new User;
+                       $res = $anon->isAllowed( $action ) ? $res : true;
+               }
+
+               return $res;
        }
 
        /**
index 4e08aa6..cbf98a3 100644 (file)
@@ -685,7 +685,7 @@ $wgUseSharedUploads = false;
 /**
  * Full path on the web server where shared uploads can be found
  */
-$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
+$wgSharedUploadPath = null;
 
 /**
  * Fetch commons image description pages and display them on the local wiki?
@@ -695,7 +695,7 @@ $wgFetchCommonsDescriptions = false;
 /**
  * Path on the file system where shared uploads can be found.
  */
-$wgSharedUploadDirectory = "/var/www/wiki3/images";
+$wgSharedUploadDirectory = null;
 
 /**
  * DB name with metadata about shared directory.
@@ -8342,7 +8342,7 @@ $wgEventRelayerConfig = [
  * PHP version, and chosen database backend. The Wikimedia Foundation shares this data with
  * MediaWiki developers to help guide future development efforts.
  *
- * For details about what data is sent, see: https://www.mediawiki.org/wiki/Pingback
+ * For details about what data is sent, see: https://www.mediawiki.org/wiki/Manual:$wgPingback
  *
  * @var bool
  * @since 1.28
index 361058b..65638f2 100644 (file)
@@ -189,31 +189,24 @@ class FileDeleteForm {
                        );
                        $page = WikiPage::factory( $title );
                        $dbw = wfGetDB( DB_MASTER );
-                       try {
-                               $dbw->startAtomic( __METHOD__ );
-                               // delete the associated article first
-                               $error = '';
-                               $deleteStatus = $page->doDeleteArticleReal( $reason, $suppress, 0, false, $error, $user );
-                               // doDeleteArticleReal() returns a non-fatal error status if the page
-                               // or revision is missing, so check for isOK() rather than isGood()
-                               if ( $deleteStatus->isOK() ) {
-                                       $status = $file->delete( $reason, $suppress, $user );
-                                       if ( $status->isOK() ) {
-                                               $status->value = $deleteStatus->value; // log id
-                                               $dbw->endAtomic( __METHOD__ );
-                                       } else {
-                                               // Page deleted but file still there? rollback page delete
-                                               wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
-                                       }
-                               } else {
-                                       // Done; nothing changed
+                       $dbw->startAtomic( __METHOD__ );
+                       // delete the associated article first
+                       $error = '';
+                       $deleteStatus = $page->doDeleteArticleReal( $reason, $suppress, 0, false, $error, $user );
+                       // doDeleteArticleReal() returns a non-fatal error status if the page
+                       // or revision is missing, so check for isOK() rather than isGood()
+                       if ( $deleteStatus->isOK() ) {
+                               $status = $file->delete( $reason, $suppress, $user );
+                               if ( $status->isOK() ) {
+                                       $status->value = $deleteStatus->value; // log id
                                        $dbw->endAtomic( __METHOD__ );
+                               } else {
+                                       // Page deleted but file still there? rollback page delete
+                                       wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
                                }
-                       } catch ( Exception $e ) {
-                               // Rollback before returning to prevent UI from displaying
-                               // incorrect "View or restore N deleted edits?"
-                               $dbw->rollback( __METHOD__ );
-                               throw $e;
+                       } else {
+                               // Done; nothing changed
+                               $dbw->endAtomic( __METHOD__ );
                        }
                }
 
index 7cb75bb..8c01448 100644 (file)
@@ -627,6 +627,17 @@ class Html {
         * @return string Raw HTML
         */
        public static function inlineStyle( $contents, $media = 'all' ) {
+               // Don't escape '>' since that is used
+               // as direct child selector.
+               // Remember, in css, there is no "x" for hexadecimal escapes, and
+               // the space immediately after an escape sequence is swallowed.
+               $contents = strtr( $contents, [
+                       '<' => '\3C ',
+                       // CDATA end tag for good measure, but the main security
+                       // is from escaping the '<'.
+                       ']]>' => '\5D\5D\3E '
+               ] );
+
                if ( preg_match( '/[<&]/', $contents ) ) {
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
                }
index 4964fe6..defdc96 100644 (file)
@@ -56,10 +56,14 @@ class MWTimestamp {
         *
         * @since 1.20
         *
-        * @param bool|string|int|float $timestamp Timestamp to set, or false for current time
+        * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
         */
        public function __construct( $timestamp = false ) {
-               $this->setTimestamp( $timestamp );
+               if ( $timestamp instanceof DateTime ) {
+                       $this->timestamp = $timestamp;
+               } else {
+                       $this->setTimestamp( $timestamp );
+               }
        }
 
        /**
index bc3305a..d17f234 100644 (file)
@@ -297,19 +297,25 @@ class MovePage {
 
                if ( $protected ) {
                        # Protect the redirect title as the title used to be...
-                       $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
-                               [
-                                       'pr_page' => $redirid,
-                                       'pr_type' => 'pr_type',
-                                       'pr_level' => 'pr_level',
-                                       'pr_cascade' => 'pr_cascade',
-                                       'pr_user' => 'pr_user',
-                                       'pr_expiry' => 'pr_expiry'
-                               ],
+                       $res = $dbw->select(
+                               'page_restrictions',
+                               '*',
                                [ 'pr_page' => $pageid ],
                                __METHOD__,
-                               [ 'IGNORE' ]
+                               'FOR UPDATE'
                        );
+                       $rowsInsert = [];
+                       foreach ( $res as $row ) {
+                               $rowsInsert[] = [
+                                       'pr_page' => $redirid,
+                                       'pr_type' => $row->pr_type,
+                                       'pr_level' => $row->pr_level,
+                                       'pr_cascade' => $row->pr_cascade,
+                                       'pr_user' => $row->pr_user,
+                                       'pr_expiry' => $row->pr_expiry
+                               ];
+                       }
+                       $dbw->insert( 'page_restrictions', $rowsInsert, __METHOD__, [ 'IGNORE' ] );
 
                        // Build comment for log
                        $comment = wfMessage(
index eb3040c..c7499b1 100644 (file)
@@ -2852,7 +2852,6 @@ class OutputPage extends ContextSource {
 
        private function isUserJsPreview() {
                return $this->getConfig()->get( 'AllowUserJs' )
-                       && $this->getUser()->isLoggedIn()
                        && $this->getTitle()
                        && $this->getTitle()->isJsSubpage()
                        && $this->userCanPreview();
@@ -2860,7 +2859,6 @@ class OutputPage extends ContextSource {
 
        private function isUserCssPreview() {
                return $this->getConfig()->get( 'AllowUserCss' )
-                       && $this->getUser()->isLoggedIn()
                        && $this->getTitle()
                        && $this->getTitle()->isCssSubpage()
                        && $this->userCanPreview();
@@ -3097,6 +3095,11 @@ class OutputPage extends ContextSource {
                }
 
                $user = $this->getUser();
+
+               if ( !$user->isLoggedIn() ) {
+                       // Anons have predictable edit tokens
+                       return false;
+               }
                if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
                        return false;
                }
index dd68102..0039d2b 100644 (file)
@@ -117,6 +117,9 @@ class Pingback {
         *
         * This is public so we can display it in the installer
         *
+        * Developers: If you're adding a new piece of data to this, please ensure
+        * that you update https://www.mediawiki.org/wiki/Manual:$wgPingback
+        *
         * @return array
         */
        public function getSystemInfo() {
index ed445cc..2021e0a 100644 (file)
@@ -2271,13 +2271,17 @@ class Title implements LinkTarget {
         * @return array List of errors
         */
        private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
+               global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
                // Account creation blocks handled at userlogin.
                // Unblocking handled in SpecialUnblock
                if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
                        return $errors;
                }
 
-               global $wgEmailConfirmToEdit;
+               // Optimize for a very common case
+               if ( $action === 'read' && !$wgBlockDisablesLogin ) {
+                       return $errors;
+               }
 
                if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
                        $errors[] = [ 'confirmedittext' ];
@@ -2434,6 +2438,7 @@ class Title implements LinkTarget {
                        $checks = [
                                'checkPermissionHooks',
                                'checkReadPermissions',
+                               'checkUserBlock', // for wgBlockDisablesLogin
                        ];
                # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
                # here as it will lead to duplicate error messages. This is okay to do
index 35fad4a..83b5d93 100644 (file)
@@ -36,6 +36,12 @@ class ApiParse extends ApiBase {
        /** @var Content $pstContent */
        private $pstContent = null;
 
+       private function checkReadPermissions( Title $title ) {
+               if ( !$title->userCan( 'read', $this->getUser() ) ) {
+                       $this->dieUsage( "You don't have permission to view this page", 'permissiondenied' );
+               }
+       }
+
        public function execute() {
                // The data is hot but user-dependent, like page views, so we set vary cookies
                $this->getMain()->setCacheMode( 'anon-public-user-private' );
@@ -102,6 +108,8 @@ class ApiParse extends ApiBase {
                                if ( !$rev ) {
                                        $this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
                                }
+
+                               $this->checkReadPermissions( $rev->getTitle() );
                                if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
                                        $this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
                                }
@@ -161,6 +169,8 @@ class ApiParse extends ApiBase {
                                if ( !$titleObj || !$titleObj->exists() ) {
                                        $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
                                }
+
+                               $this->checkReadPermissions( $titleObj );
                                $wgTitle = $titleObj;
 
                                if ( isset( $prop['revid'] ) ) {
index cfc1e46..9b45b91 100644 (file)
@@ -328,7 +328,6 @@ class ApiQueryUsers extends ApiQueryBase {
                        ],
                        'attachedwiki' => null,
                        'users' => [
-                               ApiBase::PARAM_TYPE => 'user',
                                ApiBase::PARAM_ISMULTI => true
                        ],
                        'token' => [
index ac817ba..f7ce552 100644 (file)
@@ -405,6 +405,7 @@ class ApiUpload extends ApiBase {
                        $code = $overrideCode;
                }
                if ( $moreExtraData ) {
+                       $extraData = $extraData ?: [];
                        $extraData += $moreExtraData;
                }
                $this->dieUsage( $msg, $code, 0, $extraData );
index 2712c13..d3ef0e4 100644 (file)
@@ -8,7 +8,8 @@
                        "Mfuji",
                        "Otokoume",
                        "Sujiniku",
-                       "Macofe"
+                       "Macofe",
+                       "Suchichi02"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|説明文書]]\n* [[mw:API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n</div>\n<strong>状態:</strong> このページに表示されている機能は全て動作するはずですが、この API は未だ活発に開発されており、変更される可能性があります。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n<strong>誤ったリクエスト:</strong> 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n<strong>テスト:</strong> API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。",
        "apihelp-query+watchlist-param-prop": "追加で取得するプロパティ:",
        "apihelp-query+watchlist-paramvalue-prop-ids": "版IDとページIDを追加します。",
        "apihelp-query+watchlist-paramvalue-prop-title": "ページ名を追加します。",
+       "apihelp-query+watchlist-paramvalue-prop-flags": "編集のフラグを追加します。",
        "apihelp-query+watchlist-paramvalue-prop-comment": "編集のコメントを追加します。",
        "apihelp-query+watchlist-paramvalue-prop-parsedcomment": "編集の構文解析されたコメントを追加します。",
        "apihelp-query+watchlist-paramvalue-prop-timestamp": "編集のタイムスタンプを追加します。",
index 8a069c0..7970800 100644 (file)
        "apihelp-edit-param-text": "Puslapio turinys.",
        "apihelp-edit-param-minor": "Smulkus pakeitimas.",
        "apihelp-edit-param-notminor": "Nesmulkus pakeitimas.",
+       "apihelp-edit-param-redirect": "Automatiškai išspręsti peradresavimus.",
+       "apihelp-edit-example-edit": "Redaguoti puslapį.",
        "apihelp-emailuser-description": "Siųsti el. laišką naudotojui.",
        "apihelp-expandtemplates-param-title": "Puslapio pavadinimas.",
+       "apihelp-feedrecentchanges-param-hideminor": "Slėpti smulkius pakeitimus.",
+       "apihelp-feedrecentchanges-param-hidebots": "Slėpti robotų pakeitimus.",
+       "apihelp-feedrecentchanges-param-hideanons": "Slėpti vartotojų anonimų pakeitimus.",
+       "apihelp-feedrecentchanges-param-hideliu": "Slėpti užsiregistravusių vartotojų pakeitimus.",
+       "apihelp-feedrecentchanges-param-hidemyself": "Slėpti pakeitimus, atliktus dabartinio vartotojo.",
+       "apihelp-feedrecentchanges-param-tagfilter": "Filtruoti pagal žymę.",
        "apihelp-feedrecentchanges-example-simple": "Parodyti naujausius keitimus.",
+       "apihelp-filerevert-param-comment": "Įkėlimo komentaras.",
+       "apihelp-import-param-xml": "XML failas įkeltas.",
+       "apihelp-login-param-name": "Vartotojo vardas.",
+       "apihelp-login-param-password": "Slaptažodis.",
+       "apihelp-login-param-domain": "Domenas (neprivaloma).",
        "apihelp-query+alldeletedrevisions-example-user": "Sąrašas paskutinių 50 ištrintų indėlių pagal vartotoją\n<kbd>Pavyzdys</kbd>.",
        "apihelp-query+allrevisions-param-namespace": "Rodyti puslapius tik šioje vardų srityje.",
        "apihelp-query+backlinks-example-simple": "Rodyti nuorodas <kbd>Pagrindinis puslapis</kbd>.",
index 528a298..6ddc9f7 100644 (file)
 abstract class DatabaseBase implements IDatabase {
        /** Number of times to re-try an operation in case of deadlock */
        const DEADLOCK_TRIES = 4;
-
        /** Minimum time to wait before retry, in microseconds */
        const DEADLOCK_DELAY_MIN = 500000;
-
        /** Maximum time to wait before retry */
        const DEADLOCK_DELAY_MAX = 1500000;
 
+       /** How long before it is worth doing a dummy query to test the connection */
+       const PING_TTL = 1.0;
+
+       /** @var string SQL query */
        protected $mLastQuery = '';
+       /** @var bool */
        protected $mDoneWrites = false;
+       /** @var string|bool */
        protected $mPHPError = false;
-
-       protected $mServer, $mUser, $mPassword, $mDBname;
+       /** @var string */
+       protected $mServer;
+       /** @var string */
+       protected $mUser;
+       /** @var string */
+       protected $mPassword;
+       /** @var string */
+       protected $mDBname;
 
        /** @var BagOStuff APC cache */
        protected $srvCache;
 
        /** @var resource Database connection */
        protected $mConn = null;
+       /** @var bool */
        protected $mOpened = false;
 
        /** @var array[] List of (callable, method name) */
@@ -61,20 +72,27 @@ abstract class DatabaseBase implements IDatabase {
        /** @var bool Whether to suppress triggering of post-commit callbacks */
        protected $suppressPostCommitCallbacks = false;
 
+       /** @var string */
        protected $mTablePrefix;
+       /** @var string */
        protected $mSchema;
+       /** @var integer */
        protected $mFlags;
+       /** @var bool */
        protected $mForeign;
+       /** @var array */
        protected $mLBInfo = [];
+       /** @var bool|null */
        protected $mDefaultBigSelects = null;
+       /** @var array|bool */
        protected $mSchemaVars = false;
        /** @var array */
        protected $mSessionVars = [];
-
+       /** @var array|null */
        protected $preparedArgs;
-
+       /** @var string|bool|null Stashed value of html_errors INI setting */
        protected $htmlErrors;
-
+       /** @var string */
        protected $delimiter = ';';
 
        /**
@@ -177,6 +195,9 @@ abstract class DatabaseBase implements IDatabase {
         */
        protected $allViews = null;
 
+       /** @var float UNIX timestamp */
+       protected $lastPing = 0.0;
+
        /** @var TransactionProfiler */
        protected $trxProfiler;
 
@@ -786,8 +807,8 @@ abstract class DatabaseBase implements IDatabase {
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
 
-               $isWriteQuery = $this->isWriteQuery( $sql );
-               if ( $isWriteQuery ) {
+               $isWrite = $this->isWriteQuery( $sql );
+               if ( $isWrite ) {
                        $reason = $this->getReadOnlyReason();
                        if ( $reason !== false ) {
                                throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
@@ -820,49 +841,21 @@ abstract class DatabaseBase implements IDatabase {
                }
 
                # Keep track of whether the transaction has write queries pending
-               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
+               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
                        $this->mTrxDoneWrites = true;
                        $this->getTransactionProfiler()->transactionWritingIn(
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
                }
 
-               $isMaster = !is_null( $this->getLBInfo( 'master' ) );
-               # generalizeSQL will probably cut down the query to reasonable
-               # logging size most of the time. The substr is really just a sanity check.
-               if ( $isMaster ) {
-                       $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-                       $totalProf = 'DatabaseBase::query-master';
-               } else {
-                       $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-                       $totalProf = 'DatabaseBase::query';
-               }
-               # Include query transaction state
-               $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
-
-               $profiler = Profiler::instance();
-               if ( !$profiler instanceof ProfilerStub ) {
-                       $totalProfSection = $profiler->scopedProfileIn( $totalProf );
-                       $queryProfSection = $profiler->scopedProfileIn( $queryProf );
-               }
-
                if ( $this->debug() ) {
                        wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $commentedSql ) );
                }
 
-               $queryId = MWDebug::query( $sql, $fname, $isMaster );
-
                # Avoid fatals if close() was called
                $this->assertOpen();
 
-               # Do the query and handle errors
-               $startTime = microtime( true );
-               $ret = $this->doQuery( $commentedSql );
-               $queryRuntime = microtime( true ) - $startTime;
-               # Log the query time and feed it into the DB trx profiler
-               $this->getTransactionProfiler()->recordQueryCompletion(
-                       $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
-
-               MWDebug::queryTime( $queryId );
+               # Send the query to the server
+               $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
 
                # Try reconnecting if the connection was lost
                if ( false === $ret && $this->wasErrorReissuable() ) {
@@ -883,12 +876,7 @@ abstract class DatabaseBase implements IDatabase {
                                        $this->reportQueryError( $lastError, $lastErrno, $sql, $fname );
                                } else {
                                        # Should be safe to silently retry the query
-                                       $startTime = microtime( true );
-                                       $ret = $this->doQuery( $commentedSql );
-                                       $queryRuntime = microtime( true ) - $startTime;
-                                       # Log the query time and feed it into the DB trx profiler
-                                       $this->getTransactionProfiler()->recordQueryCompletion(
-                                               $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
+                                       $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
                                }
                        } else {
                                wfDebug( "Failed\n" );
@@ -913,16 +901,47 @@ abstract class DatabaseBase implements IDatabase {
 
                $res = $this->resultObject( $ret );
 
-               // Destroy profile sections in the opposite order to their creation
-               ScopedCallback::consume( $queryProfSection );
-               ScopedCallback::consume( $totalProfSection );
+               return $res;
+       }
 
-               if ( $isWriteQuery && $this->mTrxLevel ) {
-                       $this->mTrxWriteDuration += $queryRuntime;
-                       $this->mTrxWriteCallers[] = $fname;
+       private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
+               $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+               # generalizeSQL() will probably cut down the query to reasonable
+               # logging size most of the time. The substr is really just a sanity check.
+               if ( $isMaster ) {
+                       $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+               } else {
+                       $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
                }
 
-               return $res;
+               # Include query transaction state
+               $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
+
+               $profiler = Profiler::instance();
+               if ( !( $profiler instanceof ProfilerStub ) ) {
+                       $queryProfSection = $profiler->scopedProfileIn( $queryProf );
+               }
+
+               $startTime = microtime( true );
+               $ret = $this->doQuery( $commentedSql );
+               $queryRuntime = microtime( true ) - $startTime;
+
+               unset( $queryProfSection ); // profile out (if set)
+
+               if ( $ret !== false ) {
+                       $this->lastPing = $startTime;
+                       if ( $isWrite && $this->mTrxLevel ) {
+                               $this->mTrxWriteDuration += $queryRuntime;
+                               $this->mTrxWriteCallers[] = $fname;
+                       }
+               }
+
+               $this->getTransactionProfiler()->recordQueryCompletion(
+                       $queryProf, $startTime, $isWrite, $this->affectedRows()
+               );
+               MWDebug::query( $sql, $fname, $isMaster, $queryRuntime );
+
+               return $ret;
        }
 
        private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
@@ -2559,7 +2578,7 @@ abstract class DatabaseBase implements IDatabase {
 
                $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
                /** @var Exception $e */
-               $e = $ePrior = null; // last exception
+               $e = null; // first exception
                do { // callbacks may add callbacks :)
                        $callbacks = array_merge(
                                $this->mTrxIdleCallbacks,
@@ -2577,11 +2596,9 @@ abstract class DatabaseBase implements IDatabase {
                                        } else {
                                                $this->clearFlag( DBO_TRX ); // restore auto-commit
                                        }
-                               } catch ( Exception $e ) {
-                                       if ( $ePrior ) {
-                                               MWExceptionHandler::logException( $ePrior );
-                                       }
-                                       $ePrior = $e;
+                               } catch ( Exception $ex ) {
+                                       MWExceptionHandler::logException( $ex );
+                                       $e = $e ?: $ex;
                                        // Some callbacks may use startAtomic/endAtomic, so make sure
                                        // their transactions are ended so other callbacks don't fail
                                        if ( $this->trxLevel() ) {
@@ -2592,7 +2609,7 @@ abstract class DatabaseBase implements IDatabase {
                } while ( count( $this->mTrxIdleCallbacks ) );
 
                if ( $e instanceof Exception ) {
-                       throw $e; // re-throw any last exception
+                       throw $e; // re-throw any first exception
                }
        }
 
@@ -2602,9 +2619,10 @@ abstract class DatabaseBase implements IDatabase {
         * This method should not be used outside of Database/LoadBalancer
         *
         * @since 1.22
+        * @throws Exception
         */
        public function runOnTransactionPreCommitCallbacks() {
-               $e = $ePrior = null; // last exception
+               $e = null; // first exception
                do { // callbacks may add callbacks :)
                        $callbacks = $this->mTrxPreCommitCallbacks;
                        $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
@@ -2612,17 +2630,15 @@ abstract class DatabaseBase implements IDatabase {
                                try {
                                        list( $phpCallback ) = $callback;
                                        call_user_func( $phpCallback );
-                               } catch ( Exception $e ) {
-                                       if ( $ePrior ) {
-                                               MWExceptionHandler::logException( $ePrior );
-                                       }
-                                       $ePrior = $e;
+                               } catch ( Exception $ex ) {
+                                       MWExceptionHandler::logException( $ex );
+                                       $e = $e ?: $ex;
                                }
                        }
                } while ( count( $this->mTrxPreCommitCallbacks ) );
 
                if ( $e instanceof Exception ) {
-                       throw $e; // re-throw any last exception
+                       throw $e; // re-throw any first exception
                }
        }
 
@@ -2931,6 +2947,9 @@ abstract class DatabaseBase implements IDatabase {
        }
 
        public function ping() {
+               if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
+                       return true;
+               }
                try {
                        // This will reconnect if possible, or error out if not
                        $this->query( "SELECT 1 AS ping", __METHOD__ );
@@ -2944,8 +2963,18 @@ abstract class DatabaseBase implements IDatabase {
         * @return bool
         */
        protected function reconnect() {
-               # Stub. Not essential to override.
-               return true;
+               $this->closeConnection();
+               $this->mOpened = false;
+               $this->mConn = false;
+               try {
+                       $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+                       $this->lastPing = microtime( true );
+                       $ok = true;
+               } catch ( DBConnectionError $e ) {
+                       $ok = false;
+               }
+
+               return $ok;
        }
 
        public function getSessionLagStatus() {
index d1ebe62..fa3756c 100644 (file)
@@ -599,15 +599,6 @@ abstract class DatabaseMysqlBase extends Database {
                return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
        }
 
-       function reconnect() {
-               $this->closeConnection();
-               $this->mOpened = false;
-               $this->mConn = false;
-               $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
-
-               return true;
-       }
-
        function getLag() {
                if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
                        return $this->getLagFromPtHeartbeat();
index efc6148..b320544 100644 (file)
@@ -234,6 +234,7 @@ abstract class LBFactory implements DestructibleService {
         * @param string $fname Caller name
         * @param array $options Options map:
         *   - maxWriteDuration: abort if more than this much time was spent in write queries
+        * @throws Exception
         */
        public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
                // Perform all pre-commit callbacks, aborting on failure
@@ -245,9 +246,18 @@ abstract class LBFactory implements DestructibleService {
                // Actually perform the commit on all master DB connections
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
                // Run all post-commit callbacks
-               $this->forEachLBCallMethod( 'runMasterPostCommitCallbacks' );
+               /** @var Exception $e */
+               $e = null; // first callback exception
+               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$e ) {
+                       $ex = $lb->runMasterPostCommitCallbacks();
+                       $e = $e ?: $ex;
+               } );
                // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
+               // Throw any last post-commit callback error
+               if ( $e instanceof Exception ) {
+                       throw $e;
+               }
        }
 
        /**
index 93ec037..13a0879 100644 (file)
@@ -1090,7 +1090,7 @@ class LoadBalancer {
        public function approveMasterChanges( array $options ) {
                $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $limit ) {
-                       // If atomic section or explicit transactions are still open, some caller must have
+                       // If atomic sections or explicit transactions are still open, some caller must have
                        // caught an exception but failed to properly rollback any changes. Detect that and
                        // throw and error (causing rollback).
                        if ( $conn->explicitTrxActive() ) {
@@ -1108,6 +1108,14 @@ class LoadBalancer {
                                        wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->text()
                                );
                        }
+                       // If a connection sits idle while slow queries execute on another, that connection
+                       // may end up dropped before the commit round is reached. Ping servers to detect this.
+                       if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
+                               throw new DBTransactionError(
+                                       $conn,
+                                       "A connection to the {$conn->getDBname()} database was lost before commit."
+                               );
+                       }
                } );
        }
 
@@ -1125,13 +1133,21 @@ class LoadBalancer {
 
        /**
         * Issue all pending post-commit callbacks
+        * @return Exception|null The first exception or null if there were none
         * @since 1.28
         */
        public function runMasterPostCommitCallbacks() {
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $db ) {
+               $e = null; // first exception
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $db ) use ( &$e ) {
                        $db->setPostCommitCallbackSupression( false );
-                       $db->runOnTransactionIdleCallbacks( IDatabase::TRIGGER_COMMIT );
+                       try {
+                               $db->runOnTransactionIdleCallbacks( IDatabase::TRIGGER_COMMIT );
+                       } catch ( Exception $ex ) {
+                               $e = $e ?: $ex;
+                       }
                } );
+
+               return $e;
        }
 
        /**
index d90ef8a..6ce5829 100644 (file)
@@ -347,10 +347,11 @@ class MWDebug {
         * @param string $sql
         * @param string $function
         * @param bool $isMaster
+        * @param float $runTime Query run time
         * @return int ID number of the query to pass to queryTime or -1 if the
         *  debugger is disabled
         */
-       public static function query( $sql, $function, $isMaster ) {
+       public static function query( $sql, $function, $isMaster, $runTime ) {
                if ( !self::$enabled ) {
                        return -1;
                }
@@ -384,28 +385,12 @@ class MWDebug {
                        'sql' => $sql,
                        'function' => $function,
                        'master' => (bool)$isMaster,
-                       'time' => 0.0,
-                       '_start' => microtime( true ),
+                       'time' => $runTime,
                ];
 
                return count( self::$query ) - 1;
        }
 
-       /**
-        * Calculates how long a query took.
-        *
-        * @since 1.19
-        * @param int $id
-        */
-       public static function queryTime( $id ) {
-               if ( $id === -1 || !self::$enabled ) {
-                       return;
-               }
-
-               self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start'];
-               unset( self::$query[$id]['_start'] );
-       }
-
        /**
         * Returns a list of files included, along with their size
         *
index 5b84ca9..281ac24 100644 (file)
@@ -155,18 +155,3 @@ abstract class DataUpdate implements DeferrableUpdate {
                return $remaining;
        }
 }
-
-/**
- * Interface that marks a DataUpdate as enqueuable via the JobQueue
- *
- * Such updates must be representable using IJobSpecification, so that
- * they can be serialized into jobs and enqueued for later execution
- *
- * @since 1.27
- */
-interface EnqueueableDataUpdate {
-       /**
-        * @return array (wiki => wiki ID, job => IJobSpecification)
-        */
-       public function getAsJobSpecification();
-}
diff --git a/includes/deferred/EnqueueableDataUpdate.php b/includes/deferred/EnqueueableDataUpdate.php
new file mode 100644 (file)
index 0000000..ffeb740
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Interface that marks a DataUpdate as enqueuable via the JobQueue
+ *
+ * Such updates must be representable using IJobSpecification, so that
+ * they can be serialized into jobs and enqueued for later execution
+ *
+ * @since 1.27
+ */
+interface EnqueueableDataUpdate {
+       /**
+        * @return array (wiki => wiki ID, job => IJobSpecification)
+        */
+       public function getAsJobSpecification();
+}
index 4f40c38..5e02c5c 100644 (file)
@@ -304,7 +304,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         * @param array $cats
         */
        function invalidateCategories( $cats ) {
-               $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
+               PurgeJobUtils::invalidatePages( $this->mDb, NS_CATEGORY, array_keys( $cats ) );
        }
 
        /**
@@ -323,7 +323,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         * @param array $images
         */
        function invalidateImageDescriptions( $images ) {
-               $this->invalidatePages( NS_FILE, array_keys( $images ) );
+               PurgeJobUtils::invalidatePages( $this->mDb, NS_FILE, array_keys( $images ) );
        }
 
        /**
index 9740cbe..ff06915 100644 (file)
@@ -98,53 +98,4 @@ abstract class SqlDataUpdate extends DataUpdate {
                        $this->mHasTransaction = false;
                }
        }
-
-       /**
-        * Invalidate the cache of a list of pages from a single namespace.
-        * This is intended for use by subclasses.
-        *
-        * @param int $namespace Namespace number
-        * @param array $dbkeys
-        */
-       protected function invalidatePages( $namespace, array $dbkeys ) {
-               if ( $dbkeys === [] ) {
-                       return;
-               }
-
-               $dbw = $this->mDb;
-               $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $namespace, $dbkeys ) {
-                       /**
-                        * Determine which pages need to be updated
-                        * This is necessary to prevent the job queue from smashing the DB with
-                        * large numbers of concurrent invalidations of the same page
-                        */
-                       $now = $dbw->timestamp();
-                       $ids = $dbw->selectFieldValues( 'page',
-                               'page_id',
-                               [
-                                       'page_namespace' => $namespace,
-                                       'page_title' => $dbkeys,
-                                       'page_touched < ' . $dbw->addQuotes( $now )
-                               ],
-                               __METHOD__
-                       );
-
-                       if ( $ids === [] ) {
-                               return;
-                       }
-
-                       /**
-                        * Do the update
-                        * We still need the page_touched condition, in case the row has changed since
-                        * the non-locking select above.
-                        */
-                       $dbw->update( 'page',
-                               [ 'page_touched' => $now ],
-                               [
-                                       'page_id' => $ids,
-                                       'page_touched < ' . $dbw->addQuotes( $now )
-                               ], __METHOD__
-                       );
-               } );
-       }
 }
index c9aad43..0aefbfa 100644 (file)
@@ -239,205 +239,3 @@ abstract class DBLockManager extends QuorumLockManager {
                }
        }
 }
-
-/**
- * MySQL version of DBLockManager that supports shared locks.
- *
- * All lock servers must have the innodb table defined in locking/filelocks.sql.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class MySqlLockManager extends DBLockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       protected function getLocalLB() {
-               // Use a separate connection so releaseAllLocks() doesn't rollback the main trx
-               return wfGetLBFactory()->newMainLB( $this->domain );
-       }
-
-       protected function initConnection( $lockDb, IDatabase $db ) {
-               # Let this transaction see lock rows from other transactions
-               $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
-       }
-
-       /**
-        * Get a connection to a lock DB and acquire locks on $paths.
-        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
-        *
-        * @see DBLockManager::getLocksOnServer()
-        * @param string $lockSrv
-        * @param array $paths
-        * @param string $type
-        * @return Status
-        */
-       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-
-               $keys = []; // list of hash keys for the paths
-               $data = []; // list of rows to insert
-               $checkEXKeys = []; // list of hash keys that this has no EX lock on
-               # Build up values for INSERT clause
-               foreach ( $paths as $path ) {
-                       $key = $this->sha1Base36Absolute( $path );
-                       $keys[] = $key;
-                       $data[] = [ 'fls_key' => $key, 'fls_session' => $this->session ];
-                       if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
-                               $checkEXKeys[] = $key;
-                       }
-               }
-
-               # Block new writers (both EX and SH locks leave entries here)...
-               $db->insert( 'filelocks_shared', $data, __METHOD__, [ 'IGNORE' ] );
-               # Actually do the locking queries...
-               if ( $type == self::LOCK_SH ) { // reader locks
-                       $blocked = false;
-                       # Bail if there are any existing writers...
-                       if ( count( $checkEXKeys ) ) {
-                               $blocked = $db->selectField( 'filelocks_exclusive', '1',
-                                       [ 'fle_key' => $checkEXKeys ],
-                                       __METHOD__
-                               );
-                       }
-                       # Other prospective writers that haven't yet updated filelocks_exclusive
-                       # will recheck filelocks_shared after doing so and bail due to this entry.
-               } else { // writer locks
-                       $encSession = $db->addQuotes( $this->session );
-                       # Bail if there are any existing writers...
-                       # This may detect readers, but the safe check for them is below.
-                       # Note: if two writers come at the same time, both bail :)
-                       $blocked = $db->selectField( 'filelocks_shared', '1',
-                               [ 'fls_key' => $keys, "fls_session != $encSession" ],
-                               __METHOD__
-                       );
-                       if ( !$blocked ) {
-                               # Build up values for INSERT clause
-                               $data = [];
-                               foreach ( $keys as $key ) {
-                                       $data[] = [ 'fle_key' => $key ];
-                               }
-                               # Block new readers/writers...
-                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
-                               # Bail if there are any existing readers...
-                               $blocked = $db->selectField( 'filelocks_shared', '1',
-                                       [ 'fls_key' => $keys, "fls_session != $encSession" ],
-                                       __METHOD__
-                               );
-                       }
-               }
-
-               if ( $blocked ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               $status = Status::newGood();
-
-               foreach ( $this->conns as $lockDb => $db ) {
-                       if ( $db->trxLevel() ) { // in transaction
-                               try {
-                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
-                               } catch ( DBError $e ) {
-                                       $status->fatal( 'lockmanager-fail-db-release', $lockDb );
-                               }
-                       }
-               }
-
-               return $status;
-       }
-}
-
-/**
- * PostgreSQL version of DBLockManager that supports shared locks.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class PostgreSqlLockManager extends DBLockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-               if ( !count( $paths ) ) {
-                       return $status; // nothing to lock
-               }
-
-               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-               $bigints = array_unique( array_map(
-                       function ( $key ) {
-                               return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
-                       },
-                       array_map( [ $this, 'sha1Base16Absolute' ], $paths )
-               ) );
-
-               // Try to acquire all the locks...
-               $fields = [];
-               foreach ( $bigints as $bigint ) {
-                       $fields[] = ( $type == self::LOCK_SH )
-                               ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
-                               : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
-               }
-               $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
-               $row = $res->fetchRow();
-
-               if ( in_array( 'f', $row ) ) {
-                       // Release any acquired locks if some could not be acquired...
-                       $fields = [];
-                       foreach ( $row as $kbigint => $ok ) {
-                               if ( $ok === 't' ) { // locked
-                                       $bigint = substr( $kbigint, 1 ); // strip off the "K"
-                                       $fields[] = ( $type == self::LOCK_SH )
-                                               ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
-                                               : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
-                               }
-                       }
-                       if ( count( $fields ) ) {
-                               $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
-                       }
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               $status = Status::newGood();
-
-               foreach ( $this->conns as $lockDb => $db ) {
-                       try {
-                               $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
-                       } catch ( DBError $e ) {
-                               $status->fatal( 'lockmanager-fail-db-release', $lockDb );
-                       }
-               }
-
-               return $status;
-       }
-}
diff --git a/includes/filebackend/lockmanager/MySqlLockManager.php b/includes/filebackend/lockmanager/MySqlLockManager.php
new file mode 100644 (file)
index 0000000..c51df16
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/**
+ * MySQL version of DBLockManager that supports shared locks.
+ *
+ * All lock servers must have the innodb table defined in locking/filelocks.sql.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class MySqlLockManager extends DBLockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       protected function getLocalLB() {
+               // Use a separate connection so releaseAllLocks() doesn't rollback the main trx
+               return wfGetLBFactory()->newMainLB( $this->domain );
+       }
+
+       protected function initConnection( $lockDb, IDatabase $db ) {
+               # Let this transaction see lock rows from other transactions
+               $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
+       }
+
+       /**
+        * Get a connection to a lock DB and acquire locks on $paths.
+        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
+        *
+        * @see DBLockManager::getLocksOnServer()
+        * @param string $lockSrv
+        * @param array $paths
+        * @param string $type
+        * @return Status
+        */
+       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+
+               $keys = []; // list of hash keys for the paths
+               $data = []; // list of rows to insert
+               $checkEXKeys = []; // list of hash keys that this has no EX lock on
+               # Build up values for INSERT clause
+               foreach ( $paths as $path ) {
+                       $key = $this->sha1Base36Absolute( $path );
+                       $keys[] = $key;
+                       $data[] = [ 'fls_key' => $key, 'fls_session' => $this->session ];
+                       if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                               $checkEXKeys[] = $key;
+                       }
+               }
+
+               # Block new writers (both EX and SH locks leave entries here)...
+               $db->insert( 'filelocks_shared', $data, __METHOD__, [ 'IGNORE' ] );
+               # Actually do the locking queries...
+               if ( $type == self::LOCK_SH ) { // reader locks
+                       $blocked = false;
+                       # Bail if there are any existing writers...
+                       if ( count( $checkEXKeys ) ) {
+                               $blocked = $db->selectField( 'filelocks_exclusive', '1',
+                                       [ 'fle_key' => $checkEXKeys ],
+                                       __METHOD__
+                               );
+                       }
+                       # Other prospective writers that haven't yet updated filelocks_exclusive
+                       # will recheck filelocks_shared after doing so and bail due to this entry.
+               } else { // writer locks
+                       $encSession = $db->addQuotes( $this->session );
+                       # Bail if there are any existing writers...
+                       # This may detect readers, but the safe check for them is below.
+                       # Note: if two writers come at the same time, both bail :)
+                       $blocked = $db->selectField( 'filelocks_shared', '1',
+                               [ 'fls_key' => $keys, "fls_session != $encSession" ],
+                               __METHOD__
+                       );
+                       if ( !$blocked ) {
+                               # Build up values for INSERT clause
+                               $data = [];
+                               foreach ( $keys as $key ) {
+                                       $data[] = [ 'fle_key' => $key ];
+                               }
+                               # Block new readers/writers...
+                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+                               # Bail if there are any existing readers...
+                               $blocked = $db->selectField( 'filelocks_shared', '1',
+                                       [ 'fls_key' => $keys, "fls_session != $encSession" ],
+                                       __METHOD__
+                               );
+                       }
+               }
+
+               if ( $blocked ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return Status
+        */
+       protected function releaseAllLocks() {
+               $status = Status::newGood();
+
+               foreach ( $this->conns as $lockDb => $db ) {
+                       if ( $db->trxLevel() ) { // in transaction
+                               try {
+                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
+                               } catch ( DBError $e ) {
+                                       $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+                               }
+                       }
+               }
+
+               return $status;
+       }
+}
diff --git a/includes/filebackend/lockmanager/PostgreSqlLockManager.php b/includes/filebackend/lockmanager/PostgreSqlLockManager.php
new file mode 100644 (file)
index 0000000..d55b5ae
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * PostgreSQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class PostgreSqlLockManager extends DBLockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+               if ( !count( $paths ) ) {
+                       return $status; // nothing to lock
+               }
+
+               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+               $bigints = array_unique( array_map(
+                       function ( $key ) {
+                               return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
+                       },
+                       array_map( [ $this, 'sha1Base16Absolute' ], $paths )
+               ) );
+
+               // Try to acquire all the locks...
+               $fields = [];
+               foreach ( $bigints as $bigint ) {
+                       $fields[] = ( $type == self::LOCK_SH )
+                               ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
+                               : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
+               }
+               $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+               $row = $res->fetchRow();
+
+               if ( in_array( 'f', $row ) ) {
+                       // Release any acquired locks if some could not be acquired...
+                       $fields = [];
+                       foreach ( $row as $kbigint => $ok ) {
+                               if ( $ok === 't' ) { // locked
+                                       $bigint = substr( $kbigint, 1 ); // strip off the "K"
+                                       $fields[] = ( $type == self::LOCK_SH )
+                                               ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
+                                               : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
+                               }
+                       }
+                       if ( count( $fields ) ) {
+                               $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+                       }
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return Status
+        */
+       protected function releaseAllLocks() {
+               $status = Status::newGood();
+
+               foreach ( $this->conns as $lockDb => $db ) {
+                       try {
+                               $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
+                       } catch ( DBError $e ) {
+                               $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+                       }
+               }
+
+               return $status;
+       }
+}
index 91d628c..7e6e651 100644 (file)
@@ -2596,8 +2596,9 @@ class LocalFileRestoreBatch {
 
                                // The live (current) version cannot be hidden!
                                if ( !$this->unsuppress && $row->fa_deleted ) {
-                                       $storeBatch[] = [ $deletedUrl, 'public', $destRel ];
-                                       $this->cleanupBatch[] = $row->fa_storage_key;
+                                       $status->fatal( 'undeleterevdel' );
+                                       $this->file->unlock();
+                                       return $status;
                                }
                        } else {
                                $archiveName = $row->fa_archive_name;
diff --git a/includes/htmlform/HTMLFormElement.php b/includes/htmlform/HTMLFormElement.php
new file mode 100644 (file)
index 0000000..089213c
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. A matching JS widget
+ * (defined in htmlform.Element.js) picks up the extra config when constructed using OO.ui.infuse().
+ *
+ * Currently only supports passing 'hide-if' data.
+ */
+trait HTMLFormElement {
+
+       protected $hideIf = null;
+       protected $modules = null;
+
+       public function initializeHTMLFormElement( array $config = [] ) {
+               // Properties
+               $this->hideIf = isset( $config['hideIf'] ) ? $config['hideIf'] : null;
+               $this->modules = isset( $config['modules'] ) ? $config['modules'] : [];
+
+               // Initialization
+               if ( $this->hideIf ) {
+                       $this->addClasses( [ 'mw-htmlform-hide-if' ] );
+               }
+               if ( $this->modules ) {
+                       // JS code must be able to read this before infusing (before OOjs UI is even loaded),
+                       // so we put this in a separate attribute (not with the rest of the config).
+                       // And it's not needed anymore after infusing, so we don't put it in JS config at all.
+                       $this->setAttributes( [ 'data-mw-modules' => implode( ',', $this->modules ) ] );
+               }
+               $this->registerConfigCallback( function( &$config ) {
+                       if ( $this->hideIf !== null ) {
+                               $config['hideIf'] = $this->hideIf;
+                       }
+               } );
+       }
+}
+
+class HTMLFormFieldLayout extends OOUI\FieldLayout {
+       use HTMLFormElement;
+
+       public function __construct( $fieldWidget, array $config = [] ) {
+               // Parent constructor
+               parent::__construct( $fieldWidget, $config );
+               // Traits
+               $this->initializeHTMLFormElement( $config );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.htmlform.FieldLayout';
+       }
+}
+
+class HTMLFormActionFieldLayout extends OOUI\ActionFieldLayout {
+       use HTMLFormElement;
+
+       public function __construct( $fieldWidget, $buttonWidget = false, array $config = [] ) {
+               // Parent constructor
+               parent::__construct( $fieldWidget, $buttonWidget, $config );
+               // Traits
+               $this->initializeHTMLFormElement( $config );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.htmlform.ActionFieldLayout';
+       }
+}
index 5f6460d..dd53f5e 100644 (file)
@@ -626,12 +626,31 @@ abstract class HTMLFormField {
                        'infusable' => $infusable,
                ];
 
+               $preloadModules = false;
+
+               if ( $infusable && $this->shouldInfuseOOUI() ) {
+                       $preloadModules = true;
+                       $config['classes'][] = 'mw-htmlform-field-autoinfuse';
+               }
+
                // the element could specify, that the label doesn't need to be added
                $label = $this->getLabel();
                if ( $label ) {
                        $config['label'] = new OOUI\HtmlSnippet( $label );
                }
 
+               if ( $this->mHideIf ) {
+                       $preloadModules = true;
+                       $config['hideIf'] = $this->mHideIf;
+               }
+
+               $config['modules'] = $this->getOOUIModules();
+
+               if ( $preloadModules ) {
+                       $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
+                       $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
+               }
+
                return $this->getFieldLayoutOOUI( $inputField, $config );
        }
 
@@ -650,9 +669,31 @@ abstract class HTMLFormField {
        protected function getFieldLayoutOOUI( $inputField, $config ) {
                if ( isset( $this->mClassWithButton ) ) {
                        $buttonWidget = $this->mClassWithButton->getInputOOUI( '' );
-                       return new OOUI\ActionFieldLayout( $inputField, $buttonWidget, $config );
+                       return new HTMLFormActionFieldLayout( $inputField, $buttonWidget, $config );
                }
-               return new OOUI\FieldLayout( $inputField, $config );
+               return new HTMLFormFieldLayout( $inputField, $config );
+       }
+
+       /**
+        * Whether the field should be automatically infused. Note that all OOjs UI HTMLForm fields are
+        * infusable (you can call OO.ui.infuse() on them), but not all are infused by default, since
+        * there is no benefit in doing it e.g. for buttons and it's a small performance hit on page load.
+        *
+        * @return bool
+        */
+       protected function shouldInfuseOOUI() {
+               // Always infuse fields with help text, since the interface for it is nicer with JS
+               return $this->getHelpText() !== null;
+       }
+
+       /**
+        * Get the list of extra ResourceLoader modules which must be loaded client-side before it's
+        * possible to infuse this field's OOjs UI widget.
+        *
+        * @return string[]
+        */
+       protected function getOOUIModules() {
+               return [];
        }
 
        /**
index 778aedb..0c3bc5a 100644 (file)
@@ -56,4 +56,8 @@ class HTMLComboboxField extends HTMLTextField {
                        'disabled' => $disabled,
                ] + $attribs );
        }
+
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
 }
index e5b5e68..976befe 100644 (file)
@@ -57,6 +57,10 @@ class HTMLRadioField extends HTMLFormField {
                ) );
        }
 
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
+
        function formatOptions( $options, $value ) {
                global $wgUseMediaWikiUIEverywhere;
 
index b6ad46c..40b31b5 100644 (file)
@@ -65,4 +65,8 @@ class HTMLSelectField extends HTMLFormField {
                        'disabled' => $disabled,
                ] + $attribs );
        }
+
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
 }
index ef21969..230790d 100644 (file)
@@ -33,4 +33,13 @@ class HTMLSelectNamespace extends HTMLFormField {
                        'includeAllValue' => $this->mAllValue,
                ] );
        }
+
+       protected function getOOUIModules() {
+               // FIXME: NamespaceInputWidget should be in its own module (probably?)
+               return [ 'mediawiki.widgets' ];
+       }
+
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
 }
index fcf721a..a15b90e 100644 (file)
@@ -73,7 +73,6 @@ class HTMLTitleTextField extends HTMLTextField {
        }
 
        protected function getInputWidget( $params ) {
-               $this->mParent->getOutput()->addModules( 'mediawiki.widgets' );
                if ( $this->mParams['namespace'] !== false ) {
                        $params['namespace'] = $this->mParams['namespace'];
                }
@@ -81,6 +80,15 @@ class HTMLTitleTextField extends HTMLTextField {
                return new TitleInputWidget( $params );
        }
 
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
+
+       protected function getOOUIModules() {
+               // FIXME: TitleInputWidget should be in its own module
+               return [ 'mediawiki.widgets' ];
+       }
+
        public function getInputHtml( $value ) {
                // add mw-searchInput class to enable search suggestions for non-OOUI, too
                $this->mClass .= 'mw-searchInput';
index 5a7e0b9..14b5e59 100644 (file)
@@ -40,11 +40,17 @@ class HTMLUserTextField extends HTMLTextField {
        }
 
        protected function getInputWidget( $params ) {
-               $this->mParent->getOutput()->addModules( 'mediawiki.widgets.UserInputWidget' );
-
                return new UserInputWidget( $params );
        }
 
+       protected function shouldInfuseOOUI() {
+               return true;
+       }
+
+       protected function getOOUIModules() {
+               return [ 'mediawiki.widgets.UserInputWidget' ];
+       }
+
        public function getInputHtml( $value ) {
                // add the required module and css class for user suggestions in non-OOUI mode
                $this->mParent->getOutput()->addModules( 'mediawiki.userSuggest' );
index 479ec32..b9f4592 100644 (file)
@@ -245,10 +245,7 @@ class JobQueueDB extends JobQueue {
                                count( $rowSet ) + count( $rowList ) - count( $rows )
                        );
                } catch ( DBError $e ) {
-                       if ( $flags & self::QOS_ATOMIC ) {
-                               $dbw->rollback( $method );
-                       }
-                       throw $e;
+                       $this->throwDBException( $e );
                }
                if ( $flags & self::QOS_ATOMIC ) {
                        $dbw->endAtomic( $method );
index 4b906a7..ad0abf2 100644 (file)
@@ -528,17 +528,6 @@ class JobRunner implements LoggerAwareInterface {
                        $lb->waitForAll( $pos );
                }
 
-               $fname = __METHOD__;
-               // Re-ping all masters with transactions. This throws DBError if some
-               // connection died while waiting on locks/slaves, triggering a rollback.
-               wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( $fname ) {
-                       $lb->forEachOpenConnection( function( IDatabase $conn ) use ( $fname ) {
-                               if ( $conn->writesOrCallbacksPending() ) {
-                                       $conn->ping();
-                               }
-                       } );
-               } );
-
                // Actually commit the DB master changes
                wfGetLBFactory()->commitMasterChanges( __METHOD__ );
 
diff --git a/includes/jobqueue/utils/PurgeJobUtils.php b/includes/jobqueue/utils/PurgeJobUtils.php
new file mode 100644 (file)
index 0000000..329bc23
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Base code for update jobs that put some secondary data extracted
+ * from article content into the database.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+class PurgeJobUtils {
+       /**
+        * Invalidate the cache of a list of pages from a single namespace.
+        * This is intended for use by subclasses.
+        *
+        * @param IDatabase $dbw
+        * @param int $namespace Namespace number
+        * @param array $dbkeys
+        */
+       public static function invalidatePages( IDatabase $dbw, $namespace, array $dbkeys ) {
+               if ( $dbkeys === [] ) {
+                       return;
+               }
+
+               $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $namespace, $dbkeys ) {
+                       // Determine which pages need to be updated.
+                       // This is necessary to prevent the job queue from smashing the DB with
+                       // large numbers of concurrent invalidations of the same page.
+                       $now = $dbw->timestamp();
+                       $ids = $dbw->selectFieldValues(
+                               'page',
+                               'page_id',
+                               [
+                                       'page_namespace' => $namespace,
+                                       'page_title' => $dbkeys,
+                                       'page_touched < ' . $dbw->addQuotes( $now )
+                               ],
+                               __METHOD__
+                       );
+
+                       if ( $ids === [] ) {
+                               return;
+                       }
+
+                       // Do the update.
+                       // We still need the page_touched condition, in case the row has changed since
+                       // the non-locking select above.
+                       $dbw->update(
+                               'page',
+                               [ 'page_touched' => $now ],
+                               [
+                                       'page_id' => $ids,
+                                       'page_touched < ' . $dbw->addQuotes( $now )
+                               ],
+                               __METHOD__
+                       );
+               } );
+       }
+}
index f304bd9..a333d75 100644 (file)
@@ -237,8 +237,6 @@ class VirtualRESTServiceClient {
                                        $checkReqIndexesByPrefix[$prefix][$index] = 1;
                                }
                        }
-                       // Update index of requests to inspect for replacement
-                       $replaceReqsByService = $newReplaceReqsByService;
                        // Run the actual work HTTP requests
                        foreach ( $this->http->runMulti( $executeReqs ) as $index => $ranReq ) {
                                $doneReqs[$index] = $ranReq;
index 5556dd8..9ab28aa 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Cache
  */
 
+use \MediaWiki\MediaWikiServices;
+
 /**
  * Class to store objects in the database
  *
@@ -276,6 +278,7 @@ class SqlBagOStuff extends BagOStuff {
                        if ( isset( $dataRows[$key] ) ) { // HIT?
                                $row = $dataRows[$key];
                                $this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+                               $db = null;
                                try {
                                        $db = $this->getDB( $row->serverIndex );
                                        if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
@@ -284,7 +287,7 @@ class SqlBagOStuff extends BagOStuff {
                                                $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
                                        }
                                } catch ( DBQueryError $e ) {
-                                       $this->handleWriteError( $e, $row->serverIndex );
+                                       $this->handleWriteError( $e, $db, $row->serverIndex );
                                }
                        } else { // MISS
                                $this->debug( 'get: no matching rows' );
@@ -306,10 +309,11 @@ class SqlBagOStuff extends BagOStuff {
                $result = true;
                $exptime = (int)$expiry;
                foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+                       $db = null;
                        try {
                                $db = $this->getDB( $serverIndex );
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $serverIndex );
+                               $this->handleWriteError( $e, $db, $serverIndex );
                                $result = false;
                                continue;
                        }
@@ -342,7 +346,7 @@ class SqlBagOStuff extends BagOStuff {
                                                __METHOD__
                                        );
                                } catch ( DBError $e ) {
-                                       $this->handleWriteError( $e, $serverIndex );
+                                       $this->handleWriteError( $e, $db, $serverIndex );
                                        $result = false;
                                }
 
@@ -364,6 +368,7 @@ class SqlBagOStuff extends BagOStuff {
 
        protected function cas( $casToken, $key, $value, $exptime = 0 ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               $db = null;
                try {
                        $db = $this->getDB( $serverIndex );
                        $exptime = intval( $exptime );
@@ -394,7 +399,7 @@ class SqlBagOStuff extends BagOStuff {
                                __METHOD__
                        );
                } catch ( DBQueryError $e ) {
-                       $this->handleWriteError( $e, $serverIndex );
+                       $this->handleWriteError( $e, $db, $serverIndex );
 
                        return false;
                }
@@ -404,6 +409,7 @@ class SqlBagOStuff extends BagOStuff {
 
        public function delete( $key ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               $db = null;
                try {
                        $db = $this->getDB( $serverIndex );
                        $db->delete(
@@ -411,7 +417,7 @@ class SqlBagOStuff extends BagOStuff {
                                [ 'keyname' => $key ],
                                __METHOD__ );
                } catch ( DBError $e ) {
-                       $this->handleWriteError( $e, $serverIndex );
+                       $this->handleWriteError( $e, $db, $serverIndex );
                        return false;
                }
 
@@ -420,6 +426,7 @@ class SqlBagOStuff extends BagOStuff {
 
        public function incr( $key, $step = 1 ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               $db = null;
                try {
                        $db = $this->getDB( $serverIndex );
                        $step = intval( $step );
@@ -455,7 +462,7 @@ class SqlBagOStuff extends BagOStuff {
                                $newValue = null;
                        }
                } catch ( DBError $e ) {
-                       $this->handleWriteError( $e, $serverIndex );
+                       $this->handleWriteError( $e, $db, $serverIndex );
                        return null;
                }
 
@@ -473,6 +480,7 @@ class SqlBagOStuff extends BagOStuff {
 
        public function changeTTL( $key, $expiry = 0 ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               $db = null;
                try {
                        $db = $this->getDB( $serverIndex );
                        $db->update(
@@ -485,7 +493,7 @@ class SqlBagOStuff extends BagOStuff {
                                return false;
                        }
                } catch ( DBError $e ) {
-                       $this->handleWriteError( $e, $serverIndex );
+                       $this->handleWriteError( $e, $db, $serverIndex );
                        return false;
                }
 
@@ -542,6 +550,7 @@ class SqlBagOStuff extends BagOStuff {
         */
        public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
                for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+                       $db = null;
                        try {
                                $db = $this->getDB( $serverIndex );
                                $dbTimestamp = $db->timestamp( $timestamp );
@@ -604,7 +613,7 @@ class SqlBagOStuff extends BagOStuff {
                                        }
                                }
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $serverIndex );
+                               $this->handleWriteError( $e, $db, $serverIndex );
                                return false;
                        }
                }
@@ -618,13 +627,14 @@ class SqlBagOStuff extends BagOStuff {
         */
        public function deleteAll() {
                for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+                       $db = null;
                        try {
                                $db = $this->getDB( $serverIndex );
                                for ( $i = 0; $i < $this->shards; $i++ ) {
                                        $db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
                                }
                        } catch ( DBError $e ) {
-                               $this->handleWriteError( $e, $serverIndex );
+                               $this->handleWriteError( $e, $db, $serverIndex );
                                return false;
                        }
                }
@@ -694,18 +704,19 @@ class SqlBagOStuff extends BagOStuff {
         * Handle a DBQueryError which occurred during a write operation.
         *
         * @param DBError $exception
+        * @param IDatabase|null $db DB handle or null if connection failed
         * @param int $serverIndex
+        * @throws Exception
         */
-       protected function handleWriteError( DBError $exception, $serverIndex ) {
-               if ( $exception instanceof DBConnectionError ) {
+       protected function handleWriteError( DBError $exception, IDatabase $db = null, $serverIndex ) {
+               if ( !$db ) {
                        $this->markServerDown( $exception, $serverIndex );
-               }
-               if ( $exception->db && $exception->db->wasReadOnlyError() ) {
-                       if ( $exception->db->trxLevel() ) {
-                               try {
-                                       $exception->db->rollback( __METHOD__ );
-                               } catch ( DBError $e ) {
-                               }
+               } elseif ( $db->wasReadOnlyError() ) {
+                       if ( $db->trxLevel() && $this->usesMainDB() ) {
+                               // Errors like deadlocks and connection drops already cause rollback.
+                               // For consistency, we have no choice but to throw an error and trigger
+                               // complete rollback if the main DB is also being used as the cache DB.
+                               throw $exception;
                        }
                }
 
@@ -725,7 +736,7 @@ class SqlBagOStuff extends BagOStuff {
         * @param DBError $exception
         * @param int $serverIndex
         */
-       protected function markServerDown( $exception, $serverIndex ) {
+       protected function markServerDown( DBError $exception, $serverIndex ) {
                unset( $this->conns[$serverIndex] ); // bug T103435
 
                if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
@@ -762,11 +773,19 @@ class SqlBagOStuff extends BagOStuff {
                }
        }
 
+       /**
+        * @return bool Whether the main DB is used, e.g. wfGetDB( DB_MASTER )
+        */
+       protected function usesMainDB() {
+               return !$this->serverInfos;
+       }
+
        protected function waitForSlaves() {
-               if ( !$this->serverInfos ) {
+               if ( $this->usesMainDB() ) {
                        // Main LB is used; wait for any slaves to catch up
                        try {
-                               wfGetLBFactory()->waitForReplication( [ 'wiki' => wfWikiID() ] );
+                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
                                return true;
                        } catch ( DBReplicationWaitError $e ) {
                                return false;
index 0344756..033eb67 100644 (file)
@@ -1770,7 +1770,6 @@ class WikiPage implements Page, IDBAccessObject {
                        $revisionId = $revision->insertOn( $dbw );
                        // Update page_latest and friends to reflect the new revision
                        if ( !$this->updateRevisionOn( $dbw, $revision, null, $meta['oldIsRedirect'] ) ) {
-                               $dbw->rollback( __METHOD__ ); // sanity; this should never happen
                                throw new MWException( "Failed to update page row to use new revision." );
                        }
 
@@ -1920,7 +1919,6 @@ class WikiPage implements Page, IDBAccessObject {
                $revisionId = $revision->insertOn( $dbw );
                // Update the page record with revision data
                if ( !$this->updateRevisionOn( $dbw, $revision, 0 ) ) {
-                       $dbw->rollback( __METHOD__ ); // sanity; this should never happen
                        throw new MWException( "Failed to update page row to use new revision." );
                }
 
index 4f579a9..38eb621 100644 (file)
@@ -2158,7 +2158,7 @@ class Parser {
                                $might_be_img = true;
                                $text = $m[2];
                                if ( strpos( $m[1], '%' ) !== false ) {
-                                       $m[1] = rawurldecode( $m[1] );
+                                       $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
                                }
                                $trail = "";
                        } else { # Invalid form; output directly
index 85b8dc3..68f0d00 100644 (file)
@@ -536,7 +536,7 @@ abstract class AuthManagerSpecialPage extends SpecialPage {
                $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
                $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
                $form->addHiddenField( 'authAction', $this->authAction );
-               $form->suppressDefaultSubmit( !$this->needsSubmitButton( $formDescriptor ) );
+               $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
 
                return $form;
        }
@@ -554,24 +554,38 @@ abstract class AuthManagerSpecialPage extends SpecialPage {
        }
 
        /**
-        * Returns true if the form has fields which take values. If all available providers use the
-        * redirect flow, the form might contain nothing but submit buttons, in which case we should
-        * not add an extra submit button which does nothing.
+        * Returns true if the form built from the given AuthenticationRequests has fields which take
+        * values. If all available providers use the redirect flow, the form might contain nothing
+        * but submit buttons, in which case we should not add an extra submit button which does nothing.
         *
-        * @param array $formDescriptor A HTMLForm descriptor
+        * @param AuthenticationRequest[] $requests An array of AuthenticationRequests from which the
+        *  form will be built
         * @return bool
         */
-       protected function needsSubmitButton( $formDescriptor ) {
-               return (bool)array_filter( $formDescriptor, function ( $item ) {
-                       $class = false;
-                       if ( array_key_exists( 'class', $item ) ) {
-                               $class = $item['class'];
-                       } elseif ( array_key_exists( 'type', $item ) ) {
-                               $class = HTMLForm::$typeMappings[$item['type']];
+       protected function needsSubmitButton( array $requests ) {
+               foreach ( $requests as $req ) {
+                       if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED &&
+                               $this->doesRequestNeedsSubmitButton( $req )
+                       ) {
+                               return true;
                        }
-                       return !is_a( $class, \HTMLInfoField::class, true ) &&
-                               !is_a( $class, \HTMLSubmitField::class, true );
-               } );
+               }
+               return false;
+       }
+
+       /**
+        * Checks if the given AuthenticationRequest needs a submit button or not.
+        *
+        * @param AuthenticationRequest $req The request to check
+        * @return bool
+        */
+       protected function doesRequestNeedsSubmitButton( AuthenticationRequest $req ) {
+               foreach ( $req->getFieldInfo() as $field => $info ) {
+                       if ( $info['type'] === 'button' ) {
+                               return false;
+                       }
+               }
+               return true;
        }
 
        /**
index 8a2e0d6..cf1a4d0 100644 (file)
@@ -585,7 +585,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
                // this will call onAuthChangeFormFields()
                $formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
-               $this->postProcessFormDescriptor( $formDescriptor );
+               $this->postProcessFormDescriptor( $formDescriptor, $requests );
 
                $context = $this->getContext();
                if ( $context->getRequest() !== $this->getRequest() ) {
@@ -1237,7 +1237,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
        /**
         * @param array $formDescriptor
         */
-       protected function postProcessFormDescriptor( &$formDescriptor ) {
+       protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
                // Pre-fill username (if not creating an account, T46775).
                if (
                        isset( $formDescriptor['username'] ) &&
@@ -1255,7 +1255,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
 
                // don't show a submit button if there is nothing to submit (i.e. the only form content
                // is other submit buttons, for redirect flows)
-               if ( !$this->needsSubmitButton( $formDescriptor ) ) {
+               if ( !$this->needsSubmitButton( $requests ) ) {
                        unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
                }
 
index d98504d..ff70848 100644 (file)
@@ -149,7 +149,7 @@ class SpecialChangeCredentials extends AuthManagerSpecialPage {
                return $form;
        }
 
-       protected function needsSubmitButton( $formDescriptor ) {
+       protected function needsSubmitButton( array $requests ) {
                // Change/remove forms show are built from a single AuthenticationRequest and do not allow
                // for redirect flow; they always need a submit button.
                return true;
index 3e66ab0..fe97739 100644 (file)
@@ -201,6 +201,7 @@ class SpecialExport extends SpecialPage {
                                'buttontype' => 'submit',
                                'buttonname' => 'addcat',
                                'buttondefault' => $this->msg( 'export-addcat' )->text(),
+                               'hide-if' => [ '===', 'exportall', '1' ],
                        ],
                ];
                if ( $config->get( 'ExportFromNamespaces' ) ) {
@@ -216,6 +217,7 @@ class SpecialExport extends SpecialPage {
                                        'buttontype' => 'submit',
                                        'buttonname' => 'addns',
                                        'buttondefault' => $this->msg( 'export-addns' )->text(),
+                                       'hide-if' => [ '===', 'exportall', '1' ],
                                ],
                        ];
                }
@@ -240,6 +242,7 @@ class SpecialExport extends SpecialPage {
                                'nodata' => true,
                                'rows' => 10,
                                'default' => $page,
+                               'hide-if' => [ '===', 'exportall', '1' ],
                        ],
                ];
 
index d0c44c3..9cc6745 100644 (file)
@@ -353,6 +353,7 @@ class MovePageForm extends UnlistedSpecialPage {
                                        'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
                                        'align' => 'inline',
                                        'infusable' => true,
+                                       'id' => 'wpMovetalk-field',
                                ]
                        );
                }
index 83cfa40..a9ccc4e 100644 (file)
@@ -3134,6 +3134,7 @@ class User implements IDBAccessObject {
        public function getRights() {
                if ( is_null( $this->mRights ) ) {
                        $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
+                       Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
 
                        // Deny any rights denied by the user's session, unless this
                        // endpoint has no sessions.
@@ -3144,9 +3145,24 @@ class User implements IDBAccessObject {
                                }
                        }
 
-                       Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
                        // Force reindexation of rights when a hook has unset one of them
                        $this->mRights = array_values( array_unique( $this->mRights ) );
+
+                       // If block disables login, we should also remove any
+                       // extra rights blocked users might have, in case the
+                       // blocked user has a pre-existing session (T129738).
+                       // This is checked here for cases where people only call
+                       // $user->isAllowed(). It is also checked in Title::checkUserBlock()
+                       // to give a better error message in the common case.
+                       $config = RequestContext::getMain()->getConfig();
+                       if (
+                               $this->isLoggedIn() &&
+                               $config->get( 'BlockDisablesLogin' ) &&
+                               $this->isBlocked()
+                       ) {
+                               $anon = new User;
+                               $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
+                       }
                }
                return $this->mRights;
        }
@@ -3961,7 +3977,6 @@ class User implements IDBAccessObject {
                $noPass = PasswordFactory::newInvalidPassword()->toString();
 
                $dbw = wfGetDB( DB_MASTER );
-               $inWrite = $dbw->writesOrCallbacksPending();
                $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
                $dbw->insert( 'user',
                        [
@@ -3980,25 +3995,17 @@ class User implements IDBAccessObject {
                        [ 'IGNORE' ]
                );
                if ( !$dbw->affectedRows() ) {
-                       // The queries below cannot happen in the same REPEATABLE-READ snapshot.
-                       // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
-                       if ( $inWrite ) {
-                               // Can't commit due to pending writes that may need atomicity.
-                               // This may cause some lock contention unlike the case below.
-                               $options = [ 'LOCK IN SHARE MODE' ];
-                               $flags = self::READ_LOCKING;
-                       } else {
-                               // Often, this case happens early in views before any writes when
-                               // using CentralAuth. It's should be OK to commit and break the snapshot.
-                               $dbw->commit( __METHOD__, 'flush' );
-                               $options = [];
-                               $flags = self::READ_LATEST;
-                       }
-                       $this->mId = $dbw->selectField( 'user', 'user_id',
-                               [ 'user_name' => $this->mName ], __METHOD__, $options );
+                       // Use locking reads to bypass any REPEATABLE-READ snapshot.
+                       $this->mId = $dbw->selectField(
+                               'user',
+                               'user_id',
+                               [ 'user_name' => $this->mName ],
+                               __METHOD__,
+                               [ 'LOCK IN SHARE MODE' ]
+                       );
                        $loaded = false;
                        if ( $this->mId ) {
-                               if ( $this->loadFromDatabase( $flags ) ) {
+                               if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
                                        $loaded = true;
                                }
                        }
index ffb7053..a6e47c8 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  * @ingroup Maintenance
  */
+use \MediaWiki\MediaWikiServices;
+
 class BatchRowWriter {
        /**
         * @var IDatabase $db The database to write to
@@ -54,7 +56,8 @@ class BatchRowWriter {
         *  names to update values to apply to the row.
         */
        public function write( array $updates ) {
-               $this->db->begin();
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
 
                foreach ( $updates as $update ) {
                        $this->db->update(
@@ -65,7 +68,6 @@ class BatchRowWriter {
                        );
                }
 
-               $this->db->commit();
-               wfGetLBFactory()->waitForReplication();
+               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
        }
 }
index c2a09db..1a537ce 100644 (file)
        "tooltip-undo": "ائدیلمیش ديَیشیکلیگی گئری قايتار و گئری قايتارما سببینی قئيد ائتمک اۆچون سێناق گؤستریشینی آچ",
        "tooltip-preferences-save": "ترجیحلری ساخلا",
        "tooltip-summary": "قیسا بیر خولاصه‌‌ یازین",
-       "anonymous": "{{SITENAME}} Ø³Ø§Û\8cتÛ\8cÙ\86Û\8cÙ\86 Ø¢Ù\86Ù\88Ù\86Û\8cÙ\85 {{PLURAL:$1|Û\8cستÛ\8cÙ\81ادÚ\86Û\8cسÛ\8c\8cستÛ\8cÙ\81ادÙ\87â\80\8cÚ\86Û\8câ\80\8cلری}}",
+       "anonymous": "{{SITENAME}} Ø³Ø§Û\8cتÛ\8cÙ\86Û\8cÙ\86 ØªØ§Ù\86Û\8cÙ\86Ù\85اÙ\85Û\8cØ´ {{PLURAL:$1|Û\8cØ´Ù\84دÙ\86\8cØ´Ù\84دÙ\86لری}}",
        "siteuser": "{{SITENAME}} ایستیفاده‌چی‌سی $1",
-       "anonuser": "{{SITENAME}} Ø¢Ù\86Ù\88Ù\86Û\8cÙ\85 Ø§Û\8cستÛ\8cÙ\81ادÙ\87â\80\8cÚ\86Û\8câ\80\8cسÛ\8c $1",
+       "anonuser": "{{SITENAME}} Ø¢Ù\86Ù\88Ù\86Û\8cÙ\85 Ø§Û\8cØ´Ù\84دÙ\86 $1",
        "lastmodifiedatby": "بۇ صحیفه‌‌ سوْنونجو دفعه‌‌ $1، $2 تاریخینده دَییشیلیب.",
        "othercontribs": "$1-این ایشینه اساسلانیب.",
        "others": "آیریلار",
index 7ce50cf..55d067f 100644 (file)
        "uploaded-href-attribute-svg": "les attributs href dans les fichiers SVG ne sont autorisés que pour faire référence à des cibles http:// ou https://, <code>&lt;$1 $2=\"$3\"&gt;</code> trouvé.",
        "uploaded-href-unsafe-target-svg": "Un href vers des données non sûres a été trouvé dans le fichier SVG téléchargé : URI cible <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "Balise « animate » trouvée, qui pourrait modifier le href en utilisant l’attribut « from » <code>&lt;$1 $2=\"$3\"&gt;</code> dans le fichier SVG téléchargé.",
-       "uploaded-setting-event-handler-svg": "Positionner des attributs de gestionnaire d’événement est bloqué, <code>&lt;$1 $2=\"$3\"&gt;</code> trouvé dans le fichier SVG téléchargé.",
+       "uploaded-setting-event-handler-svg": "Positionner les attributs du gestionnaire d’événements n'est pas possbile, <code>&lt;$1 $2=\"$3\"&gt;</code> trouvé dans le fichier SVG téléchargé.",
        "uploaded-setting-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
        "uploaded-wrong-setting-svg": "L’utilisation de la balise « set » pour ajouter une cible distante/données/script à un attribut quelconque est interdite. <code>&lt;set to=\"$1\"&gt;</code> a été trouvé dans le fichier SVG téléchargé.",
        "uploaded-setting-handler-svg": "Les SVG qui positionnent l’attribut « handler » avec distant/données/script sont interdits. <code>$1=\"$2\"</code> a été trouvé dans le fichier SVG téléchargé.",
        "zip-unsupported": "Le fichier est une archive ZIP qui utilise des caractéristiques non supportées par MediaWiki. \nSa sécurité ne peut pas être correctement vérifiée.",
        "uploadstash": "Cache d’import",
        "uploadstash-summary": "Cette page donne accès aux fichiers qui sont importés (ou en cours d’importation), mais ne sont pas encore publiés dans le wiki. Ces fichiers ne sont pas encore visibles, sauf pour l’utilisateur qui les a importés.",
-       "uploadstash-clear": "Effacer les fichiers en cache",
+       "uploadstash-clear": "Effacer les fichiers en cache d'import",
        "uploadstash-nofiles": "Vous n’avez pas de fichiers en cache d’import.",
        "uploadstash-badtoken": "L’exécution de cette action a échoué, peut-être parce que vos informations d’identification ont expiré. Veuillez réessayer.",
        "uploadstash-errclear": "La suppression des fichiers a échoué.",
        "listgrouprights-key": "Légende :\n*<span class=\"listgrouprights-granted\">Droit octroyé</span>\n*<span class=\"listgrouprights-revoked\">Droit révoqué</span>",
        "listgrouprights-group": "Groupe",
        "listgrouprights-rights": "Droits associés",
-       "listgrouprights-helppage": "Help:Droits des groupes",
+       "listgrouprights-helppage": "Help:Droits de groupes",
        "listgrouprights-members": "(liste des membres)",
        "listgrouprights-addgroup": "Ajouter des membres {{PLURAL:$2|au groupe|aux groupes}} : $1",
        "listgrouprights-removegroup": "Retirer des membres {{PLURAL:$2|du groupe|des groupes}} : $1",
        "trackingcategories-desc": "Critère d’inclusion de la catégorie",
        "restricted-displaytitle-ignored": "Pages avec des titres d'affichage ignorés",
        "restricted-displaytitle-ignored-desc": "La page a un <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ignoré parce qu'il n'est pas équivalent au titre actuel de la page.",
-       "noindex-category-desc": "La page contient <code><nowiki>__NOINDEX__</nowiki></code> et est dans un espace de noms où ce marquage est autorisé ; elle ne sera donc pas indexée par les robots.",
-       "index-category-desc": "La page contient <code><nowiki>__INDEX__</nowiki></code> et est dans un espace de noms où ce marquage est autorisé ; elle sera donc indexée par les robots alors qu’elle ne l’aurait pas été normalement.",
+       "noindex-category-desc": "La page n'est pas indexée par les robots car elle contient le mot magique <code><nowiki>__NOINDEX__</nowiki></code> et se trouve dans un espace de noms où ce marquage est autorisé.",
+       "index-category-desc": "La page contient <code><nowiki>__INDEX__</nowiki></code> (et est dans un espace de noms où ce marquage est autorisé), et  sera donc indexée par les robots alors qu’elle ne l’aurait pas été normalement.",
        "post-expand-template-inclusion-category-desc": "La taille de la page dépasse <code>$wgMaxArticleSize</code> après le développement de tous ses modèles ; certains n’ont donc pas été développés.",
        "post-expand-template-argument-category-desc": "La page dépasse <code>$wgMaxArticleSize</code> après avoir développé l’argument d’un modèle (quelque chose entre accolades triples, comme <code>{{{Foo}}}</code>).",
        "expensive-parserfunction-category-desc": "La page utilise trop de fonctions coûteuses de l’analyseur (comme <code>#ifexist</code>). Voyez [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
        "broken-file-category-desc": "La page contient un lien de fichier incorrect (un lien pour inclure un fichier alors que celui-ci n’existe pas).",
-       "hidden-category-category-desc": "La catégorie contient <code><nowiki>__HIDDENCAT__</nowiki></code> dans son contenu, ce qui empêche son affichage dans la zone des liens de catégorie sur les pages, par défaut.",
+       "hidden-category-category-desc": "La catégorie contient <code><nowiki>__HIDDENCAT__</nowiki></code> dans son contenu, ce qui empêche son affichage dans la zone des liens de catégorie sur les pages par défaut.",
        "trackingcategories-nodesc": "Aucune description disponible.",
        "trackingcategories-disabled": "La catégorie est désactivée",
        "mailnologin": "Pas d'adresse d'expéditeur",
-       "mailnologintext": "Vous devez être [[Special:UserLogin|identifié]] et avoir indiqué une adresse électronique valide dans vos [[Special:Preferences|préférences]] pour pouvoir envoyer des courriels à d'autres utilisateurs.",
+       "mailnologintext": "Vous devez être [[Special:UserLogin|connecté]] et avoir indiqué une adresse électronique valide dans vos [[Special:Preferences|préférences]] pour pouvoir envoyer des courriels à d'autres utilisateurs.",
        "emailuser": "Lui envoyer un courriel",
        "emailuser-title-target": "Envoyer un courriel à {{GENDER:$1|cet utilisateur|cette utilisatrice}}",
        "emailuser-title-notarget": "Envoyer un courriel à l'utilisateur",
index e4b81bf..ef1b90a 100644 (file)
        "blockedtext": "'''Vaše suradničko ime ili IP adresa je blokirana'''\n\nBlokirao Vas je $1.\nRazlog blokiranja je sljedeći: ''$2''.\n\n* Početak blokade: $8\n* Istek blokade: $6\n* Ime blokiranog suradnika: $7\n\nMožete kontaktirati $1 ili jednog od [[{{MediaWiki:Grouppage-sysop}}|administratora]] kako bi Vam pojasnili razlog blokiranja.\n\nPrimijetite da ne možete koristiti opciju \"Pošalji mu e-poruku\" ako niste upisali valjanu adresu e-pošte u Vašim [[Special:Preferences|suradničkim postavkama]] i ako niste u tome onemogućeni prilikom blokiranja.\n\nVaša trenutačna IP adresa je $3, a oznaka bloka #$5. Molimo navedite ovaj broj kod svakog upita vezano za razlog blokiranja.",
        "autoblockedtext": "Vaša IP adresa automatski je blokirana zbog toga što ju je koristio drugi suradnik, kojeg je blokirao $1.\nRazlog blokiranja je sljedeći:\n\n:''$2''\n\n* Početak blokade: $8\n* Blokada istječe: $6\n* Ime blokiranog suradnika: $7\n\nMožete kontaktirati $1 ili jednog od [[{{MediaWiki:Grouppage-sysop}}|administratora]] kako bi Vam pojasnili razlog blokiranja.\n\nPrimijetite da ne možete rabiti opciju \"Pošalji mu e-poruku\" ako niste upisali valjanu adresu e-pošte u Vašim [[Special:Preferences|suradničkim postavkama]] i ako niste u tome onemogućeni prilikom blokiranja.\n\nVaša trenutačna IP adresa je $3, a oznaka bloka #$5. Molimo navedite ovaj broj kod svakog upita vezano za razlog blokiranja.",
        "blockednoreason": "bez obrazloženja",
-       "whitelistedittext": "Za uređivanje stranice morate se $1.",
+       "whitelistedittext": "Za uređivanje stranice molimo $1.",
        "confirmedittext": "Morate potvrditi Vašu adresu e-pošte prije nego što Vam bude omogućeno uređivanje. Molim unesite i ovjerite Vašu adresu e-pošte u [[Special:Preferences|suradničkim postavkama]].",
        "nosuchsectiontitle": "Ne mogu pronaći odlomak",
        "nosuchsectiontext": "Pokušali ste uređivati odlomak koji ne postoji.\nMožda je premješten ili izbrisan dok ste pregledavali stranicu.",
        "loginreqtitle": "Nužna prijava",
        "loginreqlink": "prijavite se",
-       "loginreqpagetext": "Morate se $1 da biste vidjeli ostale stranice.",
+       "loginreqpagetext": "Da biste vidjeli ostale stranice, molimo $1.",
        "accmailtitle": "Lozinka poslana.",
        "accmailtext": "Nova lozinka za [[User talk:$1|$1]] je poslana na $2.\n\nNakon prijave, lozinka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|promijeni lozinku]]'' nakon prijave.",
        "newarticle": "(Novo)",
        "reuploaddesc": "Vratite se u obrazac za postavljanje.",
        "upload-tryagain": "Pošalji izmijenjeni opis datoteke",
        "uploadnologin": "Niste prijavljeni",
-       "uploadnologintext": "Za postavljanje datoteka morate biti $1.",
+       "uploadnologintext": "Da biste postavljali datoteke, molimo $1.",
        "upload_directory_missing": "Mapa za datoteke ($1) nedostaje i webserver ju ne može napraviti.",
        "upload_directory_read_only": "Server ne može pisati u direktorij za postavljanje ($1).",
        "uploaderror": "Pogreška kod postavljanja",
index 8d570ec..0cca8aa 100644 (file)
@@ -27,6 +27,7 @@
        "tog-watchdefault": "Agnayon kadagiti panid ken papeles nga inurnosko iti listaan ti bambantayak",
        "tog-watchmoves": "Agnayon kadagiti panid ken papeles nga inyalisko iti listaan ti bambantayak",
        "tog-watchdeletion": "Agnayon kadagiti panid ken papeles nga inikkatko iti listaan ti bambantayak",
+       "tog-watchuploads": "Inayon dagiti baro a papeles iti bambantayak",
        "tog-watchrollback": "Agnayon kadagiti panid nga adda inramidko nga insubli iti bambantayak",
        "tog-minordefault": "Markaan amin dagiti inurnos a kas bassit babaen ti kasisigud",
        "tog-previewontop": "Ipakita ti panagipadas sakbay ti pagurnosan a kahon",
@@ -51,7 +52,7 @@
        "tog-ccmeonemails": "Patulodandak kadagiti kopia ti esurat nga ipatulodko kadagiti sabali nga agar-aramat",
        "tog-diffonly": "Saan nga iparang ti linaon ti panid dita baba dagiti pagiddiatan",
        "tog-showhiddencats": "Ipakita dagiti nailemmeng a kategoria",
-       "tog-norollbackdiff": "Laksiden ti paggiddiatan kalpasan ti panagaramid ti panagisubli",
+       "tog-norollbackdiff": "Saan nga ipakita ti paggiddiatan kalpasan ti panagaramid ti panagisubli",
        "tog-useeditwarning": "Pakaunaannak no pumanawak iti maysa pagurnosan a panid nga addaan iti saan a naidulin a sinuksukatan",
        "tog-prefershttps": "Kankanayon nga agusar ti natalged a koneksion no nakastrek",
        "underline-always": "Kanayon",
        "password-change-forbidden": "Saanmo a mabaliwan dagiti kontrasenias iti daytoy a wiki.",
        "externaldberror": "Mabalin nga adda biddut iti pannakapasingked ti database wenno saanka a mapalubosan a mangpabaro ti akinruar a pakabilangam.",
        "login": "Sumrek",
+       "login-security": "Pasingkedan ti identidadmo",
        "nav-login-createaccount": "Sumrek / agpartuat iti pakabilangan",
        "userlogin": "Sumrek / agpartuat iti pakabilangan",
        "userloginnocreate": "Sumrek",
        "userlogin-resetpassword-link": "Nalipatam ti kontraseniasmo?",
        "userlogin-helplink2": "Tulong iti panagserrek",
        "userlogin-loggedin": "Nakastrekkan a kas ni {{GENDER:$1|$1}}.\nUsaren ti porma dita baba tapno sumrek a kas sabali nga agar-aramat.",
+       "userlogin-reauth": "Nasken a sumrekka manen tapno mapasingkedan a sika ni {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Agpartuat iti sabali a pakabilangan",
        "createacct-emailrequired": "Esurat a pagtaengan",
        "createacct-emailoptional": "Esurat a pagtaengan (pagpilian)",
        "createacct-email-ph": "Ikabil ti esurat a pagtaengam",
        "createacct-another-email-ph": "Ikabil ti esurat a pagtaengan",
        "createaccountmail": "Agusar iti pugto a temporario a kontrasenias ken ipatulod iti naisangayan nga esurat a pagtaengan",
+       "createaccountmail-help": "Mabalin a mausar a panagpartuat ti pakabilangan para iti sabali a tao a saan a makaammo iti kontrasenias.",
        "createacct-realname": "Pudno a nagan (pagpilian)",
        "createaccountreason": "Rason:",
        "createacct-reason": "Rason",
        "createacct-reason-ph": "Apay nga agparpartuatka manen iti sabali a pakabilangan",
+       "createacct-reason-help": "Ti mensahe a naipakita iti listaan iti panagpartuat ti pakabilangan",
        "createacct-submit": "Partuatem ti pakabilangam",
        "createacct-another-submit": "Agpartuat iti pakabilangan",
+       "createacct-continue-submit": "Agtuloy iti panagpartuat ti pakabilangan",
+       "createacct-another-continue-submit": "Agtuloy iti panagpartuat ti pakabilangan",
        "createacct-benefit-heading": "Ti {{SITENAME}} ket inar-aramid babaen ti tattao a kasla kenka.",
        "createacct-benefit-body1": "{{PLURAL:$1|nga inurnos|nga inur-urnos}}",
        "createacct-benefit-body2": "{{PLURAL:$1|a panid|a pampanid}}",
        "nocookiesnew": "Napartuaten ti pakabilangan ti agar-aramat, ngem saanka a nakastrek.\nTi {{SITENAME}} ket agus-usar kadagiti galietas tapno maiserrek dagiti agar-aramat.\nNabaldado dagiti galietam.\nPangngaasi a pakabaelam ida, ken sumrekka nga agusar iti baro a naganmo ken kontrasenias.",
        "nocookieslogin": "Ti {{SITENAME}} ket agus-usar kadagiti galietas tapno maiserrek dagiti agar-aramat.\nNabaldado dagiti galietam.\nPangngaasi a pakabaelam ida ken padasem manen ti sumrek.",
        "nocookiesfornew": "Ti pakabilangan ti agar-aramat ket saan a napartuat, saanmi a mapasingkedan ti taudanna.\nSiguraduem a napakabaelan dagita galietam, ikarga manen daytoy a panid ken padasen manen.",
+       "createacct-loginerror": "Balligi ti pannakapartuat ti pakabilangan ngem saanka a mabalin nga automatiko a maiserrek. Pangngaasi a mapan iti [[Special:UserLogin|manual a panagserrek]].",
        "noname": "Saanmo a nainaganan ti umisu a nagan ti agar-aramat.",
        "loginsuccesstitle": "Nakastrek",
        "loginsuccess": "<strong>Nakastrekkan iti {{SITENAME}} a kas ni \"$1\".</strong>",
        "noemail": "Awan ti esurat a pagtaengan a nairehistro para kenni agar-aramat \"$1\".",
        "noemailcreate": "Nasken a mangitedka ti pudno nga esurat a pagtaengan.",
        "passwordsent": "Naipatuloden ti baro a kontrasenias iti esurat a pagtaengan a nairehistro kenni \"$1\".\nPangngaasi a sumrekka manen kalpasan ti pannakaawatmo.",
-       "blocked-mailpassword": "Ti IP a pagtaengam ket naserraan manipud iti panagurnos, ken isu a saan a mapalubosan nga agusar ti annong ti panagipulang ti kontrasenias tapno mapawilan ti panagabuso.",
+       "blocked-mailpassword": "Ti adresmo ti IP ket naserraan manipud iti panagurnos. Tapno mapawilan ti panagabuso, saan a maipalubos ti agusar ti panagipulang ti kontrasenias manipud iti daytoy nga adres ti IP.",
        "eauthentsent": "Naipatuloden ti pammatalged nga esurat iti naikeddeng nga esurat a pagtaengan.\nSakbay a maipatulod ti ania man nga esurat iti pakabilangan, masapul a surotem dagiti maibagbaga iti esurat, tapno mapatalgedan ti pakabilangan ket agpayso a kukuam.",
        "throttled-mailpassword": "Ti panangisaad manen ti kontrasenias ket naipatuloden, iti kaunegan ti napalabas  {{PLURAL:$1|nga oras|a $1 nga or-oras}}.\nTapno maipawilan ti panagabuso, maysa laeng a panangisaad manen ti kontrasenias ti maipatulod iti tunggal {{PLURAL:$1|maysa nga oras|$1 nga or-oras}}.",
        "mailerror": "Biddut iti panangipatulod ti surat: $1",
        "createacct-another-realname-tip": "Saan a nasken ti pudno a nagan.\nNo kayatmo nga ited, mausarto daytoy para iti panangited ti pammadayaw para kadagiti obrada.",
        "pt-login": "Sumrek",
        "pt-login-button": "Sumrek",
+       "pt-login-continue-button": "Agtuloy a sumrek",
        "pt-createaccount": "Agpartuat iti pakabilangan",
        "pt-userlogout": "Rummuar",
        "php-mail-error-unknown": "Di ammo a biddut iti surat ti annong ti PHP().",
        "botpasswords-invalid-name": "Ti naibaga a nagan ti agar-aramat ket saan nga aglaon iti panangisina ti kontrasenias ti bot (\"$1\").",
        "botpasswords-not-exist": "Ti agar-aramat \"$1\" ket awanan iti kontrasenias ti bot nga agnagan iti \"$2\".",
        "resetpass_forbidden": "Saan a masukatan dagiti kontrasenias",
+       "resetpass_forbidden-reason": "Saan a mabaliwan ti kontrasenias: $1",
        "resetpass-no-info": "Masapul a nakastrekka tapno dagus a makapanka iti daytoy a panid.",
        "resetpass-submit-loggedin": "Sukatan ti kontrasenias",
        "resetpass-submit-cancel": "Ukasen",
        "passwordreset-emailelement": "Nagan ti agar-aramat: \n$1\n\nTemporario a kontrasenias: \n$2",
        "passwordreset-emailsentemail": "No daytoy nga adres ti esurat ket mainaig iti pakabilangam, maipatulodto ti maysa nga esurat iti panangisaad manen ti kontrasenias.",
        "passwordreset-emailsentusername": "No adda adres ti esurat a mainaig iti daytoy a nagan ti agar-aramat, addanto maipatulod nga esurat iti panangisaad manen ti kontrasenia.",
+       "passwordreset-emailsent-capture2": "Naipatulodan {{PLURAL:$1|ti esurat|dagiti esurat}} ti panangisaad manen ti kontrasenias. Ti {{PLURAL:$1|nagan ti agar-aramat ken kontrasenias|listaan dagiti nagan ti agar-aramat ken dagiti kontrasenias}} ket naipakita dita baba.",
+       "passwordreset-emailerror-capture2": "Napaay ti panangitulod ti usurat iti {{GENDER:$2|agar-aramat}}: $1 Ti {{PLURAL:$3|nagan ti agar-aramat ken kontrasenias|listaan dagiti agar-aramat ken dagiti kontrasenias}} ket naipakita dita baba.",
+       "passwordreset-nocaller": "Nasken a maited ti maysa nga agtawtawag",
+       "passwordreset-nosuchcaller": "Awan ti agtawtawag: $1",
+       "passwordreset-ignored": "Saan a natengngel ti panangisaad manen ti kontrasenias. Mabalin a saan a nakompigura ti mangited?",
+       "passwordreset-invalideamil": "Imbalido nga adres ti esurat",
+       "passwordreset-nodata": "Saan a naited ti nagan ti agar-aramat wenno maysa nga adres ti esurat",
        "changeemail": "Sukatan wenno ikkaten ti adres ti esurat",
        "changeemail-header": "Kompletuen daytoy a porma tapno masukatan ti adres ti esuratmo. No kayatmo a maikkat ti pannakainaig iti ania man nga adres ti esurat manipud iti pakabilangam, ibati a blanko ti baro nga adres ti esurat no ited ti porma.",
        "changeemail-no-info": "Masapul a nakastrekka tapno dagus a makapan iti ditoy a panid.",
        "minoredit": "Daytoy ket bassit a panagurnos",
        "watchthis": "Bantayan daytoy a panid",
        "savearticle": "Idulin ti panid",
+       "savechanges": "Idulin dagiti binaliwan",
+       "publishpage": "Ipablaak ti panid",
+       "publishchanges": "Ipablaak dagiti binaliwan",
        "preview": "Ipadas",
        "showpreview": "Ipakita ti ipadas",
        "showdiff": "Ipakita dagiti sinukatan",
        "userpage-userdoesnotexist": "Ti pakabilangan ti agar-aramat ni \"$1\" ket saan a nakarehistro. \nPangngaasi a kitaem no kayatmo ti agpartuat/agurnos iti daytoy a panid.",
        "userpage-userdoesnotexist-view": "Ti pakabilangan ti agar-aramat ni \"$1\" ket saan a nakarehistro.",
        "blocked-notice-logextract": "Agdama a naserraan daytoy nga agar-aramat.\nTi naudi a listaan ti pannakaserra ket naited dita baba para iti reperensia:",
-       "clearyourcache": "<strong>Nota:</strong> Kalpasan ti panangidulin, koma ket masapul nga ipalabas ti cahe ti pagbasabasam tapno makita dagiti sinukatam.\n* <strong>Firefox / Safari:</strong>  Tenglen ti <em>Shift</em> bayat a pinduten ti <em>Reload</em>, wenno talmegan ti <em>Ctrl-F5</em> wenno <em>Ctrl-R</em> (<em>⌘-R</em> iti Mac)\n* <strong>Google Chrome:</strong> Talmegan ti <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> iti Mac)\n* <strong>Internet Explorer:</strong> Tenglen ti <em>Ctrl</em> bayat a pinduten ti <em>Refresh</em>, wenno talmegan ti <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Dalusan ti cache idiay <em>Tools → Preferences</em>",
+       "clearyourcache": "<strong>Nota:</strong> Kalpasan ti panangidulin, koma ket masapul nga ipalabas ti cahe ti pagbasabasam tapno makita dagiti sinukatam.\n* <strong>Firefox / Safari:</strong>  Tenglen ti <em>Shift</em> bayat a pinduten ti <em>Reload</em>, wenno talmegan ti <em>Ctrl-F5</em> wenno <em>Ctrl-R</em> (<em>⌘-R</em> iti Mac)\n* <strong>Google Chrome:</strong> Talmegan ti <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> iti Mac)\n* <strong>Internet Explorer:</strong> Tenglen ti <em>Ctrl</em> bayat a pinduten ti <em>Refresh</em>, wenno talmegan ti <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Mapan iti <em>Menu → Settings</em> (<em>Opera → Preferences</em> iti Mac) ken kalpasanna iti <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "<strong>Paammo:</strong>  Usaren ti buton ti \"{{int:showpreview}}\" tapno masubokan ti baro a CSS sakbay nga agidulin.",
        "userjsyoucanpreview": "<strong>Pammo:</strong> Usaren ti buton ti \"{{int:showpreview}}\" tapno masubokan ti baro a JavaScript sakbay nga agidulin.",
        "usercsspreview": "<strong>Laglagipem nga ipadpadasmo laeng ti bukodmo a CSS ti agar-aramat.\nSaan pay a naidulin!</strong>",
        "content-model-css": "CSS",
        "content-json-empty-object": "Awan linaon a banag",
        "content-json-empty-array": "Awan linaon a rimpuok",
+       "deprecated-self-close-category": "Pampanid nga agus-usar kadagiti imbalido a bukod nga agrikrikep nga etiketa ti HTML",
+       "deprecated-self-close-category-desc": "Ti panid ket aglaon kadagiti imbalido a bukod nga agrikrikep nga etiketa ti HTML, a kas ti <code>&lt;b/></code> wenno <code>&lt;span/></code>.  Agbaliwton ti panagkukua dagitoy tapno maitunos iti espesipikasion ti HTML5, isu a nasukatanen ti usarda iti wikitext.",
        "duplicate-args-warning": "<strong>Ballaag:</strong> Tawtawagan ti [[:$1]] ti [[:$2]] iti ad-adu ngem maysa a pateg para iti parametro \"$3\". Mausarto laeng ti naudi a naited a pateg.",
        "duplicate-args-category": "Pampanid nga agus-usar kadagiti duplikado nga argumento kadagiti panagtawag ti plantilia",
        "duplicate-args-category-desc": "Ti panid ket aglaon kadagiti panagtawag ti plantilia nga agus-usar kadagiti duplikado dagiti argumento, a kas ti <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> wenno <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "right-override-export-depth": "Agipan kadagiti panid a mairaman dagiti naisilpo a panid agingana iti kauneg ti 5",
        "right-sendemail": "Agipatulod iti esurat kadagiti sabali nga agar-aramat",
        "right-passwordreset": "Agkita kadagiti esurat ti panangisaad manen ti kontrasenias",
-       "right-managechangetags": "Agpartuat ken agikkat kadagiti [[Special:Tags|etiketa]] manipud iti database",
+       "right-managechangetags": "Agpartuat ken (de)aktibuen [[Special:Tags|etiketa]]",
        "right-applychangetags": "Ipakat dagiti [[Special:Tags|etiketa]] a mairaman dagiti nabaliwan",
        "right-changetags": "Agnayon ken agikkat kadagiti arbitario nga [[Special:Tags|etiketa]] kadagiti agmaymaysa a rebision ken dagiti naikabkabil iti listaan",
+       "right-deletechangetags": "Ikkaten dagiti [[Special:Tags|etiketa]] manipud iti database",
        "grant-generic": "Raay ti karbengan ti \"$1\"",
        "grant-group-page-interaction": "Makitignay kadagiti panid",
        "grant-group-file-interaction": "Makitignay iti midia",
        "grant-group-high-volume": "Agaramid iti adu iti tomo nga aktibidad",
        "grant-group-customization": "Kustomisasion ken dagiti kakaykayatan",
        "grant-group-administration": "Agaramid kadagiti administratibo nga aksion",
+       "grant-group-private-information": "Serrekan ti pribado a datos a maipanggep kenka",
        "grant-group-other": "Nadumaduma nga aktibidad",
        "grant-blockusers": "Serraan ken ikkaten ti serra dagiti agar-aramat",
        "grant-createaccount": "Agpartuat kadagiti pakabilangan",
        "grant-highvolume": "Adu a tomo a panagurnos",
        "grant-oversight": "Ilemmeng dagiti agar-aramat ken lappedan dagiti rebision",
        "grant-patrol": "Patruliaan dagiti panagbaliw kadagiti panid",
+       "grant-privateinfo": "Serrekan ti pribado a pakaammo",
        "grant-protect": "Salakniban ken ikkaten ti salaknib dagiti panid",
        "grant-rollback": "Isubli dagiti panagbaliw kadagiti panid",
        "grant-sendemail": "Agipatulod iti esurat kadagiti sabali nga agar-aramat",
        "rightslogtext": "Daytoy ket listaan dagiti sinukatan a karbengan ti agar-aramat.",
        "action-read": "agbasa iti datoy a panid",
        "action-edit": "agurnos iti daytoy a panid",
-       "action-createpage": "agpartuat kadagiti panid",
-       "action-createtalk": "agpartuat kadagiti pagtungtungan a panid",
+       "action-createpage": "partuaten daytoy a panid",
+       "action-createtalk": "partuaten daytoy a pagtungtungan a panid",
        "action-createaccount": "agpartuat iti pakabilangan daytoy nga agar-aramat",
        "action-autocreateaccount": "automatiko a partuaten daytoy nga akinruar a pakabilangan ti agar-aramat",
        "action-history": "agkita iti pakasaritaan iti daytoy a panid",
        "action-viewmyprivateinfo": "agkita iti bukodmo a pribado a pakaammo",
        "action-editmyprivateinfo": "agurnos iti bukodmo a pribado a pakaammo",
        "action-editcontentmodel": "urnosen ti modelo ti linaon iti panid",
-       "action-managechangetags": "agpartuat ken agikkat kadagiti etiketa manipud iti database",
+       "action-managechangetags": "agpartuat ken (de)aktibuen kadagiti etiketa",
        "action-applychangetags": "ipakat dagiti etiketa a mairaman dagiti nabaliwan",
        "action-changetags": "agnayon ken agikkat kadagiti arbitario nga etiketa kadagiti agmaymaysa a rebision ken dagiti naikabkabil iti listaan",
+       "action-deletechangetags": "ikkaten dagiti etiketa manipud iti database",
+       "action-purge": "purgaen daytoy a panid",
        "nchanges": "$1 {{PLURAL:$1|sinukatan|dagiti sinukatan}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|manipud iti naudi a panagsarungkar}}",
        "enhancedrc-history": "pakasaritaan",
        "recentchangeslinked-page": "Nagan ti panid:",
        "recentchangeslinked-to": "Ipakita dagiti sinukatan kadagiti panid nga imbes a naisilpo iti naited a panid",
        "recentchanges-page-added-to-category": "nainayon ti [[:$1]] iti kategoria",
-       "recentchanges-page-added-to-category-bundled": "nainayon ti [[:$1]] ken [[Special:WhatLinksHere/$1|{{PLURAL:$2|maysa a panid|$2 a pampanid}}]] iti kategoria",
+       "recentchanges-page-added-to-category-bundled": "Nainayon ti [[:$1]] iti kategoria ken [[Special:WhatLinksHere/$1|daytoy a panid ket nairaman iti kaunegan dagiti sabali a panid]]",
        "recentchanges-page-removed-from-category": "naikkat ti [[:$1]] manipud iti kategoria",
-       "recentchanges-page-removed-from-category-bundled": "Naikkat ti [[:$1]] ken {{PLURAL:$2|maysa a panid|$2 a pampanid}} manipud iti kategoria",
+       "recentchanges-page-removed-from-category-bundled": "Naikkat ti [[:$1]] manipud iti kategoria ken, [[Special:WhatLinksHere/$1|daytoy a panid ket nairaman iti kaunegan dagiti sabali a panid]]",
        "autochange-username": "Automatiko a panagbaliw iti MediaWiki",
        "upload": "Agikarga iti papeles",
        "uploadbtn": "Agikarga iti papeles",
        "uploaded-setting-event-handler-svg": "Naserraan ti panangisaad ti kadagiti gupit ti panagtengngel ti pasamak, nakabiruk iti <code>&lt;$1 $2=\"$3\"&gt;</code> iti naikarga a papeles ti SVG.",
        "uploaded-setting-href-svg": "Ti panagusar ti etiketa ti \"set\" tapno mainayon ti gupit ti \"href\" iti elemento ti nagannak ket naserraan.",
        "uploaded-wrong-setting-svg": "Ti panagusar ti etiketa ti \"set\" tapno mainayon ti puntaan nga remote/data/script iti ania man a gupit ket naserraan. Nabirukan ti <code>&lt;set to=\"$1\"&gt;</code> iti naikarga a papeles ti SVG.",
+       "uploaded-setting-handler-svg": "Naserraan ti SVG a nangisaad ti gupit ti \"handler\" nga addaan iti remote/data/script. Nabirukan ti <code>$1=\"$2\"</code> iti naikarga a papeles ti SVG.",
+       "uploaded-remote-url-svg": "Naserraan ti SVG a nangisaad ti gupit iti ania man nga estilo nga addaan iti remote nga URL. Nabirukan ti <code>$1=\"$2\"</code> iti naikarga a papeles ti SVG.",
        "uploaded-image-filter-svg": "Nakabiruk ti sagat ti ladawan nga addaan iti URL: <code>&lt;$1 $2=\"$3\"&gt;</code> iti naikarga a papeles ti SVG.",
        "uploadscriptednamespace": "Daytoy a papeles ti SVG ket aglaon iti maysa a saan a mabalin a nagan ti espasio ti \"$1\".",
        "uploadinvalidxml": "Ti XML iti naikarga a papeles ket saan a maiwaswas.",
        "upload-options": "Dagiti pagpilian ti panagikarga",
        "watchthisupload": "Bantayan daytoy a papeles",
        "filewasdeleted": "Ti papeles iti daytoy a nagan ket dati a naikarga ken kanungpalan a naikkat.\nNasken a kitaem ti $1 sakbay nga agtuloy a mangikarga manen.",
+       "filename-thumb-name": "Daytoy ket kasla titulo ti bassit a ladawan. Pangngaasi a saan nga agikarga kadagiti bassit a ladawan iti agpada a wiki. Wenno saan, pangngaasi a simpaen ti nagan ti papeles tapno makaibuksilan, ken awan ti pasakbay ti bassit a ladawan.",
        "filename-bad-prefix": "Ti nagan ti papeles nga ikarkargam ket mangrugi iti <strong>\"$1\"</strong>,  ken saan a deskriptibo a nagan a kadawyan nga automatiko nga ited babaen dagiti digital a kamera.\nPangngaasi nga agpili ti nasaysayaat a deskriptibo a nagan ti papelesmo.",
        "upload-proto-error": "Saan a husto a protokol",
        "upload-proto-error-text": "Ti adayo a panagikarga ket makasapul kadagiti URL a mangrugi iti <code>http://</code> wenno <code>ftp://</code>.",
        "upload-too-many-redirects": "Ti URL ket naglaon kadagiti adu unay a baw-ing",
        "upload-http-error": "Adda napasamak a biddut ti HTTP: $1",
        "upload-copy-upload-invalid-domain": "Dagiti kopia a panagikarga ket saan a magun-od manipud iti daytoy a dominio.",
+       "upload-foreign-cant-upload": "Daytoy a wiki ket saan a nakompigura a mangikarga kadagiti papeles iti kiniddaw a ganganaet a repositorio ti papeles.",
+       "upload-foreign-cant-load-config": "Napaay a nangikarga ti kompigurasion para kadagiti panangikarga ti papeles iti ganganaet a repositorio ti papeles.",
+       "upload-dialog-disabled": "Nabaldado iti daytoy a wiki dagiti panangikarga ti papeles iti daytoy a dialogo.",
        "upload-dialog-title": "Agikarga iti papeles",
        "upload-dialog-button-cancel": "Ukasen",
        "upload-dialog-button-done": "Nalpasen",
        "upload-dialog-button-upload": "Agikarga",
        "upload-form-label-infoform-title": "Dagiti salaysay",
        "upload-form-label-infoform-name": "Nagan",
+       "upload-form-label-infoform-name-tooltip": "Ti naisangayan a deskriptibo a titulo para iti papeles, nga agserbinto a kas ti nagan ti papeles. Mabalinmo ti agusar ti naranas a pagsasao nga agraman kadagiti baetan. Saan nga iraman ti pagpaatiddog ti papeles.",
        "upload-form-label-infoform-description": "Deskripsion",
+       "upload-form-label-infoform-description-tooltip": "Ipalawag bassit dagiti amin a nalatak a maipanggep iti obra.\nPara iti retrato, ibaga dagiti nangruna a banag a nailadladawan, ti okasion, wenno ti lugar.",
        "upload-form-label-usage-title": "Panagusar",
        "upload-form-label-usage-filename": "Nagan ti papeles",
        "upload-form-label-own-work": "Daytoy ket bukodko nga obra",
        "upload-form-label-infoform-categories": "Katkategoria",
        "upload-form-label-infoform-date": "Petsa",
+       "upload-form-label-own-work-message-generic-local": "Pasingkedak nga ikarkargak daytoy a papeles a sumursurot kadagiti termino ti serbisio ken dagiti annuroten ti lisensia iti {{SITENAME}}.",
+       "upload-form-label-not-own-work-message-generic-local": "No saanka a makaikarga iti daytoy a papeles babaen dagiti annuroten iti {{SITENAME}}, pangngaasi nga irekep daytoy a dialogo ken padasen ti sabali a pamay-an.",
        "upload-form-label-not-own-work-local-generic-local": "Mabalinmo pay a padasen [[Special:Upload|ti kasisigud a pagikargaan a panid]].",
+       "upload-form-label-own-work-message-generic-foreign": "Maawatak nga agikarkargaak iti daytoy a papeles iti pagbibingayan a repositorio. Pasingkedak nga ar-aramidek a sumursurot kadagiti termino ti serbisio ken dagiti annuroten ti lisensia idiay.",
+       "upload-form-label-not-own-work-message-generic-foreign": "No saanka a makaikarga iti daytoy a papeles babaen dagiti annuroten iti pagbibingayan a repositorio, pangngaasi nga irekep daytoy a dialogo ken padasen ti sabali a pamay-an.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Malinmo pay a padasen ti panagusar [[Special:Upload|ti panid a pagikargaan iti {{SITENAME}}]], no daytoy a panid ket mabalin a maikarga idiay babaen dagiti bukodda nga annuroten.",
        "backend-fail-stream": "Saan a maipan ti papeles $1.",
        "backend-fail-backup": "Saan a makaidulin ti kapada ti papeles ti $1.",
        "backend-fail-notexists": "Awan ti papeles ti $1.",
        "uploadstash-badtoken": "Napaay ti panagtungpal dayta nga aramid. Mabalin a ti talekmo nga agurnos ket nagpason. Pangngaasi a padasen manen.",
        "uploadstash-errclear": "Napaay ti panagdalus kadagiti papeles.",
        "uploadstash-refresh": "Pasadiwaen dagiti listaan ti papeles",
+       "uploadstash-thumbnail": "kitaen ti bassit a ladawan",
+       "uploadstash-exception": "Saan a naidulin ti panangikarga iti stash ($1): \"$2\".",
        "invalid-chunk-offset": "Imbalido a pirgis ti timbengan",
        "img-auth-accessdenied": "Nalibak ti iseserrek",
        "img-auth-nopathinfo": "Ti servermo ket saan a naisaad nga agipasa iti daytoy a pakaammo.\nMabalin a naibatay iti CGI ken saan a makasuporta ti img_auth.\nKitaen ti https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization .",
        "listgrouprights-namespaceprotection-namespace": "Nagan ti espasio",
        "listgrouprights-namespaceprotection-restrictedto": "Karbengan wenno karkarbengan a mangpalubos nga agurnos ti agar-aramat",
        "listgrants": "Dagiti sagut",
+       "listgrants-summary": "Ti sumagand ket listaan dagiti sagut nga agraman kadagiti mainaig a panagserrek kadagiti karbengan ti agar-aramat. Dagiti agar-aramat ket mabalinda ti mangipalubos kadagiti aplikasion a mausarda iti bukodda a pakabilangan, ngem addaan iti limitado a pammalubos a naibatay kadagiti sagut nga inted ti agar-aramat iti aplikasion. Nupay kasta ti maysa nga aplikasion nga agtigtignay para iti agar-aramat ket saan a pudno a makausar kadagiti karbengan nga awanan iti agar-aramat.\nMabalin nga adda iti [[{{MediaWiki:Listgrouprights-helppage}}|maipatinayon a pakaammo]] a maipanggep kadagiti indibidual a karbengan.",
        "listgrants-grant": "Sagut",
        "listgrants-rights": "Dagiti karbengan",
        "trackingcategories": "Pagsurotan a katkategoria",
        "trackingcategories-msg": "Pagsurotan a kategoria",
        "trackingcategories-name": "Nagan ti mensahe",
        "trackingcategories-desc": "Kriteria ti panangiraman ti kategoria",
+       "restricted-displaytitle-ignored": "Pampanid nga addaan kadagiti di naikaskaso a titulo ti panangiparang",
+       "restricted-displaytitle-ignored-desc": "Ti panid ket addaan iti maysa a di naikaskaso a <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> gapu ta saan a kapada iti pudno a titulo ti panid.",
        "noindex-category-desc": "Ti panid ket saan naipagsurotan babaen dagiti robot gapu ta addaan iti salamangka a balikas iti <code><nowiki>__NOINDEX__</nowiki></code> ken adda iti nagan ti espasio a maipalubos ti wagayway.",
        "index-category-desc": "Ti panid ket addaan iti <code><nowiki>__INDEX__</nowiki></code> (ken adda iti nagan ti espasio a maipalubos ti wagayway), ken isu a naipagsurotan babaen dagiti robot ngem no iti kadawyan ket saan.",
        "post-expand-template-inclusion-category-desc": "Ti kadakkel ti panid ket dakdakkel ngem <code>$wgMaxArticleSize</code> kalpasan ti panangipadakkel amin dagiti plantilia, isu nga adda met dagiti plantilia a saan a naipadakkel",
        "watchnologin": "Saan a nakastrek",
        "addwatch": "Inayon iti listaan ti bambantayan",
        "addedwatchtext": "Ti \"[[:$1]]\" ken ti tungtunganna a panid ket nainayonen iti [[Special:Watchlist|listaan ti bambantayam]].",
+       "addedwatchtext-talk": "Ti \"[[:$1]]\" ken ti mainaig a panidna ket nainayoen iti [[Special:Watchlist|bambantayam]].",
        "addedwatchtext-short": "Ti panid ti \"$1\" ket nainayonen iti listaan ti bambantayam.",
        "removewatch": "Ikkaten manipud ti listaan ti bambantayan",
        "removedwatchtext": "Ti \"[[:$1]]\" ken ti tungtunganna a panid ket naikkaten manipud iti [[Special:Watchlist|listaan ti bambantayam]].",
+       "removedwatchtext-talk": "Ti \"[[:$1]]\" ken ti mainaig a panidna ket naikkaten manipud iti [[Special:Watchlist|bambantayam]].",
        "removedwatchtext-short": "Ti panid ti \"$1\" ket naikkaten manipud ti listaan ti bambantayam.",
        "watch": "Bantayan",
        "watchthispage": "Bantayan daytoy a panid",
        "rollbacklinkcount": "agisubli ti $1 {{PLURAL:$1|nga inurnos|nga inur-urnos}}",
        "rollbacklinkcount-morethan": "agisubli ti ad-adu ngem $1 {{PLURAL:$1|nga inurnos|nga inur-urnos}}",
        "rollbackfailed": "Napaay ti panangisubli",
+       "rollback-missingparam": "Awan dagiti nasken a parametro iti kiddaw.",
        "cantrollback": "Saan a maisubli ti panagurnos;\nti naudi a nakaaramid ket iti laeng nagsurat iti daytoy a panid.",
        "alreadyrolled": "Saan a maipasubli ti kinaudi a panagurnos iti [[:$1]] babaen ni [[User:$2|$2]] ([[User talk:$2|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nadda sabali a naurnos wenno nagipasubli ti panid.\n\nTi kinaudi a panagurnos ti daytoy a panid ket babaen ni [[User:$3|$3]] ([[User talk:$3|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Ti pakabuklan idi ti panagurnos ket: <em>$1</em>.",
        "revertpage": "Insubli ti panagurnos babaen ni [[Special:Contributions/$2|$2]] ([[User talk:$2|tungtungan]]), naisubli ti kinaudi a rebision babaen ni [[User:$1|$1]]",
        "revertpage-nouser": "Naisubli dagiti inurnos babaen ti nailemmeng nga agar-aramat iti kinaudi a rebision babaen ni {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Naibabawi dagiti panagurnos babaen ni $1;\nnaisubli manen ti naudi a rebision babaen ni $2.",
+       "rollback-success-notify": "Naibabawi dagiti panagurnos babaen ni $1;\nisubli ti naudi a rebision babaen ni $2. [$3 Ipakita dagiti binaliwan]",
        "sessionfailure-title": "Napaay ti sesion",
        "sessionfailure": "Adda parikut ti sesion ti panagserrekmo;\ndaytoy nga aramid ket naibabawi a kas pagpawilan ti panaghijack ti sesion.\nAgsublika iti naggapuam a panid, ikargam manen ti panid ken padasen manen.",
        "changecontentmodel": "Baliwan ti modelo ti linaon ti panid",
        "changecontentmodel-success-text": "Nabaliwanen ti kita ti linaon ti [[:$1]].",
        "changecontentmodel-cannot-convert": "Ti linaon iti [[:$1]] ket saan a mabaliwan iti kita ti $2.",
        "changecontentmodel-nodirectediting": "Ti modelo ti linaon ti $1 ket saan a mangsuporta ti dagus a panagurnos",
+       "changecontentmodel-emptymodels-title": "Awan ti magun-od kadagiti modelo ti linaon",
+       "changecontentmodel-emptymodels-text": "Ti linaon iti [[:$1]] ket saan a mabaliwan iti ania man a kita.",
        "log-name-contentmodel": "Listaan ti panagbaliw ti modelo ti linaon",
        "log-description-contentmodel": "Dagiti pasamak a mainaig kadagiti modelo ti linaon ti panid",
+       "logentry-contentmodel-new": "{{GENDER:$2|Pinartuat}} ni $1 ti panid ti $3 a nagusar ti saan a kasisigud a modelo ti linaon ti \"$5\"",
        "logentry-contentmodel-change": "{{GENDER:$2|Binaliwan}} ni $1 ti modelo ti panid ti $3 manipud ti \"$4\" iti \"$5\"",
        "logentry-contentmodel-change-revertlink": "isubli",
        "logentry-contentmodel-change-revert": "isubli",
        "undeletehistorynoadmin": "Daytoy a panid ket naikkaten.\nTi rason ti panagikkat ket naipakita iti pakabuklan dita baba, ken dagita a salaysay ti agar-aramat a nagurnos iti daytoy a panid sakbay a naikkat.\nTi husto a testo dagitoy a naikat a rebision ket magun-od laeng dagiti administrador.",
        "undelete-revision": "Naikkat ti rebision ti $1 (manipud idi $4, $5) babaen ni $3:",
        "undeleterevision-missing": "Imbalido wenno napukaw a rebision.\nAddaanka ngata ti madi a silpo, wenno ti rebision ket mabalin a naipasubli wenno naikkat manipud ti arkibo.",
+       "undeleterevision-duplicate-revid": "Saan a maisubli {{PLURAL:$1|ti maysa a rebision|dagiti $1 a rebision}}, gapu ta {{PLURAL:$1|ti bukona|dagiti bukodda}} nga <code>rev_id</code> ket naus-usaren.",
        "undelete-nodiff": "Awan ti nasarakan kadagiti dati a rebision.",
        "undeletebtn": "Isubli",
        "undeletelink": "kitaen/isubli",
        "undeletedrevisions": "{{PLURAL:$1|1 a rebision|dagiti $1 a rebision}} ti naisubli",
        "undeletedrevisions-files": "{{PLURAL:$1|1 a rebision|dagiti $1 a rebision}} ken {{PLURAL:$2|1 a papeles|dagiti $2 a papeles}} ti naisubli",
        "undeletedfiles": "{{PLURAL:$1|1 a papeles|dagiti $1 a papeles}} ti naisubli",
-       "cannotundelete": "Napaay ti panagisubli iti panagikkat:\n$1",
+       "cannotundelete": "Napaay ti sangkabassit wenno amin iti panagisubli ti panagikkat:\n$1",
        "undeletedpage": "<strong>Naisublin ti $1</strong>\n\nBinsiren ti [[Special:Log/delete|listaan ti panagikkat]] para iti rehistro dagiti kaudian panagikkat ken naisubsubli.",
        "undelete-header": "Kitaen [[Special:Log/delete|ti listaan ti panagikkat]] kadagiti kaudian a naikkat a panid.",
        "undelete-search-title": "Biruken dagiti naikkat a panid",
        "sp-contributions-newbies-sub": "Para kadagiti kabarbaro a pakabilangan",
        "sp-contributions-newbies-title": "Dagiti kontribusion para kadagiti baro a pakabilangan",
        "sp-contributions-blocklog": "listaan ti serra",
-       "sp-contributions-suppresslog": "pasardengen dagiti kontribusion ti agar-aramat",
-       "sp-contributions-deleted": "dagiti naikkat a kontribusion ti agar-aramat",
+       "sp-contributions-suppresslog": "dagiti napasardeng a kontribusion ti {{GENDER:$1|agar-aramat}}",
+       "sp-contributions-deleted": "dagiti naikkat a kontribusion ti {{GENDER:$1|agar-aramat}}",
        "sp-contributions-uploads": "dagiti naikarga",
        "sp-contributions-logs": "dagiti listaan",
        "sp-contributions-talk": "tungtungan",
        "sp-contributions-username": "IP a pagtaengan wenno nagan ti agar-aramat:",
        "sp-contributions-toponly": "Ipakita laeng dagiti inurnos dagiti kaudian a rebision",
        "sp-contributions-newonly": "Ipakita laeng dagiti inurnos a pannakapartuat ti pampanid",
+       "sp-contributions-hideminor": "Ilemmeng dagiti bassit a panagurnos",
        "sp-contributions-submit": "Biruken",
        "whatlinkshere": "Dagiti nakasilpo ditoy",
        "whatlinkshere-title": "Pampanid a nakasilpo iti \"$1\"",
        "unblock": "Ikkaten ti serra ti agar-aramat",
        "blockip": "Serraan ti {{GENDER:$1|agar-aramat}}",
        "blockip-legend": "Serraan ti agar-aramat",
-       "blockiptext": "Usaren ti porma dita baba tapno maserraan ti panagsurat manipud iti naisangayan nga IP a pagtaengan wenno nagan ti agar-aramat.\nUsaren laeng daytoy tapno pawilan ti bandalismo, ken panagtunos iti [[{{MediaWiki:Policy-url}}|annuroten]].\nIkkan ti naisangayan a rason dita baba (kas pagarigan, dakamaten ti maysa a panid a nabandalismo) .",
+       "blockiptext": "Usaren ti porma dita baba tapno maserraan ti panagserrek ti panagsurat manipud iti naisangayan nga adres ti IP wenno nagan ti agar-aramat.\nDaytoy ket nasken laeng a maaramid tapno mapawilan ti bandalismo, ken segun iti [[{{MediaWiki:Policy-url}}|annuroten]].\nIkabil ti naisangayan a rason dita baba (kas pagarigan, ti panagdakamat kadagiti naisangayan a panid a nabandalismo).\nMabalinmo a serraan dagiti sakup ti adres ti IP babaen ti panagusar ti sintaksis ti [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]; ti kadakkelan a maipalubos a sakup ket /$1 para iti IPv4 ken /$2 para iti IPv6.",
        "ipaddressorusername": "IP a pagtaengan wenno nagan ti agar-aramat:",
        "ipbexpiry": "Agpaso:",
        "ipbreason": "Rason:",
        "ipb-unblock": "Lukatan ti serra ti nagan ti agar-aramat wenno IP a pagtaengan",
        "ipb-blocklist": "Kitaen dagiti adda a serra",
        "ipb-blocklist-contribs": "Dagiti kontribusion para kenni {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 ti nabati",
        "unblockip": "Lukatan ti serra ti agar-aramat",
        "unblockiptext": "Usaren ti porma dita baba tapno maisubli ti panagserrek ti panagsurat ti dati a naserran nga IP a pagtaengan wenno nagan ti agar-aramat.",
        "ipusubmit": "Ikkaten daytoy a serra",
        "lockdbsuccesstext": "Nabalunetan ti database.<br />\nLaglagipem nga [[Special:UnlockDB|ikkaten ti balunetna]] kalpasan a malpaska nga agsimpa.",
        "unlockdbsuccesstext": "Nalukatanen ti database.",
        "lockfilenotwritable": "Ti papeles ti balunet ti database ket saan a masuratan.\nTapno mabalunetan ken malukatan ti database, nasken daytoy a masuratan babaen ti web server.",
+       "databaselocked": "Ti database ket agdaman a nabalutenan.",
        "databasenotlocked": "Saan a nabalunetan ti database.",
        "lockedbyandtime": "(ni {{GENDER:$1|$1}} idi $2, $3)",
        "move-page": "Iyalis ti $1",
        "move-page-legend": "Iyalis ti panid",
-       "movepagetext": "Ti panagusar ti porma dita baba, ket mangnagan manen ti panid, a mangiyalis amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin a baw-ing a panid iti baro a titulo.\nMapabarom a kas automatiko dagiti baw-ing a nakatudo dita kasisigud a titulo.\nNo agpilika a saanmo a kayat, siguraduem a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRenbbengmo ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addan sigud a panid iti baro a titulo, malaksid no ti kinaudi ket maysa a baw-ing ken awan ti napalabas a pakasaritaan ti panag-urnos. \nKayat a sawen daytoy a mabalinmo a suktan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saan mo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Ballaag!</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim a pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
-       "movepagetext-noredirectfixer": "Ti panagusar ti kinabuklan dita baba, ket panaganan ti panid, iyalisna amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin baw-ing a panid idiay baro a titulo.\nPasaruduam a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRebbengem ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addan sigud a panid iti baro a titulo, malaksid no awan linaonna wenno no maysa a baw-ing a panid ken awan ti panagbaliw iti pakasaritaan ti napalabas. \nKayat a sawen daytoy a mabalinmo a suktan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saanmo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Ballaag!</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim ta pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
+       "movepagetext": "Ti panagusar ti porma dita baba, ket mangnagan manen ti panid, a mangiyalis amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin a baw-ing a panid iti baro a titulo.\nMapabarom a kas automatiko dagiti baw-ing a nakatudo dita kasisigud a titulo.\nNo agpilika a saanmo a kayat, siguraduem a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRebbengmo ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addaan iti sigud a panid iti baro a titulo, malaksid no ti kinaudi ket maysa a baw-ing ken awan ti napalabas a pakasaritaan ti panagurnos. \nKayat a sawen daytoy a mabalinmo a sukatan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saanmo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Nota:</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim a pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
+       "movepagetext-noredirectfixer": "Ti panagusar ti kinabuklan dita baba, ket panaganan ti panid, iyalisna amin ti pakasaritaanna iti baro a nagan.\nTi daan a titulo ket agbalin baw-ing a panid idiay baro a titulo.\nSiguraduem a kitaen ti [[Special:DoubleRedirects|doble]] wenno [[Special:BrokenRedirects|nadadael a baw-ing]].\nRebbengem ti mangpatalged nga amin a silpo ket agtultuloy a nakatudo iti nasken a papananda.\n\nLaglagipen a ti panid ket <strong>saan</strong> a maiyalis no addaan iti sigud a panid iti baro a titulo, malaksid no awan linaonna wenno no maysa a baw-ing a panid ken awan ti panagbaliw iti pakasaritaan ti napalabas. \nKayat a sawen daytoy a mabalinmo a sukatan ti nagan ti maysa a panid manipud iti punto ti pannakasukat ti nagan no nagbiddutka, ken saanmo a mabalin a suratan manen ti addaan a panid.\n\n<strong>Nota:</strong>\nMabalin a maysa daytoy a nakaro ken saan a bigla a panagbaliw iti maysa a nasikat a panid;\npangngaasim ta pasingkedam a maawatam ti ibunga daytoy sakbay nga agtuloyka a mangbaliw.",
        "movepagetalktext": "No kur-item daytoy a kahon, automatikonto a maiyalis ti mainaig a tungtungan a panid, malaksid no addanto idiay iti adda linaon a tungtungan a panid.\n\nIti daytoy a kaso, masapul nga iyalis wenno manual nga itiponmo ti panid no kayatmo.",
        "moveuserpage-warning": "<strong>Ballaag:</strong> Mangrugrugika nga agiyalis ti panid ti agar-aramat. Pangngaasi a laglapipen a ti panid ket isu laeng ti maiyalis ken ti agar-aramat ket <em>saanto</em> a managanan.",
        "movecategorypage-warning": "<strong>Ballaag:</strong> Mangiyal-aliskan iti panid ti kategoria. Pangngaasi a laglagipen a ti maiyalisto laeng ket ti panid ken ti aniaman a pampanid iti daan a kategoria ket <em>saanto</em> a maikategoria iti baro.",
        "import-nonewrevisions": "Awan dagiti naala a rebision (mabalin nga adda amin dagitoyen, wenno nalabsan gapu kadagiti biddut).",
        "xml-error-string": "$1 iti linia $2, tukol $3 (byte $4): $5",
        "import-upload": "Ikarga ti datos ti XML",
-       "import-token-mismatch": "Napukaw ti sesion ti datos.\nPangngaasi a padasen manen.",
+       "import-token-mismatch": "Pannakapukaw ti sesion ti datos.\n\nMabalin a nakaruarka. <strong>Pangngaasi a pasingkedan a nakastrekka pay laeng ken padasem manen</strong>.\nNo saan pay a mabalin, padasem ti [[Special:UserLogout|rummuar]] ken sumrek manen, ken kitaen no ti pagpasabasam ket mangipalubos kadagiti galieta manipud iti daytoy a sitio.",
        "import-invalid-interwiki": "Saan a makaala manipud ti nainaganan a wiki.",
        "import-error-edit": "Ti panid ti \"$1\" ket saan idi a naala ngamin ket saanmo a mabalin nga urnosen.",
        "import-error-create": "Ti panid ti \"$1\" ket saan idi a naala ngamin ket saanmo a mabalin a partuaten.",
        "tooltip-ca-nstab-category": "Kitaen ti panid ti kategoria",
        "tooltip-minoredit": "Markaan daytoy a kas bassit a panag-urnos",
        "tooltip-save": "Idulin dagiti sinukatam",
+       "tooltip-publish": "Ipablaak dagiti binaliwam",
        "tooltip-preview": "Ipadas dagiti sinukatam, pangngaasi nga usarem daytoy sakbay nga idulin ti panid!",
        "tooltip-diff": "Ipakita no ania dagiti sinukatan nga inaramidmo iti testo",
        "tooltip-compareselectedversions": "Kitaen ti naggidiatan dagiti dua a napili a bersion iti daytoy a panid.",
        "svg-long-error": "Saan nga umiso a papeles ti SVG: $1",
        "show-big-image": "Kasisigud a papeles",
        "show-big-image-preview": "Kadakkel daytoy a panagipadas: $1.",
+       "show-big-image-preview-differ": "Kadakkel daytoy a panangipadas ti $3 iti daytoy a papeles ti $2: $1.",
        "show-big-image-other": "Sabali {{PLURAL:$2|a resolusion|kadagiti resolusion}}: $1.",
        "show-big-image-size": "$1 × $2 dagiti piksel",
        "file-info-gif-looped": "nasiluan",
        "newimages-legend": "Sagat",
        "newimages-label": "Nagan ti papeles (wenno pasetna) :",
        "newimages-showbots": "Ipakita dagiti naikarga babaen dagiti bot",
+       "newimages-hidepatrolled": "Ilemmeng dagiti panangikarga a napatruliaan",
        "noimages": "Awan ti makita.",
        "ilsubmit": "Biruken",
        "bydate": "babaen ti petsa",
        "confirm-watch-top": "Inayon daytoy a panid iti listaan ti bambantayam?",
        "confirm-unwatch-button": "Sige",
        "confirm-unwatch-top": "Ikkatem daytoy a panid manipud ti listaan ti bambantayam?",
+       "confirm-rollback-button": "Sige",
+       "confirm-rollback-top": "Isubli dagiti panagurnos iti daytoy a panid?",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← napalabas a panid",
        "imgmultipagenext": "sumaruno a panid →",
        "watchlistedit-raw-done": "Napabaron ti listaan ti bambantayam.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 a titulo|$1 kadagiti titulo}} ti nainayon:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 a titulo|$1 kadagiti titulo}} ti naikkat:",
-       "watchlistedit-clear-title": "Nadalusanen ti listaan ti bambantayan",
+       "watchlistedit-clear-title": "Dalusan ti listaan ti bambantayan",
        "watchlistedit-clear-legend": "Dalusan ti listaan ti bambantayan",
        "watchlistedit-clear-explain": "Amin dagiti titulo ket maikkatto manipud ti listaan ti bambantayam",
        "watchlistedit-clear-titles": "Dagiti titulo:",
        "timezone-local": "Lokal",
        "duplicate-defaultsort": "<strong>Ballag:</strong> Kasisigud a panagilasin ti \"$2\" ket tuonana ti immuna a kasisigud a panagilasin ti \"$1\".",
        "duplicate-displaytitle": "<strong>Ballaag:</strong> Ti maiparang a titulo ti \"$2\" ket tuonanna ti immmuna a maiparang a titulo ti \"$1\".",
+       "restricted-displaytitle": "<strong>Ballaag:</strong> Di naikaskaso ti titulo ti panagiparang ti \"$1\" gapu ta saan a kapada ti pudno a titulo ti panid.",
        "invalid-indicator-name": "<strong>Biddut:</strong> Ti gupit ti <code>name</code> a panangipakita ti kasasaad ti panid ket nasken nga adda linaon.",
        "version": "Bersion",
        "version-extensions": "Dagiti naisaad a pagpaatiddog",
        "version-libraries-description": "Deskripsion",
        "version-libraries-authors": "Dagiti mannurat",
        "redirect": "Baw-ing babaen ti papeles, agar-aramat, panid, rebision, wenno ID ti listaan",
-       "redirect-summary": "Daytoy nga espesial a panid ket maibaw-ing iti papeles (iti nagan ti papeles), ti panid (iti ID ti rebision wenno ID ti panid), wenno ti panid ti agar-aramat (iti numeriko nga ID ti agar-aramat). Panagusar:\n[[{{#Special:Redirect}}/file/Example.jpg]], \n[[{{#Special:Redirect}}/page/64308]], \n[[{{#Special:Redirect}}/revision/328429]], wenno\n[[{{#Special:Redirect}}/user/101]].",
+       "redirect-summary": "Daytoy nga espesial a panid ket maibaw-ing iti papeles (naited ti nagan ti papeles), ti panid (naited a rebision ti ID wenno ID ti panid), wenno ti panid ti agar-aramat (iti numeriko nga ID ti agar-aramat), wenno ti naikabil iti listaan (naited ti listaan ti ID). Panagusar:\n[[{{#Special:Redirect}}/file/Example.jpg]], \n[[{{#Special:Redirect}}/page/64308]], \n[[{{#Special:Redirect}}/revision/328429]], \n[[{{#Special:Redirect}}/user/101]], wenno\n[[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Inkan",
        "redirect-lookup": "Kitaen:",
        "redirect-value": "Pateg:",
        "tags-delete-not-found": "Awan ti etiketa ti \"$1\".",
        "tags-delete-too-many-uses": "Ti etiketa ti \"$1\" ket naipakat iti ad-adu ngem $2 {{PLURAL:$2|a rebision|kadagiti rebision}}, a ti kaibuksillanna ket saan a mabalin a maikkat.",
        "tags-delete-warnings-after-delete": "Ti etiketa ti \"$1\" ket naikkat, ngem nakita {{PLURAL:$2|ti sumaganad a ballaag|dagiti sumaganad a ballaag}}:",
+       "tags-delete-no-permission": "Awan ti pammalubosmo nga agikkat kadagiti etiketa ti panagbaliw.",
        "tags-activate-title": "Patarayen ti etiketa",
        "tags-activate-question": "Isagsaganamon a patarayen ti etiketa ti \"$1\".",
        "tags-activate-reason": "Rason:",
        "logentry-protect-protect-cascade": "{{GENDER:$2|Sinalakniban}} ni $1 ti $3 $4 [sariap]",
        "logentry-protect-modify": "{{GENDER:$2|Binaliwan}} ni $1 ti agpang ti salaknib para iti $3 $4",
        "logentry-protect-modify-cascade": "{{GENDER:$2|Binaliwan}} ni $1 ti agpang ti salaknib para iti $3 $4 [sariap]",
-       "logentry-rights-rights": "Ni $1 ket {{GENDER:$2|binaliwanna}} ti grupo a pannakaikameng para kenni $3 manipud ti $4 iti $5",
+       "logentry-rights-rights": "Ni $1 ket {{GENDER:$2|binaliwanna}} ti grupo a pannakaikameng para kenni {{GENDER:$6|$3}} manipud iti $4 iti $5",
        "logentry-rights-rights-legacy": "Ni $1 ket {{GENDER:$2|binaliwanna}} ti grupo a pannakaikameng para kenni $3",
        "logentry-rights-autopromote": "Ni $1 ket automatiko idi a {{GENDER:$2|naipangato}} manipud ti $4 iti $5",
        "logentry-upload-upload": "Ni $1 ket {{GENDER:$2|inkargana}} ti $3",
        "searchsuggest-containing": "naglaon ti...",
        "api-error-badaccess-groups": "Saanka mapalubosan nga agikarga kadagiti papeles iti daytoy a wiki.",
        "api-error-badtoken": "Akin-uneg a biddut: Dakes a tandaan.",
+       "api-error-blocked": "Naserraankan manipud iti panagurnos.",
        "api-error-copyuploaddisabled": "Ti panagikarga babaen ti URL ket nabaldado iti daytoy server.",
        "api-error-duplicate": "Adda {{PLURAL:$1|sabali a papeles|dagiti sabali a papeles}} nga addan iti daytoy a sitio nga agraman iti agpada a linaon.",
        "api-error-duplicate-archive": "Adda {{PLURAL:$1|idi sabali a papeles|dagidi sabali a papeles}} nga addaan ditoy a sitio nga agpada ti linaonda, ngem {{PLURAL:$1|daytoy|dagitoy}} ket naikkat.",
        "json-error-unsupported-type": "Naited ti pateg iti kita a saan a maikodigo",
        "headline-anchor-title": "Isilpo iti daytoy a paset",
        "special-characters-group-latin": "Latin",
-       "special-characters-group-latinextended": "Latin napaatiddog",
+       "special-characters-group-latinextended": "Naipaatiddog a Latin",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Dagiti simbolo",
        "special-characters-group-greek": "Griego",
+       "special-characters-group-greekextended": "Naipaatiddog a Griego",
        "special-characters-group-cyrillic": "Siriliko",
        "special-characters-group-arabic": "Arabiko",
-       "special-characters-group-arabicextended": "Arabiko a napaatiddog",
+       "special-characters-group-arabicextended": "Naipaatiddog nga Arabiko",
        "special-characters-group-persian": "Persiano",
        "special-characters-group-hebrew": "Hebreo",
        "special-characters-group-bangla": "Bangla",
        "sessionprovider-generic": "Dagiti sesion ti $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "dagiti sesion a naibatay iti galieta",
        "sessionprovider-nocookies": "Mabalin a nabaldado dagiti galieta. Siguraduem a pinakabaelam dagiti galieta ken mangrugi manen.",
-       "randomrootpage": "Pugto a ramut a panid"
+       "randomrootpage": "Pugto a ramut a panid",
+       "log-action-filter-block": "Kita ti serra:",
+       "log-action-filter-contentmodel": "Kita ti panagbaliw ti modelo ti linaon:",
+       "log-action-filter-delete": "Kita ti panagikkat:",
+       "log-action-filter-import": "Kita ti import:",
+       "log-action-filter-managetags": "Kita ti aksion ti panagtaripato ti etiketa:",
+       "log-action-filter-move": "Kita ti panagiyalis:",
+       "log-action-filter-newusers": "Kita ti panagpartuat ti pakabilangan:",
+       "log-action-filter-patrol": "Kita ti patrulia:",
+       "log-action-filter-protect": "Kita ti salaknib:",
+       "log-action-filter-rights": "Kita ti panagbaliw ti karbengan:",
+       "log-action-filter-suppress": "Kita ti panagpasardeng:",
+       "log-action-filter-upload": "Kita ti panangikarga:",
+       "log-action-filter-all": "Amin",
+       "log-action-filter-block-block": "Serra",
+       "log-action-filter-block-reblock": "Panagbaliw ti serra",
+       "log-action-filter-block-unblock": "Ikkaten ti serra",
+       "log-action-filter-contentmodel-change": "Panagbaliw ti Contentmodel",
+       "log-action-filter-contentmodel-new": "Panagpartuat ti panid iti saan a pagalagadan a Contentmodel",
+       "log-action-filter-delete-delete": "Panagikkat ti panid",
+       "log-action-filter-delete-restore": "Panangisubli ti panagikkat ti panid",
+       "log-action-filter-delete-event": "Panagikkat ti listaan",
+       "log-action-filter-delete-revision": "Panagikkat ti rebision",
+       "log-action-filter-import-interwiki": "Transwiki nga import",
+       "log-action-filter-import-upload": "Import babaen ti panangikarga ti XML",
+       "log-action-filter-managetags-create": "Panagpartuat ti etiketa",
+       "log-action-filter-managetags-delete": "Panagikkat ti etiketa",
+       "log-action-filter-managetags-activate": "Paaktibuen ti etiketa",
+       "log-action-filter-managetags-deactivate": "Deaktibuen ti etiketa",
+       "log-action-filter-move-move": "Iyalis a saan a mangisurat manen kadagiti baw-ing",
+       "log-action-filter-move-move_redir": "Iyalis a mangisurat manen kadagiti baw-ing",
+       "log-action-filter-newusers-create": "Panagpartuat babaen ti di ammo nga agar-aramat",
+       "log-action-filter-newusers-create2": "Panagpartuat babaen ti nairehistro nga agar-aramat",
+       "log-action-filter-newusers-autocreate": "Automatiko a panagpartuat",
+       "log-action-filter-newusers-byemail": "Panagpartuat nga addaan iti kontrasenias a naipatulod babaen ti esurat",
+       "log-action-filter-patrol-patrol": "Manual a patrulia",
+       "log-action-filter-patrol-autopatrol": "Automatiko a patrulia",
+       "log-action-filter-protect-protect": "Salaknib",
+       "log-action-filter-protect-modify": "Panagbaliw ti salaknib",
+       "log-action-filter-protect-unprotect": "Panagikkat ti salaknib",
+       "log-action-filter-protect-move_prot": "Salaknib ti panagiyalis",
+       "log-action-filter-rights-rights": "Manual a panagbaliw",
+       "log-action-filter-rights-autopromote": "Automatiko a panagbaliw",
+       "log-action-filter-suppress-event": "Panagpasardeng ti listaan",
+       "log-action-filter-suppress-revision": "Panagpasardeng ti rebision",
+       "log-action-filter-suppress-delete": "Panagpasardeng ti panid",
+       "log-action-filter-suppress-block": "Panagpasardeng ti agar-aramat babaen ti serra",
+       "log-action-filter-suppress-reblock": "Panagpasardeng ti agar-aramat babaen ti panagserra manen",
+       "log-action-filter-upload-upload": "Baro a panangikarga",
+       "log-action-filter-upload-overwrite": "Panangikarga manen",
+       "authmanager-authn-not-in-progress": "Saan nga agprogprogreso ti pammasingked wenno napukaw ti datos ti sesion. Pangngaasi a mangrugi manen iti pagrugian.",
+       "authmanager-authn-no-primary": "Dagiti naited a kredensial ket saan a mapasingkedan.",
+       "authmanager-authn-no-local-user": "Dagiti naited a kredensial ket saanda a mainaig iti sino man nga agar-aramat iti daytoy a wiki.",
+       "authmanager-authn-no-local-user-link": "Dagiti naited a kredensial ket husto ngem saanda a mainaig iti sino man nga agar-aramat iti daytoy a wiki. Sumrek iti sabali a waya, wenno agpartuat iti baro a pakabilangan, ken addaankanto iti maysa a pagpilian a mangisipo kadagiti dati a kredensialmo iti dayta a pakabilangan.",
+       "authmanager-authn-autocreate-failed": "Napaay ti automatiko a panagpartuat iti lokal a pakabilangan: $1",
+       "authmanager-change-not-supported": "Dagiti naited a kredensial ket saan a mabaliwan, gapu ta awan ti mangusar kaniada.",
+       "authmanager-create-disabled": "Nabaldado ti panagpartuat ti pakabilangan.",
+       "authmanager-create-from-login": "Tapno mapartuat ti pakabilangam, pangngaasi a punnuen dagiti pagikabilan dita baba.",
+       "authmanager-create-not-in-progress": "Saan nga agprogprogreso ti panagpartuat ti pakabilangan wenno napukaw ti datos ti sesion. Pangngaasi a mangrugi manen iti pagrugian.",
+       "authmanager-create-no-primary": "Dagiti naited a kredensial ket saan a mabalin a mausar para iti panagpartuat ti pakabilangan.",
+       "authmanager-link-no-primary": "Dagiti naited a kredensial ket saan a mabalin a mausar para iti panangisilpo ti pakabilangan.",
+       "authmanager-link-not-in-progress": "Saan nga agprogprogreso ti panangisilpo ti pakabilangan wenno napukaw ti datos ti sesion. Pangngaasi a mangrugi manen iti pagrugian.",
+       "authmanager-authplugin-setpass-failed-title": "Napaay ti panagbaliw ti kontrasenias",
+       "authmanager-authplugin-setpass-failed-message": "Ti plugin ti pammasingked ket di nangipalubos ti panagbaliw ti kontrasenias.",
+       "authmanager-authplugin-create-fail": "Ti plugin ti pammasingked ket di nangipalubos ti panagpartuat ti pakabilangan.",
+       "authmanager-authplugin-setpass-denied": "Ti plugin ti pammasingked ket saan a mangipalubos iti panagbaliw kadagiti kontrasenias.",
+       "authmanager-authplugin-setpass-bad-domain": "Imbalido a dominio.",
+       "authmanager-autocreate-noperm": "Saan a maipalubos ti automatiko a panagpartuat ti pakabilangan.",
+       "authmanager-autocreate-exception": "Temporario a nabaldado ti automatiko a panagpartuat iti pakabilangan gapu kadagiti dati a biddut.",
+       "authmanager-userdoesnotexist": "Ti pakabilangan ti agar-aramat ni \"$1\" ket saan a nakarehistro.",
+       "authmanager-userlogin-remembermypassword-help": "No ti kontrasenias ket nasken koma a malagip para iti napapaut ngem ti kaatiddog ti sesion.",
+       "authmanager-username-help": "Nagan ti agar-aramat para iti pammasingked.",
+       "authmanager-password-help": "Kontrasenias para iti pammasingked.",
+       "authmanager-domain-help": "Dominio para iti akinruar a pammasingked.",
+       "authmanager-retype-help": "Kontrasenias manen tapno mapasingkedan.",
+       "authmanager-email-label": "Esurat",
+       "authmanager-email-help": "Adres ti esurat",
+       "authmanager-realname-label": "Pudno a nagan",
+       "authmanager-realname-help": "Pudno a nagan ti agar-aramat",
+       "authmanager-provider-password": "Naibatay iti kontrasenias a pammasingked",
+       "authmanager-provider-password-domain": "Naibatay iti kontrasenias ken dominio a pammasingked",
+       "authmanager-provider-temporarypassword": "Temporario a kontrasenias",
+       "authprovider-confirmlink-message": "Naibatay kadagiti kinaudi a panagpadasmo a panagserrek, dagiti sumaganad a pakabilangan ket mabalin a maisilpo iti pakabilangam iti wiki. Ti panangisilpo kaniada ket mangipalubos iti panagserrek babaen kadagita a pakabilangan. Pangngaasi nga agpili no ania kadagita ti nasken a maisilpo.",
+       "authprovider-confirmlink-request-label": "Dagiti pakabilangan a nasken koma a naisilpo",
+       "authprovider-confirmlink-success-line": "$1: Balligi a naisilpo.",
+       "authprovider-confirmlink-failed": "Saan a napno a nagballigi ti panangisilpo ti pakabilangan: $1",
+       "authprovider-confirmlink-ok-help": "Agtuloy kalpasan ti panangipakita kadagiti mensahe ti pannakapaay ti panangisilpo.",
+       "authprovider-resetpass-skip-label": "Libtawan",
+       "authprovider-resetpass-skip-help": "Libtawan ti panangisaad manen ti kontrasenias.",
+       "authform-nosession-login": "Balligi ti pammasingked, ngem ti pagbasabasam ket saanna a \"malagip\" a nakastrek.\n\n$1",
+       "authform-nosession-signup": "Napartuat ti pakabilangan, ngem ti pagbasabasam ket saanna a \"malagip\" a nakastrek.\n\n$1",
+       "authform-newtoken": "Napukaw a tandaan. $1",
+       "authform-notoken": "Napukaw a tandaan",
+       "authform-wrongtoken": "Kamali a tandaan",
+       "specialpage-securitylevel-not-allowed-title": "Saan a maipalubos",
+       "specialpage-securitylevel-not-allowed": "Pasensia, saanka a mapalubosan nga agusar iti daytoy a panid gapu ta saan a mapasingkedan ti identidadmo.",
+       "authpage-cannot-login": "Saan a mabalin ti mangrugi a sumrek.",
+       "authpage-cannot-login-continue": "Saan a mabalin ti agtuloy a sumrek. Mabalin a nagsardeng ti sesionmo.",
+       "authpage-cannot-create": "Saan a mabalin ti mangrugi nga agpartuat iti pakabilangan.",
+       "authpage-cannot-create-continue": "Saan a mabalin ti agtuloy iti panagpartuat iti pakabilangan. Mabalin a nagsardeng ti sesionmo.",
+       "authpage-cannot-link": "Saan a mabalin ti mangrugi iti panangisilpo ti pakabilangan.",
+       "authpage-cannot-link-continue": "Saan a mabalin ti agtuloy iti panangisilpo ti pakabilangan. Mabalin a nagsardeng ti sesionmo.",
+       "cannotauth-not-allowed-title": "Nalibak ti pammalubos",
+       "cannotauth-not-allowed": "Saanka a mapalubosan nga agusar iti daytoy a panid",
+       "changecredentials": "Baliwan dagiti kredensial",
+       "changecredentials-submit": "Baliwan dagiti kredensial",
+       "changecredentials-invalidsubpage": "Ti $1 ket saan a husto a kita ti kredensial.",
+       "changecredentials-success": "Nabaliwanen dagiti kredensialmo.",
+       "removecredentials": "Ikkaten dagiti kredensial",
+       "removecredentials-submit": "Ikkaten dagiti kredensial",
+       "removecredentials-invalidsubpage": "Ti $1 ket saan a husto a kita ti kredensial.",
+       "removecredentials-success": "Naiyalisen dagiti kredesialmo.",
+       "credentialsform-provider": "Kita dagiti kredensial:",
+       "credentialsform-account": "Nagan ti pakabilangan:",
+       "cannotlink-no-provider-title": "Awan dagiti mabalin a maisilpo a pakabilangan",
+       "cannotlink-no-provider": "Awan dagiti mabalin a maisilpo a pakabilangan.",
+       "linkaccounts": "Isilpo dagiti pakabilangan",
+       "linkaccounts-success-text": "Naisilpon ti pakabilangan.",
+       "linkaccounts-submit": "Isilpo dagiti pakabilangan",
+       "unlinkaccounts": "Ikkaten ti silpo dagiti pakabilangan",
+       "unlinkaccounts-success": "Ti pakabilangan ket naikkat iti pannakaisilpo.",
+       "authenticationdatachange-ignored": "Saan a natengngel ti panagbaliw ti datos ti pammasingked. Mabalin nga awan ti nakompigura a mangited?"
 }
index 2402224..28f11c0 100644 (file)
        "grant-createaccount": "Gawé akun",
        "grant-createeditmovepage": "Gawé, besut, lan lih kaca",
        "grant-delete": "Busak kaca, owahan, lan isian cathetan",
-       "newuserlogpage": "Cathetan panganggo anyar",
+       "newuserlogpage": "Log naraguna anyar",
        "newuserlogpagetext": "Ing ngisor iki kapacak log pandaftaran panganggo anyar.",
        "rightslog": "Log pangowahan hak aksès",
        "rightslogtext": "Ing ngisor iki kapacak log pangowahan marang hak-hak panganggo.",
        "booksources-invalid-isbn": "ISBN sing diwènèhaké katonané ora valid; priksa kasalahan penyalinan saka sumber asli.",
        "specialloguserlabel": "Panampil:",
        "speciallogtitlelabel": "Patujon (judhul utawa panganggo) :",
-       "log": "Cathetan",
+       "log": "Log",
        "all-logs-page": "Kabèh log publik",
        "alllogstext": "Gabungan tampilam kabèh log sing ana ing {{SITENAME}}.\nPanjenengan bisa mbatesi tampilan kanthi milih jinis log, jeneng panganggo (sènsitif aksara gedhé/cilik), utawa kaca sing magepokan (uga sènsitif aksara gedhé/cilik).",
        "logempty": "Ora ditemokaké èntri log sing pas.",
        "actioncomplete": "Proses tuntas",
        "actionfailed": "Tindakan gagal",
        "deletedtext": "\"$1\" sampun kabusak. Coba pirsani $2 kanggé log paling énggal kaca ingkang kabusak.",
-       "dellogpage": "Cathetan busakan",
+       "dellogpage": "Log busak",
        "dellogpagetext": "Ing ngisor iki kapacak log pambusakan kaca sing anyar dhéwé.",
        "deletionlog": "Cathetan sing dibusak",
        "reverted": "Dibalèkaké ing revisi sadurungé",
        "rollback-success": "Suntingan dibalèkaké déning $1;\ndiowahi bali menyang vèrsi pungkasan déning $2.",
        "sessionfailure-title": "Sèsi gagal",
        "sessionfailure": "Katoné ana masalah karo sèsi log panjenengan; log panjenengan wis dibatalaké kanggo nyegah pambajakan. Mangga mencèt tombol \"back\" lan unggahaké manèh kaca sadurungé mlebu log, lan coba manèh.",
-       "protectlogpage": "Cathetan pangreksan",
+       "protectlogpage": "Log reksa",
        "protectlogtext": "Ngisor iki daptar owahan saka panjagan kaca.\nDelok [[Special:ProtectedPages|daptar kaca sing dijaga]] kanggo daptar panjagan kaca paling anyar.",
        "protectedarticle": "ngreksa \"[[$1]]\"",
        "modifiedarticleprotection": "ngowahi tingkat pangreksan \"[[$1]]\"",
        "protect-otherreason-op": "Alesan liya",
        "protect-dropdown": "*Alesan umum pangreksan\n** Vandalisme makaping-kaping\n** Spam makaping-kaping\n** Perang suntingan\n** Kaca kerep disunting",
        "protect-edit-reasonlist": "Mbesut jalaraning pangreksa",
-       "protect-expiry-options": "1 jam:1 hour,1 dina:1 day,1 minggu:1 week,2 minggu:2 weeks,1 sasi:1 month,3 sasi:3 months,6 sasi:6 months,1 taun:1 year,tanpa wates:infinite",
+       "protect-expiry-options": "1 jam:1 hour,1 dina:1 day,1 minggu:1 week,2 minggu:2 weeks,1 wulan:1 month,3 wulan:3 months,6 wulan:6 months,1 taun:1 year,tanpa wates:infinite",
        "restriction-type": "Pangreksan:",
        "restriction-level": "Tingkatan pambatesan:",
        "minimum-size": "Ukuran minimum",
        "ipbenableautoblock": "Blokir alamat IP pungkasan sing dienggo déning pengguna iki sacara otomatis, lan kabèh alamat sabanjuré sing dicoba arep dienggo nyunting.",
        "ipbsubmit": "Kirimna",
        "ipbother": "Wektu liya",
-       "ipboptions": "2 jam:2 hours,1 dina:1 day,3 dina:3 days,1 minggu:1 week,2 minggu:2 weeks,1 sasi:1 month,3 sasi:3 months,6 sasi:6 months,1 taun:1 year,tanpa wates:infinite",
+       "ipboptions": "2 jam:2 hours,1 dina:1 day,3 dina:3 days,1 minggu:1 week,2 minggu:2 weeks,1 wulan:1 month,3 wulan:3 months,6 wulan:6 months,1 taun:1 year,tanpa wates:infinite",
        "ipbhidename": "Delikna jeneng panganggo saka suntingan lan pratélan",
        "ipbwatchuser": "Wasi kaca panganggoning lan kaca gegunemaning panganggo iki",
        "ipb-disableusertalk": "Alangi panganggo iki nyunting kaca gunemané nalika diblokir",
        "movepage-page-moved": "Kaca $1 wis dipindhah menyang $2.",
        "movepage-page-unmoved": "Kaca $1 ora bisa dialihaké menyang $2.",
        "movepage-max-pages": "Paling akèh $1 {{PLURAL:$1|kaca|kaca}} wis dialihaké lan ora ana manèh sing bakal dialihaké sacara otomatis.",
-       "movelogpage": "Cathetan lih-lihan",
+       "movelogpage": "Log alih",
        "movelogpagetext": "Ing ngisor iki kapacak log pangalihan kaca.",
        "movesubpage": "{{PLURAL:$1|Anak-kaca|Anak-kaca}}",
        "movesubpagetext": "Kaca iki nduwèni $1 {{PLURAL:$1|anak-kaca|anak-kaca}} kaya kapacak ing ngisor.",
        "hours": "{{PLURAL:$1|$1 jam|$1 jam}}",
        "days": "{{PLURAL:$1|$1 dina|$1 dina}}",
        "weeks": "{{PLURAL:$1|minggu|minggu}}",
-       "months": "{{PLURAL:$1|$1 sasi|$1 sasi}}",
+       "months": "{{PLURAL:$1|$1 wulan}}",
        "years": "{{PLURAL:$1|$1 taun|$1 taun}}",
        "ago": "$1 kapungkur",
        "just-now": "baru saja",
index 594636e..f56536b 100644 (file)
        "apihelp": "API-Hëllef",
        "apihelp-no-such-module": "Modul \"$1\" net fonnt.",
        "apisandbox": "API-Sandkëscht",
+       "apisandbox-jsonly": "Fir d'API-Sandkëscht ze benotze braucht Dir JavaScript.",
        "apisandbox-api-disabled": "API ass op dësem Site ausgeschalt.",
        "apisandbox-unfullscreen": "Säit weisen",
        "apisandbox-submit": "Ufro maachen",
        "apisandbox-reset": "Eidel maachen",
        "apisandbox-retry": "Nach eng Kéier probéieren",
+       "apisandbox-no-parameters": "Dësen API-Modul huet keng Parameteren.",
        "apisandbox-helpurls": "Hëllef-Linken",
        "apisandbox-examples": "Beispiller",
        "apisandbox-dynamic-parameters": "Zousätzlech Parameteren",
        "lockdbsuccesstext": "D'{{SITENAME}}-Datebank gouf gespaart. <br />\nDenkt drun [[Special:UnlockDB|d'Spär erëm ewechzehuele]] soubaal d'Maintenance-Aarbechte fäerdeg sinn.",
        "unlockdbsuccesstext": "D'Spär vun der Datebank ass opgehuewen.",
        "lockfilenotwritable": "De Fichier mat de Späre vun der Datebank kann net geännert ginn.\nFir d'Datebank ze spären oder fir d'Spär opzehiewe muss dëse Fichier vum Webserver geännert kënne ginn.",
+       "databaselocked": "D'Datebank ass scho gespaart.",
        "databasenotlocked": "D'Datebank ass net gespaart.",
        "lockedbyandtime": "(vum $1 de(n) $2 ëm $3 Auer)",
        "move-page": "Réckel $1",
        "api-error-unknownerror": "Onbekannte Feeler: \"$1\".",
        "api-error-uploaddisabled": "D'Eroplueden ass op dëser Wiki ausgeschalt.",
        "api-error-verification-error": "Dëse Fichier kéint korrupt sinn, oder en huet eng falsch Erweiderung.",
+       "api-error-was-deleted": "E Fichier mat dësem Numm gouf virdrun eropgelueden an duerno geläscht.",
        "duration-seconds": "$1 {{PLURAL:$1|Sekonn|Sekonnen}}",
        "duration-minutes": "$1 {{PLURAL:$1|Minutt|Minutten}}",
        "duration-hours": "$1 {{PLURAL:$1|Stonn|Stonnen}}",
index 3ed3ad2..1b78704 100644 (file)
@@ -78,7 +78,7 @@
        "tog-ccmeonemails": "Siųsti man laiškų, kuriuos siunčiu kitiems naudotojams, kopijas",
        "tog-diffonly": "Nerodyti puslapio turinio po skirtumais",
        "tog-showhiddencats": "Rodyti paslėptas kategorijas",
-       "tog-norollbackdiff": "Nepaisyti skirtumo atlikus atmetimą",
+       "tog-norollbackdiff": "Nerodyti skirtumo atlikus atmetimą",
        "tog-useeditwarning": "Perspėti mane, kai palieku redagavimo puslapį, o jame yra neišsaugotų pakeitimų",
        "tog-prefershttps": "Prisiregistruojant visada naudokite saugų ryšį",
        "underline-always": "Visada",
index 150527f..581cc97 100644 (file)
@@ -67,6 +67,7 @@
        "tog-watchdefault": "Legg til sider og filer jeg endrer på i min overvåkingsliste",
        "tog-watchmoves": "Legg til sider og filer jeg flytter til min overvåkingsliste",
        "tog-watchdeletion": "Legg til sider og filer jeg sletter i min overvåkingsliste",
+       "tog-watchuploads": "Legg til nye filer jeg laster opp i overvåkningslisten min",
        "tog-watchrollback": "Legg til sider hvor jeg har utført tilbakestilling i min overvåkningsliste",
        "tog-minordefault": "Merk i utgangspunktet alle redigeringer som mindre",
        "tog-previewontop": "Vis forhåndsvisningen over redigeringsboksen",
        "tagline": "Fra {{SITENAME}}",
        "help": "Hjelp",
        "search": "Søk",
+       "search-ignored-headings": " #<!-- la denne linjen stå akkurat som den er --> <pre>\n# Overskrifter som vil bli ignorert ved søking.\n# Endringer på denne siden trer i kraft ved neste indeksering.\n# Du kan fremtvinge en reindeksering av en gitt side ved å gjøre en nullredigering.\n# Syntaksen er som følger:\n#   * Alt fra et \"#\"-tegn til slutten av en linje er en kommentar.\n#   * Enhver ikke-tom linje regnes som en ordrett tittel (inkludert skille mellom store og små bokstaver) som skal ignoreres.\nReferanser\nKilder\nEksterne lenker\nSe også\n #</pre> <!-- la denne linjen stå akkurat som den er -->",
        "searchbutton": "Søk",
        "go": "Gå",
        "searcharticle": "Gå",
        "botpasswords-insert-failed": "Kunne ikke legge til robotnavnet \"$1\". Har det allerede blitt lagt til?",
        "botpasswords-update-failed": "Kunne ikke oppdatere robotnavnet \"$1\". Er det slettet?",
        "botpasswords-created-title": "Robotpassord opprettet",
-       "botpasswords-created-body": "Robotpassordet \"$1\" ble opprettet.",
+       "botpasswords-created-body": "Robotpassordet for boten «$1» til brukeren «$2» ble opprettet.",
        "botpasswords-updated-title": "Robotpassord oppdatert",
-       "botpasswords-updated-body": "Robotpassordet \"$1\" ble oppdatert.",
+       "botpasswords-updated-body": "Robotpassordet for boten «$1» til brukeren «$2» ble oppdatert.",
        "botpasswords-deleted-title": "Robotpassord slettet",
-       "botpasswords-deleted-body": "Robotpassordet \"$1\" ble slettet.",
+       "botpasswords-deleted-body": "Robotpassordet for boten «$1» til brukeren «$2» ble slettet.",
        "botpasswords-newpassword": "Det nye passordet for å logge inn med <strong>$1</strong> er <strong>$2</strong>. <em>Vennligst lagre dette for fremtidig referanse.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider er ikke tilgjengelig.",
        "botpasswords-restriction-failed": "Begrensninger for robotpassord tillater ikke denne innloggingen.",
        "resetpass-no-info": "Du må være logget inn for å gå til denne siden direkte",
        "resetpass-submit-loggedin": "Endre passord",
        "resetpass-submit-cancel": "Avbryt",
-       "resetpass-wrong-oldpass": "Ugyldig midlertidig eller nåværende passord.\nDu kan ha allerede byttet passordet, eller bedt om et nytt midlertidig passord.",
+       "resetpass-wrong-oldpass": "Ugyldig midlertidig eller aktivt passord.\nDet kan tenkes at allerede har gjennomført et vellykket bytte av passord, eller bedt om et nytt midlertidig passord.",
        "resetpass-recycled": "Vær vennlig å endre passordet til noe annen enn gjeldende passord.",
        "resetpass-temp-emailed": "Du logget inn med en midlertidig kode sendt på e-post.\nFor å avslutte innloggingen må du angi et nytt passord her:",
        "resetpass-temp-password": "Midlertidig passord:",
        "passwordreset-emailelement": "Brukernavn: \n$1\n\nMidlertidig passord: \n$2",
        "passwordreset-emailsentemail": "Hvis denne epostadressen er koblet til din konto, så vil det bli sendt en epost om tilbakestilling av passord.",
        "passwordreset-emailsentusername": "Hvis det finnes en epostadresse knyttet til dette brukernavnet, vil en epost med informasjon om tilbakestilling av passord bli sendt.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-post}} om passordtilbakestilling har blitt sendt. {{PLURAL:$1|Brukernavnet og passordet|Listen over brukernavn og passord}} vises under.",
+       "passwordreset-emailerror-capture2": "Kunne ikke sende e-post til {{GENDER:$2|brukeren}}: $1 {{PLURAL:$3|Brukernavnet og passordet|Listen over brukernavn og passord}} vises under.",
+       "passwordreset-invalideamil": "Ugyldig e-postadresse",
+       "passwordreset-nodata": "Verken et brukernavn eller en e-postadresse ble oppgitt",
        "changeemail": "Endre eller fjerne epostadresse",
        "changeemail-header": "Fyll ut dette skjemaet for å bytte din epost-adresse. Hvis du vil fjerne epostadressen fra din konto, kan du la ny epostadresse-feltet være tomt når.",
        "changeemail-no-info": "Du må være innlogget for å få direkte tilgang til denne siden.",
        "minoredit": "Dette er en mindre endring",
        "watchthis": "Overvåk denne siden",
        "savearticle": "Lagre siden",
+       "savechanges": "Lagre endringer",
        "publishpage": "Publiser siden",
        "publishchanges": "Publiser endringene",
        "preview": "Forhåndsvisning",
        "accmailtext": "Et tilfeldig passord for [[User talk:$1|$1]] har blitt sendt til $2. Det kan endres på [[Special:ChangePassword|passordendringssiden]] under innlogging.",
        "newarticle": "(Ny)",
        "newarticletext": "Du har fulgt en lenke til en side som ikke finnes ennå.\nFor å opprette siden, begynn å skrive i boksen under (se [$1 hjelpesiden] for mer informasjon).\nOm du havnet her ved en feil, trykk '''tilbake''' i nettleseren.",
-       "anontalkpagetext": "----\n''Dette er en diskusjonsside for en uregistrert bruker som ikke har opprettet konto eller ikke er logget inn.\nVi er derfor nødt til å bruke den numeriske IP-adressen til å identifisere ham eller henne.\nEn IP-adresse kan være delt mellom flere brukere.\nHvis du er en uregistrert bruker og synes at du har fått irrelevante kommentarer på en slik side, [[Special:CreateAccount|opprett en konto]] eller [[Special:UserLogin|logg inn]] så vi unngår fremtidige forvekslinger med andre uregistrerte brukere.''",
+       "anontalkpagetext": "----\n<em>Dette er en diskusjonsside for en anonym bruker som ikke har opprettet konto enda, eller som ikke bruker den.</em>\nVi er derfor nødt til å bruke den numeriske IP-adressen til å identifisere ham eller henne.\nEn IP-adresse kan være delt mellom flere brukere.\nHvis du er en anonym bruker og opplever å få irrelevante kommentarer rettet mot deg, [[Special:CreateAccount|opprett en konto]] eller [[Special:UserLogin|logg inn]] for å unngå fremtidige forvekslinger med andre anonyme brukere.",
        "noarticletext": "Det er for tiden ingen tekst på denne siden.\nDu kan [[Special:Search/{{PAGENAME}}|søke etter denne sidetittelen]] på andre sider,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søke i relaterte logger],\neller [{{fullurl:{{FULLPAGENAME}}|action=edit}} opprette siden]</span>.",
        "noarticletext-nopermission": "Det er for tiden ingen tekst på denne siden.\nDu kan [[Special:Search/{{PAGENAME}}|søke etter sidens tittel]] blant andre sider, eller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} søke i relevante logger]</span>, men du har ikke tillatelse til å opprette denne siden.",
        "missing-revision": "Revisjonen #$1 av siden med navnet \"{{FULLPAGENAME}}\" eksisterer ikke.\n\nDette skyldes som regel at en gammel historikklenke er fulgt til en side som er slettet.\nDetaljer kan finnes i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletteloggen].",
        "userpage-userdoesnotexist": "Brukerkontoen «$1» er ikke registrert.\nSjekk om du ønsker å opprette/redigere denne siden.",
        "userpage-userdoesnotexist-view": "Kontoen «$1» er ikke registrert.",
        "blocked-notice-logextract": "Denne brukeren er for tiden blokkert.\nSiste blokkeringsloggelement kan sees nedenfor.",
-       "clearyourcache": "'''Merk:''' Etter lagring vil det kanskje være nødvendig at nettleseren sletter hurtiglageret sitt for at endringene skal tre i kraft.\n* '''Firefox / Safari:''' Hold ''Shift'' mens du klikker på ''Oppdater'' eller trykk ''Ctrl-F5'' eller ''Ctrl-R'' (''⌘-R'' på en Mac)\n* '''Google Chrome:''' Trykk ''Ctrl-Shift-R'' (''⌘-Shift-R'' på en Mac)\n* '''Internet Explorer:''' Hold ''Ctrl'' mens du klikker på ''Oppdater'' eller trykk ''Ctrl-F5''\n* '''Opera:''' Tøm hurtiglageret i ''Verktøy → Innstillinger''",
+       "clearyourcache": "<strong>Merk:</strong> Etter lagring vil det kanskje være nødvendig at nettleseren sletter hurtiglageret sitt for at endringene skal tre i kraft.\n* <strong>Firefox / Safari:</strong> Hold <em>Shift</em> mens du klikker på <em>Oppdater</em> eller trykk <em>Ctrl-F5</em> eller <em>Ctrl-R</em> (<em>⌘-R</em> på en Mac)\n* <strong>Google Chrome:</strong> Trykk <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> på en Mac)\n* <strong>Internet Explorer:</strong> Hold <em>Ctrl</em> mens du klikker på <em>Oppdater</em> eller trykk <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Tøm hurtiglageret i <em>Meny → Innstillinger</em> og deretter <em>Personvern & sikkerhet → Slett surfedata → Mellomlagrede bilder og filer</em>.",
        "usercssyoucanpreview": "'''Tips:''' Bruk «{{int:showpreview}}»-knappen for å teste din nye CSS før du lagrer.",
        "userjsyoucanpreview": "'''Tips:''' Bruk «{{int:showpreview}}»-knappen for å teste ditt nye JS før du lagrer.",
        "usercsspreview": "'''Husk at dette bare er en forhåndsvisning av din bruker-CSS og at den ikke er lagret!'''",
        "continue-editing": "Gå til redigeringsfeltet",
        "previewconflict": "Slik vil teksten i redigeringsvinduet se ut dersom du lagrer den.",
        "session_fail_preview": "'''Beklager! Klarte ikke å lagre redigeringen din på grunn av tap av øktdata.'''\n\nDu kan ha blitt logget ut. <strong>Sjekk at du fortsatt er innlogget og prøv igjen.</strong>\nOm det fortsetter å gå galt, prøv å [[Special:UserLogout|logge ut]] og så inn igjen, og sjekk at nettleseren din godtar informasjonskapsler fra denne siden.",
-       "session_fail_preview_html": "'''Beklager! Klarte ikke å lagre redigeringen din på grunn av tap av øktdata.'''\n\n''Fordi {{SITENAME}} har rå HTML slått på, er forhåndsvisningen skjult for å forhindre JavaScript-angrep.''\n\n'''Om dette er et legitimt redigeringsforsøk, prøv igjen. Om det da ikke fungerer, prøv å [[Special:UserLogout|logge ut]] og logge inn igjen.'''",
+       "session_fail_preview_html": "Beklager! Klarte ikke å lagre redigeringen din på grunn av tap av øktdata.\n\n<em>Fordi {{SITENAME}} har rå HTML slått på, er forhåndsvisningen skjult for å forhindre JavaScript-angrep.</em>\n\n<strong>Om dette er et legitimt redigeringsforsøk, prøv igjen.</strong> Om det fortsatt ikke fungerer, prøv å [[Special:UserLogout|logge ut]] og logge inn igjen og sjekk at nettleseren din godtar informasjonskapsler fra dette nettstedet.",
        "token_suffix_mismatch": "'''Redigeringen din har blitt avvist fordi klienten din ikke hadde punktasjonstegn i redigeringsteksten. Redigeringen har blitt avvist for å hindre ødeleggelse av artikkelteksten. Dette forekommer av og til når man bruker vevbaserte anonyme proxytjenester.'''",
        "edit_form_incomplete": "'''Deler av redigeringsskjemaet nådde ikke tjeneren; dobbelsjekk at redigeringen er korrekt og prøv igjen.'''",
        "editing": "Redigerer $1",
        "revdelete-unsuppress": "Fjern betingelser på gjenopprettede revisjoner",
        "revdelete-log": "Årsak:",
        "revdelete-submit": "Utfør på {{PLURAL:$1|valgt revisjon|valgte revisjoner}}",
-       "revdelete-success": "'''Revisjonssynlighet vellykket oppdatert.'''",
+       "revdelete-success": "Revisjonssynlighet ble oppdatert.",
        "revdelete-failure": "'''Kunne ikke endre versjonssynligheten:'''\n$1",
-       "logdelete-success": "'''Hendelsessynlighet satt.'''",
+       "logdelete-success": "Hendelsessynlighet ble satt.",
        "logdelete-failure": "'''Loggens synlighet kunne ikke bli stilt inn:'''\n$1",
        "revdel-restore": "endre synlighet",
        "pagehist": "Sidehistorikk",
        "userrights-unchangeable-col": "Grupper du ikke kan endre",
        "userrights-irreversible-marker": "$1 *",
        "userrights-conflict": "En konflikt med endringen av brukerrettigheter! Vær vennlig å sjekke og på nytt bekrefte endringene dine.",
-       "userrights-removed-self": "Du har fjernet dine egne rettigheter. Du har derfor ikke lengere adgang til denne siden.",
+       "userrights-removed-self": "Du har fjernet dine egne rettigheter. Du har derfor ikke lengre adgang til denne siden.",
        "group": "Gruppe:",
        "group-user": "Brukere",
        "group-autoconfirmed": "Autobekreftede brukere",
        "right-override-export-depth": "Eksporter sider inkludert lenkede sider til en dypde på 5",
        "right-sendemail": "Send e-post til andre brukere",
        "right-passwordreset": "Vis e-poster over tilbakestilte passord",
-       "right-managechangetags": "Opprette og slette [[Special:Tags|tagger]] fra databasen",
+       "right-managechangetags": "Opprette og (de)aktivere [[Special:Tags|tagger]]",
        "right-applychangetags": "Legg til [[Special:Tags|merker]] sammen med ens endringer",
        "right-changetags": "Legg til og fjern vilkårlige [[Special:Tags|merker]] på individuelle revisjoner og loggposter",
+       "right-deletechangetags": "Slette [[Special:Tags|tagger]] fra databasen",
        "grant-generic": "Rettighetspakken «$1»",
        "grant-group-page-interaction": "Interagere med sider",
        "grant-group-file-interaction": "Interagere med media",
        "rightslogtext": "Dette er en logg over endringer av brukerrettigheter.",
        "action-read": "se denne siden",
        "action-edit": "redigere denne siden",
-       "action-createpage": "opprette sider",
-       "action-createtalk": "opprette diskusjonssider",
+       "action-createpage": "opprette denne siden",
+       "action-createtalk": "opprette denne diskusjonssiden",
        "action-createaccount": "opprette denne kontoen",
        "action-history": "se historikken til denne siden",
        "action-minoredit": "merke denne redigeringen som mindre",
        "action-viewmyprivateinfo": "vise din private informasjon",
        "action-editmyprivateinfo": "rediger din private informasjon",
        "action-editcontentmodel": "rediger innholdsmodellen til en side",
-       "action-managechangetags": "opprette og slette tagger fra databasen",
+       "action-managechangetags": "opprette og (de)aktivere tagger",
        "action-applychangetags": "bruk merker sammen med dine endringer",
        "action-changetags": "legg til og fjern vilkårlige merker på individuelle revisjoner og loggposter",
+       "action-deletechangetags": "slette tagger fra databasen",
        "nchanges": "$1 {{PLURAL:$1|endring|endringer}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|siden forrige besøk}}",
        "enhancedrc-history": "historikk",
        "recentchangeslinked-page": "Sidenavn:",
        "recentchangeslinked-to": "Vis endringer på sider som lenker til den gitte siden istedet",
        "recentchanges-page-added-to-category": "[[:$1]] lagt til kategori",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] og [[Special:WhatLinksHere/$1|{{PLURAL:$2|én side|$2 sider}}]] lagt til kategori",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
        "recentchanges-page-removed-from-category": "[[:$1]] fjernet fra kategori",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] og {{PLURAL:$2|én side|$2 sider}} fjernet fra kategori",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] fjernet fra kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
        "autochange-username": "Automatisk MediaWiki-endring",
        "upload": "Last opp fil",
        "uploadbtn": "Last opp fil",
        "backend-fail-read": "Klarte ikke lese filen $1.",
        "backend-fail-create": "Kunne ikke opprette filen $1.",
        "backend-fail-maxsize": "Kunne ikke skrive filen $1 fordi den er større enn {{PLURAL:$2|én byte|$2 bytes}}.",
-       "backend-fail-readonly": "Underliggende \"$1\" er satt skrivebeskyttet fordi: \"$2\"",
+       "backend-fail-readonly": "Lagringssystemet «$1» er midlertidig skrivebeskyttet fordi: <em>$2</em>",
        "backend-fail-synced": "Fila «$1» er i en inkonsistent status innen de interne bakstykkene",
        "backend-fail-connect": "Kunne ikke koble til filbackend «$1».",
        "backend-fail-internal": "En ukjent feil oppsto i filbackend «$1».",
        "uploadstash-summary": "Denne siden gir tilgang til filer som har blitt lastet opp (eller er i ferd med å bli lastet opp) men som ennå ikke er publisert til wikien. Disse filene er ikke synlige for andre enn brukeren som lastet dem opp.",
        "uploadstash-clear": "Fjern stashede filer",
        "uploadstash-nofiles": "Du har ingen stashede filer.",
-       "uploadstash-badtoken": "Utføringen av den handlingen var mislykket, kanskje fordi redigeringsrettighetene dine har utløpt. Prøv igjen.",
-       "uploadstash-errclear": "Fjerning av filene var mislykket.",
+       "uploadstash-badtoken": "Utføringen av handlingen feilet, kanskje fordi redigeringsrettighetene dine har utløpt. Prøv igjen.",
+       "uploadstash-errclear": "Filene lot seg ikke fjerne.",
        "uploadstash-refresh": "Oppdater listen over filer",
        "invalid-chunk-offset": "Ugyldig delforskyvning",
        "img-auth-accessdenied": "Ingen tilgang",
        "apihelp-no-such-module": "Modulen «$1» ikke funnet.",
        "apisandbox": "API-sandkasse",
        "apisandbox-api-disabled": "API er deaktivert på dette nettstedet.",
-       "apisandbox-intro": "Bruk denne siden for å eksperimentere med '''MediaWiki web service APIet'''.\nSjekk [https://www.mediawiki.org/wiki/API:Main_page API-dokumentasjonen] for mer informasjon om bruk av APIet. Eksempel: [https://www.mediawiki.org/wiki/API#A_simple_example hente innholdet til en hovedside]. Velg en handling for å se flere eksempler.\n\nMerk at du kan utføre handlinger her som fører til endringer på wikien.",
+       "apisandbox-intro": "Bruk denne siden for å eksperimentere med <strong>MediaWiki webtjeneste-APIet</strong>.\nSjekk [[mw:API:Main page|API-dokumentasjonen]] for mer informasjon om bruk av APIet. Eksempel: [https://www.mediawiki.org/wiki/API#A_simple_example hente innholdet til en hovedside]. Velg en handling for å se flere eksempler.\n\nMerk at du kan utføre handlinger her som fører til endringer på wikien.",
        "apisandbox-submit": "Foreta en forespørsel",
        "apisandbox-reset": "Tilbakestill",
-       "apisandbox-examples": "Eksempel",
-       "apisandbox-results": "Resultat",
+       "apisandbox-examples": "Eksempler",
+       "apisandbox-results": "Resultater",
        "apisandbox-request-url-label": "Forespurt URL:",
-       "apisandbox-request-time": "Forespørselstid: $1",
+       "apisandbox-request-time": "Forespørselstid: {{PLURAL:$1|$1 ms}}",
        "booksources": "Bokkilder",
        "booksources-search-legend": "Søk etter bokkilder",
        "booksources-search": "Søk",
        "listgrouprights-namespaceprotection-header": "Navneromsbegrensinger",
        "listgrouprights-namespaceprotection-namespace": "Navnerom",
        "listgrouprights-namespaceprotection-restrictedto": "Rettighet(er) som tillater at brukeren redigerer",
-       "listgrants-summary": "Følgende er en liste over OAuth-tildelinger og hvilke brukerrettigheter de gir tilgang til. Brukere kan autorisere applikasjoner til å bruke kontoen deres, med rettigheter begrenset til de gitt av tildelingene brukeren har godkjent. En applikasjon som handler på vegne av en bruker kan imidlertid aldri benytte seg av rettigheter brukeren ikke selv har.\nDet kan finnes [[{{MediaWiki:Listgrouprights-helppage}}|ytterligere informasjon]] om de ulike rettighetene.",
+       "listgrants-summary": "Følgende er en liste over tildelinger samt hvilke brukerrettigheter de gir tilgang til. Brukere kan autorisere applikasjoner til å bruke kontoen deres, med rettigheter begrenset til de gitt av tildelingene brukeren har godkjent. En applikasjon som handler på vegne av en bruker kan imidlertid aldri benytte seg av rettigheter brukeren ikke selv har.\nDet kan finnes [[{{MediaWiki:Listgrouprights-helppage}}|ytterligere informasjon]] om de ulike rettighetene.",
        "listgrants-rights": "Rettigheter",
        "trackingcategories": "Sporingskategori",
        "trackingcategories-summary": "Denne siden lister sporingskategorier som er automatisk befolket av Mediawiki-programvaren. Navnene deres kan endres ved å redigere de tilhørende systembeskjedene i {{ns:8}}-navnerommet.",
        "delete-toobig": "Denne siden har en stor redigeringshistorikk, med over {{PLURAL:$1|$1&nbsp;revisjon|$1&nbsp;revisjoner}}. Muligheten til å slette slike sider er begrenset for å unngå utilsiktet forstyrring av {{SITENAME}}.",
        "delete-warning-toobig": "Denne siden har en stor redigeringshistorikk, med over {{PLURAL:$1|$1&nbsp;revisjon|$1&nbsp;revisjoner}}. Sletting av denne siden kan forstyrre databasen til {{SITENAME}}; vær varsom.",
        "deleteprotected": "Du kan ikke slette denne siden fordi den er beskyttet.",
-       "deleting-backlinks-warning": "'''Advarsel:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Andre sider]] lenker til eller inkluderer siden du er i ferd med å slette.",
+       "deleting-backlinks-warning": "<strong>Advarsel:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Andre sider]] lenker til eller inkluderer siden du er i ferd med å slette.",
        "rollback": "Fjern redigeringer",
        "rollbacklink": "tilbakestill",
        "rollbacklinkcount": "tilbakestill {{PLURAL:$1|én endring|$1 endringer}}",
        "undeletedrevisions": "{{PLURAL:$1|Én revisjon|$1 revisjoner}} gjenopprettet",
        "undeletedrevisions-files": "{{PLURAL:$1|Én revisjon|$1 revisjoner}} og {{PLURAL:$2|én fil|$2 filer}} gjenopprettet",
        "undeletedfiles": "{{PLURAL:$1|Én fil|$1 filer}} gjenopprettet",
-       "cannotundelete": "Gjennoppretting feilet:\n$1",
+       "cannotundelete": "Deler av eller hele gjennopprettingen feilet:\n$1",
        "undeletedpage": "'''$1 ble gjenopprettet'''\n\nSjekk [[Special:Log/delete|slettingsloggen]] for en liste over nylige slettinger og gjenopprettelser.",
        "undelete-header": "Se [[Special:Log/delete|slettingsloggen]] for nylig slettede sider.",
        "undelete-search-title": "Søk i slettede sider",
        "lastmodifiedatby": "Denne siden ble sist redigert $1 kl. $2 av $3.",
        "othercontribs": "Basert på arbeid av $1.",
        "others": "andre",
-       "siteusers": "{{SITENAME}}-{{PLURAL:$2|bruker|brukere}} $1",
+       "siteusers": "{{SITENAME}}-{{PLURAL:$2|{{GENDER:$1|bruker}}|brukere}} $1",
        "anonusers": "{{SITENAME}}s {{PLURAL:$2|anonyme bruker|anonyme brukere}} $1",
        "creditspage": "Sidekrediteringer",
        "nocredits": "Ingen krediteringer er tilgjengelig for denne siden.",
        "scarytranscludefailed-httpstatus": "[Henting av mal for $1 feilet: HTTP $2]",
        "scarytranscludetoolong": "[URL-en er for lang]",
        "deletedwhileediting": "'''Advarsel:''' Denne siden har blitt slettet etter at du begynte å redigere den!",
-       "confirmrecreate": "«[[User:$1|$1]]» ([[User talk:$1|diskusjon]]) slettet siden etter at du begynte å redigere den, med begrunnelsen «$2». Vennligst bekreft at du vil gjenopprette siden.",
-       "confirmrecreate-noreason": "Brukeren [[User:$1|$1]] ([[User talk:$1|diskusjon]]) slettet denne siden etter at du begynte å redigere. Bekreft at du virkelig ønsker å gjenopprette denne siden.",
+       "confirmrecreate": "Brukeren [[User:$1|$1]] ([[User talk:$1|diskusjon]]) {{GENDER:$1|slettet}} siden etter at du begynte å redigere den, med begrunnelsen:\n: <em>$2</em>\nVennligst bekreft at du vil gjenopprette siden.",
+       "confirmrecreate-noreason": "Brukeren [[User:$1|$1]] ([[User talk:$1|diskusjon]]) {{GENDER:$1|slettet}} denne siden etter at du begynte å redigere. Bekreft at du virkelig ønsker å gjenopprette denne siden.",
        "recreate": "Gjenopprett",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Vil du slette tjenerens mellomlagrede versjon (''cache'') av denne siden?",
        "watchlistedit-raw-done": "Overvåkningslisten din er oppdatert.",
        "watchlistedit-raw-added": "{{PLURAL:$1|Én tittel|$1 titler}} ble lagt til:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|Én tittel|$1 titler}} ble fjernet:",
-       "watchlistedit-clear-title": "Rensket overvåkningslisten",
+       "watchlistedit-clear-title": "Tøm overvåkningslisten",
        "watchlistedit-clear-legend": "Rensk overvåkninslisten",
        "watchlistedit-clear-explain": "Alle titlene blir fjernet fra overvåkningslisten din",
        "watchlistedit-clear-titles": "Titler:",
        "tags-edit-revision-legend": "Legg til eller fjern fra {{PLURAL:$1|denne revisjonen|alle revisjoner}}",
        "tags-edit-logentry-legend": "Legg til eller fjern fra {{PLURAL:$1|denne loggposten|alle loggposter}}",
        "tags-edit-existing-tags": "Eksisterende merker:",
-       "tags-edit-existing-tags-none": "«Ingen»",
+       "tags-edit-existing-tags-none": "<em>Ingen</em>",
        "tags-edit-new-tags": "Nye merker:",
        "tags-edit-add": "Legg til disse merkene:",
        "tags-edit-remove": "Fjern disse merkene:",
        "tags-edit-reason": "Årsak:",
        "tags-edit-revision-submit": "Utfør endringene på {{PLURAL:$1|denne revisjonen|$1 revisjoner}}",
        "tags-edit-logentry-submit": "Utfør endringene på {{PLURAL:$1|denne loggposten|$1 loggposter}}",
-       "tags-edit-success": "Endringene ble suksessfullt utført.",
+       "tags-edit-success": "Endringene ble utført.",
        "tags-edit-failure": "Denne endringen kunne ikke bli utført:\n$1",
        "tags-edit-nooldid-title": "Ugyldig målrevisjon",
        "tags-edit-nooldid-text": "Du har enten ikke angitt noen målversjon for denne funksjonen, eller så har du angitt en revisjon som ikke finnes.",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|beskyttet}} $3 $4 [cascading]",
        "logentry-protect-modify": "$1 {{GENDER:$2|endret}} beskyttelsesnivå for $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|endret}} beskyttelsesnivå for $3 $4 [cascading]",
-       "logentry-rights-rights": "$1 {{GENDER:$2|endret}} gruppemedlemskap for $3 fra $4 til $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|endret}} gruppemedlemskap for {{GENDER:$6|$3}} fra $4 til $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|endret}} gruppemedlemskap for $3",
        "logentry-rights-autopromote": "$1 ble automatisk {{GENDER:$2|forfremmet}} fra $4 til $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|lastet opp}} $3",
        "expand_templates_preview": "Forhåndsvisning",
        "expand_templates_preview_fail_html": "<em>Fordi {{SITENAME}} har slått på rå HTML og sesjonsdata ble tapt er forhåndsvisningen skjult for å beskytte mot JavaScript-angrep.</em>\n\n<strong>Om dette er et legitimt forsøk på å forhåndsvise, prøv igjen.</strong> Om det fortsatt ikke fungerer, prøv å [[Special:UserLogout|logge ut]] og logge inn igjen.",
        "expand_templates_preview_fail_html_anon": "<em>Fordi {{SITENAME}} har slått på rå HTML og du ikke er logget inn er forhåndsvisningen skjult for å beskytte mot JavaScript-angrep.</em>\n\n<strong>Om dette er et legitimt forsøk på å forhåndsvise, [[Special:UserLogin|logg inn]] og prøv igjen.</strong>",
-       "pagelanguage": "Valg av sidespråk",
+       "pagelanguage": "Endre sidespråk",
        "pagelang-name": "Side",
        "pagelang-language": "Språk",
        "pagelang-use-default": "Bruk standardspråk",
        "pagelang-submit": "Lagre",
        "right-pagelang": "Endre sidespråk",
        "action-pagelang": "endre sidespråket",
-       "log-name-pagelang": "Endre språklogg",
+       "log-name-pagelang": "Logg for språkendringer",
        "log-description-pagelang": "Dette er en logg som viser endringer i sidespråk",
-       "logentry-pagelang-pagelang": "$1 {{GENDER:$2|endret}} sidespråk for $3 fra $4 til $5.",
+       "logentry-pagelang-pagelang": "$1 {{GENDER:$2|endret}} språk for $3 fra $4 til $5.",
        "default-skin-not-found": "Ops! Standarddrakten for wikien din, definert i <code dir=\"ltr\">$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nInstallasjonen din ser ut til å inneholde følgende {{PLURAL:$4|drakt|drakter}}. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for informasjon om hvordan du kan slå {{PLURAL:$4|denne på|disse på og velge en standarddrakt}}.\n\n$2\n\n; Om du nettopp har installert MediaWiki:\n: Du har trolig installert fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org sin draktbase] ved å\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med flere drakter og utvidelser. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakter fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* klone en av <code>mediawiki/skins/*</code>-lagrene via git inn i <code>skins/</code> -mappen av din MediaWiki-installasjon.\n: Å gjøre dette skal ikke forstyrre git-mappen din om du er en MediaWiki-utvikler.\n\n; Om du nettopp har oppgradert MediaWiki:\n: MediaWiki 1.24 og nyere slår ikke lenger på automatisk installerte drakter (se [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual: Skin autodiscovery]). Du kan lime inn følgende {{PLURAL:$5|linje|linjer}} i <code>LocalSettings.php</code> for å slå på {{PLURAL:$5|den|alle}} nåværende installerte {{PLURAL:$5|drakten|drakter}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Om du nettopp har endret <code>LocalSettings.php</code>:\n: Dobbelsjekk draktnavnene for skrivefeil.",
        "default-skin-not-found-no-skins": "Ops! Standarddrakten for wikien din, definert i <code>$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nDu har ingen installerte drakter.\n\n;Om du nettopp har installert eller oppgradert MediaWiki:\n: Du installerte trolig fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. MediaWiki 1.24 og nyere inkluderer ingen drakter i hovedarkivet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.orgs draktmappe], ved å:\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med mange drakter og tillegg. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakt-tarballer fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* klone en av <code>mediawiki/skins/*</code>-arkivene via git til <code dir=\"ltr\">skins/</code>-mappa i din MediaWiki-installasjon.\n: Å gjøre dette vil ikke forstyrre ditt git-arkiv om du er en MediaWiki-utvikler. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual:Skin configuration] for informasjon om hvordan du slår på drakter og velger en standarddrakt.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (slått på)",
index 4a0ba41..5c69a6d 100644 (file)
        "createacct-reason-ph": "Waarom u een andere account aanmaakt",
        "createacct-submit": "Account aanmaken",
        "createacct-another-submit": "Account aanmaken",
+       "createacct-continue-submit": "Doorgaan met het maken van een account",
+       "createacct-another-continue-submit": "Doorgaan met het maken van een account",
        "createacct-benefit-heading": "{{SITENAME}} wordt gemaakt door mensen zoals u.",
        "createacct-benefit-body1": "bewerking{{PLURAL:$1||en}}",
        "createacct-benefit-body2": "pagina{{PLURAL:$1||'s}}",
        "botpasswords-created-title": "Botwachtwoord aangemaakt",
        "botpasswords-created-body": "Het botwachtwoord voor botnaam \"$1\" van gebruiker \"$2\" is gemaakt.",
        "botpasswords-updated-title": "Botwachtwoord bijgewerkt",
-       "botpasswords-updated-body": "Het botwachtwoord \"$1\" is succesvol bijgewerkt.",
+       "botpasswords-updated-body": "Het botwachtwoord voor de bot \"$1\" van gebruiker \"$2\" is succesvol bijgewerkt.",
        "botpasswords-deleted-title": "Botwachtwoord verwijderd",
-       "botpasswords-deleted-body": "Het botwachtwoord \"$1\" is verwijderd.",
+       "botpasswords-deleted-body": "Het botwachtwoord voor de bot \"$1\" van gebruiker \"$2\" is verwijderd.",
        "botpasswords-newpassword": "Het nieuwe wachtwoord om aan te melden met <strong>$1</strong> is nu <strong>$2</strong>. <em>Bewaar dit goed voor toekomstig gebruik.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider is niet beschikbaar.",
        "botpasswords-restriction-failed": "Botwachtwoordbeperkingen maken het aanmelden onmogelijk.",
        "userpage-userdoesnotexist": "U bewerkt een gebruikerspagina van een gebruiker die niet bestaat (gebruiker \"$1\").\nControleer of u deze pagina wel wilt aanmaken of bewerken.",
        "userpage-userdoesnotexist-view": "De gebruiker \"$1\" is niet geregistreerd.",
        "blocked-notice-logextract": "Deze gebruiker is op het moment geblokkeerd.\nDe laatste regel uit het blokkeerlogboek wordt hieronder ter referentie weergegeven:",
-       "clearyourcache": "'''Let op!''' Nadat u de wijzigingen hebt opgeslagen is het wellicht nodig uw browsercache te legen.\n* '''Firefox / Safari:''' houd ''Shift'' ingedrukt terwijl u op ''Vernieuwen'' klikt of druk op ''Ctrl-F5'' of ''Ctrl-R'' (''⌘-Shift-R'' op een Mac)\n* '''Google Chrome:''' druk op ''Ctrl-Shift-R'' (''⌘-Shift-R'' op een Mac)\n* '''Internet Explorer:''' houd ''Ctrl'' ingedrukt terwijl u op ''Vernieuwen'' klikt of druk op ''Ctrl-F5''\n* '''Opera:''' leeg uw cache in ''Extra → Voorkeuren''",
+       "clearyourcache": "<strong>Opmerking:</strong> nadat u de wijzigingen hebt opgeslagen is het wellicht nodig uw browsercache te legen.\n* <strong>Firefox / Safari:</strong> houd <em>Shift</em> ingedrukt terwijl u op <em>Vernieuwen</em> klikt of druk op <em>Ctrl-F5</em> of <em>Ctrl-R</em> (<em>⌘-Shift-R</em> op een Mac)\n* <strong>Google Chrome:</strong> druk op <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> op een Mac)\n* <strong>Internet Explorer:</strong> houd <em>Ctrl</em> ingedrukt terwijl u op <em>Vernieuwen</em> klikt of druk op <em>Ctrl-F5</em>\n* '''Opera:''' ga naar <em>Menu → Instellingen</em> (<em>Opera → Voorkeuren</em> op een Mac) en daarna naar <em>Privacy & beveiliging → Browsegegevens wissen... →  Tijdelijk opgeslgen afbeeldingen en bestanden</em>.",
        "usercssyoucanpreview": "'''Tip:''' gebruik de knop \"{{int:showpreview}}\" om uw nieuwe CSS te testen alvorens op te slaan.",
        "userjsyoucanpreview": "'''Tip:''' gebruik de knop \"{{int:showpreview}}\" om uw nieuwe JavaScript te testen alvorens op te slaan.",
        "usercsspreview": "'''Dit is alleen een voorvertoning van uw persoonlijke CSS.'''\n'''Deze is nog niet opgeslagen!'''",
        "action-read": "deze pagina te bekijken",
        "action-edit": "deze pagina te bewerken",
        "action-createpage": "deze pagina aan te maken",
-       "action-createtalk": "overlegpagina's aan te maken",
+       "action-createtalk": "deze overlegpagina aan te maken",
        "action-createaccount": "deze gebruiker aan te maken",
        "action-autocreateaccount": "dit externe gebruikersaccount automatisch aanmaken",
        "action-history": "de geschiedenis van deze pagina te bekijken",
        "action-viewmyprivateinfo": "uw eigen privégegevens te bekijken",
        "action-editmyprivateinfo": "uw eigen privégegevens te bewerken",
        "action-editcontentmodel": "het paginainhoudmodel te bewerken",
-       "action-managechangetags": "labels aan te maken en te verwijderen",
+       "action-managechangetags": "labels aan te maken en te (de)activeren",
        "action-applychangetags": "labels aan uw bewerkingen toe te voegen",
        "action-changetags": "willekeurige labels toe te voegen aan en te verwijderen van versies en logboekregels",
+       "action-deletechangetags": "labels uit de database te verwijderen",
        "action-purge": "Schoon deze pagina op",
        "nchanges": "$1 {{PLURAL:$1|bewerking|bewerkingen}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sinds uw laatste bezoek}}",
index bffd6e9..2a1628f 100644 (file)
        "emailuser-title-target": "Send epost åt {{GENDER:$1|brukaren}}",
        "emailuser-title-notarget": "Send e-post åt brukar",
        "emailpagetext": "Du kan nytte skjemaet nedanfor til å sende ein e-post til denne {{GENDER:$1|brukaren}}.\nE-postadressa du har sett i [[Special:Preferences|innstillingane dine]] vil dukke opp i «frå»-feltet på denne e-posten, så mottakaren er i stand til å svare.",
-       "defemailsubject": "{{SITENAME}} epost frå brukar \"$1\"",
+       "defemailsubject": "{{SITENAME}}-e-post frå brukar «$1»",
        "usermaildisabled": "Brukare-post slegen av",
        "usermaildisabledtext": "Du kan ikkje senda e-postar til andre brukarar på wikien",
        "noemailtitle": "Inga e-postadresse",
index 6be5db0..953be9c 100644 (file)
        "watchnologin": "Нужно представиться системе",
        "addwatch": "Добавить в список наблюдения",
        "addedwatchtext": "Страница «[[:$1]]» вместе с её обсуждением были добавлены в ваш [[Special:Watchlist|список наблюдения]].",
+       "addedwatchtext-talk": "«[[:$1]]» вместе со связанной с ней страницей были добавлены в ваш [[Special:Watchlist|список наблюдения]].",
        "addedwatchtext-short": "Страница «$1» была добавлена в ваш список наблюдения.",
        "removewatch": "Удалить из списка наблюдения",
        "removedwatchtext": "Страница «[[:$1]]» вместе с её обсуждением были удалены из вашего [[Special:Watchlist|списка наблюдения]].",
+       "removedwatchtext-talk": "«[[:$1]]» вместе со связанной с ней страницей были удалены из вашего [[Special:Watchlist|списка наблюдения]].",
        "removedwatchtext-short": "Страница «$1» была удалена из вашего списка наблюдения.",
        "watch": "Следить",
        "watchthispage": "Наблюдать за этой страницей",
        "undeletehistorynoadmin": "Статья была удалена. Причина удаления и список участников, редактировавших статью до её удаления, показаны ниже. Текст удалённой статьи могут просмотреть только администраторы.",
        "undelete-revision": "Удалённая версия $1 (от $4 $5) участника $3:",
        "undeleterevision-missing": "Неверная или отсутствующая версия. Возможно, вы перешли по неправильной ссылке, либо версия могла быть удалена из архива.",
+       "undeleterevision-duplicate-revid": "$1 {{PLURAL:$1|версия|версий|версии}} не могут быть восстановлены, поскольку {{PLURAL:$1|её|их}} <code>rev_id</code> уже используется.",
        "undelete-nodiff": "Не найдено предыдущей версии.",
        "undeletebtn": "Восстановить",
        "undeletelink": "просмотреть/восстановить",
index 643d165..4dca72d 100644 (file)
        "category-subcat-count-limited": "Бу категория {{PLURAL:$1|субкатегориялаах|$1 субкатегориялардаах}}.",
        "category-article-count": "{{PLURAL:$2|Бу категория манна эрэ көстүбүт субкатегориялаах.|$2 категорияттан {{PLURAL:$1|субкатегорията|$1 субкатегориялара}} көрдөрүлүннүлэр.}}",
        "category-article-count-limited": "Бу категорияҕа {{PLURAL:$1|1 эрэ сирэй|$1 сирэй}} баар.",
-       "category-file-count": "{{PLURAL:$2|Бу категория манна эрэ көстүбүт билэлээх.|$2 категорияттан {{PLURAL:$1|билэтэ|$1 билэлэрэ}} көрдөрүлүннүлэр.}}",
+       "category-file-count": "{{PLURAL:$2|Бу категория манна эрэ көстүбүт билэлээх.|Бу категорияҕа баар $2 билэттэн {{PLURAL:$1|билэтэ|$1 билэтэ}} көрдөрүлүннэ.}}",
        "category-file-count-limited": "Бу категорияҕа  {{PLURAL:$1|соҕотох билэ|$1 билэ}} баар.",
        "listingcontinuesabbrev": "(салгыыта)",
        "index-category": "Индекстэммит сирэйдэр",
        "userlogin-yourname-ph": "Бэлиэ-ааккын киллэр",
        "createacct-another-username-ph": "Ааккын суруй",
        "yourpassword": "Киирии тыла:",
-       "userlogin-yourpassword": "Ð\9aииÑ\80ии Ñ\82Ñ\8bл",
+       "userlogin-yourpassword": "Ð\90һаÑ\80Ñ\8bк",
        "userlogin-yourpassword-ph": "Киирии тылгын суруй",
        "createacct-yourpassword-ph": "Киирии тылгын суруй",
        "yourpasswordagain": "Киирии тылгын хатылаа:",
        "passwordtooshort": "Киирии тылыҥ наһаа кылгас.\nКырата {{PLURAL:$1|1 бэлиэлээх|$1 бэлиэлээх}} буолуохтаах.",
        "passwordtoolong": "Аһарык {{PLURAL:$1|1 бэлиэттэн|$1 бэлиэттэн}} уһун буолуо суохтаах.",
        "passwordtoopopular": "Элбэхтэ туттуллар аһарыктары туттар сатаммат. Бука диэн атын аһарыкта тал.",
-       "password-name-match": "Ð\9aииÑ\80ии Ñ\82Ñ\8bл ааккыттан атын буолуохтаах.",
+       "password-name-match": "Ð\90һаÑ\80Ñ\8bгÑ\8bÒ¥ ааккыттан атын буолуохтаах.",
        "password-login-forbidden": "Маннык ааты уонна киирии тылы туһаныы бобуллар.",
        "mailmypassword": "Киирии тылы саҥардыы",
        "passwordremindertitle": "{{SITENAME}} киирии тылын санатыы",
-       "passwordremindertext": "Ð\9aим Ñ\8dÑ\80Ñ\8d (бадаÒ\95а Ñ\8dн Ð±Ñ\83 IP-аадÑ\8bÑ\80Ñ\8bÑ\81Ñ\82ан: $1), {{SITENAME}} ($4) ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлÑ\8bн Ñ\81аҥаÑ\82Ñ\82ан Ñ\8bÑ\8bÑ\82Ñ\8bÒ¥ Ð´Ð¸Ñ\8dбиÑ\82.\n\"$2\" ÐºÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8b Ð±Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bла Ð±Ð¸Ð»Ð¸Ð³Ð¸Ð½ Ð¼Ð°Ð½Ð½Ñ\8bк: \"$3\".\nÓ¨Ñ\81кө Ð¼Ð°Ð½Ñ\8b Ñ\8dн Ñ\87аÑ\85Ñ\87Ñ\8b ÐºÓ©Ñ\80дөөбүÑ\82 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна, Ñ\81иÑ\81Ñ\82иÑ\8dмÑ\8dÒ\95Ñ\8d Ñ\81аҥаÑ\82Ñ\82ан ÐºÐ¸Ð¸Ñ\80Ñ\8dҥҥин ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлгÑ\8bн Ñ\83лаÑ\80Ñ\8bÑ\82Ñ\8bаÑ\85Ñ\85Ñ\8bн Ñ\81өп.\nÐ\91Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÐ¸Ð¸Ñ\80и Ñ\82Ñ\8bл {{PLURAL:$5|бииÑ\80 Ñ\85онÑ\83к|$5 Ñ\85онÑ\83к Ñ\83Ñ\81Ñ\82аÑ\82а}} Ò¯Ð»Ñ\8dлииÑ\80.\n\nÓ¨Ñ\81көÑ\82үн ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлÑ\8b Ñ\81аҥаÑ\82Ñ\82ан ÐºÓ©Ñ\80дөөбөÑ\82Ó©Ñ\85 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна,\nÑ\8dбÑ\8dÑ\82Ñ\8dÑ\80 Ñ\83Ñ\80Ñ\83ккÑ\83 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлгÑ\8bн Ó©Ð¹Ð´Ó©Ó©Ð½ ÐºÑ\8dлбиÑ\82 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна,\nбÑ\83 Ñ\81Ñ\83Ñ\80Ñ\83кка Ð°Ð°Ñ\85Ñ\85айÑ\8bма Ñ\83онна Ñ\83Ñ\80Ñ\83ккÑ\83 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлгын салгыы туһан.",
+       "passwordremindertext": "Ð\9aим Ñ\8dÑ\80Ñ\8d (бадаÒ\95а Ñ\8dн Ð±Ñ\83 IP-аадÑ\8bÑ\80Ñ\8bÑ\81Ñ\82ан: $1), {{SITENAME}} ($4) Ð°Ò»Ð°Ñ\80Ñ\8bгÑ\8bн Ñ\81аҥаÑ\82Ñ\82ан Ñ\8bÑ\8bÑ\82Ñ\8bÒ¥ Ð´Ð¸Ñ\8dбиÑ\82.\n\"$2\" ÐºÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8b Ð±Ñ\8bÑ\81Ñ\82аÑ\85 Ð°Ò»Ð°Ñ\80Ñ\8bга Ð±Ð¸Ð»Ð¸Ð³Ð¸Ð½ Ð¼Ð°Ð½Ð½Ñ\8bк: \"$3\".\nÓ¨Ñ\81кө Ð¼Ð°Ð½Ñ\8b Ñ\8dн Ñ\87аÑ\85Ñ\87Ñ\8b ÐºÓ©Ñ\80дөөбүÑ\82 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна, Ñ\81иÑ\81Ñ\82иÑ\8dмÑ\8dÒ\95Ñ\8d Ñ\81аҥаÑ\82Ñ\82ан ÐºÐ¸Ð¸Ñ\80Ñ\8dҥҥин Ð°Ò»Ð°Ñ\80Ñ\8bккÑ\8bн Ñ\83лаÑ\80Ñ\8bÑ\82Ñ\8bаÑ\85Ñ\85Ñ\8bн Ñ\81өп.\nÐ\91Ñ\8bÑ\81Ñ\82аÑ\85 Ð°Ò»Ð°Ñ\80Ñ\8bк {{PLURAL:$5|бииÑ\80 Ñ\85онÑ\83к|$5 Ñ\85онÑ\83к Ñ\83Ñ\81Ñ\82аÑ\82а}} Ò¯Ð»Ñ\8dлииÑ\80.\n\nÓ¨Ñ\81көÑ\82үн Ð°Ò»Ð°Ñ\80Ñ\8bгÑ\8b Ñ\81аҥаÑ\82Ñ\82ан ÐºÓ©Ñ\80дөөбөÑ\82Ó©Ñ\85 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна,\nÑ\8dбÑ\8dÑ\82Ñ\8dÑ\80 Ñ\83Ñ\80Ñ\83ккÑ\83 Ð°Ò»Ð°Ñ\80Ñ\8bккн Ó©Ð¹Ð´Ó©Ó©Ð½ ÐºÑ\8dлбиÑ\82 Ð±Ñ\83оллаÑ\85Ñ\85Ñ\8bна,\nбÑ\83 Ñ\81Ñ\83Ñ\80Ñ\83гÑ\83 Ð°Ð°Ñ\85Ñ\85айÑ\8bма Ñ\83онна Ñ\83Ñ\80Ñ\83ккÑ\83 Ð°Ò»Ð°Ñ\80Ñ\8bккын салгыы туһан.",
        "noemail": "\"$1\" ааттаах киһиэхэ эл. почтата ыйыллыбатах.",
        "noemailcreate": "Электроннай почтаҥ сөптөөх аадырыһын суруйуохтааххын",
-       "passwordsent": "Саҥа ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл \"$1\" Ñ\8dл. Ð¿Ð¾Ñ\87Ñ\82аÑ\82Ñ\8bгаÑ\80 Ñ\8bÑ\8bÑ\82Ñ\8bлÑ\8bнна.\nСиÑ\81Ñ\82емаÒ\95а Ñ\81аҥа ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлÑ\8b Ñ\82Ñ\83һанан ÐºÐ¸Ð¸Ñ\80.",
+       "passwordsent": "Саҥа Ð°Ò»Ð°Ñ\80Ñ\8bк Ñ\82Ñ\8bл \"$1\" Ñ\8dл. Ð¿Ð¾Ñ\87Ñ\82аÑ\82Ñ\8bгаÑ\80 Ñ\8bÑ\8bÑ\82Ñ\8bлÑ\8bнна.\nТиһиккÑ\8d ÐºÐ¸Ð¸Ñ\80Ñ\8dÑ\80гÑ\8d Ñ\81аҥа Ð°Ò»Ð°Ñ\80Ñ\8bгÑ\8b Ñ\82Ñ\83һан.",
        "blocked-mailpassword": "Эн IP аадырыскыттан манна тугу эмэ уларытар бобуллубут,\nонон киирии тылы өйдөтөр кыах эмиэ суох.",
        "eauthentsent": "Эл. почтаҕар сурук ыытылынна.\nБу аадырыс эйиэнэ буоларын бигэргэтэргэ өссө тугу гыныахтааҕыҥ туһунан сурукка кэпсэниллэр.",
        "throttled-mailpassword": "Киирии тылы өйдөтөр тэрил бүтэһик {{PLURAL:$1|чаас|$1 чаас}} иһигэр туттулла сылдьыбыт.\nКөмүскэнэр соруктан сылтаан киирии тылы {{PLURAL:$1|чааска|$1 чааска}} биирдэ эрэ ыйытыахха сөп.",
        "resetpass_announce": "Түмүктүүргэ саҥа киирии тылла суруй.",
        "resetpass_text": "<!-- Тиэкиһи манна эбэн суруйуҥ -->",
        "resetpass_header": "Аат киирии тылын уларытыы",
-       "oldpassword": "ЭÑ\80гÑ\8d ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл:",
-       "newpassword": "Саҥа ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл:",
+       "oldpassword": "ЭÑ\80гÑ\8d Ð°Ò»Ð°Ñ\80Ñ\8bк:",
+       "newpassword": "Саҥа Ð°Ò»Ð°Ñ\80Ñ\8bк:",
        "retypenew": "Саҥа киирии тылы хатылаа:",
        "resetpass_submit": "Киирии тылы уларыт уонна киир",
        "changepassword-success": "Киирии тылыҥ этэҥҥэ уларыйда!",
        "resetpass-no-info": "Ааккын билиһиннэрдэххинэ эрэ бу сирэйгэ быһа тиийиэххин сөп.",
        "resetpass-submit-loggedin": "Киирии тылы уларытыы",
        "resetpass-submit-cancel": "Салҕаама",
-       "resetpass-wrong-oldpass": "Ð\9aииÑ\80ии Ñ\82Ñ\8bл Ñ\81өп Ñ\82үбÑ\8dÑ\81пÑ\8dÑ\82Ñ\8d.\nÐ\91аÒ\95аÑ\80 Ñ\83лаÑ\80Ñ\8bппÑ\8bÑ\82Ñ\8bÒ¥ Ð±Ñ\83олÑ\83о Ñ\8dбÑ\8dÑ\82Ñ\8dÑ\80 Ð±Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÑ\8dмҥÑ\8d Ñ\82Ñ\83Ñ\82Ñ\82Ñ\83ллаÑ\80 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлы оҥотторбутуҥ буолуо.",
+       "resetpass-wrong-oldpass": "Ð\90һаÑ\80Ñ\8bк Ñ\81өп Ñ\82үбÑ\8dÑ\81пÑ\8dÑ\82Ñ\8d.\nÐ\91аÒ\95аÑ\80 Ñ\83лаÑ\80Ñ\8bппÑ\8bÑ\82Ñ\8bÒ¥ Ñ\8dбÑ\8dÑ\82Ñ\8dÑ\80 Ð±Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÑ\8dмҥÑ\8d Ñ\82Ñ\83Ñ\82Ñ\82Ñ\83ллаÑ\80 Ð°Ò»Ð°Ñ\80Ñ\8bгы оҥотторбутуҥ буолуо.",
        "resetpass-recycled": "Бука диэн, билиҥҥи киирии тылтан атыны суруй.",
        "resetpass-temp-emailed": "Быстах кэмҥэ туттуллар киирии тылынан киирдиҥ.\nТүмүктүүргэ саҥа киирии тылы суруйуохтааххын:",
-       "resetpass-temp-password": "Ð\91Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÑ\8dмҥÑ\8d Ñ\82Ñ\83Ñ\82Ñ\82Ñ\83ллаÑ\80 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл:",
+       "resetpass-temp-password": "Ð\91Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÑ\8dмҥÑ\8d Ñ\82Ñ\83Ñ\82Ñ\82Ñ\83ллаÑ\80 Ð°Ò»Ð°Ñ\80Ñ\8bк:",
        "resetpass-abort-generic": "Киирии тылы уларытыыны кэҥэтии тохтотто.",
        "resetpass-expired": "Киирии тылыҥ болдьоҕо ааспыт эбит. Бука диэн, саҥа киирии тылла туруорун.",
        "resetpass-expired-soft": "Киирии тылыҥ болдьоҕо бүппүт, онон уларытыллыахтаах эбит. Бука диэн атын киирии тылы суруй эбэтэр маня баттаан кэлин киллэрээр \"{{int:authprovider-resetpass-skip-label}}\".",
        "passwordreset-emailtitle": "{{SITENAME}} бырайыакка аатын туһунан",
        "passwordreset-emailtext-ip": "Ким эрэ (баҕар эн буолуо, бу IP-ттан $1)  {{SITENAME}} ($4) бырайыакка киирии тылы уларытар туһунан ыйытык биэрбит.\nБу электрон аадырыһы кытта бу {{PLURAL:$3|аат ситимнээх|ааттар ситимнээхтэр}}:\n\n$2\n\nБу быстах кэмҥэ аналлаах {{PLURAL:$3|киирии тыл|кирии тыллар}} {{PLURAL:$5|биир күн үлэлиэҕэ|$5 күн үлэлиэхтэрэ}}.\nЭн тиһиликкэ ааккын этэн саҥа киирии тылы киллэриэхтээххин.\nӨскө бу ыйытыгы ыыппатах буоллаххына, эбэтэр урукку киирии тылгын өйдөөн кэлбит буоллаххына \nбу биллэриини ааххайыа суоххун сөп.\nОччоҕо урукку киирии тылыҥ оннунан хаалыа.",
        "passwordreset-emailtext-user": "$1 диэн кыттааччы  {{SITENAME}} ($4) бырайыакка киирии тылгын уларытар туһунан ыйытык ыыппыт.\nБу электрон аадырыһы кытта бу {{PLURAL:$3|аат ситимнээх|ааттар ситимнээхтэр}}\n\n$2\n\nБу быстах кэмҥэ аналлаах {{PLURAL:$3|киирии тыл|кирии тыллар}} {{PLURAL:$5|биир күн үлэлиэҕэ|$5 күн үлэлиэхтэрэ}}.\nЭн тиһиликкэ ааккын этэн саҥа киирии тылы киллэриэхтээххин.\nӨскө бу ыйытыгы ыыппатах буоллаххына, эбэтэр урукку киирии тылгын өйдөөн кэлбит буоллаххына \nбу биллэриини ааххайыа суоххун сөп.\nОччоҕо урукку киирии тылыҥ оннунан хаалыа.",
-       "passwordreset-emailelement": "Ð\9aÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8b: \n$1\n\nÐ\91Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÐ¸Ð¸Ñ\80ии тыл: \n$2",
+       "passwordreset-emailelement": "Ð\9aÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8b: \n$1\n\nÐ\91Ñ\8bÑ\81Ñ\82аÑ\85 Ð°Ò»Ð°Ñ\80Ñ\8bк тыл: \n$2",
        "passwordreset-emailsentemail": "Өскө бу Эн ааккар баайыллыбыт аадырыс буоллаҕына, аһарык тылы уларытар туһунан сурук барыа.",
        "passwordreset-emailsentusername": "Өскө бу аакка баайыллыбыт аадырыс баар буоллаҕына, аһарык тылы уларытар туһунан сурук онно барыа.",
-       "passwordreset-emailsent-capture": "Киирии тылы уларытар туһунан сурук аллара эмиэ көрдөрүлүннэ.",
-       "passwordreset-emailerror-capture": "Манна киирии тылы уларытар туһунан сурук көрдөрүлүннэ. Ол эрэн сурук бу төрүөттэн $2 кыттааччыга сатаан барбата: $1",
        "changeemail": "Аадырыһы уларытыы уонна сотуу",
        "changeemail-header": "Бу форманы толорон аадырыскын уларыт. Уларытыыны бигэргэтэргэ аһарыккын киллэриэхтээххин. Почтаҥ аадырыһыттан бэлиэ-ааккыттан араарыаххын баҕарар буоллаххына аадырыһы сотон баран бигэргэтэн кэбиһээр.",
-       "changeemail-passwordrequired": "Бу дьайыыны бигэргэтэргэ аһарык тылгын киллэриэхтээххин.",
        "changeemail-no-info": "Бу сирэйгэ чопчу тиийэргэ, тиһиликкэ бэлиэтэммит ааккын этиэхтиэххин.",
        "changeemail-oldemail": "Билиҥҥи аадырыс:",
        "changeemail-newemail": "Саҥа аадырыс:",
        "loginreqtitle": "Бэйэҕин билиһиннэр",
        "loginreqlink": "Ааккын эт",
        "loginreqpagetext": "Атын сирэйдэри көрөргө маны оҥоруохтааххын: $1.",
-       "accmailtitle": "Ð\9aииÑ\80ии Ñ\82Ñ\8bл ыытылынна.",
-       "accmailtext": "[[User talk:$1|$1]] ÐºÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8bга Ñ\82үбÑ\8dÑ\81пиÑ\87Ñ\87Ñ\8d Ð±Ñ\8dлиÑ\8dлÑ\8dÑ\80Ñ\82Ñ\8dн Ð¾Ò¥Ð¾Ò»Ñ\83ллÑ\83бÑ\83Ñ\82 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл Ð±Ñ\83 Ð°Ð°Ð´Ñ\8bÑ\80Ñ\8bÑ\81ка $2 Ñ\8bÑ\8bÑ\82Ñ\8bлÑ\8bнна.\nТиһиккÑ\8d Ð±Ñ\8dлиÑ\8dÑ\82Ñ\8dнÑ\8dн Ð±Ð°Ñ\80ан ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bлгын ''[[Special:ChangePassword|уларытыаххын]]'' сөп.",
+       "accmailtitle": "Ð\90һаÑ\80Ñ\8bк ыытылынна.",
+       "accmailtext": "[[User talk:$1|$1]] ÐºÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8bга Ñ\82үбÑ\8dÑ\81пиÑ\87Ñ\87Ñ\8d Ð±Ñ\8dлиÑ\8dлÑ\8dÑ\80Ñ\82Ñ\8dн Ð¾Ò¥Ð¾Ò»Ñ\83ллÑ\83бÑ\83Ñ\82 Ð°Ò»Ð°Ñ\80Ñ\8bк Ñ\82Ñ\8bл Ð±Ñ\83 Ð°Ð°Ð´Ñ\8bÑ\80Ñ\8bÑ\81ка $2 Ñ\8bÑ\8bÑ\82Ñ\8bлÑ\8bнна.\nТиһиккÑ\8d Ð±Ñ\8dлиÑ\8dÑ\82Ñ\8dнÑ\8dн Ð±Ð°Ñ\80ан Ð°Ò»Ð°Ñ\80Ñ\8bккын ''[[Special:ChangePassword|уларытыаххын]]'' сөп.",
        "newarticle": "(Саҥа ыстатыйа)",
        "newarticletext": "Эн суох сирэйгэ киирэ сатаатыҥ.\nМаннык ааттаах саҥа ыстатыйаны оҥорор буоллаххына, аллара баар түннүккэ суруй\n(сиһ. [$1 көмөнү] көрүөххүн сөп).\nӨскө манна сыыһа киирбит буоллаххына интэриниэтиҥ бырагыраамматын \"төнүн\" диэххин сөп.",
        "anontalkpagetext": "----''Бу аатын эппэтэх кыттааччы ырытар сирэйэ.\nIP-аадырыһа эрэ көстөр.\nБиир IP-аадырыс хас да киһиэхэ бэриллиэн сөп. Өскө атын киһиэхэ суруллубут суругу алҕас туппут буоллаххына, бэйэҥ [[Special:CreateAccount|ааккын билиһиннэр]] эбэтэр [[Special:UserLogin|киир]], оччоҕо кэлин да булкуур тахсыа суоҕа.''",
        "undo-nochange": "Бу уларытыы хайыы-үйэ сотуллубут курдук.",
        "undo-summary": "[[Special:Contributions/$2|$2]] кыттааччы ([[User talk:$2|ырытыы]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]]) $1 нүөмэрдээх уларытыытын сотон оннугар түһэрэргэ.",
        "undo-summary-username-hidden": "Кистэммит кыттааччы $1 уларытыытын төннөр",
-       "cantcreateaccounttitle": "Саҥа ааты киллэрэр сатаммат",
        "cantcreateaccount-text": "[[User:$3|$3]] кыттааччы бу IP-ттан ('''$1''') саҥа бэлиэтэниини бопто.\n\nБыһаарыыта: $3 - ''$2''",
        "cantcreateaccount-range-text": "Бу IP-диапазонтан <strong>$1</strong> ааты бэлиэтиири [[User:$3|$3]] боппут. Эн IP-аадырыһыҥ (<strong>$4</strong>) онно киирсэр эбит. \n\nЫйыллыбыт төрүөтэ: $2.",
        "viewpagelogs": "Бу сирэй сурунаалларын көрүү",
        "mw-widgets-dateinput-no-date": "Күнэ-дьыла ыйыллыбатах",
        "mw-widgets-titleinput-description-new-page": "сирэй суох эбит",
        "mw-widgets-titleinput-description-redirect": "манна $1 утаарыы",
-       "api-error-blacklisted": "Бука диэн өйдөнөр аатта тал дуу.",
        "randomrootpage": "Түбэһиэх төрүт сирэй."
 }
index 4ba4a01..efe836e 100644 (file)
        "password-change-forbidden": "Na tejto wiki si nemôžete zmeniť heslo.",
        "externaldberror": "Buď nastala chyba externej autentifikačnej databázy alebo vám nie je povolené aktualizovať váš externý účet.",
        "login": "Prihlásiť",
+       "login-security": "Overte svoju identitu",
        "nav-login-createaccount": "Prihlásenie / vytvorenie účtu",
        "userlogin": "Prihlásenie / vytvorenie účtu",
        "userloginnocreate": "Prihlásiť",
        "userlogin-resetpassword-link": "Zabudli ste heslo?",
        "userlogin-helplink2": "Pomoc s prihlásením",
        "userlogin-loggedin": "Ste už {{GENDER:$1|prihĺasený|prihlásená}} ako $1.\nPomocou formulára nižšie sa môžete prihlásiť ako iný redaktor.",
+       "userlogin-reauth": "Aby ste preukázali, že ste $1, musíte sa znovu prihlásiť.",
        "userlogin-createanother": "Vytvoriť ďalší účet",
        "createacct-emailrequired": "E-mailová adresa",
        "createacct-emailoptional": "E-mailová adresa (nepovinné)",
        "createacct-email-ph": "Zadajte vašu e-mailovú adresu",
        "createacct-another-email-ph": "Zadajte vašu e-mailovú adresu",
        "createaccountmail": "Použiť dočasné náhodné heslo a poslať ho na uvedenú e-mailovú adresu",
+       "createaccountmail-help": "Môže byť použité na vytvorenie účtu pre inú osobu bez prezradenia hesla.",
        "createacct-realname": "Skutočné meno (nepovinné)",
        "createaccountreason": "Dôvod:",
        "createacct-reason": "Dôvod",
        "createacct-reason-ph": "Prečo si vytvárate ďalší účet",
+       "createacct-reason-help": "Správa zobrazená v knihe nových používateľov",
        "createacct-submit": "Vytvoriť účet",
        "createacct-another-submit": "Vytvoriť účet",
+       "createacct-continue-submit": "Pokračovať v zakladaní účtu",
+       "createacct-another-continue-submit": "Pokračovať v zakladaní účtu",
        "createacct-benefit-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako vy.",
        "createacct-benefit-body1": "{{PLURAL:$1|úprava|úpravy|úprav}}",
        "createacct-benefit-body2": "{{PLURAL:$1|stránka|stránky|stránok}}",
        "nocookiesnew": "Používateľské konto bolo vytvorené, ale nie ste prihlásený. {{SITENAME}} používa cookies na prihlásenie. Máte cookies vypnuté. Zapnite ich a potom sa prihláste pomocou vášho nového používateľského mena a hesla.",
        "nocookieslogin": "{{SITENAME}} používa cookies na prihlásenie. Vy máte cookies vypnuté. Prosíme, zapnite ich a skúste znovu.",
        "nocookiesfornew": "Používateľský účet nebol vytvorený, pretože sme nemohli potvrdiť jeho zdroj. \nUbezpečte sa, že máte povolené cookies, obnovte túto stránku a skúste to znova.",
+       "createacct-loginerror": "Účet bol úspešne vytvorený, ale neboli ste automaticky prihlásený. Prosím, [[Special:UserLogin|prihláste sa]].",
        "noname": "Nezadali ste platné používateľské meno.",
        "loginsuccesstitle": "Prihlásenie úspešné",
        "loginsuccess": "'''Teraz ste prihlásený do {{GRAMMAR:genitív|{{SITENAME}}}} ako „$1“.'''",
        "createaccount-title": "Vytvorenie účtu na {{GRAMMAR:lokál|{{SITENAME}}}}",
        "createaccount-text": "Niekto vytvoril účet pre vašu emailovú adresu na {{GRAMMAR:lokál|{{SITENAME}}}}\n($4) s názvom „$2“, s heslom „$3“. Mali by ste sa prihlásiť a svoje heslo teraz zmeniť.\n\nAk bol účet vytvorený omylom, túto správu môžete ignorovať.",
        "login-throttled": "Uskutočnili ste príliš mnoho neúspešných pokusov o prihlásenie.\nProsím, počkajte $1 predtým, než to skúsite znova.",
-       "login-abort-generic": "Vaše prihlásenie nebolo úspešné - zrušené",
+       "login-abort-generic": "Vaše prihlásenie bolo neúspešné – zrušené",
        "login-migrated-generic": "Váš účet bol presťahovaný a vaše používateľské meno už viac na tejto wiki neexistuje.",
        "loginlanguagelabel": "Jazyk: $1",
        "suspicious-userlogout": "Vaša požiadavka odhlásiť sa bola zamietnutá, pretože to vyzerá, že ju poslal pokazený prehliadač alebo proxy server.",
        "createacct-another-realname-tip": "Skutočné meno je nepovinné.\nAk sa rozhodnete ho poskytnúť, použije sa na označenie vašej práce.",
        "pt-login": "Prihlásiť sa",
        "pt-login-button": "Prihlásiť sa",
+       "pt-login-continue-button": "Pokračovať v prihlasovaní",
        "pt-createaccount": "Vytvoriť účet",
        "pt-userlogout": "Odhlásiť sa",
        "php-mail-error-unknown": "Neznáma chyba vo funkcii PHP mail()",
        "resetpass_submit": "Nastaviť heslo a prihlásiť sa",
        "changepassword-success": "Vaše heslo bolo úspešne zmenené!",
        "changepassword-throttled": "Uskutočnili ste príliš mnoho neúspešných pokusov o prihlásenie. Prosím, počkajte $1 predtým, než to skúsite znova.",
+       "botpasswords": "Heslá pre botov",
+       "botpasswords-summary": "<em>Heslá pre botov</em> umožňujú pristupovať k redaktorskému účtu prostredníctvom API bez použitia hlavných prihlasovacích údajov účtu. Redaktorské oprávnenia dostupné po prihlásení pomocou hesla pre botov môžu byť obmedzené.\n\nAk neviete, k čomu by ste to mohli použiť, pravdepodobne by ste to použiť nemali. Nikto by vám nikdy nemal žiadať, aby ste si tu vygenerovali heslo a dali mu ho.",
+       "botpasswords-disabled": "Heslá pre botov sú zakázané.",
+       "botpasswords-no-central-id": "Aby ste mohli použiť heslá pre botov musíte byť prihlásený k centrálnemu účtu.",
+       "botpasswords-existing": "Jestvujúce heslá pre botov",
+       "botpasswords-createnew": "Vytvoriť nové heslo pre botov",
        "botpasswords-label-appid": "Názov bota:",
        "botpasswords-label-create": "Vytvoriť",
        "botpasswords-label-update": "Aktualizovať",
        "resetpass-no-info": "Aby ste mohli priamo pristupovať k tejto stránke, musíte sa prihlásiť.",
        "resetpass-submit-loggedin": "Zmeniť heslo",
        "resetpass-submit-cancel": "Zrušiť",
-       "resetpass-wrong-oldpass": "Neplatné dočasné alebo aktuálne heslo.\nJe možné, že sa vám už podarilo úspešne zmeniť svoje heslo alebo ste si vyžiadali nové dočasné heslo.",
+       "resetpass-wrong-oldpass": "Neplatné, dočasné alebo aktuálne heslo.\nJe možné, že sa vám už podarilo úspešne zmeniť svoje heslo alebo ste si vyžiadali nové dočasné heslo.",
        "resetpass-recycled": "Ako nové heslo si prosím nastavte niečo iné než súčasné heslo.",
        "resetpass-temp-emailed": "Prihlasujete sa dočasným heslom, zaslaným e-mailom. Aby ste dokončili prihlásenie, nastavte si tu nové heslo:",
        "resetpass-temp-password": "Dočasné heslo:",
        "passwordreset-emailtext-ip": "Niekto (pravdepodobne vy z IP adresy $1) požiadal o obnovenie vášho hesla na {{GRAMMAR:genitív|{{SITENAME}}}} ($4). {{PLURAL:$3|Nasledujúci používateľský účet je spojený|Nasledujúce používateľské účty sú spojené}}\ns touto emailovou adresou:\n\n$2\n\n{{PLURAL:$3|Platnosť tohto dočasného hesla vyprší|Platnosť týchto dočasných hesiel vyprší}} o {{PLURAL:$5|jeden deň|$5 dni|$5 dní}}.\nMali by ste sa prihlásiť teraz a zvoliť nové heslo. Ak túto žiadosť podal niekto iný alebo\nak ste si spomenuli svoje pôvodné heslo a už ho chcete zmeniť, môžete túto správu\nignorovať a ďalej používať vaše staré heslo.",
        "passwordreset-emailtext-user": "Používateľ $1 na {{GRAMMAR:genitív|{{SITENAME}}}} požiadal o obnovenie vášho hesla na na {{GRAMMAR:genitív|{{SITENAME}}}} ($4). {{PLURAL:$3|Nasledujúci používateľský účet je spojený|Nasledujúce používateľské účty sú spojené}}\ns touto emailovou adresou:\n\n$2\n\n{{PLURAL:$3|Platnosť tohto dočasného hesla vyprší|Platnosť týchto dočasných hesiel vyprší}} o {{PLURAL:$5|jeden deň|$5 dni|$5 dní}}.\nMali by ste sa prihlásiť teraz a zvoliť nové heslo. Ak túto žiadosť podal niekto iný alebo\nak ste si spomenuli svoje pôvodné heslo a už ho chcete zmeniť, môžete túto správu\nignorovať a ďalej používať vaše staré heslo.",
        "passwordreset-emailelement": "Používateľské meno: \n$1\n\nDočasné heslo:\n$2",
-       "passwordreset-emailsentemail": "Pokiaľ je toto e-mailová adresa, zaregistrovaná k vášmu účtu, bude na ňu zaslaný e-mail pre získanie nového hesla.",
+       "passwordreset-emailsentemail": "Pokiaľ je toto e-mailová adresa zaregistrovaná k vášmu účtu, bude na ňu zaslaný e-mail pre získanie nového hesla.",
        "passwordreset-emailsentusername": "Pokiaľ je príslušná mailová adresa zaregistrovaná, bude na ňu zaslaný e-mail s novým heslom.",
-       "passwordreset-emailsent-capture": "Bol odoslaný email s novým heslom, ktorý je zobrazený nižšie.",
-       "passwordreset-emailerror-capture": "Bol odoslaný email s novým heslom, ktorý je zobrazený nižšie, ale nepodarilo sa ho odoslať {{GENDER:$2|používateľovi}}: $1",
        "changeemail": "Zmeniť alebo odstrániť e-mailovú adresu",
        "changeemail-header": "Zmena e-mailovej adresy pre účet",
-       "changeemail-passwordrequired": "Pre potvrdenie tejto zmeny budete musieť zadať svoje heslo.",
        "changeemail-no-info": "Na prístup k tejto stránke musíte byť prihlásený.",
        "changeemail-oldemail": "Súčasná e-mailová adresa:",
        "changeemail-newemail": "Nová e-mailová adresa:",
        "undo-nochange": "Zdá sa, že úprava už bola zrušená.",
        "undo-summary": "Revízia $1 používateľa [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusia]]) bola vrátená",
        "undo-summary-username-hidden": "Vrátiť revíziu $1, ktorú vykonal skrytý používateľ",
-       "cantcreateaccounttitle": "Nie je možné vytvoriť účet",
        "cantcreateaccount-text": "Zakladanie nových účtov z tejto IP adresy ('''$1''') bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: ''$2''",
        "cantcreateaccount-range-text": "Zakladanie nových účtov z IP adries v rozsahu <strong>$1</strong>, ktorý zahŕňa aj vašu IP adresu (<strong>$4</strong>), bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
        "viewpagelogs": "Zobraziť záznamy pre túto stránku",
index 2ac715d..ea5f273 100644 (file)
        "sp-contributions-search": "تلاش برائے مساہمات",
        "sp-contributions-username": "آئی.پی پتہ یا اسمِ صارف:",
        "sp-contributions-toponly": "صرف حالیہ ترین نظرثانی ترمیمات دِکھاؤ",
+       "sp-contributions-hideminor": "معمولی ترامیم چھپائیں",
        "sp-contributions-submit": "تلاش",
        "whatlinkshere": "ادھر کونسا ربط ہے",
        "whatlinkshere-title": "\"$1\" سے مربوط صفحات",
index e58564c..0b01858 100644 (file)
        "unblock": "解除封鎖使用者",
        "blockip": "封鎖{{GENDER:$1|使用者}}",
        "blockip-legend": "封鎖使用者",
-       "blockiptext": "填寫以下表單可封鎖特定 IP 位址或使用者名稱的存取權限。\n這個動作應用來避免破壞行為,可根據 [[{{MediaWiki:Policy-url}}|管理政策]]。\n請在下方填寫一個具體的原因 (例如:引述一段破壞頁面的事實)。\n您可以使用 [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] 語法格式封鎖 IP 範圍,最大允許的範圍 IPv4 為 /$1、IPv6 為 /$2。",
+       "blockiptext": "填寫以下表單可封鎖特定 IP 位址或使用者的編輯權限。\n只有為了防止破壞,並符合[[{{MediaWiki:Policy-url}}|方針或政策]]的情況下方可採取此行動。\n請在下方填寫一個具體的原因(例如:引述一個被破壞的頁面)。\n您可以使用[//zh.wikipedia.org/wiki/无类别域间路由 CIDR]語法格式封鎖 IP 範圍,最大允許的範圍 IPv4 為 /$1、IPv6 為 /$2。",
        "ipaddressorusername": "IP 位址或使用者名稱:",
        "ipbexpiry": "期限:",
        "ipbreason": "原因:",
        "ipbreason-dropdown": "*常見的封鎖原因\n** 填寫不實資訊\n** 刪除頁面內容\n** 散佈外部廣告連結\n** 在頁面填寫無意義文字\n** 無禮的行為、攻擊/騷擾別人\n** 濫用多個帳號\n** 使用不受歡迎的使用者名稱",
        "ipb-hardblock": "禁止使用此 IP 位址登入的使用者編輯",
-       "ipbcreateaccount": "é\98²æ­¢å¸³è\99\9f建ç«\8b",
+       "ipbcreateaccount": "é\98²æ­¢å»ºç«\8bæ\96°å¸³è\99\9f",
        "ipbemailban": "禁止使用者傳送電子郵件",
        "ipbenableautoblock": "自動封鎖此使用者最後使用的 IP 位址,以及所有之後嘗試編輯使用的 IP 位址",
        "ipbsubmit": "封鎖此使用者",
        "ipboptions": "2 小時:2 hours,1 天:1 day,3 天:3 days,1 週:1 week,2 週:2 weeks,1 個月:1 month,3 個月:3 months,6 個月:6 months,1 年:1 year,無限期:infinite",
        "ipbhidename": "在編輯及清單中隱藏使用者名稱",
        "ipbwatchuser": "監視這位使用者的使用者頁面及其對話頁面",
-       "ipb-disableusertalk": "é\98²æ­¢æ­¤ä½¿ç\94¨è\80\85å\9c¨å°\81æ\9c\9fé\96\93編輯ä»\96自己的對話頁面",
+       "ipb-disableusertalk": "é\98»æ­¢æ­¤ä½¿ç\94¨è\80\85å\9c¨å°\81ç¦\81æ\9c\9fé\96\93編輯自己的對話頁面",
        "ipb-change-block": "使用現有設定重新封鎖使用者",
        "ipb-confirm": "確認封鎖",
        "badipaddress": "無效的 IP 位址",
index 8fd25a6..d6a9ba8 100644 (file)
@@ -596,6 +596,8 @@ class NamespaceConflictChecker extends Maintenance {
 
                $this->db->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
 
+               $this->commitTransaction( $this->db, __METHOD__ );
+
                /* Call LinksDeletionUpdate to delete outgoing links from the old title,
                 * and update category counts.
                 *
@@ -605,9 +607,8 @@ class NamespaceConflictChecker extends Maintenance {
                 * accidentally introduce an assumption of title validity to the code we
                 * are calling.
                 */
-               $update = new LinksDeletionUpdate( $wikiPage );
-               $update->doUpdate();
-               $this->commitTransaction( $this->db, __METHOD__ );
+               $updates = [ new LinksDeletionUpdate( $wikiPage ) ];
+               DataUpdate::runUpdates( $updates );
 
                return true;
        }
index d2ee1cd..cfaaf5f 100644 (file)
@@ -979,7 +979,6 @@ return [
                ],
                'dependencies' => [
                        'jquery.footHovzer',
-                       'jquery.tipsy',
                ],
                'position' => 'bottom',
        ],
@@ -1068,7 +1067,17 @@ return [
                'styles' => 'resources/src/mediawiki/mediawiki.hlist.css',
        ],
        'mediawiki.htmlform' => [
-               'scripts' => 'resources/src/mediawiki/mediawiki.htmlform.js',
+               'scripts' => [
+                       'resources/src/mediawiki/htmlform/htmlform.js',
+                       'resources/src/mediawiki/htmlform/autocomplete.js',
+                       'resources/src/mediawiki/htmlform/autoinfuse.js',
+                       'resources/src/mediawiki/htmlform/checkmatrix.js',
+                       'resources/src/mediawiki/htmlform/cloner.js',
+                       'resources/src/mediawiki/htmlform/hide-if.js',
+                       'resources/src/mediawiki/htmlform/multiselect.js',
+                       'resources/src/mediawiki/htmlform/selectandother.js',
+                       'resources/src/mediawiki/htmlform/selectorother.js',
+               ],
                'dependencies' => [
                        'mediawiki.RegExp',
                        'jquery.byteLimit',
@@ -1080,13 +1089,22 @@ return [
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
+       'mediawiki.htmlform.ooui' => [
+               'scripts' => [
+                       'resources/src/mediawiki/htmlform/htmlform.Element.js',
+               ],
+               'dependencies' => [
+                       'oojs-ui-core',
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'mediawiki.htmlform.styles' => [
-               'styles' => 'resources/src/mediawiki/mediawiki.htmlform.css',
+               'styles' => 'resources/src/mediawiki/htmlform/styles.css',
                'position' => 'top',
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.htmlform.ooui.styles' => [
-               'styles' => 'resources/src/mediawiki/mediawiki.htmlform.ooui.css',
+               'styles' => 'resources/src/mediawiki/htmlform/ooui.styles.css',
                'position' => 'top',
                'targets' => [ 'desktop', 'mobile' ],
        ],
index 501e898..ac60e8f 100644 (file)
                                        }
 
                                } else if ( $collapsible.parent().is( 'li' ) &&
-                                       $collapsible.parent().children( '.mw-collapsible' ).size() === 1
+                                       $collapsible.parent().children( '.mw-collapsible' ).length === 1 &&
+                                       $collapsible.find( '> .mw-collapsible-toggle' ).length === 0
                                ) {
                                        // special case of one collapsible in <li> tag
                                        $toggleLink = buildDefaultToggleLink();
index 6d88c51..9af81b8 100644 (file)
@@ -2,6 +2,10 @@
  * JavaScript for Special:MovePage
  */
 jQuery( function () {
+       // Infuse for pretty dropdown
        OO.ui.infuse( 'wpNewTitle' );
+       // Limit to 255 bytes, not characters
        OO.ui.infuse( 'wpReason' ).$input.byteLimit();
+       // Infuse for nicer "help" popup
+       OO.ui.infuse( 'wpMovetalk-field' );
 } );
index b4edc50..0035601 100644 (file)
@@ -33,8 +33,8 @@
        // Standalone icons
        //
        // Markup:
-       // <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok">OK</div><br/>
-       // <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok mw-ui-button mw-ui-progressive">OK</div><br/>
+       // <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok">OK</div><br>
+       // <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok mw-ui-button mw-ui-progressive">OK</div><br>
        // <button class="mw-ui-icon mw-ui-icon-ok mw-ui-icon-element mw-ui-button mw-ui-quiet" title="">Close</button>
        //
        // Styleguide 6.1.1.
                        margin-right: @iconGutterWidth;
                }
        }
-}
+
+       // Icons small for elements like indicators
+       //
+       // Markup:
+       // <div class="mw-ui-icon mw-ui-icon-small mw-ui-icon-help"></div>
+       //
+       // Styleguide 6.1.3
+       &.mw-ui-icon-small:before {
+               background-size: 66.67% auto; // 66.67% of 24px equals 16px
+       }
+}
\ No newline at end of file
index 7b7ef3d..86018a4 100644 (file)
                border-radius: 0.1em;
                line-height: 1.275em;
                background-color: #fff;
+
+               > .oo-ui-labelElement-label {
+                       padding: 0;
+               }
        }
 
        &.oo-ui-indicatorElement .mw-widget-dateInputWidget-handle > .oo-ui-indicatorElement-indicator {
diff --git a/resources/src/mediawiki/htmlform/autocomplete.js b/resources/src/mediawiki/htmlform/autocomplete.js
new file mode 100644 (file)
index 0000000..8157975
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * HTMLForm enhancements:
+ * Set up autocomplete fields.
+ */
+( function ( mw, $ ) {
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var $autocomplete = $root.find( '.mw-htmlform-autocomplete' );
+               if ( $autocomplete.length ) {
+                       mw.loader.using( 'jquery.suggestions', function () {
+                               $autocomplete.suggestions( {
+                                       fetch: function ( val ) {
+                                               var $el = $( this );
+                                               $el.suggestions( 'suggestions',
+                                                       $.grep( $el.data( 'autocomplete' ), function ( v ) {
+                                                               return v.indexOf( val ) === 0;
+                                                       } )
+                                               );
+                                       }
+                               } );
+                       } );
+               }
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/autoinfuse.js b/resources/src/mediawiki/htmlform/autoinfuse.js
new file mode 100644 (file)
index 0000000..f2e0f4d
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * HTMLForm enhancements:
+ * Infuse some OOjs UI HTMLForm fields (those which benefit from always being infused).
+ */
+( function ( mw, $ ) {
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var $oouiNodes, modules, extraModules;
+
+               $oouiNodes = $root.find( '.mw-htmlform-field-autoinfuse' );
+               if ( $oouiNodes.length ) {
+                       // The modules are preloaded (added server-side in HTMLFormField, and the individual fields
+                       // which need extra ones), but this module doesn't depend on them. Wait until they're loaded.
+                       modules = [ 'mediawiki.htmlform.ooui' ];
+                       $oouiNodes.each( function () {
+                               var data = $( this ).data( 'mw-modules' );
+                               if ( data ) {
+                                       // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
+                                       extraModules = data.split( ',' );
+                                       modules.push.apply( modules, extraModules );
+                               }
+                       } );
+                       mw.loader.using( modules ).done( function () {
+                               $oouiNodes.each( function () {
+                                       OO.ui.infuse( this );
+                               } );
+                       } );
+               }
+
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/checkmatrix.js b/resources/src/mediawiki/htmlform/checkmatrix.js
new file mode 100644 (file)
index 0000000..b825f12
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * HTMLForm enhancements:
+ * Show fancy tooltips for checkmatrix fields.
+ */
+( function ( mw ) {
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
+               if ( $matrixTooltips.length ) {
+                       mw.loader.using( 'jquery.tipsy', function () {
+                               $matrixTooltips.tipsy( { gravity: 's' } );
+                       } );
+               }
+       } );
+
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki/htmlform/cloner.js b/resources/src/mediawiki/htmlform/cloner.js
new file mode 100644 (file)
index 0000000..ab81580
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * HTMLForm enhancements:
+ * Add/remove cloner clones without having to resubmit the form.
+ */
+( function ( mw, $ ) {
+
+       var cloneCounter = 0;
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               $root.find( '.mw-htmlform-cloner-delete-button' ).filter( ':input' ).click( function ( ev ) {
+                       ev.preventDefault();
+                       $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
+               } );
+
+               $root.find( '.mw-htmlform-cloner-create-button' ).filter( ':input' ).click( function ( ev ) {
+                       var $ul, $li, html;
+
+                       ev.preventDefault();
+
+                       $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
+
+                       html = $ul.data( 'template' ).replace(
+                               new RegExp( mw.RegExp.escape( $ul.data( 'uniqueId' ) ), 'g' ),
+                               'clone' + ( ++cloneCounter )
+                       );
+
+                       $li = $( '<li>' )
+                               .addClass( 'mw-htmlform-cloner-li' )
+                               .html( html )
+                               .appendTo( $ul );
+
+                       mw.hook( 'htmlform.enhance' ).fire( $li );
+               } );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/hide-if.js b/resources/src/mediawiki/htmlform/hide-if.js
new file mode 100644 (file)
index 0000000..cb717af
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * HTMLForm enhancements:
+ * Set up 'hide-if' behaviors for form fields that have them.
+ */
+( function ( mw, $ ) {
+
+       /*jshint -W024*/
+
+       /**
+        * Helper function for hide-if to find the nearby form field.
+        *
+        * Find the closest match for the given name, "closest" being the minimum
+        * level of parents to go to find a form field matching the given name or
+        * ending in array keys matching the given name (e.g. "baz" matches
+        * "foo[bar][baz]").
+        *
+        * @ignore
+        * @private
+        * @param {jQuery} $el
+        * @param {string} name
+        * @return {jQuery|OO.ui.Widget|null}
+        */
+       function hideIfGetField( $el, name ) {
+               var $found, $p, $widget,
+                       suffix = name.replace( /^([^\[]+)/, '[$1]' );
+
+               function nameFilter() {
+                       return this.name === name ||
+                               ( this.name === ( 'wp' + name ) ) ||
+                               this.name.slice( -suffix.length ) === suffix;
+               }
+
+               for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
+                       $found = $p.find( '[name]' ).filter( nameFilter );
+                       if ( $found.length ) {
+                               $widget = $found.closest( '.oo-ui-widget[data-ooui]' );
+                               if ( $widget.length ) {
+                                       return OO.ui.Widget.static.infuse( $widget );
+                               }
+                               return $found;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Helper function for hide-if to return a test function and list of
+        * dependent fields for a hide-if specification.
+        *
+        * @ignore
+        * @private
+        * @param {jQuery} $el
+        * @param {Array} spec
+        * @return {Array}
+        * @return {Array} return.0 Dependent fields, array of jQuery objects or OO.ui.Widgets
+        * @return {Function} return.1 Test function
+        */
+       function hideIfParse( $el, spec ) {
+               var op, i, l, v, field, $field, fields, func, funcs, getVal;
+
+               op = spec[ 0 ];
+               l = spec.length;
+               switch ( op ) {
+                       case 'AND':
+                       case 'OR':
+                       case 'NAND':
+                       case 'NOR':
+                               funcs = [];
+                               fields = [];
+                               for ( i = 1; i < l; i++ ) {
+                                       if ( !$.isArray( spec[ i ] ) ) {
+                                               throw new Error( op + ' parameters must be arrays' );
+                                       }
+                                       v = hideIfParse( $el, spec[ i ] );
+                                       fields = fields.concat( v[ 0 ] );
+                                       funcs.push( v[ 1 ] );
+                               }
+
+                               l = funcs.length;
+                               switch ( op ) {
+                                       case 'AND':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( !funcs[ i ]() ) {
+                                                                       return false;
+                                                               }
+                                                       }
+                                                       return true;
+                                               };
+                                               break;
+
+                                       case 'OR':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( funcs[ i ]() ) {
+                                                                       return true;
+                                                               }
+                                                       }
+                                                       return false;
+                                               };
+                                               break;
+
+                                       case 'NAND':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( !funcs[ i ]() ) {
+                                                                       return true;
+                                                               }
+                                                       }
+                                                       return false;
+                                               };
+                                               break;
+
+                                       case 'NOR':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( funcs[ i ]() ) {
+                                                                       return false;
+                                                               }
+                                                       }
+                                                       return true;
+                                               };
+                                               break;
+                               }
+
+                               return [ fields, func ];
+
+                       case 'NOT':
+                               if ( l !== 2 ) {
+                                       throw new Error( 'NOT takes exactly one parameter' );
+                               }
+                               if ( !$.isArray( spec[ 1 ] ) ) {
+                                       throw new Error( 'NOT parameters must be arrays' );
+                               }
+                               v = hideIfParse( $el, spec[ 1 ] );
+                               fields = v[ 0 ];
+                               func = v[ 1 ];
+                               return [ fields, function () {
+                                       return !func();
+                               } ];
+
+                       case '===':
+                       case '!==':
+                               if ( l !== 3 ) {
+                                       throw new Error( op + ' takes exactly two parameters' );
+                               }
+                               field = hideIfGetField( $el, spec[ 1 ] );
+                               if ( !field ) {
+                                       return [ [], function () {
+                                               return false;
+                                       } ];
+                               }
+                               v = spec[ 2 ];
+
+                               if ( field instanceof OO.ui.Widget ) {
+                                       if ( field.supports( 'isSelected' ) ) {
+                                               getVal = function () {
+                                                       var selected = field.isSelected();
+                                                       return selected ? field.getValue() : '';
+                                               };
+                                       } else {
+                                               getVal = function () {
+                                                       return field.getValue();
+                                               };
+                                       }
+                               } else {
+                                       $field = $( field );
+                                       if ( $field.prop( 'type' ) === 'radio' || $field.prop( 'type' ) === 'checkbox' ) {
+                                               getVal = function () {
+                                                       var $selected = $field.filter( ':checked' );
+                                                       return $selected.length ? $selected.val() : '';
+                                               };
+                                       } else {
+                                               getVal = function () {
+                                                       return $field.val();
+                                               };
+                                       }
+                               }
+
+                               switch ( op ) {
+                                       case '===':
+                                               func = function () {
+                                                       return getVal() === v;
+                                               };
+                                               break;
+                                       case '!==':
+                                               func = function () {
+                                                       return getVal() !== v;
+                                               };
+                                               break;
+                               }
+
+                               return [ [ field ], func ];
+
+                       default:
+                               throw new Error( 'Unrecognized operation \'' + op + '\'' );
+               }
+       }
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               $root.find( '.mw-htmlform-hide-if' ).each( function () {
+                       var v, i, fields, test, func, spec, self, modules, data,extraModules,
+                               $el = $( this );
+
+                       modules = [];
+                       if ( $el.is( '[data-ooui]' ) ) {
+                               modules.push( 'mediawiki.htmlform.ooui' );
+                               data = $el.data( 'mw-modules' );
+                               if ( data ) {
+                                       // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
+                                       extraModules = data.split( ',' );
+                                       modules.push.apply( modules, extraModules );
+                               }
+                       }
+
+                       mw.loader.using( modules ).done( function () {
+                               if ( $el.is( '[data-ooui]' ) ) {
+                                       // self should be a FieldLayout that mixes in mw.htmlform.Element
+                                       self = OO.ui.FieldLayout.static.infuse( $el );
+                                       spec = self.hideIf;
+                                       // The original element has been replaced with infused one
+                                       $el = self.$element;
+                               } else {
+                                       self = $el;
+                                       spec = $el.data( 'hideIf' );
+                               }
+
+                               if ( !spec ) {
+                                       return;
+                               }
+
+                               v = hideIfParse( $el, spec );
+                               fields = v[ 0 ];
+                               test = v[ 1 ];
+                               // The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
+                               func = function () {
+                                       self.toggle( !test() );
+                               };
+                               for ( i = 0; i < fields.length; i++ ) {
+                                       // The .on() method works mostly the same for jQuery objects and OO.ui.Widget
+                                       fields[ i ].on( 'change', func );
+                               }
+                               func();
+                       } );
+               } );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/htmlform.Element.js b/resources/src/mediawiki/htmlform/htmlform.Element.js
new file mode 100644 (file)
index 0000000..37474f6
--- /dev/null
@@ -0,0 +1,45 @@
+( function ( mw ) {
+
+       mw.htmlform = {};
+
+       /**
+        * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. This picks up the
+        * extra config from a matching PHP widget (defined in HTMLFormElement.php) when constructed using
+        * OO.ui.infuse().
+        *
+        * Currently only supports passing 'hide-if' data.
+        *
+        * @ignore
+        */
+       mw.htmlform.Element = function ( config ) {
+               // Configuration initialization
+               config = config || {};
+
+               // Properties
+               this.hideIf = config.hideIf;
+
+               // Initialization
+               if ( this.hideIf ) {
+                       this.$element.addClass( 'mw-htmlform-hide-if' );
+               }
+       };
+
+       mw.htmlform.FieldLayout = function ( config ) {
+               // Parent constructor
+               mw.htmlform.FieldLayout.parent.call( this, config );
+               // Mixin constructors
+               mw.htmlform.Element.call( this, config );
+       };
+       OO.inheritClass( mw.htmlform.FieldLayout, OO.ui.FieldLayout );
+       OO.mixinClass( mw.htmlform.FieldLayout, mw.htmlform.Element );
+
+       mw.htmlform.ActionFieldLayout = function ( config ) {
+               // Parent constructor
+               mw.htmlform.ActionFieldLayout.parent.call( this, config );
+               // Mixin constructors
+               mw.htmlform.Element.call( this, config );
+       };
+       OO.inheritClass( mw.htmlform.ActionFieldLayout, OO.ui.ActionFieldLayout );
+       OO.mixinClass( mw.htmlform.ActionFieldLayout, mw.htmlform.Element );
+
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki/htmlform/htmlform.js b/resources/src/mediawiki/htmlform/htmlform.js
new file mode 100644 (file)
index 0000000..19f8f3e
--- /dev/null
@@ -0,0 +1,7 @@
+( function ( mw, $ ) {
+
+       $( function () {
+               mw.hook( 'htmlform.enhance' ).fire( $( document ) );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/images/question.png b/resources/src/mediawiki/htmlform/images/question.png
new file mode 100644 (file)
index 0000000..acce58c
Binary files /dev/null and b/resources/src/mediawiki/htmlform/images/question.png differ
diff --git a/resources/src/mediawiki/htmlform/images/question.svg b/resources/src/mediawiki/htmlform/images/question.svg
new file mode 100644 (file)
index 0000000..98fbe8d
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="21.059" height="21.06"><path fill="#575757" d="M10.529 0c-5.814 0-10.529 4.714-10.529 10.529s4.715 10.53 10.529 10.53c5.816 0 10.529-4.715 10.529-10.53s-4.712-10.529-10.529-10.529zm-.002 16.767c-.861 0-1.498-.688-1.498-1.516 0-.862.637-1.534 1.498-1.534.828 0 1.5.672 1.5 1.534 0 .827-.672 1.516-1.5 1.516zm2.137-6.512c-.723.568-1 .931-1 1.739v.5h-2.205v-.603c0-1.517.449-2.136 1.154-2.688.707-.552 1.139-.845 1.139-1.637 0-.672-.414-1.051-1.24-1.051-.707 0-1.328.189-1.982.638l-1.051-1.807c.861-.604 1.93-1.034 3.342-1.034 1.912 0 3.516 1.051 3.516 3.066-.001 1.43-.794 2.188-1.673 2.877z"/></svg>
\ No newline at end of file
diff --git a/resources/src/mediawiki/htmlform/multiselect.js b/resources/src/mediawiki/htmlform/multiselect.js
new file mode 100644 (file)
index 0000000..3cdab3c
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * HTMLForm enhancements:
+ * Convert multiselect fields from checkboxes to Chosen selector when requested.
+ */
+( function ( mw, $ ) {
+
+       function addMulti( $oldContainer, $container ) {
+               var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
+                       oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
+                       $select = $( '<select>' ),
+                       dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
+               oldClass = $.trim( oldClass );
+               $select.attr( {
+                       name: name,
+                       multiple: 'multiple',
+                       'data-placeholder': dataPlaceholder.plain(),
+                       'class': 'htmlform-chzn-select mw-input ' + oldClass
+               } );
+               $oldContainer.find( 'input' ).each( function () {
+                       var $oldInput = $( this ),
+                       checked = $oldInput.prop( 'checked' ),
+                       $option = $( '<option>' );
+                       $option.prop( 'value', $oldInput.prop( 'value' ) );
+                       if ( checked ) {
+                               $option.prop( 'selected', true );
+                       }
+                       $option.text( $oldInput.prop( 'value' ) );
+                       $select.append( $option );
+               } );
+               $container.append( $select );
+       }
+
+       function convertCheckboxesToMulti( $oldContainer, type ) {
+               var $fieldLabel = $( '<td>' ),
+               $td = $( '<td>' ),
+               $fieldLabelText = $( '<label>' ),
+               $container;
+               if ( type === 'tr' ) {
+                       addMulti( $oldContainer, $td );
+                       $container = $( '<tr>' );
+                       $container.append( $td );
+               } else if ( type === 'div' ) {
+                       $fieldLabel = $( '<div>' );
+                       $container = $( '<div>' );
+                       addMulti( $oldContainer, $container );
+               }
+               $fieldLabel.attr( 'class', 'mw-label' );
+               $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
+               $fieldLabel.append( $fieldLabelText );
+               $container.prepend( $fieldLabel );
+               $oldContainer.replaceWith( $container );
+               return $container;
+       }
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               if ( $root.find( '.mw-chosen' ).length ) {
+                       mw.loader.using( 'jquery.chosen', function () {
+                               $root.find( '.mw-chosen' ).each( function () {
+                                       var type = this.nodeName.toLowerCase(),
+                                               $converted = convertCheckboxesToMulti( $( this ), type );
+                                       $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
+                               } );
+                       } );
+               }
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/ooui.styles.css b/resources/src/mediawiki/htmlform/ooui.styles.css
new file mode 100644 (file)
index 0000000..a9e75d7
--- /dev/null
@@ -0,0 +1,29 @@
+/* OOUIHTMLForm styles */
+
+.mw-htmlform-ooui-wrapper {
+       margin: 1em 0;
+}
+
+.mw-htmlform-ooui .mw-htmlform-submit-buttons {
+       margin-top: 1em;
+}
+
+.mw-htmlform-ooui .mw-htmlform-field-HTMLCheckMatrix,
+.mw-htmlform-ooui .mw-htmlform-matrix,
+.mw-htmlform-ooui .mw-htmlform-matrix tr {
+       width: 100%;
+}
+
+.mw-htmlform-ooui .mw-htmlform-matrix tr td.first {
+       margin-right: 5%;
+       width: 39%;
+}
+
+/* Flatlist styling for PHP widgets... */
+.mw-htmlform-flatlist .oo-ui-fieldLayout-align-inline,
+/* ...and for JS widgets */
+.mw-htmlform-flatlist .oo-ui-optionWidget,
+.mw-htmlform-flatlist .oo-ui-multioptionWidget {
+       display: inline-block;
+       margin-right: 1em;
+}
diff --git a/resources/src/mediawiki/htmlform/selectandother.js b/resources/src/mediawiki/htmlform/selectandother.js
new file mode 100644 (file)
index 0000000..95227d0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * HTMLForm enhancements:
+ * Add a dynamic max length to the reason field of SelectAndOther.
+ */
+( function ( mw, $ ) {
+
+       // cache the separator to avoid object creation on each keypress
+       var colonSeparator = mw.message( 'colon-separator' ).text();
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               // This checks the length together with the value from the select field
+               // When the reason list is changed and the bytelimit is longer than the allowed,
+               // nothing is done
+               $root
+                       .find( '.mw-htmlform-select-and-other-field' )
+                       .each( function () {
+                               var $this = $( this ),
+                                       // find the reason list
+                                       $reasonList = $root.find( '#' + $this.data( 'id-select' ) ),
+                                       // cache the current selection to avoid expensive lookup
+                                       currentValReasonList = $reasonList.val();
+
+                               $reasonList.change( function () {
+                                       currentValReasonList = $reasonList.val();
+                               } );
+
+                               $this.byteLimit( function ( input ) {
+                                       // Should be built the same as in HTMLSelectAndOtherField::loadDataFromRequest
+                                       var comment = currentValReasonList;
+                                       if ( comment === 'other' ) {
+                                               comment = input;
+                                       } else if ( input !== '' ) {
+                                               // Entry from drop down menu + additional comment
+                                               comment += colonSeparator + input;
+                                       }
+                                       return comment;
+                               } );
+                       } );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/selectorother.js b/resources/src/mediawiki/htmlform/selectorother.js
new file mode 100644 (file)
index 0000000..66879e9
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * HTMLForm enhancements:
+ * Animate the SelectOrOther fields, to only show the text field when 'other' is selected.
+ */
+( function ( mw, $ ) {
+
+       /**
+        * @class jQuery.plugin.htmlform
+        */
+
+       /**
+        * jQuery plugin to fade or snap to visible state.
+        *
+        * @param {boolean} [instantToggle=false]
+        * @return {jQuery}
+        * @chainable
+        */
+       $.fn.goIn = function ( instantToggle ) {
+               if ( instantToggle === true ) {
+                       return this.show();
+               }
+               return this.stop( true, true ).fadeIn();
+       };
+
+       /**
+        * jQuery plugin to fade or snap to hiding state.
+        *
+        * @param {boolean} [instantToggle=false]
+        * @return {jQuery}
+        * @chainable
+        */
+       $.fn.goOut = function ( instantToggle ) {
+               if ( instantToggle === true ) {
+                       return this.hide();
+               }
+               return this.stop( true, true ).fadeOut();
+       };
+
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               /**
+                * @ignore
+                * @param {boolean|jQuery.Event} instant
+                */
+               function handleSelectOrOther( instant ) {
+                       var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
+                       $other = $other.add( $other.siblings( 'br' ) );
+                       if ( $( this ).val() === 'other' ) {
+                               $other.goIn( instant );
+                       } else {
+                               $other.goOut( instant );
+                       }
+               }
+
+               $root
+                       .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther )
+                       .each( function () {
+                               handleSelectOrOther.call( this, true );
+                       } );
+       } );
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/htmlform/styles.css b/resources/src/mediawiki/htmlform/styles.css
new file mode 100644 (file)
index 0000000..1603130
--- /dev/null
@@ -0,0 +1,49 @@
+/* HTMLForm styles */
+
+table.mw-htmlform-nolabel td.mw-label {
+       display: none;
+}
+
+.mw-htmlform-invalid-input td.mw-input input {
+       border-color: #f00;
+}
+
+.mw-htmlform-flatlist div.mw-htmlform-flatlist-item {
+       display: inline;
+       margin-right: 1em;
+       white-space: nowrap;
+}
+
+/* HTMLCheckMatrix */
+
+.mw-htmlform-matrix td {
+       padding-left: 0.5em;
+       padding-right: 0.5em;
+}
+
+tr.mw-htmlform-vertical-label td.mw-label {
+       text-align: left !important;
+}
+
+.mw-icon-question {
+       /* SVG support using a transparent gradient to guarantee cross-browser
+        * compatibility (browsers able to understand gradient syntax support also SVG).
+        * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */
+       background-image: url( images/question.png );
+       /* @embed */
+       background-image: linear-gradient( transparent, transparent ), url( images/question.svg );
+       background-repeat: no-repeat;
+       background-size: 13px 13px;
+       display: inline-block;
+       height: 13px;
+       width: 13px;
+       margin-left: 4px;
+}
+
+.mw-icon-question:lang(ar),
+.mw-icon-question:lang(fa),
+.mw-icon-question:lang(ur) {
+       -webkit-transform: scaleX( -1 );
+       -ms-transform: scaleX( -1 );
+       transform: scaleX( -1 );
+}
diff --git a/resources/src/mediawiki/images/question.png b/resources/src/mediawiki/images/question.png
deleted file mode 100644 (file)
index acce58c..0000000
Binary files a/resources/src/mediawiki/images/question.png and /dev/null differ
diff --git a/resources/src/mediawiki/images/question.svg b/resources/src/mediawiki/images/question.svg
deleted file mode 100644 (file)
index 98fbe8d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="21.059" height="21.06"><path fill="#575757" d="M10.529 0c-5.814 0-10.529 4.714-10.529 10.529s4.715 10.53 10.529 10.53c5.816 0 10.529-4.715 10.529-10.53s-4.712-10.529-10.529-10.529zm-.002 16.767c-.861 0-1.498-.688-1.498-1.516 0-.862.637-1.534 1.498-1.534.828 0 1.5.672 1.5 1.534 0 .827-.672 1.516-1.5 1.516zm2.137-6.512c-.723.568-1 .931-1 1.739v.5h-2.205v-.603c0-1.517.449-2.136 1.154-2.688.707-.552 1.139-.845 1.139-1.637 0-.672-.414-1.051-1.24-1.051-.707 0-1.328.189-1.982.638l-1.051-1.807c.861-.604 1.93-1.034 3.342-1.034 1.912 0 3.516 1.051 3.516 3.066-.001 1.43-.794 2.188-1.673 2.877z"/></svg>
\ No newline at end of file
diff --git a/resources/src/mediawiki/mediawiki.htmlform.css b/resources/src/mediawiki/mediawiki.htmlform.css
deleted file mode 100644 (file)
index 1603130..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* HTMLForm styles */
-
-table.mw-htmlform-nolabel td.mw-label {
-       display: none;
-}
-
-.mw-htmlform-invalid-input td.mw-input input {
-       border-color: #f00;
-}
-
-.mw-htmlform-flatlist div.mw-htmlform-flatlist-item {
-       display: inline;
-       margin-right: 1em;
-       white-space: nowrap;
-}
-
-/* HTMLCheckMatrix */
-
-.mw-htmlform-matrix td {
-       padding-left: 0.5em;
-       padding-right: 0.5em;
-}
-
-tr.mw-htmlform-vertical-label td.mw-label {
-       text-align: left !important;
-}
-
-.mw-icon-question {
-       /* SVG support using a transparent gradient to guarantee cross-browser
-        * compatibility (browsers able to understand gradient syntax support also SVG).
-        * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */
-       background-image: url( images/question.png );
-       /* @embed */
-       background-image: linear-gradient( transparent, transparent ), url( images/question.svg );
-       background-repeat: no-repeat;
-       background-size: 13px 13px;
-       display: inline-block;
-       height: 13px;
-       width: 13px;
-       margin-left: 4px;
-}
-
-.mw-icon-question:lang(ar),
-.mw-icon-question:lang(fa),
-.mw-icon-question:lang(ur) {
-       -webkit-transform: scaleX( -1 );
-       -ms-transform: scaleX( -1 );
-       transform: scaleX( -1 );
-}
diff --git a/resources/src/mediawiki/mediawiki.htmlform.js b/resources/src/mediawiki/mediawiki.htmlform.js
deleted file mode 100644 (file)
index c3464ea..0000000
+++ /dev/null
@@ -1,417 +0,0 @@
-/**
- * Utility functions for jazzing up HTMLForm elements.
- *
- * @class jQuery.plugin.htmlform
- */
-( function ( mw, $ ) {
-
-       var cloneCounter = 0;
-
-       /**
-        * Helper function for hide-if to find the nearby form field.
-        *
-        * Find the closest match for the given name, "closest" being the minimum
-        * level of parents to go to find a form field matching the given name or
-        * ending in array keys matching the given name (e.g. "baz" matches
-        * "foo[bar][baz]").
-        *
-        * @private
-        * @param {jQuery} $el
-        * @param {string} name
-        * @return {jQuery|null}
-        */
-       function hideIfGetField( $el, name ) {
-               var $found, $p,
-                       suffix = name.replace( /^([^\[]+)/, '[$1]' );
-
-               function nameFilter() {
-                       return this.name === name ||
-                               ( this.name === ( 'wp' + name ) ) ||
-                               this.name.slice( -suffix.length ) === suffix;
-               }
-
-               for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
-                       $found = $p.find( '[name]' ).filter( nameFilter );
-                       if ( $found.length ) {
-                               return $found;
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * Helper function for hide-if to return a test function and list of
-        * dependent fields for a hide-if specification.
-        *
-        * @private
-        * @param {jQuery} $el
-        * @param {Array} spec
-        * @return {Array}
-        * @return {jQuery} return.0 Dependent fields
-        * @return {Function} return.1 Test function
-        */
-       function hideIfParse( $el, spec ) {
-               var op, i, l, v, $field, $fields, fields, func, funcs, getVal;
-
-               op = spec[ 0 ];
-               l = spec.length;
-               switch ( op ) {
-                       case 'AND':
-                       case 'OR':
-                       case 'NAND':
-                       case 'NOR':
-                               funcs = [];
-                               fields = [];
-                               for ( i = 1; i < l; i++ ) {
-                                       if ( !$.isArray( spec[ i ] ) ) {
-                                               throw new Error( op + ' parameters must be arrays' );
-                                       }
-                                       v = hideIfParse( $el, spec[ i ] );
-                                       fields = fields.concat( v[ 0 ].toArray() );
-                                       funcs.push( v[ 1 ] );
-                               }
-                               $fields = $( fields );
-
-                               l = funcs.length;
-                               switch ( op ) {
-                                       case 'AND':
-                                               func = function () {
-                                                       var i;
-                                                       for ( i = 0; i < l; i++ ) {
-                                                               if ( !funcs[ i ]() ) {
-                                                                       return false;
-                                                               }
-                                                       }
-                                                       return true;
-                                               };
-                                               break;
-
-                                       case 'OR':
-                                               func = function () {
-                                                       var i;
-                                                       for ( i = 0; i < l; i++ ) {
-                                                               if ( funcs[ i ]() ) {
-                                                                       return true;
-                                                               }
-                                                       }
-                                                       return false;
-                                               };
-                                               break;
-
-                                       case 'NAND':
-                                               func = function () {
-                                                       var i;
-                                                       for ( i = 0; i < l; i++ ) {
-                                                               if ( !funcs[ i ]() ) {
-                                                                       return true;
-                                                               }
-                                                       }
-                                                       return false;
-                                               };
-                                               break;
-
-                                       case 'NOR':
-                                               func = function () {
-                                                       var i;
-                                                       for ( i = 0; i < l; i++ ) {
-                                                               if ( funcs[ i ]() ) {
-                                                                       return false;
-                                                               }
-                                                       }
-                                                       return true;
-                                               };
-                                               break;
-                               }
-
-                               return [ $fields, func ];
-
-                       case 'NOT':
-                               if ( l !== 2 ) {
-                                       throw new Error( 'NOT takes exactly one parameter' );
-                               }
-                               if ( !$.isArray( spec[ 1 ] ) ) {
-                                       throw new Error( 'NOT parameters must be arrays' );
-                               }
-                               v = hideIfParse( $el, spec[ 1 ] );
-                               $fields = v[ 0 ];
-                               func = v[ 1 ];
-                               return [ $fields, function () {
-                                       return !func();
-                               } ];
-
-                       case '===':
-                       case '!==':
-                               if ( l !== 3 ) {
-                                       throw new Error( op + ' takes exactly two parameters' );
-                               }
-                               $field = hideIfGetField( $el, spec[ 1 ] );
-                               if ( !$field ) {
-                                       return [ $(), function () {
-                                               return false;
-                                       } ];
-                               }
-                               v = spec[ 2 ];
-
-                               if ( $field.first().prop( 'type' ) === 'radio' ||
-                                       $field.first().prop( 'type' ) === 'checkbox'
-                               ) {
-                                       getVal = function () {
-                                               var $selected = $field.filter( ':checked' );
-                                               return $selected.length ? $selected.val() : '';
-                                       };
-                               } else {
-                                       getVal = function () {
-                                               return $field.val();
-                                       };
-                               }
-
-                               switch ( op ) {
-                                       case '===':
-                                               func = function () {
-                                                       return getVal() === v;
-                                               };
-                                               break;
-                                       case '!==':
-                                               func = function () {
-                                                       return getVal() !== v;
-                                               };
-                                               break;
-                               }
-
-                               return [ $field, func ];
-
-                       default:
-                               throw new Error( 'Unrecognized operation \'' + op + '\'' );
-               }
-       }
-
-       /**
-        * jQuery plugin to fade or snap to visible state.
-        *
-        * @param {boolean} [instantToggle=false]
-        * @return {jQuery}
-        * @chainable
-        */
-       $.fn.goIn = function ( instantToggle ) {
-               if ( instantToggle === true ) {
-                       return this.show();
-               }
-               return this.stop( true, true ).fadeIn();
-       };
-
-       /**
-        * jQuery plugin to fade or snap to hiding state.
-        *
-        * @param {boolean} [instantToggle=false]
-        * @return {jQuery}
-        * @chainable
-        */
-       $.fn.goOut = function ( instantToggle ) {
-               if ( instantToggle === true ) {
-                       return this.hide();
-               }
-               return this.stop( true, true ).fadeOut();
-       };
-
-       function enhance( $root ) {
-               var $matrixTooltips, $autocomplete,
-                       // cache the separator to avoid object creation on each keypress
-                       colonSeparator = mw.message( 'colon-separator' ).text();
-
-               /**
-                * @ignore
-                * @param {boolean|jQuery.Event} instant
-                */
-               function handleSelectOrOther( instant ) {
-                       var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
-                       $other = $other.add( $other.siblings( 'br' ) );
-                       if ( $( this ).val() === 'other' ) {
-                               $other.goIn( instant );
-                       } else {
-                               $other.goOut( instant );
-                       }
-               }
-
-               // Animate the SelectOrOther fields, to only show the text field when
-               // 'other' is selected.
-               $root
-                       .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther )
-                       .each( function () {
-                               handleSelectOrOther.call( this, true );
-                       } );
-
-               // Add a dynamic max length to the reason field of SelectAndOther
-               // This checks the length together with the value from the select field
-               // When the reason list is changed and the bytelimit is longer than the allowed,
-               // nothing is done
-               $root
-                       .find( '.mw-htmlform-select-and-other-field' )
-                       .each( function () {
-                               var $this = $( this ),
-                                       // find the reason list
-                                       $reasonList = $root.find( '#' + $this.data( 'id-select' ) ),
-                                       // cache the current selection to avoid expensive lookup
-                                       currentValReasonList = $reasonList.val();
-
-                               $reasonList.change( function () {
-                                       currentValReasonList = $reasonList.val();
-                               } );
-
-                               $this.byteLimit( function ( input ) {
-                                       // Should be built the same as in HTMLSelectAndOtherField::loadDataFromRequest
-                                       var comment = currentValReasonList;
-                                       if ( comment === 'other' ) {
-                                               comment = input;
-                                       } else if ( input !== '' ) {
-                                               // Entry from drop down menu + additional comment
-                                               comment += colonSeparator + input;
-                                       }
-                                       return comment;
-                               } );
-                       } );
-
-               // Set up hide-if elements
-               $root.find( '.mw-htmlform-hide-if' ).each( function () {
-                       var v, $fields, test, func,
-                               $el = $( this ),
-                               spec = $el.data( 'hideIf' );
-
-                       if ( !spec ) {
-                               return;
-                       }
-
-                       v = hideIfParse( $el, spec );
-                       $fields = v[ 0 ];
-                       test = v[ 1 ];
-                       func = function () {
-                               if ( test() ) {
-                                       $el.hide();
-                               } else {
-                                       $el.show();
-                               }
-                       };
-                       $fields.on( 'change', func );
-                       func();
-               } );
-
-               function addMulti( $oldContainer, $container ) {
-                       var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
-                               oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
-                               $select = $( '<select>' ),
-                               dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
-                       oldClass = $.trim( oldClass );
-                       $select.attr( {
-                               name: name,
-                               multiple: 'multiple',
-                               'data-placeholder': dataPlaceholder.plain(),
-                               'class': 'htmlform-chzn-select mw-input ' + oldClass
-                       } );
-                       $oldContainer.find( 'input' ).each( function () {
-                               var $oldInput = $( this ),
-                               checked = $oldInput.prop( 'checked' ),
-                               $option = $( '<option>' );
-                               $option.prop( 'value', $oldInput.prop( 'value' ) );
-                               if ( checked ) {
-                                       $option.prop( 'selected', true );
-                               }
-                               $option.text( $oldInput.prop( 'value' ) );
-                               $select.append( $option );
-                       } );
-                       $container.append( $select );
-               }
-
-               function convertCheckboxesToMulti( $oldContainer, type ) {
-                       var $fieldLabel = $( '<td>' ),
-                       $td = $( '<td>' ),
-                       $fieldLabelText = $( '<label>' ),
-                       $container;
-                       if ( type === 'tr' ) {
-                               addMulti( $oldContainer, $td );
-                               $container = $( '<tr>' );
-                               $container.append( $td );
-                       } else if ( type === 'div' ) {
-                               $fieldLabel = $( '<div>' );
-                               $container = $( '<div>' );
-                               addMulti( $oldContainer, $container );
-                       }
-                       $fieldLabel.attr( 'class', 'mw-label' );
-                       $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
-                       $fieldLabel.append( $fieldLabelText );
-                       $container.prepend( $fieldLabel );
-                       $oldContainer.replaceWith( $container );
-                       return $container;
-               }
-
-               if ( $root.find( '.mw-chosen' ).length ) {
-                       mw.loader.using( 'jquery.chosen', function () {
-                               $root.find( '.mw-chosen' ).each( function () {
-                                       var type = this.nodeName.toLowerCase(),
-                                               $converted = convertCheckboxesToMulti( $( this ), type );
-                                       $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
-                               } );
-                       } );
-               }
-
-               $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
-               if ( $matrixTooltips.length ) {
-                       mw.loader.using( 'jquery.tipsy', function () {
-                               $matrixTooltips.tipsy( { gravity: 's' } );
-                       } );
-               }
-
-               // Set up autocomplete fields
-               $autocomplete = $root.find( '.mw-htmlform-autocomplete' );
-               if ( $autocomplete.length ) {
-                       mw.loader.using( 'jquery.suggestions', function () {
-                               $autocomplete.suggestions( {
-                                       fetch: function ( val ) {
-                                               var $el = $( this );
-                                               $el.suggestions( 'suggestions',
-                                                       $.grep( $el.data( 'autocomplete' ), function ( v ) {
-                                                               return v.indexOf( val ) === 0;
-                                                       } )
-                                               );
-                                       }
-                               } );
-                       } );
-               }
-
-               // Add/remove cloner clones without having to resubmit the form
-               $root.find( '.mw-htmlform-cloner-delete-button' ).filter( ':input' ).click( function ( ev ) {
-                       ev.preventDefault();
-                       $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
-               } );
-
-               $root.find( '.mw-htmlform-cloner-create-button' ).filter( ':input' ).click( function ( ev ) {
-                       var $ul, $li, html;
-
-                       ev.preventDefault();
-
-                       $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
-
-                       html = $ul.data( 'template' ).replace(
-                               new RegExp( mw.RegExp.escape( $ul.data( 'uniqueId' ) ), 'g' ),
-                               'clone' + ( ++cloneCounter )
-                       );
-
-                       $li = $( '<li>' )
-                               .addClass( 'mw-htmlform-cloner-li' )
-                               .html( html )
-                               .appendTo( $ul );
-
-                       enhance( $li );
-               } );
-
-               mw.hook( 'htmlform.enhance' ).fire( $root );
-
-       }
-
-       $( function () {
-               enhance( $( document ) );
-       } );
-
-       /**
-        * @class jQuery
-        * @mixins jQuery.plugin.htmlform
-        */
-}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/mediawiki.htmlform.ooui.css b/resources/src/mediawiki/mediawiki.htmlform.ooui.css
deleted file mode 100644 (file)
index a9e75d7..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/* OOUIHTMLForm styles */
-
-.mw-htmlform-ooui-wrapper {
-       margin: 1em 0;
-}
-
-.mw-htmlform-ooui .mw-htmlform-submit-buttons {
-       margin-top: 1em;
-}
-
-.mw-htmlform-ooui .mw-htmlform-field-HTMLCheckMatrix,
-.mw-htmlform-ooui .mw-htmlform-matrix,
-.mw-htmlform-ooui .mw-htmlform-matrix tr {
-       width: 100%;
-}
-
-.mw-htmlform-ooui .mw-htmlform-matrix tr td.first {
-       margin-right: 5%;
-       width: 39%;
-}
-
-/* Flatlist styling for PHP widgets... */
-.mw-htmlform-flatlist .oo-ui-fieldLayout-align-inline,
-/* ...and for JS widgets */
-.mw-htmlform-flatlist .oo-ui-optionWidget,
-.mw-htmlform-flatlist .oo-ui-multioptionWidget {
-       display: inline-block;
-       margin-right: 1em;
-}
index 11784fb..491564a 100644 (file)
                         * State machine:
                         *
                         * - `registered`:
-                        *    The module is known to the system but not yet requested.
+                        *    The module is known to the system but not yet required.
                         *    Meta data is registered via mw.loader#register. Calls to that method are
                         *    generated server-side by the startup module.
                         * - `loading`:
-                        *    The module is requested through mw.loader (either directly or as dependency of
-                        *    another module). The client will be fetching module contents from the server.
+                        *    The module was required through mw.loader (either directly or as dependency of
+                        *    another module). The client will fetch module contents from the server.
                         *    The contents are then stashed in the registry via mw.loader#implement.
                         * - `loaded`:
-                        *    The module has been requested from the server and stashed via mw.loader#implement.
-                        *    If the module has no more dependencies in-fight, the module will be executed
-                        *    right away. Otherwise execution is deferred, controlled via #handlePending.
+                        *    The module has been loaded from the server and stashed via mw.loader#implement.
+                        *    If the module has no more dependencies in-flight, the module will be executed
+                        *    immediately. Otherwise execution is deferred, controlled via #handlePending.
                         * - `executing`:
                         *    The module is being executed.
                         * - `ready`:
                                //
                                sources = {},
 
-                               // List of modules which will be loaded as when ready
-                               batch = [],
-
-                               // Pending queueModuleScript() requests
+                               // For queueModuleScript()
                                handlingPendingRequests = false,
                                pendingRequests = [],
 
                                /**
                                 * List of callback jobs waiting for modules to be ready.
                                 *
-                                * Jobs are created by #request() and run by #handlePending().
+                                * Jobs are created by #enqueue() and run by #handlePending().
                                 *
                                 * Typically when a job is created for a module, the job's dependencies contain
-                                * both the module being requested and all its recursive dependencies.
+                                * both the required module and all its recursive dependencies.
                                 *
                                 * Format:
                                 *
                        }
 
                        /**
-                        * Adds all dependencies to the queue with optional callbacks to be run
-                        * when the dependencies are ready or fail
+                        * Add one or more modules to the module load queue.
+                        *
+                        * See also #work().
                         *
                         * @private
                         * @param {string|string[]} dependencies Module name or array of string module names
                         * @param {Function} [ready] Callback to execute when all dependencies are ready
                         * @param {Function} [error] Callback to execute when any dependency fails
                         */
-                       function request( dependencies, ready, error ) {
+                       function enqueue( dependencies, ready, error ) {
                                // Allow calling by single module name
                                if ( typeof dependencies === 'string' ) {
                                        dependencies = [ dependencies ];
                        }
 
                        /**
-                        * Load modules from load.php
+                        * Make a network request to load modules from the server.
                         *
                         * @private
                         * @param {Object} moduleMap Module map, see #buildModulesString
                         * @param {string} sourceLoadScript URL of load.php
                         */
                        function doRequest( moduleMap, currReqBase, sourceLoadScript ) {
-                               var request = $.extend(
+                               var query = $.extend(
                                        { modules: buildModulesString( moduleMap ) },
                                        currReqBase
                                );
-                               request = sortQuery( request );
-                               addScript( sourceLoadScript + '?' + $.param( request ) );
+                               query = sortQuery( query );
+                               addScript( sourceLoadScript + '?' + $.param( query ) );
                        }
 
                        /**
                                } );
                        }
 
+                       /**
+                        * Create network requests for a batch of modules.
+                        *
+                        * This is an internal method for #work(). This must not be called directly
+                        * unless the modules are already registered, and no request is in progress,
+                        * and the module state has already been set to `loading`.
+                        *
+                        * @private
+                        * @param {string[]} batch
+                        */
+                       function batchRequest( batch ) {
+                               var reqBase, splits, maxQueryLength, b, bSource, bGroup, bSourceGroup,
+                                       source, group, i, modules, sourceLoadScript,
+                                       currReqBase, currReqBaseLength, moduleMap, l,
+                                       lastDotIndex, prefix, suffix, bytesAdded;
+
+                               if ( !batch.length ) {
+                                       return;
+                               }
+
+                               // Always order modules alphabetically to help reduce cache
+                               // misses for otherwise identical content.
+                               batch.sort();
+
+                               // Build a list of query parameters common to all requests
+                               reqBase = {
+                                       skin: mw.config.get( 'skin' ),
+                                       lang: mw.config.get( 'wgUserLanguage' ),
+                                       debug: mw.config.get( 'debug' )
+                               };
+                               maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', 2000 );
+
+                               // Split module list by source and by group.
+                               splits = {};
+                               for ( b = 0; b < batch.length; b++ ) {
+                                       bSource = registry[ batch[ b ] ].source;
+                                       bGroup = registry[ batch[ b ] ].group;
+                                       if ( !hasOwn.call( splits, bSource ) ) {
+                                               splits[ bSource ] = {};
+                                       }
+                                       if ( !hasOwn.call( splits[ bSource ], bGroup ) ) {
+                                               splits[ bSource ][ bGroup ] = [];
+                                       }
+                                       bSourceGroup = splits[ bSource ][ bGroup ];
+                                       bSourceGroup.push( batch[ b ] );
+                               }
+
+                               for ( source in splits ) {
+
+                                       sourceLoadScript = sources[ source ];
+
+                                       for ( group in splits[ source ] ) {
+
+                                               // Cache access to currently selected list of
+                                               // modules for this group from this source.
+                                               modules = splits[ source ][ group ];
+
+                                               currReqBase = $.extend( {
+                                                       version: getCombinedVersion( modules )
+                                               }, reqBase );
+                                               // For user modules append a user name to the query string.
+                                               if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
+                                                       currReqBase.user = mw.config.get( 'wgUserName' );
+                                               }
+                                               currReqBaseLength = $.param( currReqBase ).length;
+                                               // We may need to split up the request to honor the query string length limit,
+                                               // so build it piece by piece.
+                                               l = currReqBaseLength + 9; // '&modules='.length == 9
+
+                                               moduleMap = {}; // { prefix: [ suffixes ] }
+
+                                               for ( i = 0; i < modules.length; i++ ) {
+                                                       // Determine how many bytes this module would add to the query string
+                                                       lastDotIndex = modules[ i ].lastIndexOf( '.' );
+
+                                                       // If lastDotIndex is -1, substr() returns an empty string
+                                                       prefix = modules[ i ].substr( 0, lastDotIndex );
+                                                       suffix = modules[ i ].slice( lastDotIndex + 1 );
+
+                                                       bytesAdded = hasOwn.call( moduleMap, prefix )
+                                                               ? suffix.length + 3 // '%2C'.length == 3
+                                                               : modules[ i ].length + 3; // '%7C'.length == 3
+
+                                                       // If the url would become too long, create a new one,
+                                                       // but don't create empty requests
+                                                       if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
+                                                               // This url would become too long, create a new one, and start the old one
+                                                               doRequest( moduleMap, currReqBase, sourceLoadScript );
+                                                               moduleMap = {};
+                                                               l = currReqBaseLength + 9;
+                                                               mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
+                                                       }
+                                                       if ( !hasOwn.call( moduleMap, prefix ) ) {
+                                                               moduleMap[ prefix ] = [];
+                                                       }
+                                                       moduleMap[ prefix ].push( suffix );
+                                                       l += bytesAdded;
+                                               }
+                                               // If there's anything left in moduleMap, request that too
+                                               if ( !$.isEmptyObject( moduleMap ) ) {
+                                                       doRequest( moduleMap, currReqBase, sourceLoadScript );
+                                               }
+                                       }
+                               }
+                       }
+
                        /* Public Members */
                        return {
                                /**
                                addStyleTag: newStyleTag,
 
                                /**
-                                * Batch-request queued dependencies from the server.
+                                * Start loading of all queued module dependencies.
                                 *
                                 * @protected
                                 */
                                work: function () {
-                                       var     reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
-                                               source, concatSource, origBatch, group, i, modules, sourceLoadScript,
-                                               currReqBase, currReqBaseLength, moduleMap, l,
-                                               lastDotIndex, prefix, suffix, bytesAdded;
-
-                                       // Build a list of request parameters common to all requests.
-                                       reqBase = {
-                                               skin: mw.config.get( 'skin' ),
-                                               lang: mw.config.get( 'wgUserLanguage' ),
-                                               debug: mw.config.get( 'debug' )
-                                       };
-                                       // Split module batch by source and by group.
-                                       splits = {};
-                                       maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', 2000 );
+                                       var q, batch, concatSource, origBatch;
+
+                                       batch = [];
 
                                        // Appends a list of modules from the queue to the batch
                                        for ( q = 0; q < queue.length; q++ ) {
-                                               // Only request modules which are registered
+                                               // Only load modules which are registered
                                                if ( hasOwn.call( registry, queue[ q ] ) && registry[ queue[ q ] ].state === 'registered' ) {
                                                        // Prevent duplicate entries
                                                        if ( $.inArray( queue[ q ], batch ) === -1 ) {
                                                }
                                        }
 
-                                       // Early exit if there's nothing to load...
-                                       if ( !batch.length ) {
-                                               return;
-                                       }
-
-                                       // The queue has been processed into the batch, clear up the queue.
+                                       // Now that the queue has been processed into a batch, clear up the queue.
+                                       // This MUST happen before we initiate any network request. Else it's possible
+                                       // that a script will be locally cached, instantly load, and work the queue
+                                       // again; all before we've cleared it causing each request to include modules
+                                       // which are already loaded.
                                        queue = [];
 
-                                       // Always order modules alphabetically to help reduce cache
-                                       // misses for otherwise identical content.
-                                       batch.sort();
-
-                                       // Split batch by source and by group.
-                                       for ( b = 0; b < batch.length; b++ ) {
-                                               bSource = registry[ batch[ b ] ].source;
-                                               bGroup = registry[ batch[ b ] ].group;
-                                               if ( !hasOwn.call( splits, bSource ) ) {
-                                                       splits[ bSource ] = {};
-                                               }
-                                               if ( !hasOwn.call( splits[ bSource ], bGroup ) ) {
-                                                       splits[ bSource ][ bGroup ] = [];
-                                               }
-                                               bSourceGroup = splits[ bSource ][ bGroup ];
-                                               bSourceGroup.push( batch[ b ] );
-                                       }
-
-                                       // Clear the batch - this MUST happen before we append any
-                                       // script elements to the body or it's possible that a script
-                                       // will be locally cached, instantly load, and work the batch
-                                       // again, all before we've cleared it causing each request to
-                                       // include modules which are already loaded.
-                                       batch = [];
-
-                                       for ( source in splits ) {
-
-                                               sourceLoadScript = sources[ source ];
-
-                                               for ( group in splits[ source ] ) {
-
-                                                       // Cache access to currently selected list of
-                                                       // modules for this group from this source.
-                                                       modules = splits[ source ][ group ];
-
-                                                       currReqBase = $.extend( {
-                                                               version: getCombinedVersion( modules )
-                                                       }, reqBase );
-                                                       // For user modules append a user name to the request.
-                                                       if ( group === 'user' && mw.config.get( 'wgUserName' ) !== null ) {
-                                                               currReqBase.user = mw.config.get( 'wgUserName' );
-                                                       }
-                                                       currReqBaseLength = $.param( currReqBase ).length;
-                                                       // We may need to split up the request to honor the query string length limit,
-                                                       // so build it piece by piece.
-                                                       l = currReqBaseLength + 9; // '&modules='.length == 9
-
-                                                       moduleMap = {}; // { prefix: [ suffixes ] }
-
-                                                       for ( i = 0; i < modules.length; i++ ) {
-                                                               // Determine how many bytes this module would add to the query string
-                                                               lastDotIndex = modules[ i ].lastIndexOf( '.' );
-
-                                                               // If lastDotIndex is -1, substr() returns an empty string
-                                                               prefix = modules[ i ].substr( 0, lastDotIndex );
-                                                               suffix = modules[ i ].slice( lastDotIndex + 1 );
-
-                                                               bytesAdded = hasOwn.call( moduleMap, prefix )
-                                                                       ? suffix.length + 3 // '%2C'.length == 3
-                                                                       : modules[ i ].length + 3; // '%7C'.length == 3
-
-                                                               // If the request would become too long, create a new one,
-                                                               // but don't create empty requests
-                                                               if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
-                                                                       // This request would become too long, create a new one
-                                                                       // and fire off the old one
-                                                                       doRequest( moduleMap, currReqBase, sourceLoadScript );
-                                                                       moduleMap = {};
-                                                                       l = currReqBaseLength + 9;
-                                                                       mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
-                                                               }
-                                                               if ( !hasOwn.call( moduleMap, prefix ) ) {
-                                                                       moduleMap[ prefix ] = [];
-                                                               }
-                                                               moduleMap[ prefix ].push( suffix );
-                                                               l += bytesAdded;
-                                                       }
-                                                       // If there's anything left in moduleMap, request that too
-                                                       if ( !$.isEmptyObject( moduleMap ) ) {
-                                                               doRequest( moduleMap, currReqBase, sourceLoadScript );
-                                                       }
-                                               }
-                                       }
+                                       batchRequest( batch );
                                },
 
                                /**
                                /**
                                 * Implement a module given the components that make up the module.
                                 *
-                                * When #load or #using requests one or more modules, the server
+                                * When #load() or #using() requests one or more modules, the server
                                 * response contain calls to this function.
                                 *
                                 * @param {string} module Name of module
                                                        dependencies
                                                );
                                        } else {
-                                               // Not all dependencies are ready: queue up a request
-                                               request( dependencies, function () {
+                                               // Not all dependencies are ready, add to the load queue
+                                               enqueue( dependencies, function () {
                                                        deferred.resolve( mw.loader.require );
                                                }, deferred.reject );
                                        }
                                        if ( allReady( filtered ) || anyFailed( filtered ) ) {
                                                return;
                                        }
-                                       // Since some modules are not yet ready, queue up a request.
-                                       request( filtered, undefined, undefined );
+                                       // Some modules are not yet ready, add to module load queue.
+                                       enqueue( filtered, undefined, undefined );
                                },
 
                                /**
index 3b779d1..d228f3e 100644 (file)
@@ -36,7 +36,7 @@
 
        // Things outside the wikipage content
        $( function () {
-               var $nodes, $oouiNodes;
+               var $nodes;
 
                if ( !supportsPlaceholder ) {
                        // Exclude content to avoid hitting it twice for the (first) wikipage content
                // Add accesskey hints to the tooltips
                $( '[accesskey]' ).updateTooltipAccessKeys();
 
-               // Infuse OOUI widgets, if any are present
-               $oouiNodes = $( '[data-ooui]' );
-               if ( $oouiNodes.length ) {
-                       // FIXME: We should only load the widgets that are being infused
-                       mw.loader.using( [
-                               'mediawiki.widgets',
-                               'mediawiki.widgets.UserInputWidget',
-                               'mediawiki.widgets.SearchInputWidget'
-                       ] ).done( function () {
-                               $oouiNodes.each( function () {
-                                       OO.ui.infuse( this );
-                               } );
-                       } );
-               }
-
                $nodes = $( '.catlinks[data-mw="interface"]' );
                if ( $nodes.length ) {
                        /**
index d6d2b29..c7bbc62 100644 (file)
@@ -27140,3 +27140,12 @@ Thumbnail output
 </div>
 </div>
 !! end
+
+!! test
+unclosed internal link XSS (T137264)
+!! wikitext
+[[#%3Cscript%3Ealert(1)%3C/script%3E|
+!! html
+<p>[[#&lt;script&gt;alert(1)&lt;/script&gt;|
+</p>
+!! end
index 7fe8055..a01cc6b 100644 (file)
@@ -5,6 +5,10 @@
  */
 class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
 
+       /**
+        * @covers CachedBagOStuff::__construct
+        * @covers CachedBagOStuff::doGet
+        */
        public function testGetFromBackend() {
                $backend = new HashBagOStuff;
                $cache = new CachedBagOStuff( $backend );
@@ -16,6 +20,10 @@ class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
                $this->assertEquals( 'bar', $cache->get( 'foo' ), 'cached' );
        }
 
+       /**
+        * @covers CachedBagOStuff::set
+        * @covers CachedBagOStuff::delete
+        */
        public function testSetAndDelete() {
                $backend = new HashBagOStuff;
                $cache = new CachedBagOStuff( $backend );
@@ -30,6 +38,10 @@ class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
                }
        }
 
+       /**
+        * @covers CachedBagOStuff::set
+        * @covers CachedBagOStuff::delete
+        */
        public function testWriteCacheOnly() {
                $backend = new HashBagOStuff;
                $cache = new CachedBagOStuff( $backend );
@@ -50,6 +62,9 @@ class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
                $this->assertEquals( 'old', $cache->get( 'foo' ) ); // Reloaded from backend
        }
 
+       /**
+        * @covers CachedBagOStuff::doGet
+        */
        public function testCacheBackendMisses() {
                $backend = new HashBagOStuff;
                $cache = new CachedBagOStuff( $backend );
index fce09ae..c4db0cf 100644 (file)
@@ -5,6 +5,9 @@
  */
 class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
 
+       /**
+        * @covers HashBagOStuff::delete
+        */
        public function testDelete() {
                $cache = new HashBagOStuff();
                for ( $i = 0; $i < 10; $i++ ) {
@@ -15,6 +18,9 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
                }
        }
 
+       /**
+        * @covers HashBagOStuff::clear
+        */
        public function testClear() {
                $cache = new HashBagOStuff();
                for ( $i = 0; $i < 10; $i++ ) {
@@ -27,6 +33,10 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
                }
        }
 
+       /**
+        * @covers HashBagOStuff::doGet
+        * @covers HashBagOStuff::expire
+        */
        public function testExpire() {
                $cache = new HashBagOStuff();
                $cacheInternal = TestingAccessWrapper::newFromObject( $cache );
@@ -45,6 +55,9 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
 
        /**
         * Ensure maxKeys eviction prefers keeping new keys.
+        *
+        * @covers HashBagOStuff::__construct
+        * @covers HashBagOStuff::set
         */
        public function testEvictionAdd() {
                $cache = new HashBagOStuff( [ 'maxKeys' => 10 ] );
@@ -62,6 +75,9 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
        /**
         * Ensure maxKeys eviction prefers recently set keys
         * even if the keys pre-exist.
+        *
+        * @covers HashBagOStuff::__construct
+        * @covers HashBagOStuff::set
         */
        public function testEvictionSet() {
                $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] );
@@ -85,6 +101,10 @@ class HashBagOStuffTest extends PHPUnit_Framework_TestCase {
 
        /**
         * Ensure maxKeys eviction prefers recently retrieved keys (LRU).
+        *
+        * @covers HashBagOStuff::__construct
+        * @covers HashBagOStuff::doGet
+        * @covers HashBagOStuff::hasKey
         */
        public function testEvictionGet() {
                $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] );
index 6df74d6..38d63e3 100644 (file)
@@ -23,6 +23,10 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
                ] );
        }
 
+       /**
+        * @covers MultiWriteBagOStuff::set
+        * @covers MultiWriteBagOStuff::doWrite
+        */
        public function testSetImmediate() {
                $key = wfRandomString();
                $value = wfRandomString();
@@ -34,6 +38,9 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
                $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' );
        }
 
+       /**
+        * @covers MultiWriteBagOStuff
+        */
        public function testSyncMerge() {
                $key = wfRandomString();
                $value = wfRandomString();
@@ -69,6 +76,9 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
                $dbw->commit();
        }
 
+       /**
+        * @covers MultiWriteBagOStuff::set
+        */
        public function testSetDelayed() {
                $key = wfRandomString();
                $value = wfRandomString();
index bd076ba..985554b 100644 (file)
@@ -92,6 +92,57 @@ class UserTest extends MediaWikiTestCase {
                $this->assertNotContains( 'nukeworld', $rights );
        }
 
+       /**
+        * @covers User::getRights
+        */
+       public function testUserGetRightsHooks() {
+               $user = new User;
+               $user->addGroup( 'unittesters' );
+               $user->addGroup( 'testwriters' );
+               $userWrapper = TestingAccessWrapper::newFromObject( $user );
+
+               $rights = $user->getRights();
+               $this->assertContains( 'test', $rights, 'sanity check' );
+               $this->assertContains( 'runtest', $rights, 'sanity check' );
+               $this->assertContains( 'writetest', $rights, 'sanity check' );
+               $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
+
+               // Add a hook manipluating the rights
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+                       $rights[] = 'nukeworld';
+                       $rights = array_diff( $rights, [ 'writetest' ] );
+               } ] ] );
+
+               $userWrapper->mRights = null;
+               $rights = $user->getRights();
+               $this->assertContains( 'test', $rights );
+               $this->assertContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertContains( 'nukeworld', $rights );
+
+               // Add a Session that limits rights
+               $mock = $this->getMockBuilder( stdclass::class )
+                       ->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
+                       ->getMock();
+               $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
+               $mock->method( 'getSessionId' )->willReturn(
+                       new MediaWiki\Session\SessionId( str_repeat( 'X', 32 ) )
+               );
+               $session = MediaWiki\Session\TestUtils::getDummySession( $mock );
+               $mockRequest = $this->getMockBuilder( FauxRequest::class )
+                       ->setMethods( [ 'getSession' ] )
+                       ->getMock();
+               $mockRequest->method( 'getSession' )->willReturn( $session );
+               $userWrapper->mRequest = $mockRequest;
+
+               $userWrapper->mRights = null;
+               $rights = $user->getRights();
+               $this->assertContains( 'test', $rights );
+               $this->assertNotContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+       }
+
        /**
         * @dataProvider provideGetGroupsWithPermission
         * @covers User::getGroupsWithPermission