Merge "Type hint array for HTMLFormFieldCloner::getInputHTMLForKey()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 18 Aug 2016 20:40:46 +0000 (20:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 18 Aug 2016 20:40:46 +0000 (20:40 +0000)
164 files changed:
Gemfile
Gemfile.lock
RELEASE-NOTES-1.28
autoload.php
composer.json
includes/DefaultSettings.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/MovePage.php
includes/OutputPage.php
includes/StubObject.php
includes/WatchedItemStore.php
includes/api/ApiBase.php
includes/api/ApiLogin.php
includes/api/ApiMain.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiUpload.php
includes/api/i18n/fr.json
includes/api/i18n/he.json
includes/api/i18n/hu.json
includes/api/i18n/zh-hans.json
includes/auth/AuthManager.php
includes/auth/AuthenticationRequest.php
includes/auth/PrimaryAuthenticationProvider.php
includes/changes/RecentChange.php
includes/content/CodeContentHandler.php
includes/content/ContentHandler.php
includes/content/TextContent.php
includes/content/TextContentHandler.php
includes/content/WikitextContentHandler.php
includes/db/DBConnRef.php
includes/db/Database.php
includes/db/DatabaseMysqlBase.php
includes/db/DatabasePostgres.php
includes/db/DatabaseSqlite.php
includes/db/IDatabase.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LBFactoryMulti.php
includes/db/loadbalancer/LBFactorySimple.php
includes/db/loadbalancer/LoadBalancer.php
includes/deferred/AtomicSectionUpdate.php
includes/deferred/AutoCommitUpdate.php
includes/deferred/DataUpdate.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksDeletionUpdate.php
includes/deferred/LinksUpdate.php
includes/deferred/MWCallableUpdate.php
includes/filebackend/lockmanager/DBLockManager.php
includes/filerepo/file/LocalFile.php
includes/installer/Installer.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/DeleteLinksJob.php
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/objectcache/SqlBagOStuff.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/parser/Preprocessor_DOM.php
includes/parser/Preprocessor_Hash.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderImageModule.php
includes/revisiondelete/RevDelList.php
includes/skins/SkinTemplate.php
includes/specials/SpecialConfirmemail.php
includes/specials/SpecialEmailInvalidate.php
includes/specials/SpecialTags.php
includes/upload/UploadBase.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromStash.php
includes/user/User.php
languages/Language.php
languages/i18n/ang.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gor.json
languages/i18n/got.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/ky.json
languages/i18n/lb.json
languages/i18n/mk.json
languages/i18n/mr.json
languages/i18n/nap.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/si.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/uz.json
languages/i18n/yo.json
languages/i18n/zh-hans.json
maintenance/Maintenance.php
maintenance/cleanupCaps.php
maintenance/findDeprecated.php
maintenance/hhvm/makeRepo.php [new file with mode: 0644]
maintenance/hhvm/run-server [new file with mode: 0755]
maintenance/hhvm/server.conf [new file with mode: 0644]
maintenance/hiphop/run-server [deleted file]
maintenance/hiphop/server.conf [deleted file]
maintenance/refreshImageMetadata.php
mw-config/overrides/README
resources/Resources.php
resources/ResourcesOOUI.php
resources/assets/licenses/README [new file with mode: 0644]
resources/lib/oojs-ui/i18n/ckb.json
resources/lib/oojs-ui/i18n/da.json
resources/lib/oojs-ui/i18n/diq.json
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.ui/components/icons.less
resources/src/mediawiki/mediawiki.raggett.css [deleted file]
resources/src/mediawiki/page/gallery-slideshow.js
resources/src/mediawiki/page/watch.js
resources/src/oojs-ui-styles-skip.js [deleted file]
tests/browser/environments.yml
tests/parser/parserTest.inc
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/TextContentTest.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/libs/ObjectFactoryTest.php
tests/phpunit/includes/search/SearchEngineTest.php
tests/phpunit/structure/ResourcesTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.test.js

diff --git a/Gemfile b/Gemfile
index 19d2f52..8a349bf 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,5 @@
 source 'https://rubygems.org'
 
-gem 'mediawiki_selenium', '~> 1.7', '>= 1.7.1'
+gem 'mediawiki_selenium', '~> 1.7', '>= 1.7.2'
 gem 'rake', '~> 11.1', '>= 11.1.1'
 gem 'rubocop', '~> 0.32.1', require: false
index 2d6e655..982619a 100644 (file)
@@ -17,16 +17,18 @@ GEM
       faker (>= 1.1.2)
       yml_reader (>= 0.6)
     diff-lcs (1.2.5)
-    domain_name (0.5.20160310)
+    domain_name (0.5.20160615)
       unf (>= 0.0.5, < 1.0.0)
-    faker (1.6.3)
+    faker (1.6.6)
       i18n (~> 0.5)
     faraday (0.9.2)
       multipart-post (>= 1.2, < 3)
     faraday-cookie_jar (0.0.6)
       faraday (>= 0.7.4)
       http-cookie (~> 1.0.0)
-    ffi (1.9.10)
+    faraday_middleware (0.10.0)
+      faraday (>= 0.7.4, < 0.10)
+    ffi (1.9.14)
     gherkin (2.12.2)
       multi_json (~> 1.3)
     headless (2.2.3)
@@ -34,14 +36,15 @@ GEM
       domain_name (~> 0.5)
     i18n (0.7.0)
     json (1.8.3)
-    mediawiki_api (0.6.0)
+    mediawiki_api (0.7.0)
       faraday (~> 0.9, >= 0.9.0)
       faraday-cookie_jar (~> 0.0, >= 0.0.6)
-    mediawiki_selenium (1.7.1)
+      faraday_middleware (~> 0.10, >= 0.10.0)
+    mediawiki_selenium (1.7.2)
       cucumber (~> 1.3, >= 1.3.20)
       headless (~> 2.0, >= 2.1.0)
       json (~> 1.8, >= 1.8.1)
-      mediawiki_api (~> 0.6, >= 0.6.0)
+      mediawiki_api (~> 0.7, >= 0.7.0)
       page-object (~> 1.0)
       rest-client (~> 1.6, >= 1.6.7)
       rspec-core (~> 2.14, >= 2.14.4)
@@ -53,7 +56,7 @@ GEM
     multi_test (0.1.2)
     multipart-post (2.0.0)
     netrc (0.11.0)
-    page-object (1.1.1)
+    page-object (1.2.0)
       page_navigation (>= 0.9)
       selenium-webdriver (>= 2.44.0)
       watir-webdriver (>= 0.6.11)
@@ -79,7 +82,7 @@ GEM
       ruby-progressbar (~> 1.4)
     ruby-progressbar (1.7.5)
     rubyzip (1.2.0)
-    selenium-webdriver (2.53.1)
+    selenium-webdriver (2.53.4)
       childprocess (~> 0.5)
       rubyzip (~> 1.0)
       websocket (~> 1.0)
@@ -88,7 +91,7 @@ GEM
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.2)
-    watir-webdriver (0.9.1)
+    watir-webdriver (0.9.3)
       selenium-webdriver (>= 2.46.2)
     websocket (1.2.3)
     yml_reader (0.7)
@@ -97,9 +100,6 @@ PLATFORMS
   ruby
 
 DEPENDENCIES
-  mediawiki_selenium (~> 1.7, >= 1.7.1)
+  mediawiki_selenium (~> 1.7, >= 1.7.2)
   rake (~> 11.1, >= 11.1.1)
   rubocop (~> 0.32.1)
-
-BUNDLED WITH
-   1.10.6
index 80166ad..f6c3530 100644 (file)
@@ -106,6 +106,11 @@ changes to languages because of Phabricator reports.
 * DifferenceEngine::generateDiffBody() was removed (deprecated since 1.21).
 * UploadBase::stashFileGetKey() and UploadBase::stashSession() were deprecated.
   Use ...->stashFile()->getFileKey() instead.
+* "Public domain" was removed as a wiki license option from the installer, in
+  favour of CC-0.
+* 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.
 
 == Compatibility ==
 
index f6edd94..fc6577e 100644 (file)
@@ -513,6 +513,7 @@ $wgAutoloadLocalClasses = [
        'GitInfo' => __DIR__ . '/includes/GitInfo.php',
        'GlobalDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
        'GlobalVarConfig' => __DIR__ . '/includes/config/GlobalVarConfig.php',
+       'HHVMMakeRepo' => __DIR__ . '/maintenance/hhvm/makeRepo.php',
        'HTMLApiField' => __DIR__ . '/includes/htmlform/fields/HTMLApiField.php',
        'HTMLAutoCompleteSelectField' => __DIR__ . '/includes/htmlform/fields/HTMLAutoCompleteSelectField.php',
        'HTMLButtonField' => __DIR__ . '/includes/htmlform/fields/HTMLButtonField.php',
index 9bd0fa1..956739d 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.7",
+               "oojs/oojs-ui": "0.17.8",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
@@ -48,7 +48,7 @@
                "justinrainbow/json-schema": "~1.6",
                "mediawiki/mediawiki-codesniffer": "0.7.2",
                "monolog/monolog": "~1.18.2",
-               "nikic/php-parser": "1.4.1",
+               "nikic/php-parser": "2.1.0",
                "nmred/kafka-php": "0.1.5",
                "phpunit/phpunit": "4.8.24",
                "wikimedia/avro": "1.7.7"
index a5d8c5b..4e08aa6 100644 (file)
@@ -5710,6 +5710,8 @@ $wgGrantPermissions['sendemail']['sendemail'] = true;
 
 $wgGrantPermissions['createaccount']['createaccount'] = true;
 
+$wgGrantPermissions['privateinfo']['viewmyprivateinfo'] = true;
+
 /**
  * @var Array Map of grants to their UI grouping
  * @since 1.27
@@ -5743,6 +5745,8 @@ $wgGrantPermissionGroups = [
        'createaccount'       => 'administration',
 
        'highvolume'          => 'high-volume',
+
+       'privateinfo'         => 'private-information',
 ];
 
 /**
index ee06993..9b862b9 100644 (file)
@@ -1474,7 +1474,7 @@ class EditPage {
 
                        case self::AS_CANNOT_USE_CUSTOM_MODEL:
                        case self::AS_PARSE_ERROR:
-                               $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
+                               $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
                                return true;
 
                        case self::AS_SUCCESS_NEW_ARTICLE:
@@ -1551,7 +1551,7 @@ class EditPage {
                                // is if an extension hook aborted from inside ArticleSave.
                                // Render the status object into $this->hookError
                                // FIXME this sucks, we should just use the Status object throughout
-                               $this->hookError = '<div class="error">' . $status->getWikiText() .
+                               $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
                                        '</div>';
                                return true;
                }
index 7117f4c..0d66908 100644 (file)
@@ -811,7 +811,7 @@ function wfUrlProtocolsWithoutProtRel() {
  * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
  *
  * @param string $url A URL to parse
- * @return string[] Bits of the URL in an associative array, per PHP docs
+ * @return string[]|bool Bits of the URL in an associative array, per PHP docs, false on failure
  */
 function wfParseUrl( $url ) {
        global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
index 70b6738..bc3305a 100644 (file)
@@ -256,7 +256,7 @@ class MovePage {
                $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
                $protected = $this->oldTitle->isProtected();
 
-               // Do the actual move
+               // Do the actual move; if this fails, it will throw an MWException(!)
                $nullRevision = $this->moveToInternal( $user, $this->newTitle, $reason, $createRedirect );
 
                // Refresh the sortkey for this row.  Be careful to avoid resetting
@@ -412,7 +412,7 @@ class MovePage {
         *
         * @fixme This was basically directly moved from Title, it should be split into smaller functions
         * @param User $user the User doing the move
-        * @param Title $nt The page to move to, which should be a redirect or nonexistent
+        * @param Title $nt The page to move to, which should be a redirect or non-existent
         * @param string $reason The reason for the move
         * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
         *   if the user has the suppressredirect right
@@ -430,6 +430,29 @@ class MovePage {
                        $logType = 'move';
                }
 
+               if ( $moveOverRedirect ) {
+                       $overwriteMessage = wfMessage(
+                                       'delete_and_move_reason',
+                                       $this->oldTitle->getPrefixedText()
+                               )->text();
+                       $newpage = WikiPage::factory( $nt );
+                       $errs = [];
+                       $status = $newpage->doDeleteArticleReal(
+                               $overwriteMessage,
+                               /* $suppress */ false,
+                               $nt->getArticleId(),
+                               /* $commit */ false,
+                               $errs,
+                               $user
+                       );
+
+                       if ( !$status->isGood() ) {
+                               throw new MWException( 'Failed to delete page-move revision: ' . $status );
+                       }
+
+                       $nt->resetArticleID( false );
+               }
+
                if ( $createRedirect ) {
                        if ( $this->oldTitle->getNamespace() == NS_CATEGORY
                                && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
@@ -484,19 +507,6 @@ class MovePage {
 
                $newpage = WikiPage::factory( $nt );
 
-               if ( $moveOverRedirect ) {
-                       $newid = $nt->getArticleID();
-                       $newcontent = $newpage->getContent();
-
-                       # Delete the old redirect. We don't save it to history since
-                       # by definition if we've got here it's rather uninteresting.
-                       # We have to remove it so that the next step doesn't trigger
-                       # a conflict on the unique namespace+title index...
-                       $dbw->delete( 'page', [ 'page_id' => $newid ], __METHOD__ );
-
-                       $newpage->doDeleteUpdates( $newid, $newcontent );
-               }
-
                # Save a null revision in the page's history notifying of the move
                $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user );
                if ( !is_object( $nullRevision ) ) {
@@ -542,9 +552,7 @@ class MovePage {
                        );
                }
 
-               if ( !$moveOverRedirect ) {
-                       WikiPage::onArticleCreate( $nt );
-               }
+               WikiPage::onArticleCreate( $nt );
 
                # Recreate the redirect, this time in the other direction.
                if ( $redirectContent ) {
index cc377bc..374e7af 100644 (file)
@@ -2671,7 +2671,7 @@ class OutputPage extends ContextSource {
                                'user.options',
                                'user.tokens',
                        ] );
-                       $rlClient = new ResourceLoaderClientHtml( $context );
+                       $rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
                        $rlClient->setConfig( $this->getJSVars() );
                        $rlClient->setModules( $this->getModules( /*filter*/ true ) );
                        $rlClient->setModuleStyles( $this->getModuleStyles( /*filter*/ true ) );
@@ -2790,9 +2790,12 @@ class OutputPage extends ContextSource {
         * @return string|WrappedStringList HTML
         */
        public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
+               // Apply 'target' and 'origin' filters
+               $modules = $this->filterModules( (array)$modules, null, $only );
+
                return ResourceLoaderClientHtml::makeLoad(
                        $this->getRlClientContext(),
-                       (array)$modules,
+                       $modules,
                        $only,
                        $extraQuery
                );
@@ -3810,9 +3813,6 @@ class OutputPage extends ContextSource {
                        'oojs-ui.styles.textures',
                        'mediawiki.widgets.styles',
                ] );
-               // Used by 'skipFunction' of the four 'oojs-ui.styles.*' modules. Please don't treat this as a
-               // public API or you'll be severely disappointed when T87871 is fixed and it disappears.
-               $this->addMeta( 'X-OOUI-PHP', '1' );
        }
 
        /**
index 0b4d048..0210ed9 100644 (file)
@@ -48,6 +48,9 @@ class StubObject {
        /** @var null|string */
        protected $class;
 
+       /** @var null|callable */
+       protected $factory;
+
        /** @var array */
        protected $params;
 
@@ -55,12 +58,17 @@ class StubObject {
         * Constructor.
         *
         * @param string $global Name of the global variable.
-        * @param string $class Name of the class of the real object.
+        * @param string|callable $class Name of the class of the real object
+        *                               or a factory function to call
         * @param array $params Parameters to pass to constructor of the real object.
         */
        public function __construct( $global = null, $class = null, $params = [] ) {
                $this->global = $global;
-               $this->class = $class;
+               if ( is_callable( $class ) ) {
+                       $this->factory = $class;
+               } else {
+                       $this->class = $class;
+               }
                $this->params = $params;
        }
 
@@ -110,8 +118,10 @@ class StubObject {
         * @return object
         */
        public function _newObject() {
-               return ObjectFactory::getObjectFromSpec( [
-                       'class' => $this->class,
+               $params = $this->factory
+                       ? [ 'factory' => $this->factory ]
+                       : [ 'class' => $this->class ];
+               return ObjectFactory::getObjectFromSpec( $params + [
                        'args' => $this->params,
                        'closure_expansion' => false,
                ] );
index 89ca50c..a13609b 100644 (file)
@@ -741,6 +741,8 @@ class WatchedItemStore implements StatsdAwareInterface {
                                        global $wgUpdateRowsPerQuery;
 
                                        $dbw = $this->getConnection( DB_MASTER );
+                                       $factory = wfGetLBFactory();
+                                       $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
 
                                        $watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery );
                                        foreach ( $watchersChunks as $watchersChunk ) {
@@ -754,14 +756,17 @@ class WatchedItemStore implements StatsdAwareInterface {
                                                        ], $fname
                                                );
                                                if ( count( $watchersChunks ) > 1 ) {
-                                                       $dbw->commit( __METHOD__, 'flush' );
-                                                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $dbw->getWikiID() ] );
+                                                       $factory->commitAndWaitForReplication(
+                                                               __METHOD__, $ticket, [ 'wiki' => $dbw->getWikiID() ]
+                                                       );
                                                }
                                        }
                                        $this->uncacheLinkTarget( $target );
 
                                        $this->reuseConnection( $dbw );
-                               }
+                               },
+                               DeferredUpdates::POSTSEND,
+                               $dbw
                        );
                }
 
index b45eacb..4a1a520 100644 (file)
@@ -2458,6 +2458,7 @@ abstract class ApiBase extends ContextSource {
 
                // Build map of extension directories to extension info
                if ( self::$extensionInfo === null ) {
+                       $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
                        self::$extensionInfo = [
                                realpath( __DIR__ ) ?: __DIR__ => [
                                        'path' => $IP,
@@ -2465,6 +2466,7 @@ abstract class ApiBase extends ContextSource {
                                        'license-name' => 'GPL-2.0+',
                                ],
                                realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
+                               realpath( $extDir ) ?: $extDir => null,
                        ];
                        $keep = [
                                'path' => null,
index 851252c..28937f7 100644 (file)
@@ -155,10 +155,14 @@ class ApiLogin extends ApiBase {
                                        $authRes = 'Failed';
                                        $message = $res->message;
                                        \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
-                                               ->info( __METHOD__ . ': Authentication failed: ' . $message->plain() );
+                                               ->info( __METHOD__ . ': Authentication failed: '
+                                               . $message->inLanguage( 'en' )->plain() );
                                        break;
 
                                default:
+                                       \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
+                                               ->info( __METHOD__ . ': Authentication failed due to unsupported response type: '
+                                               . $res->status, $this->getAuthenticationResponseLogData( $res ) );
                                        $authRes = 'Aborted';
                                        break;
                        }
@@ -273,4 +277,32 @@ class ApiLogin extends ApiBase {
        public function getHelpUrls() {
                return 'https://www.mediawiki.org/wiki/API:Login';
        }
+
+       /**
+        * Turns an AuthenticationResponse into a hash suitable for passing to Logger
+        * @param AuthenticationResponse $response
+        * @return array
+        */
+       protected function getAuthenticationResponseLogData( AuthenticationResponse $response ) {
+               $ret = [
+                       'status' => $response->status,
+               ];
+               if ( $response->message ) {
+                       $ret['message'] = $response->message->inLanguage( 'en' )->plain();
+               };
+               $reqs = [
+                       'neededRequests' => $response->neededRequests,
+                       'createRequest' => $response->createRequest,
+                       'linkRequest' => $response->linkRequest,
+               ];
+               foreach ( $reqs as $k => $v ) {
+                       if ( $v ) {
+                               $v = is_array( $v ) ? $v : [ $v ];
+                               $reqClasses = array_unique( array_map( 'get_class', $v ) );
+                               sort( $reqClasses );
+                               $ret[$k] = implode( ', ', $reqClasses );
+                       }
+               }
+               return $ret;
+       }
 }
index 0478027..565e829 100644 (file)
@@ -25,6 +25,8 @@
  * @defgroup API API
  */
 
+use MediaWiki\Logger\LoggerFactory;
+
 /**
  * This is the main API class, used for both external and internal processing.
  * When executed, it will create the requested formatter object,
@@ -206,7 +208,7 @@ class ApiMain extends ApiBase {
                                        $config->get( 'CrossSiteAJAXdomainExceptions' )
                                )
                        ) ) {
-                               MediaWiki\Logger\LoggerFactory::getInstance( 'cors' )->warning(
+                               LoggerFactory::getInstance( 'cors' )->warning(
                                        'Non-whitelisted CORS request with session cookies', [
                                                'origin' => $originHeader,
                                                'cookies' => $sessionCookies,
@@ -1453,6 +1455,7 @@ class ApiMain extends ApiBase {
        protected function setRequestExpectations( ApiBase $module ) {
                $limits = $this->getConfig()->get( 'TrxProfilerLimits' );
                $trxProfiler = Profiler::instance()->getTransactionProfiler();
+               $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
                if ( $this->getRequest()->hasSafeMethod() ) {
                        $trxProfiler->setExpectations( $limits['GET'], __METHOD__ );
                } elseif ( $this->getRequest()->wasPosted() && !$module->isWriteMode() ) {
index 3810e90..236fb9e 100644 (file)
@@ -53,6 +53,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                        'code' => 'lh',
                        'prefix' => 'pl',
                        'linktable' => 'pagelinks',
+                       'indexes' => [ 'pl_namespace', 'pl_backlinks_namespace' ],
                        'from_namespace' => true,
                        'showredirects' => true,
                ],
@@ -60,6 +61,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                        'code' => 'ti',
                        'prefix' => 'tl',
                        'linktable' => 'templatelinks',
+                       'indexes' => [ 'tl_namespace', 'tl_backlinks_namespace' ],
                        'from_namespace' => true,
                        'showredirects' => true,
                ],
@@ -67,6 +69,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                        'code' => 'fu',
                        'prefix' => 'il',
                        'linktable' => 'imagelinks',
+                       'indexes' => [ 'il_to', 'il_backlinks_namespace' ],
                        'from_namespace' => true,
                        'to_namespace' => NS_FILE,
                        'exampletitle' => 'File:Example.jpg',
@@ -249,6 +252,18 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                // Override any ORDER BY from above with what we calculated earlier.
                $this->addOption( 'ORDER BY', array_keys( $sortby ) );
 
+               // MySQL's optimizer chokes if we have too many values in "$bl_title IN
+               // (...)" and chooses the wrong index, so specify the correct index to
+               // use for the query. See T139056 for details.
+               if ( !empty( $settings['indexes'] ) ) {
+                       list( $idxNoFromNS, $idxWithFromNS ) = $settings['indexes'];
+                       if ( $params['namespace'] !== null && !empty( $settings['from_namespace'] ) ) {
+                               $this->addOption( 'USE INDEX', [ $settings['linktable'] => $idxWithFromNS ] );
+                       } else {
+                               $this->addOption( 'USE INDEX', [ $settings['linktable'] => $idxNoFromNS ] );
+                       }
+               }
+
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
 
                $res = $this->select( __METHOD__ );
index 89d9af8..fc2fd59 100644 (file)
@@ -240,7 +240,7 @@ class ApiUpload extends ApiBase {
                                        'offset' => $this->mUpload->getOffset(),
                                ];
 
-                               $this->dieUsage( $status->getWikiText( false, false, 'en' ), 'stashfailed', 0, $extradata );
+                               $this->dieStatusWithCode( $status, 'stashfailed', $extradata );
                        }
                }
 
@@ -271,7 +271,7 @@ class ApiUpload extends ApiBase {
                                                $filekey,
                                                [ 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status ]
                                        );
-                                       $this->dieUsage( $status->getWikiText( false, false, 'en' ), 'stashfailed' );
+                                       $this->dieStatusWithCode( $status, 'stashfailed' );
                                }
 
                                // The fully concatenated file has a new filekey. So remove
@@ -315,8 +315,9 @@ class ApiUpload extends ApiBase {
         * @return string|null File key
         */
        private function performStash( $failureMode, &$data = null ) {
+               $isPartial = (bool)$this->mParams['chunk'];
                try {
-                       $status = $this->mUpload->tryStashFile( $this->getUser() );
+                       $status = $this->mUpload->tryStashFile( $this->getUser(), $isPartial );
 
                        if ( $status->isGood() && !$status->getValue() ) {
                                // Not actually a 'good' status...
@@ -381,6 +382,28 @@ class ApiUpload extends ApiBase {
                $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
        }
 
+       /**
+        * Like dieStatus(), but always uses $overrideCode for the error code, unless the code comes from
+        * IApiMessage.
+        *
+        * @param Status $status
+        * @param string $overrideCode Error code to use if there isn't one from IApiMessage
+        * @param array|null $moreExtraData
+        * @throws UsageException
+        */
+       public function dieStatusWithCode( $status, $overrideCode, $moreExtraData = null ) {
+               $extraData = null;
+               list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
+               $errors = $status->getErrorsByType( 'error' ) ?: $status->getErrorsByType( 'warning' );
+               if ( !( $errors[0]['message'] instanceof IApiMessage ) ) {
+                       $code = $overrideCode;
+               }
+               if ( $moreExtraData ) {
+                       $extraData += $moreExtraData;
+               }
+               $this->dieUsage( $msg, $code, 0, $extraData );
+       }
+
        /**
         * Select an upload module and set it to mUpload. Dies on failure. If the
         * request was a status request and not a true upload, returns false;
@@ -403,7 +426,7 @@ class ApiUpload extends ApiBase {
                        if ( !$progress ) {
                                $this->dieUsage( 'No result in status data', 'missingresult' );
                        } elseif ( !$progress['status']->isGood() ) {
-                               $this->dieUsage( $progress['status']->getWikiText( false, false, 'en' ), 'stashfailed' );
+                               $this->dieStatusWithCode( $progress['status'], 'stashfailed' );
                        }
                        if ( isset( $progress['status']->value['verification'] ) ) {
                                $this->checkVerification( $progress['status']->value['verification'] );
@@ -421,7 +444,7 @@ class ApiUpload extends ApiBase {
 
                if ( $this->mParams['chunk'] ) {
                        // Chunk upload
-                       $this->mUpload = new UploadFromChunks();
+                       $this->mUpload = new UploadFromChunks( $this->getUser() );
                        if ( isset( $this->mParams['filekey'] ) ) {
                                if ( $this->mParams['offset'] === 0 ) {
                                        $this->dieUsage( 'Cannot supply a filekey when offset is 0', 'badparams' );
index b34131b..4b42964 100644 (file)
@@ -25,7 +25,8 @@
                        "Lbayle",
                        "Verdy p",
                        "Yasten",
-                       "Trial"
+                       "Trial",
+                       "Pols12"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].",
        "apihelp-stashedit-param-section": "Numéro de section. <kbd>0</kbd> pour la section du haut, <kbd>new</kbd> pour une nouvelle section.",
        "apihelp-stashedit-param-sectiontitle": "Le titre pour une nouvelle section.",
        "apihelp-stashedit-param-text": "Contenu de la page.",
-       "apihelp-stashedit-param-stashedtexthash": "Hachage du contenu de la page d’une réserve antérieure à utiliser à la place.",
+       "apihelp-stashedit-param-stashedtexthash": "Empreinte du contenu de la page venant d’une réserve préalable à utiliser à la place.",
        "apihelp-stashedit-param-contentmodel": "Modèle de contenu du nouveau contenu.",
        "apihelp-stashedit-param-contentformat": "Format de sérialisation de contenu utilisé pour le texte saisi.",
        "apihelp-stashedit-param-baserevid": "ID de révision de la révision de base.",
index 08a27d6..a28cbda 100644 (file)
        "apihelp-linkaccount-example-link": "תחילת תהליך הקישור לחשבון מ־<kbd>Example</kbd>.",
        "apihelp-login-description": "להיכנס ולקבל עוגיות אימות.\n\nהפעולה הזאת צריכה לשמש רק בשילוב [[Special:BotPasswords]]; שימוש לכניסה לחשבון ראשי מיושן ועשוי להיכשל ללא אזהרה. כדי להיכנס בבטחה לחשבון הראשי, יש להשתמש ב־<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
        "apihelp-login-description-nobotpasswords": "להיכנס ולקבל עוגיות אימות.\n\nהפעולה הזאת מיושנת ועשויה להיכשל ללא אזהרה. כדי להיכנס בבטחה, יש להשתמש ב־<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
-       "apihelp-login-description-nonauthmanager": "להיכנס ולקבל עוגיות אימות.\n\nבמקרה של כניסה מוצלחת, העוגיות המקוננות תיכללנה בכותרות תשובות ה־HTTP. במקרה של כניסה כושלת, הניסיונות הבאים יוגבלו למספר ניסויי ניחוש הססמה האוטומטיים.",
        "apihelp-login-param-name": "שם משתמש.",
        "apihelp-login-param-password": "ססמה.",
        "apihelp-login-param-domain": "שם מתחם (רשות).",
        "apihelp-stashedit-param-section": "מספר הפסקה. <kbd>0</kbd> עבור הפסקה הראשונה, <kbd>new</kbd> עבור פסקה חדשה.",
        "apihelp-stashedit-param-sectiontitle": "כותרת הפסקה החדשה.",
        "apihelp-stashedit-param-text": "תוכן הדף.",
+       "apihelp-stashedit-param-stashedtexthash": "גיבוב של תוכן דף מסליק קודם שישמש במקום זה.",
        "apihelp-stashedit-param-contentmodel": "מודל התוכן של התוכן החדש.",
        "apihelp-stashedit-param-contentformat": "תסדיר הסדרת תוכן עבור טקסט הקלט.",
        "apihelp-stashedit-param-baserevid": "מזהה גסה של גרסת הבסיס.",
index 7334fab..e0920f6 100644 (file)
@@ -6,7 +6,8 @@
                        "Tacsipacsi",
                        "ViDam",
                        "Macofe",
-                       "Wolf Rex"
+                       "Wolf Rex",
+                       "Dj"
                ]
        },
        "apihelp-main-param-action": "Milyen műveletet hajtson végre.",
@@ -42,6 +43,7 @@
        "apihelp-feedrecentchanges-param-hidepatrolled": "Ellenőrzött változtatások elrejtése.",
        "apihelp-login-param-name": "Szerkesztőnév.",
        "apihelp-login-param-password": "Jelszó.",
+       "apihelp-login-param-domain": "Tartomány (opcionális)",
        "apihelp-login-example-login": "Bejelentkezés.",
        "apihelp-logout-example-logout": "Aktuális felhasználó kijelentkeztetése.",
        "apihelp-mergehistory-description": "Laptörténetek egyesítése",
index 037aff4..860b7a7 100644 (file)
        "apihelp-query+search-paramvalue-prop-snippet": "Adds a parsed snippet of the page.",
        "apihelp-query+search-paramvalue-prop-titlesnippet": "Adds a parsed snippet of the page title.",
        "apihelp-query+search-paramvalue-prop-redirectsnippet": "添加被解析的重定向标题的片段。",
-       "apihelp-query+search-paramvalue-prop-redirecttitle": "Adds the title of the matching redirect.",
+       "apihelp-query+search-paramvalue-prop-redirecttitle": "添加匹配的重定向的标题。",
        "apihelp-query+search-paramvalue-prop-sectionsnippet": "Adds a parsed snippet of the matching section title.",
        "apihelp-query+search-paramvalue-prop-sectiontitle": "Adds the title of the matching section.",
        "apihelp-query+search-paramvalue-prop-categorysnippet": "Adds a parsed snippet of the matching category.",
        "apihelp-query+search-param-enablerewrites": "启用内部查询重写。一些搜索后端可以重写查询到它认为会给出更好结果的地方,例如纠正拼写错误。",
        "apihelp-query+search-example-simple": "搜索<kbd>meaning</kbd>。",
        "apihelp-query+search-example-text": "搜索文本<kbd>meaning</kbd>。",
-       "apihelp-query+search-example-generator": "è\8e·å¾\97有关搜索<kbd>meaning</kbd>返回页面的页面信息。",
+       "apihelp-query+search-example-generator": "è\8e·å\8f\96有关搜索<kbd>meaning</kbd>返回页面的页面信息。",
        "apihelp-query+siteinfo-description": "返回有关网站的一般信息。",
        "apihelp-query+siteinfo-param-prop": "要获取的信息:",
        "apihelp-query+siteinfo-paramvalue-prop-general": "全部系统信息。",
index eab5068..acdc01b 100644 (file)
@@ -1605,9 +1605,6 @@ class AuthManager implements LoggerAwareInterface {
                        }
                }
 
-               // Ignore warnings about master connections/writes...hard to avoid here
-               \Profiler::instance()->getTransactionProfiler()->resetExpectations();
-
                $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
                if ( $cache->get( $backoffKey ) ) {
                        $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
@@ -1625,6 +1622,9 @@ class AuthManager implements LoggerAwareInterface {
                        'from' => $from,
                ] );
 
+               // Ignore warnings about master connections/writes...hard to avoid here
+               $trxProfiler = \Profiler::instance()->getTransactionProfiler();
+               $trxProfiler->setSilenced( true );
                try {
                        $status = $user->addToDatabase();
                        if ( !$status->isOk() ) {
@@ -1652,6 +1652,7 @@ class AuthManager implements LoggerAwareInterface {
                                return $status;
                        }
                } catch ( \Exception $ex ) {
+                       $trxProfiler->setSilenced( false );
                        $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
                                'username' => $username,
                                'exception' => $ex,
@@ -1673,9 +1674,10 @@ class AuthManager implements LoggerAwareInterface {
 
                // Update user count
                \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
-
                // Watch user's userpage and talk page
-               $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
+               \DeferredUpdates::addCallableUpdate( function () use ( $user ) {
+                       $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
+               } );
 
                // Log the creation
                if ( $this->config->get( 'NewUserLog' ) ) {
@@ -1686,12 +1688,10 @@ class AuthManager implements LoggerAwareInterface {
                        $logEntry->setParameters( [
                                '4::userid' => $user->getId(),
                        ] );
-                       $logid = $logEntry->insert();
+                       $logEntry->insert();
                }
 
-               // Commit database changes, so even if something else later blows up
-               // the newly-created user doesn't get lost.
-               wfGetLBFactory()->commitMasterChanges( __METHOD__ );
+               $trxProfiler->setSilenced( false );
 
                if ( $login ) {
                        $this->setSessionDataForUser( $user );
@@ -2022,37 +2022,26 @@ class AuthManager implements LoggerAwareInterface {
 
                // Query them and merge results
                $reqs = [];
-               $allPrimaryRequired = null;
                foreach ( $providers as $provider ) {
                        $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
-                       $thisRequired = [];
                        foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
                                $id = $req->getUniqueId();
 
-                               // If it's from a Primary, mark it as "primary-required" but
-                               // track it for later.
+                               // If a required request if from a Primary, mark it as "primary-required" instead
                                if ( $isPrimary ) {
                                        if ( $req->required ) {
-                                               $thisRequired[$id] = true;
                                                $req->required = AuthenticationRequest::PRIMARY_REQUIRED;
                                        }
                                }
 
-                               if ( !isset( $reqs[$id] ) || $req->required === AuthenticationRequest::REQUIRED ) {
+                               if (
+                                       !isset( $reqs[$id] )
+                                       || $req->required === AuthenticationRequest::REQUIRED
+                                       || $reqs[$id] === AuthenticationRequest::OPTIONAL
+                               ) {
                                        $reqs[$id] = $req;
                                }
                        }
-
-                       // Track which requests are required by all primaries
-                       if ( $isPrimary ) {
-                               $allPrimaryRequired = $allPrimaryRequired === null
-                                       ? $thisRequired
-                                       : array_intersect_key( $allPrimaryRequired, $thisRequired );
-                       }
-               }
-               // Any requests that were required by all primaries are required.
-               foreach ( (array)$allPrimaryRequired as $id => $dummy ) {
-                       $reqs[$id]->required = AuthenticationRequest::REQUIRED;
                }
 
                // AuthManager has its own req for some actions
index ff4d52e..f6f949e 100644 (file)
@@ -43,7 +43,8 @@ abstract class AuthenticationRequest {
        const REQUIRED = 1;
 
        /** Indicates that the request is required by a primary authentication
-        * provdier, but other primary authentication providers do not require it. */
+        * provdier. Since the user can choose which primary to authenticate with,
+        * the request might or might not end up being actually required. */
        const PRIMARY_REQUIRED = 2;
 
        /** @var string|null The AuthManager::ACTION_* constant this request was
index c44c8fc..35f3287 100644 (file)
@@ -57,6 +57,14 @@ interface PrimaryAuthenticationProvider extends AuthenticationProvider {
        /** Provider cannot create or link to accounts */
        const TYPE_NONE = 'none';
 
+       /**
+        * {@inheritdoc}
+        *
+        * Of the requests returned by this method, exactly one should have
+        * {@link AuthenticationRequest::$required} set to REQUIRED.
+        */
+       public function getAuthenticationRequests( $action, array $options );
+
        /**
         * Start an authentication flow
         *
index 159cfd9..daf76df 100644 (file)
@@ -589,16 +589,20 @@ class RecentChange {
                        'pageStatus' => 'changed'
                ];
 
-               DeferredUpdates::addCallableUpdate( function() use ( $rc, $tags ) {
-                       $rc->save();
-                       if ( $rc->mAttribs['rc_patrolled'] ) {
-                               PatrolLog::record( $rc, true, $rc->getPerformer() );
-                       }
-                       if ( count( $tags ) ) {
-                               ChangeTags::addTags( $tags, $rc->mAttribs['rc_id'],
-                                       $rc->mAttribs['rc_this_oldid'], null, null );
-                       }
-               } );
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $rc, $tags ) {
+                               $rc->save();
+                               if ( $rc->mAttribs['rc_patrolled'] ) {
+                                       PatrolLog::record( $rc, true, $rc->getPerformer() );
+                               }
+                               if ( count( $tags ) ) {
+                                       ChangeTags::addTags( $tags, $rc->mAttribs['rc_id'],
+                                               $rc->mAttribs['rc_this_oldid'], null, null );
+                               }
+                       },
+                       DeferredUpdates::POSTSEND,
+                       wfGetDB( DB_MASTER )
+               );
 
                return $rc;
        }
@@ -661,16 +665,20 @@ class RecentChange {
                        'pageStatus' => 'created'
                ];
 
-               DeferredUpdates::addCallableUpdate( function() use ( $rc, $tags ) {
-                       $rc->save();
-                       if ( $rc->mAttribs['rc_patrolled'] ) {
-                               PatrolLog::record( $rc, true, $rc->getPerformer() );
-                       }
-                       if ( count( $tags ) ) {
-                               ChangeTags::addTags( $tags, $rc->mAttribs['rc_id'],
-                                       $rc->mAttribs['rc_this_oldid'], null, null );
-                       }
-               } );
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $rc, $tags ) {
+                               $rc->save();
+                               if ( $rc->mAttribs['rc_patrolled'] ) {
+                                       PatrolLog::record( $rc, true, $rc->getPerformer() );
+                               }
+                               if ( count( $tags ) ) {
+                                       ChangeTags::addTags( $tags, $rc->mAttribs['rc_id'],
+                                               $rc->mAttribs['rc_this_oldid'], null, null );
+                               }
+                       },
+                       DeferredUpdates::POSTSEND,
+                       wfGetDB( DB_MASTER )
+               );
 
                return $rc;
        }
index 2bbf6ca..20893bb 100644 (file)
@@ -64,11 +64,4 @@ abstract class CodeContentHandler extends TextContentHandler {
                throw new MWException( 'Subclass must override' );
        }
 
-       /**
-        * @param SearchEngine $engine
-        * @return array
-        */
-       public function getFieldsForSearchIndex( SearchEngine $engine ) {
-               return [];
-       }
 }
index 7184980..5a5c0d8 100644 (file)
@@ -1156,62 +1156,19 @@ abstract class ContentHandler {
         *
         * @param string $event Event name
         * @param array $args Parameters passed to hook functions
-        * @param bool $warn Whether to log a warning.
-        *                    Default to self::$enableDeprecationWarnings.
-        *                    May be set to false for testing.
+        * @param string|null $deprecatedVersion Emit a deprecation notice
+        *   when the hook is run for the provided version
         *
         * @return bool True if no handler aborted the hook
-        *
-        * @see ContentHandler::$enableDeprecationWarnings
         */
        public static function runLegacyHooks( $event, $args = [],
-               $warn = null
+               $deprecatedVersion = null
        ) {
 
-               if ( $warn === null ) {
-                       $warn = self::$enableDeprecationWarnings;
-               }
-
                if ( !Hooks::isRegistered( $event ) ) {
                        return true; // nothing to do here
                }
 
-               if ( $warn ) {
-                       // Log information about which handlers are registered for the legacy hook,
-                       // so we can find and fix them.
-
-                       $handlers = Hooks::getHandlers( $event );
-                       $handlerInfo = [];
-
-                       MediaWiki\suppressWarnings();
-
-                       foreach ( $handlers as $handler ) {
-                               if ( is_array( $handler ) ) {
-                                       if ( is_object( $handler[0] ) ) {
-                                               $info = get_class( $handler[0] );
-                                       } else {
-                                               $info = $handler[0];
-                                       }
-
-                                       if ( isset( $handler[1] ) ) {
-                                               $info .= '::' . $handler[1];
-                                       }
-                               } elseif ( is_object( $handler ) ) {
-                                       $info = get_class( $handler[0] );
-                                       $info .= '::on' . $event;
-                               } else {
-                                       $info = $handler;
-                               }
-
-                               $handlerInfo[] = $info;
-                       }
-
-                       MediaWiki\restoreWarnings();
-
-                       wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " .
-                               implode( ', ', $handlerInfo ), 2 );
-               }
-
                // convert Content objects to text
                $contentObjects = [];
                $contentTexts = [];
@@ -1229,7 +1186,7 @@ abstract class ContentHandler {
                }
 
                // call the hook functions
-               $ok = Hooks::run( $event, $args );
+               $ok = Hooks::run( $event, $args, $deprecatedVersion );
 
                // see if the hook changed the text
                foreach ( $contentTexts as $k => $orig ) {
index 225522e..de650b9 100644 (file)
@@ -149,7 +149,7 @@ class TextContent extends AbstractContent {
 
        /**
         * Returns a Content object with pre-save transformations applied.
-        * This implementation just trims trailing whitespace.
+        * This implementation just trims trailing whitespace and normalizes newlines.
         *
         * @param Title $title
         * @param User $user
@@ -160,6 +160,7 @@ class TextContent extends AbstractContent {
        public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
                $text = $this->getNativeData();
                $pst = rtrim( $text );
+               $pst = str_replace( [ "\r\n", "\r" ], "\n", $pst );
 
                return ( $text === $pst ) ? $this : new static( $pst, $this->getModel() );
        }
index 74cbd16..09cdcee 100644 (file)
@@ -143,9 +143,10 @@ class TextContentHandler extends ContentHandler {
        }
 
        public function getFieldsForSearchIndex( SearchEngine $engine ) {
-               $fields = [];
+               $fields = parent::getFieldsForSearchIndex( $engine );
                $fields['language'] =
                        $engine->makeSearchFieldMapping( 'language', SearchIndexField::INDEX_TYPE_KEYWORD );
+
                return $fields;
        }
 
index d9f1e86..3ad7665 100644 (file)
@@ -109,7 +109,7 @@ class WikitextContentHandler extends TextContentHandler {
        }
 
        public function getFieldsForSearchIndex( SearchEngine $engine ) {
-               $fields = [];
+               $fields = parent::getFieldsForSearchIndex( $engine );
 
                $fields['category'] =
                        $engine->makeSearchFieldMapping( 'category', SearchIndexField::INDEX_TYPE_TEXT );
@@ -154,7 +154,11 @@ class WikitextContentHandler extends TextContentHandler {
        protected function getFileText( Title $title ) {
                $file = wfLocalFile( $title );
                if ( $file && $file->exists() ) {
-                       return $file->getHandler()->getEntireText( $file );
+                       $handler = $file->getHandler();
+                       if ( !$handler ) {
+                               return null;
+                       }
+                       return $handler->getEntireText( $file );
                }
 
                return null;
index 53862b9..4a78363 100644 (file)
@@ -445,7 +445,7 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function begin( $fname = __METHOD__ ) {
+       public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
index 5023acd..933bea6 100644 (file)
@@ -703,7 +703,7 @@ abstract class DatabaseBase implements IDatabase {
                                                " performing implicit commit before closing connection!" );
                                }
 
-                               $this->commit( __METHOD__, 'flush' );
+                               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
                        }
 
                        $closed = $this->closeConnection();
@@ -815,7 +815,7 @@ abstract class DatabaseBase implements IDatabase {
                if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
                        && $this->isTransactableQuery( $sql )
                ) {
-                       $this->begin( __METHOD__ . " ($fname)" );
+                       $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
                        $this->mTrxAutomatic = true;
                }
 
@@ -2203,7 +2203,7 @@ abstract class DatabaseBase implements IDatabase {
 
                $useTrx = !$this->mTrxLevel;
                if ( $useTrx ) {
-                       $this->begin( $fname );
+                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
                }
                try {
                        # Update any existing conflicting row(s)
@@ -2221,7 +2221,7 @@ abstract class DatabaseBase implements IDatabase {
                        throw $e;
                }
                if ( $useTrx ) {
-                       $this->commit( $fname );
+                       $this->commit( $fname, self::TRANSACTION_INTERNAL );
                }
 
                return $ok;
@@ -2520,7 +2520,7 @@ abstract class DatabaseBase implements IDatabase {
                        $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
                } else {
                        // If no transaction is active, then make one for this callback
-                       $this->begin( __METHOD__ );
+                       $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
                        try {
                                call_user_func( $callback );
                                $this->commit( __METHOD__ );
@@ -2628,7 +2628,7 @@ abstract class DatabaseBase implements IDatabase {
 
        final public function startAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
-                       $this->begin( $fname );
+                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
                        $this->mTrxAutomatic = true;
                        // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
                        // in all changes being in one transaction to keep requests transactional.
@@ -2651,58 +2651,43 @@ abstract class DatabaseBase implements IDatabase {
                }
 
                if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
-                       $this->commit( $fname, 'flush' );
+                       $this->commit( $fname, self::FLUSHING_INTERNAL );
                }
        }
 
        final public function doAtomicSection( $fname, callable $callback ) {
                $this->startAtomic( $fname );
                try {
-                       call_user_func_array( $callback, [ $this, $fname ] );
+                       $res = call_user_func_array( $callback, [ $this, $fname ] );
                } catch ( Exception $e ) {
                        $this->rollback( $fname );
                        throw $e;
                }
                $this->endAtomic( $fname );
+
+               return $res;
        }
 
-       final public function begin( $fname = __METHOD__ ) {
-               if ( $this->mTrxLevel ) { // implicit commit
+       final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
+               // Protect against mismatched atomic section, transaction nesting, and snapshot loss
+               if ( $this->mTrxLevel ) {
                        if ( $this->mTrxAtomicLevels ) {
-                               // If the current transaction was an automatic atomic one, then we definitely have
-                               // a problem. Same if there is any unclosed atomic level.
                                $levels = implode( ', ', $this->mTrxAtomicLevels );
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "Got explicit BEGIN from $fname while atomic section(s) $levels are open."
-                               );
+                               $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
+                               throw new DBUnexpectedError( $this, $msg );
                        } elseif ( !$this->mTrxAutomatic ) {
-                               // We want to warn about inadvertently nested begin/commit pairs, but not about
-                               // auto-committing implicit transactions that were started by query() via DBO_TRX
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
-                                               " performing implicit commit!"
-                               );
-                       } elseif ( $this->mTrxDoneWrites ) {
-                               // The transaction was automatic and has done write operations
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "$fname: Automatic transaction with writes in progress" .
-                                               " (from {$this->mTrxFname}), performing implicit commit!\n"
-                               );
-                       }
-
-                       $this->runOnTransactionPreCommitCallbacks();
-                       $writeTime = $this->pendingWriteQueryDuration();
-                       $this->doCommit( $fname );
-                       if ( $this->mTrxDoneWrites ) {
-                               $this->mDoneWrites = microtime( true );
-                               $this->getTransactionProfiler()->transactionWritingOut(
-                                       $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
+                               $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
+                               throw new DBUnexpectedError( $this, $msg );
+                       } else {
+                               // @TODO: make this an exception at some point
+                               $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
+                               wfLogDBError( $msg );
+                               return; // join the main transaction set
                        }
-
-                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+               } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
+                       // @TODO: make this an exception at some point
+                       wfLogDBError( "$fname: Implicit transaction expected (DBO_TRX set)." );
+                       return; // let any writes be in the main transaction
                }
 
                // Avoid fatals if close() was called
@@ -2742,28 +2727,27 @@ abstract class DatabaseBase implements IDatabase {
                        $levels = implode( ', ', $this->mTrxAtomicLevels );
                        throw new DBUnexpectedError(
                                $this,
-                               "Got COMMIT while atomic sections $levels are still open"
+                               "$fname: Got COMMIT while atomic sections $levels are still open."
                        );
                }
 
-               if ( $flush === 'flush' ) {
+               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
                        if ( !$this->mTrxLevel ) {
                                return; // nothing to do
                        } elseif ( !$this->mTrxAutomatic ) {
                                throw new DBUnexpectedError(
                                        $this,
-                                       "$fname: Flushing an explicit transaction, getting out of sync!"
+                                       "$fname: Flushing an explicit transaction, getting out of sync."
                                );
                        }
                } else {
                        if ( !$this->mTrxLevel ) {
-                               wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+                               wfWarn( "$fname: No transaction to commit, something got out of sync." );
                                return; // nothing to do
                        } elseif ( $this->mTrxAutomatic ) {
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "$fname: Explicit commit of implicit transaction."
-                               );
+                               // @TODO: make this an exception at some point
+                               wfLogDBError( "$fname: Explicit commit of implicit transaction." );
+                               return; // wait for the main transaction set commit round
                        }
                }
 
@@ -2796,14 +2780,19 @@ abstract class DatabaseBase implements IDatabase {
        }
 
        final public function rollback( $fname = __METHOD__, $flush = '' ) {
-               if ( $flush !== 'flush' ) {
+               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
                        if ( !$this->mTrxLevel ) {
-                               wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
                                return; // nothing to do
                        }
                } else {
                        if ( !$this->mTrxLevel ) {
+                               wfWarn( "$fname: No transaction to rollback, something got out of sync." );
                                return; // nothing to do
+                       } elseif ( $this->getFlag( DBO_TRX ) ) {
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
+                               );
                        }
                }
 
@@ -3297,16 +3286,32 @@ abstract class DatabaseBase implements IDatabase {
        }
 
        public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+               if ( $this->writesOrCallbacksPending() ) {
+                       // This only flushes transactions to clear snapshots, not to write data
+                       throw new DBUnexpectedError(
+                               $this,
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+                       );
+               }
+
                if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
                        return null;
                }
 
                $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
-                       $this->commit( __METHOD__, 'flush' );
-                       $this->unlock( $lockKey, $fname );
+                       if ( $this->trxLevel() ) {
+                               // There is a good chance an exception was thrown, causing any early return
+                               // from the caller. Let any error handler get a chance to issue rollback().
+                               // If there isn't one, let the error bubble up and trigger server-side rollback.
+                               $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
+                                       $this->unlock( $lockKey, $fname );
+                               } );
+                       } else {
+                               $this->unlock( $lockKey, $fname );
+                       }
                } );
 
-               $this->commit( __METHOD__, 'flush' );
+               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
 
                return $unlocker;
        }
index 6380fe7..d1ebe62 100644 (file)
@@ -767,9 +767,6 @@ abstract class DatabaseMysqlBase extends Database {
                        return 0; // already reached this point for sure
                }
 
-               // Commit any open transactions
-               $this->commit( __METHOD__, 'flush' );
-
                // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
                if ( $this->useGTIDs && $pos->gtids ) {
                        // Wait on the GTID set (MariaDB only)
index 867aeb8..1ecdd26 100644 (file)
@@ -149,7 +149,7 @@ class SavepointPostgres {
                $this->didbegin = false;
                /* If we are not in a transaction, we need to be for savepoint trickery */
                if ( !$dbw->trxLevel() ) {
-                       $dbw->begin( "FOR SAVEPOINT" );
+                       $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
                        $this->didbegin = true;
                }
        }
@@ -1207,7 +1207,7 @@ __INDEXATTR__;
         * @param string $desiredSchema
         */
        function determineCoreSchema( $desiredSchema ) {
-               $this->begin( __METHOD__ );
+               $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
                if ( $this->schemaExists( $desiredSchema ) ) {
                        if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
                                $this->mCoreSchema = $desiredSchema;
index 9d0a0f7..5bbba88 100644 (file)
@@ -911,7 +911,7 @@ class DatabaseSqlite extends Database {
        public function lock( $lockName, $method, $timeout = 5 ) {
                if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
                        if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
-                               throw new DBError( "Cannot create directory \"{$this->dbDir}/locks\"." );
+                               throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
                        }
                }
 
index 43592e2..bdab09e 100644 (file)
  * @ingroup Database
  */
 interface IDatabase {
-       /* Constants to onTransactionResolution() callbacks */
+       /** @var int Callback triggered immediately due to no active transaction */
        const TRIGGER_IDLE = 1;
+       /** @var int Callback triggered by commit */
        const TRIGGER_COMMIT = 2;
+       /** @var int Callback triggered by rollback */
        const TRIGGER_ROLLBACK = 3;
 
+       /** @var string Transaction is requested by regular caller outside of the DB layer */
+       const TRANSACTION_EXPLICIT = '';
+       /** @var string Transaction is requested interally via DBO_TRX/startAtomic() */
+       const TRANSACTION_INTERNAL = 'implicit';
+
+       /** @var string Transaction operation comes from service managing all DBs */
+       const FLUSHING_ALL_PEERS = 'flush';
+       /** @var string Transaction operation comes from the database class internally */
+       const FLUSHING_INTERNAL = 'flush';
+
        /**
         * A string describing the current software version, and possibly
         * other details in a user-friendly way. Will be listed on Special:Version, etc.
@@ -1335,6 +1347,7 @@ interface IDatabase {
         *
         * @param string $fname Caller name (usually __METHOD__)
         * @param callable $callback Callback that issues DB updates
+        * @return mixed $res Result of the callback (since 1.28)
         * @throws DBError
         * @throws RuntimeException
         * @throws UnexpectedValueException
@@ -1346,6 +1359,10 @@ interface IDatabase {
         * Begin a transaction. If a transaction is already in progress,
         * that transaction will be committed before the new transaction is started.
         *
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        * Nesting of transactions is not supported.
+        *
         * Note that when the DBO_TRX flag is set (which is usually the case for web
         * requests, but not for maintenance scripts), any previous database query
         * will have started a transaction automatically.
@@ -1355,20 +1372,23 @@ interface IDatabase {
         * automatically because of the DBO_TRX flag.
         *
         * @param string $fname
+        * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
         * @throws DBError
         */
-       public function begin( $fname = __METHOD__ );
+       public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
 
        /**
         * Commits a transaction previously started using begin().
         * If no transaction is in progress, a warning is issued.
         *
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
         * Nesting of transactions is not supported.
         *
         * @param string $fname
-        * @param string $flush Flush flag, set to 'flush' to disable warnings about
-        *   explicitly committing implicit transactions, or calling commit when no
-        *   transaction is in progress.
+        * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
+        *   constant to disable warnings about explicitly committing implicit transactions,
+        *   or calling commit when no transaction is in progress.
         *
         *   This will trigger an exception if there is an ongoing explicit transaction.
         *
@@ -1383,13 +1403,17 @@ interface IDatabase {
         * Rollback a transaction previously started using begin().
         * If no transaction is in progress, a warning is issued.
         *
-        * No-op on non-transactional databases.
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        * Nesting of transactions is not supported. If a serious unexpected error occurs,
+        * throwing an Exception is preferrable, using a pre-installed error handler to trigger
+        * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
         *
         * @param string $fname
-        * @param string $flush Flush flag, set to 'flush' to disable warnings about
-        *   calling rollback when no transaction is in progress. This will silently
-        *   break any ongoing explicit transaction. Only set the flush flag if you
-        *   are sure that it is safe to ignore these warnings in your context.
+        * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
+        *   constant to disable warnings about calling rollback when no transaction is in
+        *   progress. This will silently break any ongoing explicit transaction. Only set the
+        *   flush flag if you are sure that it is safe to ignore these warnings in your context.
         * @throws DBUnexpectedError
         * @since 1.23 Added $flush parameter
         */
@@ -1554,10 +1578,14 @@ interface IDatabase {
        /**
         * Acquire a named lock, flush any transaction, and return an RAII style unlocker object
         *
+        * Only call this from outer transcation scope and when only one DB will be affected.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        *
         * This is suitiable for transactions that need to be serialized using cooperative locks,
         * where each transaction can see each others' changes. Any transaction is flushed to clear
         * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
-        * any transaction will be committed and the lock will be released.
+        * the lock will be released unless a transaction is active. If one is active, then the lock
+        * will be released when it either commits or rolls back.
         *
         * If the lock acquisition failed, then no transaction flush happens, and null is returned.
         *
index 4078a39..efc6148 100644 (file)
@@ -42,6 +42,8 @@ abstract class LBFactory implements DestructibleService {
        /** @var WANObjectCache */
        protected $wanCache;
 
+       /** @var mixed */
+       protected $ticket;
        /** @var string|bool Reason all LBs are read-only or false if not */
        protected $readOnlyReason = false;
 
@@ -72,6 +74,7 @@ abstract class LBFactory implements DestructibleService {
                        $this->wanCache = WANObjectCache::newEmpty();
                }
                $this->trxLogger = LoggerFactory::getInstance( 'DBTransaction' );
+               $this->ticket = mt_rand();
        }
 
        /**
@@ -404,6 +407,44 @@ abstract class LBFactory implements DestructibleService {
                }
        }
 
+       /**
+        * Get a token asserting that no transaction writes are active
+        *
+        * @param string $fname Caller name (e.g. __METHOD__)
+        * @return mixed A value to pass to commitAndWaitForReplication()
+        * @since 1.28
+        */
+       public function getEmptyTransactionTicket( $fname ) {
+               if ( $this->hasMasterChanges() ) {
+                       $this->trxLogger->error( __METHOD__ . ": $fname does not have outer scope." );
+                       return null;
+               }
+
+               return $this->ticket;
+       }
+
+       /**
+        * Convenience method for safely running commitMasterChanges()/waitForReplication()
+        *
+        * This will commit and wait unless $ticket indicates it is unsafe to do so
+        *
+        * @param string $fname Caller name (e.g. __METHOD__)
+        * @param mixed $ticket Result of getOuterTransactionScopeTicket()
+        * @param array $opts Options to waitForReplication()
+        * @throws DBReplicationWaitError
+        * @since 1.28
+        */
+       public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
+               if ( $ticket !== $this->ticket ) {
+                       $logger = LoggerFactory::getInstance( 'DBPerformance' );
+                       $logger->error( __METHOD__ . ": cannot commit; $fname does not have outer scope." );
+                       return;
+               }
+
+               $this->commitMasterChanges( $fname );
+               $this->waitForReplication( $opts );
+       }
+
        /**
         * Disable the ChronologyProtector for all load balancers
         *
index 0f3ca93..4b9cccc 100644 (file)
@@ -364,6 +364,8 @@ class LBFactoryMulti extends LBFactory {
                        }
                        $serverInfo['hostName'] = $serverName;
                        $serverInfo['load'] = $load;
+                       $serverInfo += [ 'flags' => DBO_DEFAULT ];
+
                        $servers[] = $serverInfo;
                }
 
index 14baf2e..3702c8b 100644 (file)
@@ -56,6 +56,7 @@ class LBFactorySimple extends LBFactory {
                                } else {
                                        $server['slave'] = true;
                                }
+                               $server += [ 'flags' => DBO_DEFAULT ];
                        }
                } else {
                        global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
index a7c486c..d08172a 100644 (file)
@@ -1062,7 +1062,7 @@ class LoadBalancer {
         */
        public function commitAll( $fname = __METHOD__ ) {
                $this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( $fname ) {
-                       $conn->commit( $fname, 'flush' );
+                       $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
                } );
        }
 
@@ -1109,7 +1109,7 @@ class LoadBalancer {
        public function commitMasterChanges( $fname = __METHOD__ ) {
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $fname ) {
                        if ( $conn->writesOrCallbacksPending() ) {
-                               $conn->commit( $fname, 'flush' );
+                               $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
                        }
                } );
        }
@@ -1143,7 +1143,7 @@ class LoadBalancer {
                        foreach ( $conns2[$masterIndex] as $conn ) {
                                if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
                                        try {
-                                               $conn->rollback( $fname, 'flush' );
+                                               $conn->rollback( $fname, IDatabase::FLUSHING_ALL_PEERS );
                                        } catch ( DBError $e ) {
                                                MWExceptionHandler::logException( $e );
                                                $failedServers[] = $conn->getServer();
index 0da5d7d..a348719 100644 (file)
@@ -9,7 +9,7 @@ class AtomicSectionUpdate implements DeferrableUpdate, DeferrableCallback {
        private $dbw;
        /** @var string */
        private $fname;
-       /** @var callable */
+       /** @var callable|null */
        private $callback;
 
        /**
index ef5903b..d26cf9d 100644 (file)
@@ -9,7 +9,7 @@ class AutoCommitUpdate implements DeferrableUpdate, DeferrableCallback {
        private $dbw;
        /** @var string */
        private $fname;
-       /** @var callable */
+       /** @var callable|null */
        private $callback;
 
        /**
index 2865461..5b84ca9 100644 (file)
  *       subclasses can override the beginTransaction() and commitTransaction() methods.
  */
 abstract class DataUpdate implements DeferrableUpdate {
+       /** @var mixed Result from LBFactory::getEmptyTransactionTicket() */
+       protected $ticket;
+
        public function __construct() {
                // noop
        }
 
+       /**
+        * @param mixed $ticket Result of getEmptyTransactionTicket()
+        * @since 1.28
+        */
+       public function setTransactionTicket( $ticket ) {
+               $this->ticket = $ticket;
+       }
+
        /**
         * Begin an appropriate transaction, if any.
         * This default implementation does nothing.
index 9768838..ee14e1a 100644 (file)
@@ -68,9 +68,12 @@ class DeferredUpdates {
         *
         * @param callable $callable
         * @param integer $type DeferredUpdates constant (PRESEND or POSTSEND) (since 1.27)
+        * @param IDatabase|null $dbw Abort if this DB is rolled back [optional] (since 1.28)
         */
-       public static function addCallableUpdate( $callable, $type = self::POSTSEND ) {
-               self::addUpdate( new MWCallableUpdate( $callable, wfGetCaller() ), $type );
+       public static function addCallableUpdate(
+               $callable, $type = self::POSTSEND, IDatabase $dbw = null
+       ) {
+               self::addUpdate( new MWCallableUpdate( $callable, wfGetCaller(), $dbw ), $type );
        }
 
        /**
index 0009781..47f2b21 100644 (file)
@@ -54,6 +54,7 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
        public function doUpdate() {
                $config = RequestContext::getMain()->getConfig();
                $batchSize = $config->get( 'UpdateRowsPerQuery' );
+               $factory = wfGetLBFactory();
 
                // Page may already be deleted, so don't just getId()
                $id = $this->pageId;
@@ -77,8 +78,9 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
                foreach ( $catBatches as $catBatch ) {
                        $this->page->updateCategoryCounts( [], $catBatch, $id );
                        if ( count( $catBatches ) > 1 ) {
-                               $this->mDb->commit( __METHOD__, 'flush' );
-                               wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+                               $factory->commitAndWaitForReplication(
+                                       __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+                               );
                        }
                }
 
@@ -173,8 +175,9 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
                        foreach ( $rcIdBatches as $rcIdBatch ) {
                                $this->mDb->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
                                if ( count( $rcIdBatches ) > 1 ) {
-                                       $this->mDb->commit( __METHOD__, 'flush' );
-                                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+                                       $factory->commitAndWaitForReplication(
+                                               __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+                                       );
                                }
                        }
                }
@@ -185,6 +188,7 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
 
        private function batchDeleteByPK( $table, array $conds, array $pk, $bSize ) {
                $dbw = $this->mDb; // convenience
+               $factory = wfGetLBFactory();
                $res = $dbw->select( $table, $pk, $conds, __METHOD__ );
 
                $pkDeleteConds = [];
@@ -192,8 +196,9 @@ class LinksDeletionUpdate extends SqlDataUpdate implements EnqueueableDataUpdate
                        $pkDeleteConds[] = $this->mDb->makeList( (array)$row, LIST_AND );
                        if ( count( $pkDeleteConds ) >= $bSize ) {
                                $dbw->delete( $table, $dbw->makeList( $pkDeleteConds, LIST_OR ), __METHOD__ );
-                               $dbw->commit( __METHOD__, 'flush' );
-                               wfGetLBFactory()->waitForReplication( [ 'wiki' => $dbw->getWikiID() ] );
+                               $factory->commitAndWaitForReplication(
+                                       __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+                               );
                                $pkDeleteConds = [];
                        }
                }
index 22944eb..4f40c38 100644 (file)
@@ -335,6 +335,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
         */
        private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
                $bSize = RequestContext::getMain()->getConfig()->get( 'UpdateRowsPerQuery' );
+               $factory = wfGetLBFactory();
 
                if ( $table === 'page_props' ) {
                        $fromField = 'pp_page';
@@ -386,15 +387,17 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
                foreach ( $deleteWheres as $deleteWhere ) {
                        $this->mDb->delete( $table, $deleteWhere, __METHOD__ );
-                       $this->mDb->commit( __METHOD__, 'flush' );
-                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+                       $factory->commitAndWaitForReplication(
+                               __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+                       );
                }
 
                $insertBatches = array_chunk( $insertions, $bSize );
                foreach ( $insertBatches as $insertBatch ) {
                        $this->mDb->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
-                       $this->mDb->commit( __METHOD__, 'flush' );
-                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+                       $factory->commitAndWaitForReplication(
+                               __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+                       );
                }
 
                if ( count( $insertions ) ) {
index d63c292..47b162c 100644 (file)
@@ -4,7 +4,7 @@
  * Deferrable Update for closure/callback
  */
 class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
-       /** @var callable */
+       /** @var callable|null */
        private $callback;
        /** @var string */
        private $fname;
@@ -12,14 +12,27 @@ class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
        /**
         * @param callable $callback
         * @param string $fname Calling method
+        * @param IDatabase|null $dbw Abort if this DB is rolled back [optional] (since 1.28)
         */
-       public function __construct( callable $callback, $fname = 'unknown' ) {
+       public function __construct( callable $callback, $fname = 'unknown', IDatabase $dbw = null ) {
                $this->callback = $callback;
                $this->fname = $fname;
+
+               if ( $dbw && $dbw->trxLevel() ) {
+                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+               }
        }
 
        public function doUpdate() {
-               call_user_func( $this->callback );
+               if ( $this->callback ) {
+                       call_user_func( $this->callback );
+               }
+       }
+
+       public function cancelOnRollback( $trigger ) {
+               if ( $trigger === IDatabase::TRIGGER_ROLLBACK ) {
+                       $this->callback = null;
+               }
        }
 
        public function getOrigin() {
index b61b08d..c9aad43 100644 (file)
@@ -26,9 +26,8 @@
  *
  * This is meant for multi-wiki systems that may share files.
  *
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer DBs, each on their
- * own server, all having the filelocks.sql tables (with row-level locking).
+ * All lock requests for a resource, identified by a hash string, will map to one bucket.
+ * Each bucket maps to one or several peer DBs, each on their own server.
  * A majority of peer DBs must agree for a lock to be acquired.
  *
  * Caching is used to avoid hitting servers that are down.
@@ -243,6 +242,8 @@ 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
index b8be4ea..91d628c 100644 (file)
@@ -117,6 +117,9 @@ class LocalFile extends File {
        /** @var bool Whether the row was upgraded on load */
        private $upgraded;
 
+       /** @var bool Whether the row was scheduled to upgrade on load */
+       private $upgrading;
+
        /** @var bool True if the image row is locked */
        private $locked;
 
@@ -559,37 +562,43 @@ class LocalFile extends File {
         */
        function maybeUpgradeRow() {
                global $wgUpdateCompatibleMetadata;
-               if ( wfReadOnly() ) {
+
+               if ( wfReadOnly() || $this->upgrading ) {
                        return;
                }
 
                $upgrade = false;
-               if ( is_null( $this->media_type ) ||
-                       $this->mime == 'image/svg'
-               ) {
+               if ( is_null( $this->media_type ) || $this->mime == 'image/svg' ) {
                        $upgrade = true;
                } else {
                        $handler = $this->getHandler();
                        if ( $handler ) {
                                $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
-                               if ( $validity === MediaHandler::METADATA_BAD
-                                       || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
-                               ) {
+                               if ( $validity === MediaHandler::METADATA_BAD ) {
                                        $upgrade = true;
+                               } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE ) {
+                                       $upgrade = $wgUpdateCompatibleMetadata;
                                }
                        }
                }
 
                if ( $upgrade ) {
-                       try {
-                               $this->upgradeRow();
-                       } catch ( LocalFileLockError $e ) {
-                               // let the other process handle it (or do it next time)
-                       }
-                       $this->upgraded = true; // avoid rework/retries
+                       $this->upgrading = true;
+                       // Defer updates unless in auto-commit CLI mode
+                       DeferredUpdates::addCallableUpdate( function() {
+                               $this->upgrading = false; // avoid duplicate updates
+                               try {
+                                       $this->upgradeRow();
+                               } catch ( LocalFileLockError $e ) {
+                                       // let the other process handle it (or do it next time)
+                               }
+                       } );
                }
        }
 
+       /**
+        * @return bool Whether upgradeRow() ran for this object
+        */
        function getUpgraded() {
                return $this->upgraded;
        }
@@ -639,7 +648,7 @@ class LocalFile extends File {
                $this->invalidateCache();
 
                $this->unlock(); // done
-
+               $this->upgraded = true; // avoid rework/retries
        }
 
        /**
@@ -964,6 +973,33 @@ class LocalFile extends File {
                DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
        }
 
+       /**
+        * Prerenders a configurable set of thumbnails
+        *
+        * @since 1.28
+        */
+       public function prerenderThumbnails() {
+               global $wgUploadThumbnailRenderMap;
+
+               $jobs = [];
+
+               $sizes = $wgUploadThumbnailRenderMap;
+               rsort( $sizes );
+
+               foreach ( $sizes as $size ) {
+                       if ( $this->isVectorized() || $this->getWidth() > $size ) {
+                               $jobs[] = new ThumbnailRenderJob(
+                                       $this->getTitle(),
+                                       [ 'transformParams' => [ 'width' => $size ] ]
+                               );
+                       }
+               }
+
+               if ( $jobs ) {
+                       JobQueueGroup::singleton()->lazyPush( $jobs );
+               }
+       }
+
        /**
         * Delete a list of thumbnails visible at urls
         * @param string $dir Base dir of the files.
@@ -1516,6 +1552,8 @@ class LocalFile extends File {
                                                # Update backlink pages pointing to this title if created
                                                LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
                                        }
+
+                                       $this->prerenderThumbnails();
                                }
                        ),
                        DeferredUpdates::PRESEND
index 5e3758d..d508d76 100644 (file)
@@ -294,10 +294,6 @@ abstract class Installer {
                        'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
                        'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
                ],
-               'pd' => [
-                       'url' => '',
-                       'icon' => '$wgResourceBasePath/resources/assets/licenses/public-domain.png',
-               ],
                'gfdl' => [
                        'url' => 'https://www.gnu.org/copyleft/fdl.html',
                        'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
index a132dc5..4b906a7 100644 (file)
@@ -522,10 +522,10 @@ class JobRunner implements LoggerAwareInterface {
                        // This will trigger a rollback in the main loop
                        throw new DBError( $dbwSerial, "Timed out waiting on commit queue." );
                }
-               // Wait for the generic slave to catch up
+               // Wait for the slave DBs to catch up
                $pos = $lb->getMasterPos();
                if ( $pos ) {
-                       $lb->waitForOne( $pos );
+                       $lb->waitForAll( $pos );
                }
 
                $fname = __METHOD__;
index 57e69b4..b561021 100644 (file)
@@ -64,7 +64,7 @@ class CategoryMembershipChangeJob extends Job {
                        return false;
                }
                // Clear any stale REPEATABLE-READ snapshot
-               $dbr->commit( __METHOD__, 'flush' );
+               wfGetLBFactory()->commitAll( __METHOD__ );
 
                $cutoffUnix = wfTimestamp( TS_UNIX, $this->params['revTimestamp'] );
                // Using ENQUEUE_FUDGE_SEC handles jobs inserted out of revision order due to the delay
@@ -157,6 +157,9 @@ class CategoryMembershipChangeJob extends Job {
                }
 
                $dbw = wfGetDB( DB_MASTER );
+               $factory = wfGetLBFactory();
+               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+
                $catMembChange = new CategoryMembershipChange( $title, $newRev );
                $catMembChange->checkTemplateLinks();
 
@@ -167,8 +170,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryAddedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
-                               $dbw->commit( __METHOD__, 'flush' );
-                               wfGetLBFactory()->waitForReplication();
+                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
                        }
                }
 
@@ -176,8 +178,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {
-                               $dbw->commit( __METHOD__, 'flush' );
-                               wfGetLBFactory()->waitForReplication();
+                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
                        }
                }
        }
index f39f8fd..8d565bd 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup JobQueue
  */
+use \MediaWiki\MediaWikiServices;
 
 /**
  * Job to prune link tables for pages that were deleted
@@ -52,10 +53,12 @@ class DeleteLinksJob extends Job {
                        return false;
                }
 
+               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $timestamp = isset( $this->params['timestamp'] ) ? $this->params['timestamp'] : null;
-
                $page = WikiPage::factory( $this->title ); // title when deleted
+
                $update = new LinksDeletionUpdate( $page, $pageId, $timestamp );
+               $update->setTransactionTicket( $factory->getEmptyTransactionTicket( __METHOD__ ) );
                DataUpdate::runUpdates( [ $update ] );
 
                return true;
index a14cdd7..f09ba57 100644 (file)
@@ -113,11 +113,12 @@ class HTMLCacheUpdateJob extends Job {
                $touchTimestamp = wfTimestampNow();
 
                $dbw = wfGetDB( DB_MASTER );
+               $factory = wfGetLBFactory();
+               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                // Update page_touched (skipping pages already touched since the root job).
                // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
                foreach ( array_chunk( $pageIds, $wgUpdateRowsPerQuery ) as $batch ) {
-                       $dbw->commit( __METHOD__, 'flush' );
-                       wfGetLBFactory()->waitForReplication();
+                       $factory->commitAndWaitForReplication( __METHOD__, $ticket );
 
                        $dbw->update( 'page',
                                [ 'page_touched' => $dbw->timestamp( $touchTimestamp ) ],
index fbc1572..2fd3899 100644 (file)
@@ -81,6 +81,8 @@ class RecentChangesUpdateJob extends Job {
                        return; // already in progress
                }
 
+               $factory = wfGetLBFactory();
+               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
                do {
                        $rcIds = $dbw->selectFieldValues( 'recentchanges',
@@ -91,14 +93,11 @@ class RecentChangesUpdateJob extends Job {
                        );
                        if ( $rcIds ) {
                                $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
-                       }
-                       // Commit in chunks to avoid slave lag
-                       $dbw->commit( __METHOD__, 'flush' );
-
-                       if ( count( $rcIds ) === $wgUpdateRowsPerQuery ) {
                                // There might be more, so try waiting for slaves
                                try {
-                                       wfGetLBFactory()->waitForReplication( [ 'timeout' => 3 ] );
+                                       $factory->commitAndWaitForReplication(
+                                               __METHOD__, $ticket, [ 'timeout' => 3 ]
+                                       );
                                } catch ( DBReplicationWaitError $e ) {
                                        // Another job will continue anyway
                                        break;
@@ -121,6 +120,8 @@ class RecentChangesUpdateJob extends Job {
                // JobRunner uses DBO_TRX, but doesn't call begin/commit itself;
                // onTransactionIdle() will run immediately since there is no trx.
                $dbw->onTransactionIdle( function() use ( $dbw, $days, $window ) {
+                       $factory = wfGetLBFactory();
+                       $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                        // Avoid disconnect/ping() cycle that makes locks fall off
                        $dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
 
@@ -204,7 +205,7 @@ class RecentChangesUpdateJob extends Job {
                                }
                                foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
                                        $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
-                                       wfGetLBFactory()->waitForReplication();
+                                       $factory->commitAndWaitForReplication( __METHOD__, $ticket );
                                }
                        }
 
index 8fba728..9cdb161 100644 (file)
@@ -241,7 +241,10 @@ class RefreshLinksJob extends Job {
                        $parserOutput
                );
 
+               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                foreach ( $updates as $key => $update ) {
+                       $update->setTransactionTicket( $ticket );
                        // FIXME: This code probably shouldn't be here?
                        // Needed by things like Echo notifications which need
                        // to know which user caused the links update
index c48880f..5556dd8 100644 (file)
@@ -471,6 +471,27 @@ class SqlBagOStuff extends BagOStuff {
                return $ok;
        }
 
+       public function changeTTL( $key, $expiry = 0 ) {
+               list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+               try {
+                       $db = $this->getDB( $serverIndex );
+                       $db->update(
+                               $tableName,
+                               [ 'exptime' => $db->timestamp( $this->convertExpiry( $expiry ) ) ],
+                               [ 'keyname' => $key, 'exptime > ' . $db->addQuotes( $db->timestamp( time() ) ) ],
+                               __METHOD__
+                       );
+                       if ( $db->affectedRows() == 0 ) {
+                               return false;
+                       }
+               } catch ( DBError $e ) {
+                       $this->handleWriteError( $e, $serverIndex );
+                       return false;
+               }
+
+               return true;
+       }
+
        /**
         * @param IDatabase $db
         * @param string $exptime
index 8eba1cc..0344756 100644 (file)
@@ -90,6 +90,14 @@ class WikiPage implements Page, IDBAccessObject {
                $this->mTitle = $title;
        }
 
+       /**
+        * Makes sure that the mTitle object is cloned
+        * to the newly cloned WikiPage.
+        */
+       public function __clone() {
+               $this->mTitle = clone $this->mTitle;
+       }
+
        /**
         * Create a WikiPage object of the appropriate class for the given title.
         *
@@ -887,9 +895,13 @@ class WikiPage implements Page, IDBAccessObject {
                // Update the DB post-send if the page has not cached since now
                $that = $this;
                $latest = $this->getLatest();
-               DeferredUpdates::addCallableUpdate( function() use ( $that, $retval, $latest ) {
-                       $that->insertRedirectEntry( $retval, $latest );
-               } );
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $that, $retval, $latest ) {
+                               $that->insertRedirectEntry( $retval, $latest );
+                       },
+                       DeferredUpdates::POSTSEND,
+                       wfGetDB( DB_MASTER )
+               );
 
                return $retval;
        }
@@ -1048,9 +1060,9 @@ class WikiPage implements Page, IDBAccessObject {
         * @param bool          $forceParse Force reindexing, regardless of cache settings
         * @return bool|ParserOutput ParserOutput or false if the revision was not found
         */
-       public function getParserOutput( ParserOptions $parserOptions, $oldid = null,
-                                        $forceParse = false ) {
-
+       public function getParserOutput(
+               ParserOptions $parserOptions, $oldid = null, $forceParse = false
+       ) {
                $useParserCache =
                        ( !$forceParse ) && $this->shouldCheckParserCache( $parserOptions, $oldid );
                wfDebug( __METHOD__ .
@@ -2934,6 +2946,11 @@ class WikiPage implements Page, IDBAccessObject {
                // Save this so we can pass it to the ArticleDeleteComplete hook.
                $archivedRevisionCount = $dbw->affectedRows();
 
+               // Clone the title and wikiPage, so we have the information we need when
+               // we log and run the ArticleDeleteComplete hook.
+               $logTitle = clone $this->mTitle;
+               $wikiPageBeforeDelete = clone $this;
+
                // Now that it's safely backed up, delete it
                $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
 
@@ -2941,9 +2958,6 @@ class WikiPage implements Page, IDBAccessObject {
                        $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
                }
 
-               // Clone the title, so we have the information we need when we log
-               $logTitle = clone $this->mTitle;
-
                // Log the deletion, if the page was suppressed, put it in the suppression log instead
                $logtype = $suppress ? 'suppress' : 'delete';
 
@@ -2962,8 +2976,15 @@ class WikiPage implements Page, IDBAccessObject {
 
                $this->doDeleteUpdates( $id, $content );
 
-               Hooks::run( 'ArticleDeleteComplete',
-                       [ &$this, &$user, $reason, $id, $content, $logEntry, $archivedRevisionCount ] );
+               Hooks::run( 'ArticleDeleteComplete', [
+                       &$wikiPageBeforeDelete,
+                       &$user,
+                       $reason,
+                       $id,
+                       $content,
+                       $logEntry,
+                       $archivedRevisionCount
+               ] );
                $status->value = $logid;
 
                // Show log excerpt on 404 pages rather than just a link
index 206ad00..4f579a9 100644 (file)
@@ -4364,11 +4364,7 @@ class Parser {
                $this->startParse( $title, $options, self::OT_WIKI, $clearState );
                $this->setUser( $user );
 
-               $pairs = [
-                       "\r\n" => "\n",
-                       "\r" => "\n",
-               ];
-               $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
+               $text = str_replace( [ "\r\n", "\r" ], "\n", $text );
                if ( $options->getPreSaveTransform() ) {
                        $text = $this->pstPass2( $text, $user );
                }
index a28c0aa..5da7cd7 100644 (file)
@@ -539,7 +539,7 @@ class Preprocessor_DOM extends Preprocessor {
                        } elseif ( $found == 'line-end' ) {
                                $piece = $stack->top;
                                // A heading must be open, otherwise \n wouldn't have been in the search list
-                               assert( '$piece->open == "\n"' );
+                               assert( $piece->open === "\n" );
                                $part = $piece->getCurrentPart();
                                // Search back through the input to see if it has a proper close.
                                // Do this using the reversed string since the other solutions
index 012288f..8a4637e 100644 (file)
@@ -475,7 +475,7 @@ class Preprocessor_Hash extends Preprocessor {
                        } elseif ( $found == 'line-end' ) {
                                $piece = $stack->top;
                                // A heading must be open, otherwise \n wouldn't have been in the search list
-                               assert( '$piece->open == "\n"' );
+                               assert( $piece->open === "\n" );
                                $part = $piece->getCurrentPart();
                                // Search back through the input to see if it has a proper close.
                                // Do this using the reversed string since the other solutions
index 3093cde..dc70af4 100644 (file)
@@ -33,6 +33,9 @@ class ResourceLoaderClientHtml {
        /** @var ResourceLoader */
        private $resourceLoader;
 
+       /** @var string|null */
+       private $target;
+
        /** @var array */
        private $config = [];
 
@@ -53,10 +56,12 @@ class ResourceLoaderClientHtml {
 
        /**
         * @param ResourceLoaderContext $context
+        * @param aray $target [optional] Custom 'target' parameter for the startup module
         */
-       public function __construct( ResourceLoaderContext $context ) {
+       public function __construct( ResourceLoaderContext $context, $target = null ) {
                $this->context = $context;
                $this->resourceLoader = $context->getResourceLoader();
+               $this->target = $target;
        }
 
        /**
@@ -316,7 +321,13 @@ class ResourceLoaderClientHtml {
                }
 
                // Async scripts. Once the startup is loaded, inline RLQ scripts will run.
-               $chunks[] = $this->getLoad( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
+               // Pass-through a custom target from OutputPage (T143066).
+               $startupQuery = $this->target ? [ 'target' => $this->target ] : [];
+               $chunks[] = $this->getLoad(
+                       'startup',
+                       ResourceLoaderModule::TYPE_SCRIPTS,
+                       $startupQuery
+               );
 
                return WrappedStringList::join( "\n", $chunks );
        }
@@ -358,8 +369,8 @@ class ResourceLoaderClientHtml {
                return self::makeContext( $this->context, $group, $type );
        }
 
-       private function getLoad( $modules, $only ) {
-               return self::makeLoad( $this->context, (array)$modules, $only );
+       private function getLoad( $modules, $only, array $extraQuery = [] ) {
+               return self::makeLoad( $this->context, (array)$modules, $only, $extraQuery );
        }
 
        private static function makeContext( ResourceLoaderContext $mainContext, $group, $type,
index 3b3bdf7..6cdab1b 100644 (file)
@@ -455,4 +455,11 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                $this->loadFromDefinition();
                return $this->position;
        }
+
+       /**
+        * @return string
+        */
+       public function getType() {
+               return self::LOAD_STYLES;
+       }
 }
index d365bf6..48604e1 100644 (file)
@@ -275,7 +275,8 @@ abstract class RevDelList extends RevisionListBase {
                        function () use ( $visibilityChangeMap ) {
                                $this->doPostCommitUpdates( $visibilityChangeMap );
                        },
-                       DeferredUpdates::PRESEND
+                       DeferredUpdates::PRESEND,
+                       $dbw
                );
 
                $dbw->endAtomic( __METHOD__ );
index 749a686..b4be461 100644 (file)
@@ -478,6 +478,9 @@ class SkinTemplate extends Skin {
                $tpl->set( 'sidebar', $this->buildSidebar() );
                $tpl->set( 'nav_urls', $this->buildNavUrls() );
 
+               // Do this last in case hooks above add bottom scripts
+               $tpl->set( 'bottomscripts', $this->bottomScripts() );
+
                // Set the head scripts near the end, in case the above actions resulted in added scripts
                $tpl->set( 'headelement', $out->headElement( $this ) );
 
@@ -508,9 +511,6 @@ class SkinTemplate extends Skin {
                // See Skin::afterContentHook() for further documentation.
                $tpl->set( 'dataAfterContent', $this->afterContentHook() );
 
-               // Do this last in case hooks above add bottom scripts
-               $tpl->set( 'bottomscripts', $this->bottomScripts() );
-
                return $tpl;
        }
 
index a656c2e..7b4e9db 100644 (file)
@@ -49,10 +49,9 @@ class EmailConfirmation extends UnlistedSpecialPage {
        function execute( $code ) {
                // Ignore things like master queries/connections on GET requests.
                // It's very convenient to just allow formless link usage.
-               Profiler::instance()->getTransactionProfiler()->resetExpectations();
+               $trxProfiler = Profiler::instance()->getTransactionProfiler();
 
                $this->setHeaders();
-
                $this->checkReadOnly();
                $this->checkPermissions();
 
@@ -70,7 +69,9 @@ class EmailConfirmation extends UnlistedSpecialPage {
                                $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
                        }
                } else {
+                       $trxProfiler->setSilenced( true );
                        $this->attemptConfirm( $code );
+                       $trxProfiler->setSilenced( false );
                }
        }
 
@@ -146,7 +147,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
         *
         * @param string $code Confirmation code
         */
-       function attemptConfirm( $code ) {
+       private function attemptConfirm( $code ) {
                $user = User::newFromConfirmationCode( $code, User::READ_LATEST );
                if ( !is_object( $user ) ) {
                        $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
index b5c66ff..d2e3e7f 100644 (file)
@@ -39,12 +39,15 @@ class EmailInvalidation extends UnlistedSpecialPage {
        function execute( $code ) {
                // Ignore things like master queries/connections on GET requests.
                // It's very convenient to just allow formless link usage.
-               Profiler::instance()->getTransactionProfiler()->resetExpectations();
+               $trxProfiler = Profiler::instance()->getTransactionProfiler();
 
                $this->setHeaders();
                $this->checkReadOnly();
                $this->checkPermissions();
+
+               $trxProfiler->setSilenced( true );
                $this->attemptInvalidate( $code );
+               $trxProfiler->setSilenced( false );
        }
 
        /**
@@ -53,7 +56,7 @@ class EmailInvalidation extends UnlistedSpecialPage {
         *
         * @param string $code Confirmation code
         */
-       function attemptInvalidate( $code ) {
+       private function attemptInvalidate( $code ) {
                $user = User::newFromConfirmationCode( $code, User::READ_LATEST );
                if ( !is_object( $user ) ) {
                        $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
index e957b3c..47bed62 100644 (file)
@@ -142,7 +142,7 @@ class SpecialTags extends SpecialPage {
                        Xml::tags( 'th', null, $this->msg( 'tags-source-header' )->parse() ) .
                        Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
                        Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() ) .
-                       ( $userCanManage ?
+                       ( ( $userCanManage || $userCanDelete ) ?
                                Xml::tags( 'th', [ 'class' => 'unsortable' ],
                                        $this->msg( 'tags-actions-header' )->parse() ) :
                                '' )
@@ -266,7 +266,7 @@ class SpecialTags extends SpecialPage {
 
                }
 
-               if ( $actionLinks ) {
+               if ( $showDeleteActions || $showManageActions ) {
                        $newRow .= Xml::tags( 'td', null, $this->getLanguage()->pipeList( $actionLinks ) );
                }
 
index 03c864a..ae16f70 100644 (file)
@@ -774,27 +774,6 @@ abstract class UploadBase {
         * @since  1.25
         */
        public function postProcessUpload() {
-               global $wgUploadThumbnailRenderMap;
-
-               $jobs = [];
-
-               $sizes = $wgUploadThumbnailRenderMap;
-               rsort( $sizes );
-
-               $file = $this->getLocalFile();
-
-               foreach ( $sizes as $size ) {
-                       if ( $file->isVectorized() || $file->getWidth() > $size ) {
-                               $jobs[] = new ThumbnailRenderJob(
-                                       $file->getTitle(),
-                                       [ 'transformParams' => [ 'width' => $size ] ]
-                               );
-                       }
-               }
-
-               if ( $jobs ) {
-                       JobQueueGroup::singleton()->push( $jobs );
-               }
        }
 
        /**
@@ -945,23 +924,22 @@ abstract class UploadBase {
        }
 
        /**
-        * Like stashFile(), but respects extensions' wishes to prevent the stashing.
+        * Like stashFile(), but respects extensions' wishes to prevent the stashing. verifyUpload() must
+        * be called before calling this method (unless $isPartial is true).
         *
         * Upload stash exceptions are also caught and converted to an error status.
         *
         * @since 1.28
         * @param User $user
+        * @param bool $isPartial Pass `true` if this is a part of a chunked upload (not a complete file).
         * @return Status If successful, value is an UploadStashFile instance
         */
-       public function tryStashFile( User $user ) {
-               $props = $this->mFileProps;
-               $error = null;
-               Hooks::run( 'UploadStashFile', [ $this, $user, $props, &$error ] );
-               if ( $error ) {
-                       if ( !is_array( $error ) ) {
-                               $error = [ $error ];
+       public function tryStashFile( User $user, $isPartial = false ) {
+               if ( !$isPartial ) {
+                       $error = $this->runUploadStashFileHook( $user );
+                       if ( $error ) {
+                               return call_user_func_array( 'Status::newFatal', $error );
                        }
-                       return call_user_func_array( 'Status::newFatal', $error );
                }
                try {
                        $file = $this->doStashFile( $user );
@@ -971,6 +949,22 @@ abstract class UploadBase {
                }
        }
 
+       /**
+        * @param User $user
+        * @return array|null Error message and parameters, null if there's no error
+        */
+       protected function runUploadStashFileHook( User $user ) {
+               $props = $this->mFileProps;
+               $error = null;
+               Hooks::run( 'UploadStashFile', [ $this, $user, $props, &$error ] );
+               if ( $error ) {
+                       if ( !is_array( $error ) ) {
+                               $error = [ $error ];
+                       }
+               }
+               return $error;
+       }
+
        /**
         * If the user does not supply all necessary information in the first upload
         * form submission (either by accident or by design) then we may want to
index cc1d698..6368db8 100644 (file)
@@ -38,12 +38,11 @@ class UploadFromChunks extends UploadFromFile {
        /**
         * Setup local pointers to stash, repo and user (similar to UploadFromStash)
         *
-        * @param User|null $user Default: null
+        * @param User $user
         * @param UploadStash|bool $stash Default: false
         * @param FileRepo|bool $repo Default: false
         */
-       public function __construct( $user = null, $stash = false, $repo = false ) {
-               // user object. sometimes this won't exist, as when running from cron.
+       public function __construct( User $user, $stash = false, $repo = false ) {
                $this->user = $user;
 
                if ( $repo ) {
@@ -162,7 +161,20 @@ class UploadFromChunks extends UploadFromFile {
                // Update the mTempPath and mLocalFile
                // (for FileUpload or normal Stash to take over)
                $tStart = microtime( true );
-               $this->mLocalFile = parent::doStashFile( $this->user );
+               // This is a re-implementation of UploadBase::tryStashFile(), we can't call it because we
+               // override doStashFile() with completely different functionality in this class...
+               $error = $this->runUploadStashFileHook( $this->user );
+               if ( $error ) {
+                       call_user_func_array( [ $status, 'fatal' ], $error );
+                       return $status;
+               }
+               try {
+                       $this->mLocalFile = parent::doStashFile( $this->user );
+               } catch ( UploadStashException $e ) {
+                       $status->fatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
+                       return $status;
+               }
+
                $tAmount = microtime( true ) - $tStart;
                $this->mLocalFile->setLocalReference( $tmpFile ); // reuse (e.g. for getImageInfo())
                wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds." );
@@ -235,8 +247,6 @@ class UploadFromChunks extends UploadFromFile {
                        $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
 
                $dbw = $this->repo->getMasterDB();
-               // Use a quick transaction since we will upload the full temp file into shared
-               // storage, which takes time for large files. We don't want to hold locks then.
                $dbw->update(
                        'uploadstash',
                        [
@@ -247,7 +257,6 @@ class UploadFromChunks extends UploadFromFile {
                        [ 'us_key' => $this->mFileKey ],
                        __METHOD__
                );
-               $dbw->commit( __METHOD__, 'flush' );
        }
 
        /**
index 50bcbc4..1fbdb7d 100644 (file)
@@ -143,24 +143,6 @@ class UploadFromStash extends UploadBase {
                return $this->mFileProps['sha1'];
        }
 
-       /*
-        * protected function verifyFile() inherited
-        */
-
-       /**
-        * Stash the file.
-        *
-        * @param User $user
-        * @return UploadStashFile
-        */
-       protected function doStashFile( User $user = null ) {
-               // replace mLocalFile with an instance of UploadStashFile, which adds some methods
-               // that are useful for stashed files.
-               $this->mLocalFile = parent::doStashFile( $user );
-
-               return $this->mLocalFile;
-       }
-
        /**
         * Remove a temporarily kept file stashed by saveTempUploadedFile().
         * @return bool Success
index 7ba1d91..83cfa40 100644 (file)
@@ -1537,9 +1537,12 @@ class User implements IDBAccessObject {
                foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
                        $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
                }
-               $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
-               foreach ( $namespaces as $nsnum => $nsname ) {
-                       $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
+
+               // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
+               // since extensions may change the set of searchable namespaces depending
+               // on user groups/permissions.
+               foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
+                       $defOpt['searchNs' . $nsnum] = (boolean)$val;
                }
                $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
 
@@ -4187,6 +4190,8 @@ class User implements IDBAccessObject {
         * login credentials aren't being hijacked with a foreign form
         * submission.
         *
+        * The $salt for 'edit' and 'csrf' tokens is the default (empty string).
+        *
         * @since 1.19
         * @param string|array $salt Array of Strings Optional function-specific data for hashing
         * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
index d96710a..8b39d77 100644 (file)
  * @defgroup Language Language
  */
 
-if ( !defined( 'MEDIAWIKI' ) ) {
-       echo "This file is part of MediaWiki, it is not a valid entry point.\n";
-       exit( 1 );
-}
-
 use CLDRPluralRuleParser\Evaluator;
 
 /**
index d10b150..0646f21 100644 (file)
@@ -14,7 +14,9 @@
                        "아라",
                        "Dpk",
                        "Macofe",
-                       "WhatamIdoing"
+                       "WhatamIdoing",
+                       "Hogweard",
+                       "Amire80"
                ]
        },
        "tog-underline": "Mearc under hlencan:",
        "october-date": "$1. Winterfylleðes",
        "november-date": "$1. Blōtmōnaðes",
        "december-date": "$1. Ǣrran Gēolan",
+       "period-am": "on undernmǣl",
+       "period-pm": "on ofernōn",
        "pagecategories": "{{PLURAL:$1|Flocc|Floccas}}",
        "category_header": "Trametas in \"$1\" flocce",
        "subcategories": "Underfloccas",
        "jumptonavigation": "þurhfōr",
        "jumptosearch": "sēcan",
        "view-pool-error": "Wālā, þā þegntōlas nū oferlīce wyrcaþ.\nTō mænige brūcendas gesēcaþ tō sēonne þisne tramet.\nWē biddaþ þæt þū abīde scortne tīman ǣr þū gesēce to sēonne þisne tramet eft.\n\n$1",
+       "pool-queuefull": "Pundfaldes forepenn is full",
        "pool-errorunknown": "Uncūþ wōh",
+       "pool-servererror": "Seo pundaldgetalere þēgnung nis gearo",
        "aboutsite": "Gecȳþness ymbe {{GRAMMAR:wrēgendlīc|{{SITENAME}}}}",
        "aboutpage": "Project:Gefrǣge",
        "copyright": "Man mæg innunge under $1 findan, būton þǣr hit is elles amearcod.",
        "minoredit": "Þēos is lytel adihtung",
        "watchthis": "Behealdan þisne tramet",
        "savearticle": "Hordian tramet",
+       "publishpage": "Geswutele tramet",
        "publishchanges": "Geswutele wrixlung",
        "preview": "Forebysen",
        "showpreview": "Īwan forebysene",
        "watchlisttools-view": "Sēon andwendunga",
        "watchlisttools-edit": "Sēon and adihtan behealdungtæl",
        "watchlisttools-raw": "Adihtan hrēaw behealdungtæl",
-       "signature": "[[{{ns:user}}:$1|$2]]\n([[{{ns:user_talk}}:$1|mōtung]])",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|mōtung]])",
        "version": "Fadung",
        "version-specialpages": "Syndrige trametas",
        "version-other": "Ōðer",
index 80df33f..5e8b4b2 100644 (file)
        "undeletedrevisions-files": "أسترجعت {{PLURAL:$1||مراجعة واحدة|مراجعتان|$1 مراجعات|$1 مراجعة}}  و{{PLURAL:$2||ملف واحد|ملفان|$2 ملفات|$2 ملفًا|$2 ملف}}",
        "undeletedfiles": "أسترجع {{PLURAL:$1||ملف واحد|ملفان|$1 ملفات|$1 ملفًا|$1 ملف}}",
        "cannotundelete": "فشل الاسترجاع؛\n$1",
-       "undeletedpage": "'''تÙ\85 Ø§Ø³ØªØ±Ø¬Ø§Ø¹ $1'''\n\nراجع [[Special:Log/delete|سجÙ\84 Ø§Ù\84حدف]] لمعاينة عمليات الحذف والاسترجاعات الحديثة.",
+       "undeletedpage": "'''تÙ\85 Ø§Ø³ØªØ±Ø¬Ø§Ø¹ $1'''\n\nراجع [[Special:Log/delete|سجÙ\84 Ø§Ù\84حذف]] لمعاينة عمليات الحذف والاسترجاعات الحديثة.",
        "undelete-header": "انظر الصفحات المحذوفة حديثا في [[Special:Log/delete|سجل الحذف]].",
        "undelete-search-title": "البحث في الصفحات المحذوفة",
        "undelete-search-box": "ابحث في الصفحات المحذوفة",
index 4c8c6d0..bd5035f 100644 (file)
        "action-applychangetags": "aplicar etiquetes xunto colos cambios",
        "action-changetags": "amestar y desaniciar etiquetes arbitraries en revisiones individuales y entraes del rexistru",
        "action-deletechangetags": "desaniciar etiquetes de la base de datos",
+       "action-purge": "purgar esta páxina",
        "nchanges": "{{PLURAL:$1|un cambiu|$1 cambios}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|dende la última visita}}",
        "enhancedrc-history": "historial",
        "uploadstash-errclear": "Falló'l desaniciu de los ficheros.",
        "uploadstash-refresh": "Anovar la llista de ficheros",
        "uploadstash-thumbnail": "ver miniatura",
+       "uploadstash-exception": "Nun pudo guardase la subida nel almacén ($1): «$2».",
        "invalid-chunk-offset": "Allugamientu inválidu del fragmentu",
        "img-auth-accessdenied": "Accesu denegáu",
        "img-auth-nopathinfo": "Falta PATH_INFO.\nEl to sirvidor nun ta configuráu pa pasar esta información.\nPue tar basáu en CGI y nun tener sofitu pa img_auth.\nVer https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
        "watchnologin": "Non identificáu",
        "addwatch": "Amestar a la llista de siguimientu",
        "addedwatchtext": "«[[:$1]]» y la so páxina d'alderique amestáronse a la to [[Special:Watchlist|llista de siguimientu]].",
+       "addedwatchtext-talk": "«[[:$1]]» y la so páxina asociada amestáronse a la to [[Special:Watchlist|llista de siguimientu]].",
        "addedwatchtext-short": "Amestóse la páxina «$1» a la to llista de siguimientu.",
        "removewatch": "Desaniciar de la llista de siguimientu",
        "removedwatchtext": "«[[:$1]]» y la so páxina d'alderique desaniciáronse de la to [[Special:Watchlist|llista de siguimientu]].",
+       "removedwatchtext-talk": "«[[:$1]]» y la so páxina asociada desaniciáronse de la to [[Special:Watchlist|llista de siguimientu]].",
        "removedwatchtext-short": "Desanicióse la páxina «$1» de la to llista de siguimientu.",
        "watch": "Vixilar",
        "watchthispage": "Vixilar esta páxina",
        "undeletehistorynoadmin": "Esta páxina foi esborrada. El motivu del esborráu amuésase\nnel resume d'embaxo, amás de detalles de los usuarios qu'editaron esta páxina enantes\nde ser esborrada. El testu actual d'estes revisiones esborraes ta disponible namái pa los alministradores.",
        "undelete-revision": "Revisión esborrada de $1 ($4, a les $5) fecha por $3:",
        "undeleterevision-missing": "Falta la revisión o nun ye válida. Sieque l'enllaz nun seya correutu, o que la\nrevisión fuera restaurada o eliminada del archivu.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Una revisión nun pudo|$1 revisiones nun pudieron}} restaurase porque {{PLURAL:$1|taba usándose la so|taben usándose les sos}} <code>rev_id</code>.",
        "undelete-nodiff": "Nun s'atopó revisión previa.",
        "undeletebtn": "Restaurar",
        "undeletelink": "ver/restaurar",
        "undeletedrevisions": "{{PLURAL:$1|1 revisión restaurada|$1 revisiones restauraes}}",
        "undeletedrevisions-files": "{{PLURAL:$1|1 revisión|$1 revisiones}} y {{PLURAL:$2|1 archivu|$2 archivos}} restauraos",
        "undeletedfiles": "{{PLURAL:$1|1 archivu restauráu|$1 archivos restauraos}}",
-       "cannotundelete": "Falló la restauración:\n$1",
+       "cannotundelete": "Falló total o parcialmente la restauración:\n$1",
        "undeletedpage": "'''Restauróse $1'''\n\nConsulta'l [[Special:Log/delete|rexistru d'esborraos]] pa ver los esborraos y restauraciones de recién.",
        "undelete-header": "Mira nel [[Special:Log/delete|rexistru d'esborraos]] les páxines esborraes recién.",
        "undelete-search-title": "Buscar páxines desaniciaes",
        "sp-contributions-newbies-sub": "Namái les cuentes nueves",
        "sp-contributions-newbies-title": "Contribuciones d'usuariu pa cuentes nueves",
        "sp-contributions-blocklog": "rexistru de bloqueos",
-       "sp-contributions-suppresslog": "collaboraciones del usuariu desaniciaes",
-       "sp-contributions-deleted": "Contribuciones d'usuariu desaniciaes",
+       "sp-contributions-suppresslog": "collaboraciones {{GENDER:$1|del usuariu|de la usuaria}} suprimíes",
+       "sp-contributions-deleted": "collaboraciones {{GENDER:$1|del usuariu|de la usuaria}} desaniciaes",
        "sp-contributions-uploads": "xubes",
        "sp-contributions-logs": "rexistros",
        "sp-contributions-talk": "alderique",
        "authform-notoken": "Falta token",
        "authform-wrongtoken": "Token incorreutu",
        "specialpage-securitylevel-not-allowed-title": "Nun ta permitío",
+       "specialpage-securitylevel-not-allowed": "Sentímoslo, nun tienes permisu pa usar esta páxina porque nun pudo comprobase la to identidá.",
+       "authpage-cannot-login": "Nun pudo empecipiase l'aniciu de sesión.",
+       "authpage-cannot-login-continue": "Nun pudo siguise col aniciu de sesión. Probablemente la sesión caducó.",
+       "authpage-cannot-create": "Nun pudo empecipiase la creación de la cuenta.",
+       "authpage-cannot-create-continue": "Nun pudo siguise la creación de cuenta. Probablemente la sesión caducó.",
+       "authpage-cannot-link": "Nun pudo empecipiase l'enllazáu de la cuenta.",
+       "authpage-cannot-link-continue": "Nun pudo siguise col enllazáu de cuenta. Probablemente la sesión caducó.",
        "cannotauth-not-allowed-title": "Permisu refugáu",
        "cannotauth-not-allowed": "Nun tienes permisu pa usar esta páxina",
+       "changecredentials": "Camudar les credenciales",
+       "changecredentials-submit": "Camudar credenciales",
+       "changecredentials-invalidsubpage": "$1 nun ye un tipu de credencial válidu.",
+       "changecredentials-success": "Camudáronse les tos credenciales.",
+       "removecredentials": "Desaniciar credenciales",
+       "removecredentials-submit": "Desaniciar credenciales",
+       "removecredentials-invalidsubpage": "$1 nun ye un tipu de credencial válidu.",
+       "removecredentials-success": "Desaniciáronse les tos credenciales.",
+       "credentialsform-provider": "Tipu de credenciales:",
        "credentialsform-account": "Nome de la cuenta:",
        "cannotlink-no-provider-title": "Nun hai cuentes enllazables",
        "cannotlink-no-provider": "Nun hai cuentes enllazables.",
        "linkaccounts-success-text": "Enllazóse la cuenta.",
        "linkaccounts-submit": "Enllazar cuentes",
        "unlinkaccounts": "Desenllazar cuentes",
-       "unlinkaccounts-success": "Desenllazóse la cuenta."
+       "unlinkaccounts-success": "Desenllazóse la cuenta.",
+       "authenticationdatachange-ignored": "Nun se xestionó'l cambéu de los datos d'autentificacion. ¿Seique, nun se configuró un fornidor?"
 }
index 026c3f8..3432c23 100644 (file)
        "alllogstext": "{{SITENAME}} üçün bütün mövcud qeydlərin birgə göstərişi.\nQeyd növü, istifadəçi adı və ya təsir edilmiş səhifəni seçməklə daha spesifik ola bilərsiniz.",
        "logempty": "Jurnalda uyğun qeyd tapılmadı.",
        "log-title-wildcard": "Bu mətnlə başlayan başlıqları axtar",
+       "checkbox-select": "Seçin: $1",
+       "checkbox-all": "Hamısı",
+       "checkbox-none": "Heç biri",
+       "checkbox-invert": "Çevir",
        "allpages": "Bütün səhifələr",
        "nextpage": "Sonrakı səhifə ($1)",
        "prevpage": "Əvvəlki səhifə ($1)",
index fdb5b84..f89f294 100644 (file)
        "passwordreset-emailelement": "Імя ўдзельніка: \n$1\n\nЧасовы пароль: \n$2",
        "passwordreset-emailsentemail": "Калі гэты адрас электроннай пошты злучаны з вашым уліковым запісам, будзе адпраўлены ліст пра скід пароля.",
        "passwordreset-emailsentusername": "Калі ёсць адрас электроннай пошты, злучаны з гэтым імем удзельніка, то будзе дасланы ліст пра скід пароля.",
-       "passwordreset-emailsent-capture": "Ніжэй прыведзены адпраўлены ліст пра скід пароля.",
-       "passwordreset-emailerror-capture": "Ніжэй прыведзены створаны ліст пра скід пароля, яго адпраўка не атрымалася па прычыне: $1",
        "passwordreset-invalideamil": "Няслушны адрас электроннай пошты",
        "passwordreset-nodata": "Не былі пададзены ні імя ўдзельніка, ні адрас электроннай пошты",
        "changeemail": "Змяніць або выдаліць адрас электроннай пошты",
        "changeemail-header": "Запоўніце гэтую форму, каб змяніць свой адрас электроннай пошты. Калі хочаце выдаліць адрас электроннай пошты, злучаны з вашым уліковым запісам, пакіньце поле новага адраса электроннай пошты пустым пры адпраўцы формы.",
-       "changeemail-passwordrequired": "Вам трэба будзе ўвесці свой пароль, каб пацвердзіць гэта змяненне.",
        "changeemail-no-info": "Каб звяртацца непасрэдна да гэтай старонкі, вам варта прадставіцца сістэме.",
        "changeemail-oldemail": "Бягучы адрас электроннай пошты:",
        "changeemail-newemail": "Новы адрас электроннай пошты:",
        "undo-nochange": "Выглядае на тое, што праўка ўжо была адкочаная.",
        "undo-summary": "Адкат версіі $1 аўтарства [[Special:Contributions/$2|$2]] ([[User talk:$2|размова]])",
        "undo-summary-username-hidden": "Адкат версіі $1 схаванага ўдзельніка",
-       "cantcreateaccounttitle": "Немагчыма стварыць рахунак",
        "cantcreateaccount-text": "Стварэнне рахункаў было забаронена для гэтага адрасу IP ('''$1''').\n\nЗабарона зроблена ўдзельнікам [[User:$3|$3]], з такім тлумачэннем: ''$2''",
        "cantcreateaccount-range-text": "Стварэнне ўліковага запісу ўдзельніка з IP-адрасоў у дыяпазоне <strong>$1</strong>, што ўключае ваш адрас IP (<strong>$4</strong>), было забаронена {{GENDER:$3|ўдзельнікам|ўдзельніцай}} [[User:$3|$3]].\n\n$3 {{GENDER:$3|патлумачыў|патлумачыла}} гэта так: <em>$2</em>",
        "viewpagelogs": "Паказаць журналы для гэтай старонкі",
        "special-characters-title-endash": "кароткі працяжнік",
        "special-characters-title-emdash": "доўгі працяжнік",
        "special-characters-title-minus": "мінус",
+       "mw-widgets-dateinput-no-date": "Дата не выбрана",
+       "mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе",
+       "mw-widgets-titleinput-description-redirect": "перанакіраванне на $1",
        "log-action-filter-all": "Усе",
        "log-action-filter-block-block": "заблакаваць",
        "log-action-filter-block-reblock": "Змена блакіроўкі",
index f7f6f55..80279a5 100644 (file)
        "recentchanges-legend-heading": "<strong>Легенда:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (вижте също [[Special:NewPages|списъка с нови страници]])",
        "recentchanges-submit": "Покажи",
-       "rcnotefrom": "Дадени са промените от <strong>$2</strong> (до <strong>$1</strong> показани).",
+       "rcnotefrom": "{{PLURAL:$5|Дадена е промяната|Дадени са промените}} от <strong>$3, $4</strong> (до <strong>$1</strong> показани).",
        "rclistfrom": "Показване на промени, като се започва от $3 $2",
        "rcshowhideminor": "$1 на малки промени",
        "rcshowhideminor-show": "Показване",
        "uploadstash-summary": "Тази страница предоставя достъп до файловете, които са качени (или са в процес на качване), но все още не са публикувани в уикито. Тези файлове не са достъпни само за потребителя, който ги е качил.",
        "uploadstash-clear": "Изчистване на скритите качвания",
        "uploadstash-nofiles": "Нямате скрити файлове",
-       "uploadstash-badtoken": "Ð\98звÑ\8aÑ\80Ñ\88ване Ð½Ð° Ñ\82ова Ð´ÐµÐ¹Ñ\81Ñ\82вие Ðµ Ð½ÐµÑ\83Ñ\81пеÑ\88но, Ð²ÐµÑ\80оÑ\8fÑ\82но Ð·Ð°Ñ\80ади Ð¸Ð·Ñ\82екла Ñ\81еÑ\81иÑ\8f. Ð\9eпитайте отново.",
+       "uploadstash-badtoken": "Ð\98звÑ\8aÑ\80Ñ\88ване Ð½Ð° Ñ\82ова Ð´ÐµÐ¹Ñ\81Ñ\82вие Ðµ Ð½ÐµÑ\83Ñ\81пеÑ\88но, Ð²ÐµÑ\80оÑ\8fÑ\82но Ð·Ð°Ñ\80ади Ð¸Ð·Ñ\82екла Ñ\81еÑ\81иÑ\8f. Ð\9cолÑ\8f, Ð¾питайте отново.",
        "uploadstash-errclear": "Изчистването на файловете беше неуспешно.",
        "uploadstash-refresh": "Обновяване на списъка с файлове",
        "img-auth-accessdenied": "Достъпът е отказан",
index 97596c4..aa81ac2 100644 (file)
        "right-changetags": "নির্দিষ্ট সংস্করণ এবং দীর্ঘ সম্পাদনাগুলোতে [[Special:Tags|ট্যাগ]] সংযোজন ও অপসারণ করুন",
        "right-deletechangetags": "ডাটাবেজ থেকে [[Special:Tags|ট্যাগ]] অপসারণ করা",
        "grant-group-email": "ইমেইল পাঠান",
+       "grant-group-private-information": "আপনার সম্পর্কিত ব্যক্তিগত তথ্যে প্রবেশাধিকার পায়",
        "grant-group-other": "বিবিধ কার্যকলাপ",
        "grant-createaccount": "অ্যাকাউন্ট তৈরি করুন",
        "grant-createeditmovepage": "পাতা তৈরি, সম্পাদনা এবং স্থানান্তর করুন",
        "grant-editmyoptions": "আপনার ব্যবহারকারী পছন্দসমূহ সম্পাদনা করুন",
        "grant-editmywatchlist": "আপনার নজরতালিকা সম্পাদনা করুন",
        "grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
+       "grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
        "grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
        "grant-uploadfile": "নতুন ফাইল আপলোড করুন",
        "grant-basic": "মৌলিক অধিকার",
        "log-action-filter-rights-autopromote": "স্বয়ংক্রিয় পরিবর্তন",
        "log-action-filter-upload-upload": "নতুন আপলোড",
        "log-action-filter-upload-overwrite": "পুনঃআপলোড",
+       "authmanager-create-from-login": "আপনার একাউন্ট তৈরি করতে, নীচের ক্ষেত্রগুলি পূরণ করুন।",
        "authmanager-authplugin-setpass-failed-title": "পাসওয়ার্ড পরিবর্তন ব্যর্থ হয়েছে",
        "authmanager-authplugin-setpass-bad-domain": "অবৈধ ডোমেইন।",
        "authmanager-autocreate-noperm": "স্বয়ংক্রিয় অ্যাকাউন্ট সৃষ্টি মঞ্জুরিপ্রাপ্ত নয়।",
index 052c3ce..1fb2401 100644 (file)
        "editlink": "دەستکاری",
        "viewsourcelink": "بینینی سەرچاوە",
        "editsectionhint": "دەستکاریکردنی بەش: $1",
-       "toc": "Ù\86اÙ\88Û\95Ú\95Û\86Ú©",
+       "toc": "Ù¾Û\8eرست",
        "showtoc": "نیشانیبدە",
        "hidetoc": "بیشارەوە",
        "collapsible-collapse": "کۆی بکەوە",
index 8b0d350..7e9eecf 100644 (file)
@@ -33,7 +33,8 @@
                        "Matma Rex",
                        "Dvorapa",
                        "Walter Klosse",
-                       "Martin Urbanec"
+                       "Martin Urbanec",
+                       "Marek Pavlica"
                ]
        },
        "tog-underline": "Podtrhávat odkazy:",
        "grant-group-high-volume": "Velkoobjemové činnosti",
        "grant-group-customization": "Nastavení a přizpůsobení",
        "grant-group-administration": "Provádění správcovských činností",
+       "grant-group-private-information": "Přístup k soukromým údajům o vás",
        "grant-group-other": "Různé činnosti",
        "grant-blockusers": "Blokovat a odblokovávat uživatele",
        "grant-createaccount": "Zakládat účty",
        "grant-highvolume": "Hromadné editace",
        "grant-oversight": "Skrývat uživatele a utajovat revize",
        "grant-patrol": "Patrolovat změny stránek",
+       "grant-privateinfo": "Přístup k soukromým údajům",
        "grant-protect": "Zamykat a odemykat stránky",
        "grant-rollback": "Vracet editace zpět",
        "grant-sendemail": "Posílat e-maily ostatním uživatelům",
        "uploadstash-errclear": "Soubory se nepodařilo vymazat.",
        "uploadstash-refresh": "Aktualizovat seznam souborů",
        "uploadstash-thumbnail": "zobrazit náhled",
+       "uploadstash-exception": "Načtený soubor se nepodařilo uložit do skrýše ($1): „$2“.",
        "invalid-chunk-offset": "Neplatný posun bloku",
        "img-auth-accessdenied": "Přístup odepřen",
        "img-auth-nopathinfo": "Chybí PATH_INFO.\nVáš server není nastaven tak, aby tuto informaci poskytoval.\nMožná funguje pomocí CGI a img_auth na něm nemůže fungovat.\nVizte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "trackingcategories-name": "Název hlášení",
        "trackingcategories-desc": "Kritéria pro vložení do kategorie",
        "restricted-displaytitle-ignored": "Stránky s ignorovanými zobrazovanými názvy",
-       "restricted-displaytitle-ignored-desc": "Stránka obsahuje příkaz <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, který se ignoruje, protože není ekvivalentní skutečnému názvu stránky.",
+       "restricted-displaytitle-ignored-desc": "Stránka obsahuje příkaz <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, který se ignoruje, neboť není ekvivalentní skutečnému názvu stránky.",
        "noindex-category-desc": "Stránka není indexována roboty, protože obsahuje kouzelné slovo <code><nowiki>__NOINDEX__</nowiki></code> a je ve jmenném prostoru, ve kterém je tento příznak dovolen.",
        "index-category-desc": "Stránka obsahuje kouzelné slovo <code><nowiki>__INDEX__</nowiki></code> (a je ve jmenném prostoru, ve kterém je tento příznak dovolen), takže je indexována roboty, přestože by normálně nebyla.",
        "post-expand-template-inclusion-category-desc": "Stránka je po rozbalení všech šablon větší než <code>$wgMaxArticleSize</code>, takže některé šablony rozbaleny nebyly.",
index 49e0a22..8a8fe7b 100644 (file)
        "grant-group-high-volume": "Massenaktivitäten ausführen",
        "grant-group-customization": "Anpassung und Einstellungen",
        "grant-group-administration": "Administrative Aktionen ausführen",
+       "grant-group-private-information": "Auf private Daten über dich zugreifen",
        "grant-group-other": "Verschiedene Aktivitäten",
        "grant-blockusers": "Benutzer sperren und freigeben",
        "grant-createaccount": "Benutzerkonten erstellen",
        "grant-highvolume": "Massenbearbeitungen",
        "grant-oversight": "Benutzer verstecken und Versionen unterdrücken",
        "grant-patrol": "Änderungen an Seiten kontrollieren",
+       "grant-privateinfo": "Auf private Informationen zugreifen",
        "grant-protect": "Seiten schützen und freigeben",
        "grant-rollback": "Änderungen an Seiten zurücksetzen",
        "grant-sendemail": "E-Mails an andere Benutzer versenden",
index ae2cd38..7ca8358 100644 (file)
        "august-date": "Tebaxe $1",
        "september-date": "Keşkelun $1",
        "october-date": "Tışrino Verên $1",
-       "november-date": "$1 Kelverdan",
+       "november-date": "Tışrino Peyên $1",
        "december-date": "Kanun $1",
        "period-am": "AM",
        "period-pm": "PM",
        "anontalk": "Werênayış",
        "navigation": "Pusula",
        "and": "&#32;u",
-       "qbfind": "Bıvêne",
+       "qbfind": "Bıvin",
        "qbbrowse": "Çım ra viyarne",
        "qbedit": "Bıvurne",
        "qbpageoptions": "Ena pele",
        "talkpagelinktext": "werênayış",
        "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê şexsiy",
-       "articlepage": "Pela zerreki bıvêne",
+       "articlepage": "Pera zerreki bıvin",
        "talk": "Werênayış",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "imagepage": "Pera dosya bıasne",
        "mediawikipage": "Pera mesaci bıasne",
        "templatepage": "Pera şabloni bıasne",
-       "viewhelppage": "Pela peşti bıvêne",
+       "viewhelppage": "Pera peşti bıvin",
        "categorypage": "Pela kategoriya bıasne",
        "viewtalkpage": "Werênayışi bıvêne",
        "otherlanguages": "Zıwananê binan de",
        "redirectedfrom": "($1 ra kırışı yê)",
        "redirectpagesub": "Pela berdışi",
        "redirectto": "Beno hetê:",
-       "lastmodifiedat": "Ena pele tewr peyên roca $1, seate $2 de rocneya.",
+       "lastmodifiedat": "Per roca $1, sehat $2 de biye neye.",
        "viewcount": "Ena pele {{PLURAL:$1|rae|$1 rey}} vêniya.",
        "protectedpage": "Pela pawıtiye",
        "jumpto": "Şo be:",
        "confirmable-yes": "Eya",
        "confirmable-no": "Nê",
        "thisisdeleted": "Bıvêne ya zi $1 peyser biya?",
-       "viewdeleted": "$1 bıvêne?",
+       "viewdeleted": "$1 bıvin?",
        "restorelink": "{{PLURAL:$1|jew vurnayış besteriya|$1 vurnayışi besteriyaye}}",
        "feedlinks": "Warikerdış:",
        "feed-invalid": "Qeydey cıresnayışê  beğşi nêvêreno.",
        "perfcached": "Datay cı ver hazır biye. No semedê ra nıkayin niyo! tewr zaf {{PLURAL:$1|netice|$1 netice}} debêno de",
        "perfcachedts": "Cêr de malumatê nımıteyi esti, demdê newe kerdışo peyın: $1. Tewr zaf {{PLURAL:$4|netice|$4 neticey cı}} debyayo de",
        "querypage-no-updates": "Rocanebiyayışê na pele nıka cadayiyê.\nDayiyi tiya nıka newe nêbenê.",
-       "viewsource": "Çımey bıvêne",
+       "viewsource": "Çemi bıvin",
        "viewsource-title": "Cı geyrayışê $1'i bıvin",
        "actionthrottled": "Kerden peysnaya",
        "actionthrottledtext": "Riyê tedbirê anti-spami ra,  wextê do kılmek de şıma nê fealiyeti nêşkenê zaf zêde bıkerê, şıma ki no hedi viyarna ra.\nÇend deqey ra tepeya reyna bıcerrebnên.",
        "nologinlink": "Yew hesab ake",
        "createaccount": "Hesab vıraze",
        "gotaccount": "Hesabê şıma esto? '''$1'''.",
-       "gotaccountlink": "Ronıştış ak",
+       "gotaccountlink": "Cıkewtış",
        "userlogin-resetlink": "Melumatê cıkewtışi xo vira kerdê?",
        "userlogin-resetpassword-link": "Parola xo kerda xo vira?",
        "userlogin-helplink2": "Heqa qeydbiyayışi de peşti bıgêrên",
        "botpasswords-label-appid": "Nameyê boti:",
        "botpasswords-label-create": "Vıraze",
        "botpasswords-label-update": "Rocane ke",
-       "botpasswords-label-cancel": "Bıtexelne",
+       "botpasswords-label-cancel": "İbtal ke",
        "botpasswords-label-delete": "Bestere",
        "botpasswords-label-resetpassword": "Parola raçarne",
        "botpasswords-label-grants-column": "Dayen",
        "resetpass_forbidden": "parolayi nêvuryayi",
        "resetpass-no-info": "şıma gani hesab akere u hona bıeşke bırese cı",
        "resetpass-submit-loggedin": "Parola bıvurne",
-       "resetpass-submit-cancel": "Bıtexelne",
+       "resetpass-submit-cancel": "İbtal ke",
        "resetpass-wrong-oldpass": "parolayo parola maqbul niyo.\nşıma ya parolaye xo vurnayo ya zi parolayo muwaqqat waşto.",
        "resetpass-recycled": "Parolaya şımaya newiye wa paroloya şımaya verêne ra ferqıne bo.",
        "resetpass-temp-emailed": "E postaya rışyayê yubkoda şıma ronıştış akerdo.  Ronıştışi xo temammkerdışi rê yu parolaya newi lazım a",
        "hr_tip": "Xeta verardiye (teserrufın bıgureyne/bıxebetne)",
        "summary": "Xulasa:",
        "subject": "Mewzu:",
-       "minoredit": "No yew vurnayışo werdiyo",
-       "watchthis": "Ena pele seyr ke",
-       "savearticle": "Pele qeyd ke",
+       "minoredit": "Vurriyayışo werdiyo",
+       "watchthis": "Seyr kı",
+       "savearticle": "Qeyd kı",
        "savechanges": "Vurnayışan qeyd ke",
        "publishpage": "Perer bıhesırne",
        "publishchanges": "Vurnayışa vıla ke",
        "accmailtext": "[[User talk:$1|$1]] parolayo ke raşt ameyo şırawiyo na adres $2.\n\nQey na hesabê newe parola, cıkewtış dıma şıma eşkeni na qısım de ''[[Special:ChangePassword|parola bıvurn]]'' bıvurni.",
        "newarticle": "(Newe)",
        "newarticletext": "To yew gıre tıkna be ra yew pela ke hewna çıniya.\nSeba afernayışê pele ra, qutiya metnê cêrêni bıgurene (seba melumati qaytê [$1 pela peşti] ke).\nEke be ğeletine ameya tiya, wa gocega <strong>peyser</strong>i programê xo de bıtıkne.",
-       "anontalkpagetext": "----''No pel, pel o karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres şuxulneni û ney IP adresan herkes eşkeno bıvino. Eke şıma qayil niye ina bo xo ri [[Special:CreateAccount|yew hesab bıvıraze]] veyaxut [[Special:UserLogin|hesab akere]].''",
+       "anontalkpagetext": "----''Na per, perêk kı karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres xebetneno û ney IP adresan herkes nêşeno bıvino. Eke şıma qayil niye ina bo xorê [[Special:CreateAccount|yew hesab bıvıraze]] veya xut [[Special:UserLogin|hesab akere]].''",
        "noarticletext": "Ena pele de hewna theba çıniyo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê  sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
        "noarticletext-nopermission": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê na pele cı geyre]], ya zi <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre]</span>, ema destur çıniyo ke na pele vırazê.",
        "missing-revision": "Rewizyonê name dê pela da #$1 \"{{FULLPAGENAME}}\" dı çıniyo.\n\nNo normal de tarix dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
        "next-page": "Pela peyên",
        "prevn-title": "$1o verên  {{PLURAL:$1|netice|neticeyan}}",
        "nextn-title": "$1o ke yeno {{PLURAL:$1|netice|neticey}}",
-       "shown-title": "bimocne $1î  {{PLURAL:$1|netice|neticeyan}} ser her pel",
+       "shown-title": "Herg per sero $1 {{PLURAL:$1|netici|netica}} bıasne",
        "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bıvênên",
        "searchmenu-exists": "''Ena 'Wikipediya de ser \"[[:$1]]\" yew pel esto'''",
        "searchmenu-new": "<strong>Na wiki de pela \"[[:$1]]\" vıraze!</strong> {{PLURAL:$2|0=|Sewbina pela ke şıma geyrayê cı aye bıvênê.|Yew zi neticanê cıgeyrayışê xo bıvênê.}}",
        "rightslogtext": "Ena listeyê loganê ke heqqa karbaranî mucneno.",
        "action-read": "ena pela wanayış",
        "action-edit": "ena pela bıvurnê",
-       "action-createpage": "Ena perer bıvıraze",
+       "action-createpage": "na perer bıvıraz",
        "action-createtalk": "pelanê werênayışi bıvıraze",
        "action-createaccount": "hesabê nê karberi bıvıraze",
        "action-autocreateaccount": "nê hesabê karberiyê teberi otomatik vıraze",
        "enhancedrc-history": "tarix",
        "recentchanges": "Vurriyayışê peyêni",
        "recentchanges-legend": "Tercihê vurnayışanê peyênan",
-       "recentchanges-summary": "\"Wiki sero vurnayışanê peyênan ena perer ra teqib ke.\"\n{{vp-diq}}",
+       "recentchanges-summary": "Wiki sero vurriyayışê peyêni asenê.",
        "recentchanges-noresult": "Goreyê kriteranê kıfşkerdeyan ra qet yew vurnayış nêvêniya.",
        "recentchanges-feed-description": "Ena feed dı vurnayişanê tewr peniyan teqip bık.",
        "recentchanges-label-newpage": "Enê vurnayışi ra yew pela newiye vıraziye",
-       "recentchanges-label-minor": "No yew vurnayışo werdiyo",
+       "recentchanges-label-minor": "Vurriyayışo werdiyo",
        "recentchanges-label-bot": "Eno vurnayış terefê yew boti ra vıraziyo",
        "recentchanges-label-unpatrolled": "Eno vurnayış hewna dewriya nêbiyo",
        "recentchanges-label-plusminus": "Ebadê pele de bazê bayti de vayeyê cı",
        "rcshowhidecategorization": "kategorizasyonê pele $1",
        "rcshowhidecategorization-show": "Bıasne",
        "rcshowhidecategorization-hide": "Bınımne",
-       "rclinks": "$2 rocan de $1 vurriyayışanê peyêna bıasne <br />$3",
+       "rclinks": "Peyniya $2 rocan de $1 vurriyayışê <br />$3 asenê",
        "diff": "ferq",
        "hist": "verên",
        "hide": "Bınımne",
        "removedwatchtext": "Ena pela \"[[:$1]]\" biya wedariya [[Special:Watchlist|listeyê seyr-kerdışi şıma]].",
        "removedwatchtext-short": "Pera $1`i listeya seyran de şıma ra wedari yê",
        "watch": "Seyr ke",
-       "watchthispage": "Ena pele seyr ke",
+       "watchthispage": "Seyr kı",
        "unwatch": "Teqib meke",
        "unwatchthispage": "temaşa kerdışê peli vındarn.",
        "notanarticle": "mebhesê peli niyo",
        "undeleterevision-missing": "revizyonê nemeqbul u vindbiyayeyi.\nRevizyoni ya hewn a biyê ya arşiw ra veciyayê ya zi cıresayişê şımayi şaş o.",
        "undelete-nodiff": "revizyonê verıni nidiya",
        "undeletebtn": "Timar bike",
-       "undeletelink": "bıvêne/peyser biya",
+       "undeletelink": "bıewni/peyser biya",
        "undeleteviewlink": "bıvin",
        "undeleteinvert": "Weçinayışi dimlaşt ke",
        "undeletecomment": "Sebeb:",
        "unblocked-id": "Blokê $1î wedariyayo",
        "blocklist": "Karberê kılitbiyayey",
        "ipblocklist": "Karberê kılitbiyayey",
-       "ipblocklist-legend": "Yew karberê kılitbiyayey bıvêne",
+       "ipblocklist-legend": "Karberê kılit biyayey bıvin",
        "blocklist-userblocks": "Kılitkerdışê hesaban bınımne",
        "blocklist-tempblocks": "Kılitkerdışan mıweqet bınımne",
        "blocklist-addressblocks": "Tenya kılitkerdışanê IPy bınımne",
        "articleexists": "Ena nameyê pela database ma dı esta ya zi tı raşt nınuşt. .\nYewna name bınus.",
        "cantmove-titleprotected": "şıma nêşkeni yew peli bıhewelnê tiya çunke pawıyeno",
        "movetalk": "Pela werênayışiê elaqedare bere",
-       "move-subpages": "pelê bınini bıkırış($1 heta tiya)",
-       "move-talk-subpages": "pelê bınini yê pelê werê ameyeşi bıkırış ($1 heta tiya)",
+       "move-subpages": "Peranê bınênan bıkırış (hetana $1)",
+       "move-talk-subpages": "Bın peranê peranê vatena bıkırış (hetana $1)",
        "movepage-page-exists": "maddeya $1i ca ra esta u newe ra otomatikmen nênusyena.",
        "movepage-page-moved": "pelê $1i kırışiya pelê $2i.",
        "movepage-page-unmoved": "pelê $1i nêkırışiyeno sernameyê $2i.",
        "anonymous": "{{PLURAL:$1|karberê|karberê}} anonimi yê keyepelê {{SITENAME}}i",
        "siteuser": "karberê {{SITENAME}}i $1",
        "anonuser": "karberê anonim o {{SITENAME}}i $1",
-       "lastmodifiedatby": "Ena pele tewr peyên roca $2, $1 de tereftê $3 ra vuriya ya.",
+       "lastmodifiedatby": "Ena per tewr peyên roca $2, $1 de terefê $3 ra vurmaya ya.",
        "othercontribs": "xebatê $1 ıney geriyayo diqqeti/geriyayo nezer.",
        "others": "bini",
        "siteusers": "{{SITENAME}} {{PLURAL:$2|karberê ey|karberanê ey}} $1",
        "fileduplicatesearch-result-1": "Dosyayê ''$1î'' de hem-kopya çini yo.",
        "fileduplicatesearch-result-n": "Dosyayê ''$1î'' de {{PLURAL:$2|1 hem-kopya|$2 hem-kopyayî'}} esto.",
        "fileduplicatesearch-noresults": "Ebe namey \"$1\" ra dosya nêdiyayê.",
-       "specialpages": "Pelê xısusiyi",
+       "specialpages": "Page bağsey",
        "specialpages-note-top": "Kıtabek",
        "specialpages-note": "* Pelê xasê normali.\n* <span class=\"mw-specialpagerestricted\">Pelê xasê nımıtey.</span>",
        "specialpages-group-maintenance": "Raporê pawıtışi",
        "feedback-bugcheck": "Harika! Sadece [xırabina ke $1 ] çınyayışê cı kontrol keno.",
        "feedback-bugnew": "Mı qontrol ke. Xetaya newi xeber ke",
        "feedback-bugornote": "Jew mersela teferruato teknik esta şıma reca malumatê şıma hazıro se [ $1  jew xırab rapor] bıvinê.Zewbi zi, formê cerê xo rê şenê karfiyê. Vatışê xo pela da \"[ $3  $2 ]\", namey karber dê xoya piya u wasteriya karfiye.",
-       "feedback-cancel": "Bıtexelne",
+       "feedback-cancel": "İbtal kı",
        "feedback-close": "Biya star",
        "feedback-error1": "Xeta: API ra neticey ne vıcyay",
        "feedback-error2": "Xeta: Timar kerdış nebı",
index cf5710a..e1c37c8 100644 (file)
        "grant-group-high-volume": "Perform high volume activity",
        "grant-group-customization": "Customization and preferences",
        "grant-group-administration": "Perform administrative actions",
+       "grant-group-private-information": "Access private data about you",
        "grant-group-other": "Miscellaneous activity",
        "grant-blockusers": "Block and unblock users",
        "grant-createaccount": "Create accounts",
        "grant-highvolume": "High-volume editing",
        "grant-oversight": "Hide users and suppress revisions",
        "grant-patrol": "Patrol changes to pages",
+       "grant-privateinfo": "Access private information",
        "grant-protect": "Protect and unprotect pages",
        "grant-rollback": "Rollback changes to pages",
        "grant-sendemail": "Send email to other users",
index 323671c..10e56f2 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "Malplena objeto",
        "content-json-empty-array": "Malplena tabelo",
-       "deprecated-self-close-category": "Paĝoj kun nevalida memferma HTML‑etikedon",
+       "deprecated-self-close-category": "Paĝoj kun nevalida memferma HTML‑etikedo",
        "duplicate-args-warning": "'''Averto:''' [[:$1]] vokas al [[:$2]] kun pli ol unu valoro por la parametro \"$3\". Nur la lasta liverita valoro estos uzata.",
        "duplicate-args-category": "Paĝoj kun pluroblaj argumentoj en ŝablonvokoj",
        "duplicate-args-category-desc": "La paĝo enhavas uzon de ŝablono kun pluroble uzitaj argumentoj, kiel ekzemple <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> aŭ <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
index cd16fd1..ac4112f 100644 (file)
        "logentry-protect-modify-cascade": "$1 سطح حفاظت برای $3 $4 را {{GENDER:$2|تغییر داد}}[آبشاری]",
        "logentry-rights-rights": "$1 دسترسی $3 را از گروه $4 به $5 تغییر داد",
        "logentry-rights-rights-legacy": "$1 گروه عضویت $3 را {{GENDER:$2|تغییر داد}}",
-       "logentry-rights-autopromote": "$1 به طور خودکار از $4 به $5 {{GENDER:$2|ارتقاء داد}}",
+       "logentry-rights-autopromote": "$1 به طور خودکار از $4 به $5 {{GENDER:$2|ارتقاء یافت}}",
        "logentry-upload-upload": "$1 $3 را {{GENDER:$2|بارگذاری کرد}}",
        "logentry-upload-overwrite": "$1 نسخهٔ تازه‌ای از $3 را {{GENDER:$2|بارگذاری کرد}}",
        "logentry-upload-revert": "$1 {{GENDER:$2|بارگذاری کرد}} $3",
index dbc0cf1..f145ca9 100644 (file)
        "grant-group-high-volume": "Effectuer une activité de fort volume",
        "grant-group-customization": "Personnalisation et préférences",
        "grant-group-administration": "Effectuer des actions administratives",
+       "grant-group-private-information": "Accéder à vos données privées",
        "grant-group-other": "Activités diverses",
        "grant-blockusers": "Bloquer et débloquer des utilisateurs",
        "grant-createaccount": "Créer des comptes",
        "grant-highvolume": "Modification de gros volumes",
        "grant-oversight": "Masquer les utilisateurs et supprimer les révisions",
        "grant-patrol": "Vérifier les modifications de pages",
+       "grant-privateinfo": "Accéder aux informations privées",
        "grant-protect": "Protéger et déprotéger des pages",
        "grant-rollback": "Révoquer des modifications sur des pages",
        "grant-sendemail": "Envoyer des courriels aux autres utilisateurs",
        "upload_directory_missing": "Le répertoire d’import de fichier ($1) est introuvable et n’a pas pu être créé par le serveur web.",
        "upload_directory_read_only": "Le serveur web n’a pas accès en écriture au répertoire d’import de fichier ($1).",
        "uploaderror": "Erreur lors de l’import",
-       "upload-recreate-warning": "'''Attention : Un fichier portant ce nom a été supprimé ou déplacé.'''\n\nLe journal des suppressions et celui des déplacements de cette page sont affichés ici pour informations :",
+       "upload-recreate-warning": "<strong>Attention : Un fichier portant ce nom a été supprimé ou déplacé.</strong>\n\nLe journal des suppressions et celui des déplacements de cette page sont affichés ici pour informations :",
        "uploadtext": "Utilisez ce formulaire pour importer des fichiers sur le serveur.\nPour voir ou rechercher des images précédemment envoyées, consultez la [[Special:FileList|liste des images]]. L’import est aussi enregistré dans le [[Special:Log/upload|journal d’import des fichiers]], et les suppressions dans le [[Special:Log/delete|journal des suppressions]].\n\nPour inclure un fichier dans une page, utilisez un lien de la forme :\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:fichier.jpg]]</nowiki></code>''', pour afficher le fichier en pleine résolution (dans le cas d’une image) ;\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:fichier.png|200px|thumb|left|texte descriptif]]</nowiki></code>''' pour utiliser une miniature de 200 pixels de large dans une boîte à gauche avec « texte descriptif » comme description ;\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:fichier.ogg]]</nowiki></code>''' pour lier directement vers le fichier sans l’afficher.",
        "upload-permitted": "{{PLURAL:$2|Format|Formats}} de fichiers {{PLURAL:$2|autorisé|autorisés}} : $1.",
        "upload-preferred": "{{PLURAL:$2|Format|Formats}} de fichiers {{PLURAL:$2|préféré|préférés}} : $1.",
        "unknown-error": "Une erreur inconnue s’est produite.",
        "tmp-create-error": "Impossible de créer le fichier temporaire.",
        "tmp-write-error": "Erreur d'écriture du fichier temporaire.",
-       "large-file": "Les fichiers importés ne devraient pas dépasser $1 ; ce fichier fait $2.",
-       "largefileserver": "La taille de ce fichier est supérieure au maximum autorisé.",
+       "large-file": "Les fichiers importés ne devraient pas dépasser $1 ; \nce fichier fait $2.",
+       "largefileserver": "La taille de ce fichier est supérieure au maximum autorisé par le serveur.",
        "emptyfile": "Le fichier que vous voulez importer semble vide.\nCeci peut être dû à une erreur dans le nom du fichier.\nVeuillez vérifier que vous désirez vraiment importer ce fichier.",
        "windows-nonascii-filename": "Ce wiki ne supporte pas les noms de fichiers avec des caractères spéciaux.",
        "fileexists": "Un fichier existe déjà sous ce nom.\nMerci de vérifier <strong>[[:$1]]</strong> si vous n'êtes pas certain{{GENDER:||e|}} de vouloir le remplacer.\n[[$1|thumb]]",
-       "filepageexists": "La page de description pour ce fichier a déjà été créée ici <strong>[[:$1]]</strong>, mais aucun fichier n'existe actuellement sous ce nom.\nLe résumé que vous allez spécifier n'apparaîtra pas sur la page de description.\nPour que ce soit le cas, vous devrez modifier manuellement la page. [[$1|thumb]]",
+       "filepageexists": "La page de description pour ce fichier a déjà été créée ici <strong>[[:$1]]</strong>, mais aucun fichier n'existe actuellement sous ce nom.\nLe résumé que vous allez spécifier n'apparaîtra pas sur la page de description.\nPour que ce soit le cas, vous devrez modifier manuellement la page. \n[[$1|thumb]]",
        "fileexists-extension": "Un fichier existe avec un nom proche : [[$2|thumb]]\n* Nom du fichier à importer : <strong>[[:$1]]</strong>\n* Nom du fichier existant : <strong>[[:$2]]</strong>\nPeut-être voulez-vous utiliser un nom plus explicite ?",
        "fileexists-thumbnail-yes": "Le fichier semble être une image en taille réduite <em>(vignette)</em>. \n[[$1|thumb]]\nVeuillez vérifier le fichier <strong>[[:$1]]</strong>.\nSi le fichier vérifié est la même image avec la taille initiale, il n'y a pas besoin d'importer une version réduite.",
        "file-thumbnail-no": "Le nom du fichier commence par <strong>$1</strong>.\nIl est possible qu'il s'agisse d'une version réduite <em>(vignette)</em>.\nSi vous disposez du fichier en haute résolution, importez-le, sinon veuillez modifier son nom.",
        "uploadscripted": "Ce fichier contient du code HTML ou un script qui pourrait être interprété de façon incorrecte par un navigateur web.",
        "upload-scripted-pi-callback": "Impossible de charger un fichier qui contient des instructions de traitement de feuille de style XML.",
        "uploaded-script-svg": "Élément scriptable « $1 » trouvé dans le fichier SVG téléchargé.",
-       "uploaded-hostile-svg": "CSS non sûr trouvé dans l’élément style d’un fichier SVG téléchargé.",
+       "uploaded-hostile-svg": "CSS non sûr trouvé dans l’élément style d’un fichier SVG téléversé.",
        "uploaded-event-handler-on-svg": "Fixer des attributs de gestionnaire d’événement <code>$1=\"$2\"</code> n’est pas autorisé dans les fichiers SVG.",
        "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": "href vers des données non sûres trouvé dans le fichier SVG téléchargé : URI cible <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "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-href-svg": "L’utilisation de la balise « set » pour ajouter un attribut « href » à l’élément parent est interdite.",
        "nolinkstoimage": "Aucune page n'utilise ce fichier.",
        "morelinkstoimage": "Voir [[Special:WhatLinksHere/$1|plus de liens]] vers ce fichier.",
        "linkstoimage-redirect": "$1 (redirection de fichier) $2",
-       "duplicatesoffile": "{{PLURAL:$1|Le fichier suivant est un duplicata|Les fichiers suivants sont des duplicatas}} de celui-ci ([[Special:FileDuplicateSearch/$2|plus de détails]]) :",
+       "duplicatesoffile": "{{PLURAL:$1|Le fichier suivant est un doublon|Les $1 fichiers suivants sont des doublons}} de celui-ci ([[Special:FileDuplicateSearch/$2|plus de détails]]) :",
        "sharedupload": "Ce fichier provient de : $1. Il peut être utilisé par d'autres projets.",
        "sharedupload-desc-there": "Ce fichier provient de : $1. Il peut être utilisé par d'autres projets.\nVeuillez consulter [$2 sa page de description] pour plus d'informations.",
        "sharedupload-desc-here": "Ce fichier provient de $1. Il peut être utilisé par d'autres projets.\nSa description sur sa [$2 page de description] est affichée ci-dessous.",
        "filedelete-intro-old": "Vous êtes en train d'effacer la version de '''[[Media:$1|$1]]''' du [$4 $2 à $3].",
        "filedelete-comment": "Motif :",
        "filedelete-submit": "Supprimer",
-       "filedelete-success": "'''$1''' a été supprimé.",
-       "filedelete-success-old": "La version de '''[[Media:$1|$1]]''' du $2 à $3 a été supprimée.",
-       "filedelete-nofile": "'''$1''' n'existe pas.",
-       "filedelete-nofile-old": "Il n'existe aucune version archivée de '''$1''' avec les attributs indiqués.",
+       "filedelete-success": "<strong>$1</strong> a été supprimé.",
+       "filedelete-success-old": "La version de <strong>[[Media:$1|$1]]</strong> du $2 à $3 a été supprimée.",
+       "filedelete-nofile": "<strong>$1</strong> n'existe pas.",
+       "filedelete-nofile-old": "Il n'existe aucune version archivée de <strong>$1</strong> avec les attributs indiqués.",
        "filedelete-otherreason": "Motif autre / supplémentaire :",
        "filedelete-reason-otherlist": "Autre motif",
        "filedelete-reason-dropdown": "* Motifs fréquents de suppression de fichiers\n** Violation du droit d'auteur\n** Fichier dupliqué",
-       "filedelete-edit-reasonlist": "Modifier les motifs fréquents de suppression",
-       "filedelete-maintenance": "La suppression et restauration de fichiers est temporairement désactivée durant la maintenance.",
+       "filedelete-edit-reasonlist": "Modifier les motifs de suppression",
+       "filedelete-maintenance": "La suppression et la restauration de fichiers sont  temporairement désactivées durant la maintenance.",
        "filedelete-maintenance-title": "Impossible de supprimer le fichier",
        "mimesearch": "Recherche par type de contenu MIME",
        "mimesearch-summary": "Cette page vous permet de filtrer les fichiers par leur type de contenu MIME.\nEntrée : type_de_contenu/sous-type ou type_de_contenu/*, par ex. <code>image/jpeg</code>.",
        "randompage-nopages": "Il n'y a aucune page dans {{PLURAL:$2|l'espace de noms|les espaces de noms}} : $1.",
        "randomincategory": "Page au hasard dans la catégorie",
        "randomincategory-invalidcategory": "« $1 » n’est pas un nom de catégorie valide.",
-       "randomincategory-nopages": "Il n’y a pas de page dans [[:Category:$1]].",
+       "randomincategory-nopages": "Il n’y a pas de pages dans la catégorie [[:Category:$1|$1]].",
        "randomincategory-category": "Catégorie :",
        "randomincategory-legend": "Page aléatoire dans la catégorie",
        "randomincategory-submit": "Lancer",
        "apisandbox-fullscreen-tooltip": "Étendre le panneau du bac à sable pour remplir la fenêtre du navigateur.",
        "apisandbox-unfullscreen": "Afficher la page",
        "apisandbox-unfullscreen-tooltip": "Réduire le panneau du bac à sable, pour que les liens de navigation de MediaWiki soient disponibles.",
-       "apisandbox-submit": "Faire la demande",
+       "apisandbox-submit": "Envoyer la requête",
        "apisandbox-reset": "Effacer",
        "apisandbox-retry": "Réessayer",
        "apisandbox-loading": "Chargement des informations du module \"$1\" de l'API...",
index 10bb66a..f76e5b8 100644 (file)
@@ -22,7 +22,8 @@
                        "VaiPolaSombra",
                        "Macofe",
                        "Banjo",
-                       "Josep Maria Roca Peña"
+                       "Josep Maria Roca Peña",
+                       "Luan"
                ]
        },
        "tog-underline": "Subliñar as ligazóns:",
        "grant-group-high-volume": "Realizar actividades de alto volume",
        "grant-group-customization": "Personalización e preferencias",
        "grant-group-administration": "Realizar accións administrativas",
+       "grant-group-private-information": "Acceder a datos privados sobre ti",
        "grant-group-other": "Outras actividades",
        "grant-blockusers": "Bloquear e desbloquear usuarios",
        "grant-createaccount": "Crear contas",
        "grant-highvolume": "Edicións de gran volume",
        "grant-oversight": "Agochar usuarios e eliminar revisións",
        "grant-patrol": "Patrullar os cambios feitos nas páxinas",
+       "grant-privateinfo": "Acceder a información privada",
        "grant-protect": "Protexer e desprotexer páxinas",
        "grant-rollback": "Reverter os cambios feitos nas páxinas",
        "grant-sendemail": "Enviar correos electrónicos a outros usuarios",
        "undeletehistorynoadmin": "Esta páxina foi borrada.\nO motivo do borrado consta no resumo que aparece a continuación, xunto cos detalles dos usuarios que editaron esta páxina antes da súa eliminación.\nO texto destas revisións eliminadas só está á disposición dos administradores.",
        "undelete-revision": "Revisión eliminada de \"$1\" (o $4 ás $5) feita por $3:",
        "undeleterevision-missing": "Revisión non válida ou inexistente. Pode que a ligazón conteña un erro ou que a revisión se restaurase ou eliminase do arquivo.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Unha revisión non pode ser restaurada|$1 revisións non poden ser restauradas}} porque {{PLURAL:$1|o seu|os seus}}  <code>rev_id</code> xa {{PLURAL:$1|está|están}} en uso.",
        "undelete-nodiff": "Non se atopou ningunha revisión anterior.",
        "undeletebtn": "Restaurar",
        "undeletelink": "ver/restaurar",
        "undeletedrevisions": "{{PLURAL:$1|Restaurouse $1 revisión|Restauráronse $1 revisións}}",
        "undeletedrevisions-files": "Restauráronse $1 {{PLURAL:$1|revisión|revisións}} e $2 {{PLURAL:$2|ficheiro|ficheiros}}",
        "undeletedfiles": "{{PLURAL:$1|Restaurouse $1 ficheiro|Restauráronse $1 ficheiros}}",
-       "cannotundelete": "Houbo un erro durante a restauración:\n$1",
+       "cannotundelete": "Algunhas ou todas as restauracións fallaronː\n$1",
        "undeletedpage": "'''A páxina \"$1\" foi restaurada'''\n\nComprobe o [[Special:Log/delete|rexistro de borrados]] para ver as entradas recentes no rexistro de páxinas eliminadas e restauradas.",
        "undelete-header": "Consulte [[Special:Log/delete|no rexistro de borrados]] as páxinas borradas recentemente.",
        "undelete-search-title": "Procurar páxinas borradas",
        "sp-contributions-newbies-sub": "Contribucións dos usuarios novos",
        "sp-contributions-newbies-title": "Contribucións dos usuarios novos",
        "sp-contributions-blocklog": "rexistro de bloqueos",
-       "sp-contributions-suppresslog": "contribucións borradas do usuario",
-       "sp-contributions-deleted": "contribucións borradas do usuario",
+       "sp-contributions-suppresslog": "contribucións {{GENDER:$1|do usuario|da usuaria}} suprimidas",
+       "sp-contributions-deleted": "contribucións {{GENDER:$1|do usuario|da usuaria}} borradas",
        "sp-contributions-uploads": "cargas",
        "sp-contributions-logs": "rexistros",
        "sp-contributions-talk": "conversa",
index b14e01a..432338d 100644 (file)
@@ -6,7 +6,8 @@
                        "Marwan Mohamad",
                        "Matma Rex",
                        "NoiX180",
-                       "Zhoelyakin"
+                       "Zhoelyakin",
+                       "Amire80"
                ]
        },
        "tog-underline": "Garisiyi totibawa pranala",
        "exif-orientation-1": "Normal",
        "namespacesall": "nga'amila",
        "monthsall": "nga'amila",
-       "signature": "[[{{ns:user}}:$1|$2]]\n([[{{ns:user_talk}}:$1|bisala]])",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|bisala]])",
        "specialpages": "Halaman Spesial",
        "tag-filter": "[[Special:Tags|Tag]]filter:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
index 090585a..bda094a 100644 (file)
        "mainpage-nstab": "𐌰𐌽𐌰𐍃𐍄𐍉𐌳𐌴𐌹𐌽𐌹𐌻𐌰𐌿𐍆𐍃",
        "error": "𐌰𐌹𐍂𐌶𐌴𐌹",
        "databaseerror-error": "𐌰𐌹𐍂𐌶𐌴𐌹: $1",
-       "missing-article": "ð\90\8d\83ð\90\8c° ð\90\8c³ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c±ð\90\8c¿ð\90\8d\83 ð\90\8c½ð\90\8c¹ ð\90\8c²ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8c¼ ð\90\8c¸ð\90\8c°ð\90\8c½ð\90\8c° ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8c°ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c°ð\90\8c½ ð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8d\84ð\90\8c° ð\90\8d\83ð\90\8cºð\90\8c°ð\90\8c» ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½: \"$1\" $2\n\n(The data base did not find the text of a page that it should have found, named \"$1\" $2.\n\nThis is usually caused by following an outdated diff or history link to a page that has been deleted.\n\nIf this is not the case, you may have found a bug in the software.\nPlease report this to an [[Special:ListUsers/sysop|administrator]], making note of the URL.)",
+       "missing-article": "ð\90\8c³ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c±ð\90\8c´ð\90\8d\83 ð\90\8c½ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c°ð\90\8d\84 ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8d\89ð\90\8d\83 ð\90\8c»ð\90\8c°ð\90\8c¿ð\90\8c±ð\90\8c¹ð\90\8d\83 ð\90\8c¸ð\90\8c¹ð\90\8c¶ð\90\8c´ð\90\8c¹ ð\90\8d\83ð\90\8cºð\90\8c¿ð\90\8c»ð\90\8c³ð\90\8c´ð\90\8c³ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½, ð\90\8c·ð\90\8c°ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½ð\90\8d\83 \"$1\" $2. \n\nð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° ð\90\8c¿ð\90\8d\86ð\90\8d\84ð\90\8c° ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c¸ð\90\8c¹ð\90\8c¸ ð\90\8c¾ð\90\8c°ð\90\8c±ð\90\8c°ð\90\8c¹ ð\90\8c»ð\90\8c°ð\90\8c¹ð\90\8d\83ð\90\8d\84ð\90\8c¾ð\90\8c°ð\90\8c³ð\90\8c° ð\90\8d\86ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c½ð\90\8c¾ð\90\8c° ð\90\8c³ð\90\8c¹ð\90\8d\86ð\90\8d\86 ð\90\8c¸ð\90\8c°ð\90\8c¿ ð\90\8d\83ð\90\8d\80ð\90\8c¹ð\90\8c»ð\90\8c»ð\90\8c°ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8d\83ð\90\8d\83 ð\90\8d\83ð\90\8c´ð\90\8c¹ ð\90\8d\86ð\90\8d\82ð\90\8c°ð\90\8cµð\90\8c¹ð\90\8d\83ð\90\8d\84ð\90\8c¹ð\90\8c³ð\90\8c° ð\90\8c¹ð\90\8d\83ð\90\8d\84. ð\90\8c½ð\90\8c¹ð\90\8c±ð\90\8c°ð\90\8c¹ ð\90\8c¹ð\90\8d\83ð\90\8d\84, ð\90\8c¼ð\90\8c°ð\90\8c·ð\90\8d\84ð\90\8d\83 ð\90\8c¹ð\90\8d\83ð\90\8d\84 ð\90\8c´ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c´ð\90\8d\84ð\90\8c´ð\90\8c¹ð\90\8d\83 ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c¶ð\90\8c´ð\90\8c¹ð\90\8c½ ð\90\8c¹ð\90\8c½ ð\90\8d\83ð\90\8c°ð\90\8c¿ð\90\8d\86ð\90\8d\84ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c°. \n\nð\90\8c±ð\90\8c¹ð\90\8c³ð\90\8c¾ð\90\8c°ð\90\8c¼ ð\90\8c¸ð\90\8c¿ð\90\8cº, ð\90\8c¼ð\90\8c´ð\90\8d\82ð\90\8c´ð\90\8c¹ ð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° ð\90\8c³ð\90\8c¿ [[Special:ListUsers/sysop\n|ð\90\8d\82ð\90\8c´ð\90\8c¹ð\90\8cº]] ð\90\8c²ð\90\8c¹ð\90\8d\86ð\90\8c¿ð\90\8c· ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8d\83ð\90\8d\83.",
        "badtitle": "𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐍄𐌰 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹",
        "badtitletext": "𐍆𐍂𐌰𐌹𐌷𐌰𐌽𐍃 𐌻𐌰𐌿𐍆𐍃 𐍅𐌰𐍃 𐌿𐌽𐌲𐌰𐌼𐌰𐌲𐌰𐌽𐌳𐍃, 𐌻𐌰𐌿𐍃, 𐌰𐌹𐌸𐌸𐌰𐌿 𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐌱𐌰 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐍃 𐌼𐌹𐌸𐍂𐌰𐌶𐌳𐌰 𐌸𐌰𐌿 𐌼𐌹𐌸-𐍅𐌹𐌺𐌹 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹. 𐌼𐌰𐌲𐌹 𐌷𐌰𐌱𐌰𐌽 𐌰𐌹𐌽𐌰 𐌸𐌰𐌿 𐌼𐌰𐌽𐌰𐌲𐌹𐌶𐍉𐍃 𐌱𐍉𐌺𐍉𐍃 𐌱𐍂𐌿𐌺𐌹𐌳𐍉𐍃 𐌹𐌽 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌾𐌰𐌼.",
        "viewsource": "𐍃𐌰𐌹𐍈 𐌱𐍂𐌿𐌽𐌽𐌰𐌽",
index 84e13ef..7f73d57 100644 (file)
        "datedefault": "ברירת המחדל",
        "prefs-labs": "אפשרויות מעבדה",
        "prefs-user-pages": "דפי משתמש",
-       "prefs-personal": "פרטי המשתמש",
+       "prefs-personal": "פרטי ה{{GENDER:|משתמש|משתמשת}}",
        "prefs-rc": "שינויים אחרונים",
        "prefs-watchlist": "רשימת המעקב",
        "prefs-editwatchlist": "עריכת רשימת המעקב",
        "grant-group-file-interaction": "אינטראקציה עם קבצים",
        "grant-group-watchlist-interaction": "אינטראקציה עם רשימת המעקב שלך",
        "grant-group-email": "שליחת דוא\"ל",
-       "grant-group-high-volume": "×\91×\99צ×\95×\99 פעולות מרובות",
+       "grant-group-high-volume": "×\91×\99צ×\95×¢ פעולות מרובות",
        "grant-group-customization": "התאמה אישית והעדפות",
        "grant-group-administration": "ביצוע פעולות ניהול",
-       "grant-group-other": "פעילות שונה",
+       "grant-group-private-information": "גישה למידע פרטי על עצמך",
+       "grant-group-other": "פעולות שונות",
        "grant-blockusers": "חסימת משתמשים ושחרורם",
        "grant-createaccount": "יצירת חשבונות",
        "grant-createeditmovepage": "יצירה, עריכה והעברה של דפים",
        "grant-highvolume": "ביצוע עריכות מרובות",
        "grant-oversight": "החבאת משתמשים והעלמת גרסאות",
        "grant-patrol": "ניטור שינויים לדפים",
+       "grant-privateinfo": "גישה למידע פרטי",
        "grant-protect": "הפעלת הגנה על דפים והסרתה",
        "grant-rollback": "שחזור שינויים בדפים",
        "grant-sendemail": "שליחת דואר אלקטרוני למשתמשים אחרים",
        "tags-active-yes": "כן",
        "tags-active-no": "לא",
        "tags-source-extension": "הוגדר על־ידי הרחבה",
-       "tags-source-manual": "×\94וחל באופן ידני על־ידי משתמשים ובוטים",
+       "tags-source-manual": "×\9eוחל באופן ידני על־ידי משתמשים ובוטים",
        "tags-source-none": "אינו בשימוש כעת",
        "tags-edit": "עריכה",
        "tags-delete": "מחיקה",
index 9ad03b8..d241144 100644 (file)
        "botpasswords-bad-appid": "A(z) „$1” botnév érvénytelen.",
        "botpasswords-insert-failed": "A(z) „$1” botnév hozzáadása sikertelen. Nem lehet, hogy már hozzá lett adva?",
        "botpasswords-created-title": "Botjelszó létrehozva",
-       "botpasswords-created-body": "Bot jelszó \"$1\" sikeresen létrehozva.",
+       "botpasswords-created-body": "\"$2\" felhasználó \"$1\" bot jelszava létrehozva.",
        "botpasswords-updated-title": "Botjelszó frissítve",
-       "botpasswords-updated-body": "Bot jelszó \"$1\" frissítése sikerült.",
+       "botpasswords-updated-body": "\"$2\" felhasználó \"$1\" bot jelszava módosítva.",
        "botpasswords-deleted-title": "Botjelszó törölve",
-       "botpasswords-deleted-body": "Bot jelszó \"$1\" törölve.",
+       "botpasswords-deleted-body": "\"$2\" felhasználó \"$1\" bot jelszava törölve.",
        "botpasswords-no-provider": "A BotPasswordsSessionProvider nem áll rendelkezésre.",
        "resetpass_forbidden": "A jelszavak nem változtathatók meg",
+       "resetpass_forbidden-reason": "A jelszavakat nem változtathatóak meg: $1",
        "resetpass-no-info": "Be kell jelentkezned, hogy közvetlenül elérd ezt a lapot.",
        "resetpass-submit-loggedin": "Jelszó megváltoztatása",
        "resetpass-submit-cancel": "Mégse",
        "minoredit": "Apró változtatás",
        "watchthis": "A lap figyelése",
        "savearticle": "Lap mentése",
+       "savechanges": "Módosítások mentése",
        "publishpage": "Lap közzététele",
        "publishchanges": "Változtatások közzététele",
        "preview": "Előnézet",
index 72788b3..b807488 100644 (file)
        "grant-group-high-volume": "Esegue azioni massive",
        "grant-group-customization": "Personalizzazione e preferenze",
        "grant-group-administration": "Esegue azioni amministrative",
+       "grant-group-private-information": "Accede ai dati privati su di te",
        "grant-group-other": "Attività varie",
        "grant-blockusers": "Blocca e sblocca utenti",
        "grant-createaccount": "Crea un'utenza",
        "grant-highvolume": "Modifiche massive",
        "grant-oversight": "Nasconde utenti e sopprime le versioni",
        "grant-patrol": "Segna le modifiche alle pagine come verificate",
+       "grant-privateinfo": "Accede a informazioni private",
        "grant-protect": "Protegge e sprotegge pagine",
        "grant-rollback": "Rollback delle modifiche alle pagine",
        "grant-sendemail": "Invia email ad altri utenti",
        "undeletehistorynoadmin": "Questa pagina è stata cancellata.\nIl motivo della cancellazione è mostrato qui sotto, assieme ai dettagli dell'utente che ha modificato questa pagina prima della cancellazione.\nIl testo contenuto nelle versioni cancellate è disponibile solo agli amministratori.",
        "undelete-revision": "Versione cancellata della pagina $1, inserita il $4 alle $5 da $3:",
        "undeleterevision-missing": "Versione errata o mancante. Il collegamento è errato oppure la versione è stata già ripristinata o eliminata dall'archivio.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Una versione non può essere ripristinata|$1 versioni non possono essere ripristinate}}, poiché {{PLURAL:$1|il suo|i loro}} <code>rev_id</code> {{PLURAL:$1|è già utilizzato|sono già utilizzati}}.",
        "undelete-nodiff": "Non è stata trovata nessuna versione precedente.",
        "undeletebtn": "Ripristina",
        "undeletelink": "visualizza/ripristina",
        "undeletedrevisions": "{{PLURAL:$1|Una versione recuperata|$1 versioni recuperate}}",
        "undeletedrevisions-files": "{{PLURAL:$1|Una versione|$1 versioni}} e $2 file recuperati",
        "undeletedfiles": "{{PLURAL:$1|Un file recuperato|$1 file recuperati}}",
-       "cannotundelete": "Ripristino non riuscito:\n$1",
+       "cannotundelete": "Alcuni o tutti i ripristini non riusciti:\n$1",
        "undeletedpage": "'''La pagina $1 è stata recuperata'''\n\nConsulta il [[Special:Log/delete|registro delle cancellazioni]] per vedere le cancellazioni e i recuperi più recenti.",
        "undelete-header": "Consulta il [[Special:Log/delete|registro delle cancellazioni]] per vedere le cancellazioni più recenti.",
        "undelete-search-title": "Ricerca nelle pagine cancellate",
index 491e492..321c5d4 100644 (file)
        "undeletehistorynoadmin": "このページは削除されています。\n削除の理由は、削除前にこのページを編集していた利用者の詳細情報と共に、以下に表示されています。\n管理者以外の利用者には、削除された各版の本文への制限がかけられています。",
        "undelete-revision": "削除されたページ $1 の $4 $5 時点での $3 による版:",
        "undeleterevision-missing": "無効または存在しない版です。\n間違ったリンクをたどったか、この版は既に復元されたか、もしくは保存版から除去された可能性があります。",
+       "undeleterevision-duplicate-revid": "<code>rev_id</code> は既に使用されているため、{{PLURAL:$1|1件の版|$1件の版}}を復元できませんでした。",
        "undelete-nodiff": "これより前の版はありません。",
        "undeletebtn": "復元",
        "undeletelink": "閲覧/復元",
        "undeletedrevisions": "{{PLURAL:$1|$1版}}を復元しました",
        "undeletedrevisions-files": "{{PLURAL:$1|$1版}}と{{PLURAL:$2|$2ファイル}}を復元しました",
        "undeletedfiles": "{{PLURAL:$1|$1ファイル}}を復元しました",
-       "cannotundelete": "復元に失敗しました:\n$1",
+       "cannotundelete": "復元に一部またはすべて失敗しました:\n$1",
        "undeletedpage": "<strong>$1を復元しました。</strong>\n\n最近の削除と復元の記録については[[Special:Log/delete|削除記録]]を参照してください。",
        "undelete-header": "最近削除されたページは[[Special:Log/delete|削除記録]]で確認できます。",
        "undelete-search-title": "削除されたページの検索",
index f3a6409..2402224 100644 (file)
        "listfiles_thumb": "Gambar mini",
        "listfiles_date": "Tanggal",
        "listfiles_name": "Jeneng",
-       "listfiles_user": "Panganggo",
+       "listfiles_user": "Naraguna",
        "listfiles_size": "Ukuran (bita)",
        "listfiles_description": "Dèskripsi",
        "listfiles_count": "Vèrsi",
        "filehist-thumb": "Gambar cilik",
        "filehist-thumbtext": "Gambar cilik kanggo owahan $1",
        "filehist-nothumb": "Ora ana miniatur",
-       "filehist-user": "Panganggo",
+       "filehist-user": "Naraguna",
        "filehist-dimensions": "Alang ujur",
        "filehist-filesize": "Gedhené berkas",
        "filehist-comment": "Tanggapan",
        "blocklogpage": "Log pamblokiran",
        "blocklog-showlog": "Panganggo iki wis tau diblokir sakdurungé.\nLog blokiran sumadhiya nèng ngisor kanggo rujukan:",
        "blocklog-showsuppresslog": "Panganggo iki wis tau diblokir lan didhelikaké sakdurungé.\nLog brèdèlan sumadhiya nèng ngisor kanggo rujukan:",
-       "blocklogentry": "mblokir \"[[$1]]\" dipun watesi wekdalipun $2 $3",
-       "reblock-logentry": "Ngowahi sèting pamblokiran [[$1]] kanthi wektu daluwarsa $2 $3",
+       "blocklogentry": "mblokir [[$1]] kanthi wektu kadaluwarsa $2 $3",
+       "reblock-logentry": "ngowah setèlan blokir tumrap [[$1]] kanthi wektu kadaluwarsa $2 $3",
        "blocklogtext": "Ing ngisor iki kapacak log pamblokiran lan panjabelan blokir panganggo.\nAlamat IP sing diblokir sacara otomatis ora ana ing daftar iki.\nMangga mirsani [[Special:BlockList|daftar panganggo sing diblokir]] kanggo daftar blokir pungkasan.",
        "unblocklogentry": "njabel blokir \"$1\"",
        "block-log-flags-anononly": "namung panganggo anonim waé",
        "block-log-flags-hiddenname": "jeneng panganggo didhelikaké",
        "range_block_disabled": "Fungsi pamblokir blok IP kanggo para opsis dipatèni.",
        "ipb_expiry_invalid": "Wektu kadaluwarsa ora absah.",
+       "ipb_expiry_old": "Wektu kadaluwarsa ana ing nguni.",
        "ipb_expiry_temp": "Pamblokiran tumrap jeneng panganggo sing didhelikaké kudu permanèn.",
        "ipb_hide_invalid": "Ora bisa ndhelikaké akun iki; manawa wis kakèhan suntingan.",
        "ipb_already_blocked": "\"$1\" wis diblokir",
        "revdelete-uname-unhid": "jeneng panganggo dituduhaké",
        "revdelete-restricted": "rèstriksi ditrapaké marang para opsis",
        "revdelete-unrestricted": "rèstriksi marang para opsis dijabel",
+       "logentry-block-block": "$1 {{GENDER:$2|mblokir}} {{GENDER:$4|$3}} kanthi wektu kadaluwarsa $5 $6",
+       "logentry-block-reblock": "$1 {{GENDER:$2|ngowah}} setèlan blokir tumrap {{GENDER:$4|$3}} kanthi wektu kadaluwarsa $5 $6",
+       "logentry-suppress-block": "$1 {{GENDER:$2|mblokir}} {{GENDER:$4|$3}} kanthi wektu kadaluwarsa $5 $6",
+       "logentry-suppress-reblock": "$1 {{GENDER:$2|ngowah}} setèlan blokir tumrap {{GENDER:$4|$3}} kanthi wektu kadaluwarsa $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|ngalih}} kaca $3 nyang $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4 tanpa ninggalaké pangalihan",
        "logentry-move-move_redir": "$1 {{GENDER:$2|mindhahaké}} kaca $3 nèng $4 nindesi pangalihan liyane",
index c634c56..cc3b52e 100644 (file)
        "sp-contributions-username": "IP-мекенжайы немесе қатысушы аты:",
        "sp-contributions-toponly": "Өңдемелердің тек соңғы нұсқаларын көрсету",
        "sp-contributions-newonly": "Бет бастау өңдемелерін ғана көрсету",
+       "sp-contributions-hideminor": "Шағын өңдемелерді жасыру",
        "sp-contributions-submit": "Іздеу",
        "whatlinkshere": "Мұнда сілтейтін беттер",
        "whatlinkshere-title": "$1 дегенге сілтейтін беттер",
index 5b666c7..8552d01 100644 (file)
@@ -75,7 +75,7 @@
        "tog-numberheadings": "자동으로 머릿글 번호 매기기",
        "tog-showtoolbar": "편집 도구 모음 보이기",
        "tog-editondblclick": "더블 클릭으로 문서 편집하기",
-       "tog-editsectiononrightclick": "제목을 오른쪽 클릭해서 문단 편집하기 활성화",
+       "tog-editsectiononrightclick": "문단 제목을 오른쪽 클릭해서 문단 편집하기 활성화",
        "tog-watchcreations": "내가 만든 문서와 내가 올린 파일을 주시문서 목록에 추가",
        "tog-watchdefault": "내가 편집한 문서와 파일을 주시문서 목록에 추가",
        "tog-watchmoves": "내가 이동한 문서와 파일을 주시문서 목록에 추가",
@@ -93,7 +93,7 @@
        "tog-oldsig": "현재 서명:",
        "tog-fancysig": "서명을 위키텍스트로 취급 (자동으로 링크를 걸지 않음)",
        "tog-uselivepreview": "실시간 미리 보기 사용하기",
-       "tog-forceeditsummary": "편집 요약을 쓰지 않았을 때 물어보기",
+       "tog-forceeditsummary": "í\8e¸ì§\91 ì\9a\94ì\95½ì\9d\84 ì\93°ì§\80 ì\95\8aì\95\98ì\9d\84 ë\95\8c ë\82´ê²\8c ë¬¼ì\96´ë³´ê¸°",
        "tog-watchlisthideown": "주시문서 목록에서 내 편집을 숨기기",
        "tog-watchlisthidebots": "주시문서 목록에서 봇 편집을 숨기기",
        "tog-watchlisthideminor": "주시문서 목록에서 사소한 편집을 숨기기",
        "tog-diffonly": "편집 차이를 비교할 때 문서 내용을 보지 않기",
        "tog-showhiddencats": "숨은 분류 보이기",
        "tog-norollbackdiff": "되돌리기 후 차이를 보지 않기",
-       "tog-useeditwarning": "바꾼 내용을 저장하지 않고 편집 페이지를 벗어날 때 알림",
+       "tog-useeditwarning": "바꾼 내용을 저장하지 않고 편집 페이지를 벗어날 때 내게 경고하기",
        "tog-prefershttps": "로그인할 때 항상 보안 연결 사용",
        "underline-always": "항상",
        "underline-never": "항상 치지 않기",
        "underline-default": "스킨 또는 브라우저 기본값",
-       "editfont-style": "편집 영역의 글꼴:",
+       "editfont-style": "편집 영역의 글꼴 형식:",
        "editfont-default": "브라우저 기본값",
        "editfont-monospace": "고정폭 글꼴",
        "editfont-sansserif": "산세리프 글꼴",
        "acct_creation_throttle_hit": "당신의 IP 주소를 이용한 방문자가 이전에 이미 {{PLURAL:$1|계정 $1개}}를 만들어, 계정 만들기 한도를 초과하였습니다.\n따라서 지금은 이 IP 주소로는 더 이상 계정을 만들 수 없습니다.",
        "emailauthenticated": "이메일 주소가 $2 $3에 인증되었습니다.",
        "emailnotauthenticated": "이메일 주소를 인증하지 않았습니다.\n이메일 확인 절차를 거치지 않으면 다음 이메일 기능을 사용할 수 없습니다.",
-       "noemailprefs": "이 기능을 사용하기 위해서는 사용자 환경 설정에서 이메일 주소를 설정해야 합니다.",
+       "noemailprefs": "이 기능을 사용하려면 사용자 환경 설정에서 이메일 주소를 지정하세요.",
        "emailconfirmlink": "이메일 주소 확인",
        "invalidemailaddress": "이메일 주소의 형식이 잘못되어 인식할 수 없습니다.\n정상적인 형식의 이메일을 입력하거나 칸을 비워 주세요.",
        "cannotchangeemail": "이 위키에서는 계정의 이메일 주소를 바꿀 수 없습니다.",
        "rev-showdeleted": "보이기",
        "revisiondelete": "판 삭제/되살리기",
        "revdelete-nooldid-title": "대상 판이 잘못되었습니다.",
-       "revdelete-nooldid-text": "이 기능을 수행할 특정 판을 제시하지 않았거나 해당 판이 없습니다. 또는 현재 판을 숨기려 하고 있을 수도 있습니다.",
+       "revdelete-nooldid-text": "이 기능을 수행할 대상 판을 지정하지 않았거나 해당 판이 존재하지 않습니다. 아니면 현재 판을 숨기려 하고 있을 수도 있습니다.",
        "revdelete-no-file": "해당 파일이 존재하지 않습니다.",
        "revdelete-show-file-confirm": "정말 \"<nowiki>$1</nowiki>\" 파일의 삭제된 $2 $3 버전을 보시겠습니까?",
        "revdelete-show-file-submit": "예",
        "search-category": "(분류 $1)",
        "search-file-match": "(내용이 일치하는 파일 있음)",
        "search-suggest": "$1 문서를 찾고 있으신가요?",
-       "search-rewritten": "$1의 결과를 보여주고 있습니다. $2 대신 검색합니다.",
+       "search-rewritten": "$1의 결과를 보여주고 있습니다. $2을(를) 대신 검색합니다.",
        "search-interwiki-caption": "자매 프로젝트",
        "search-interwiki-default": "$1로부터의 결과:",
        "search-interwiki-more": "(더 보기)",
        "search-external": "바깥 검색",
        "searchdisabled": "{{SITENAME}} 검색이 비활성화되어 있습니다.\n검색이 작동하지 않는 동안 Google을 통해 검색할 수 있습니다.\n검색 엔진의 내용은 최신이 아닐 수 있다는 점을 참고하세요.",
        "search-error": "검색하는 동안 오류가 발생했습니다: $1",
-       "preferences": "사용자 환경 설정",
+       "preferences": "환경 설정",
        "mypreferences": "환경 설정",
        "prefs-edits": "편집 수:",
        "prefsnologintext2": "사용자 환경 설정을 바꾸려면 로그인하세요.",
        "prefs-resetpass": "비밀번호 바꾸기",
        "prefs-changeemail": "이메일 주소를 바꾸거나 제거하기",
        "prefs-setemail": "이메일 주소 설정하기",
-       "prefs-email": "ì\9d´ë©\94ì\9d¼ ì\84¤ì \95",
-       "prefs-rendering": "문ì\84\9c ë³´ì\9d´ê¸°",
+       "prefs-email": "ì\9d´ë©\94ì\9d¼ ì\98µì\85\98",
+       "prefs-rendering": "보이기",
        "saveprefs": "저장",
        "restoreprefs": "(모든 부분에서) 모두 기본 설정으로 되돌리기",
        "prefs-editing": "편집",
        "recentchangesdays-max": "최대 $1{{PLURAL:$1|일}}",
        "recentchangescount": "기본으로 보여줄 편집 수:",
        "prefs-help-recentchangescount": "이 설정은 최근 바뀜, 문서 역사와 기록에 적용됩니다.",
-       "prefs-help-watchlist-token2": "내 주시문서 목록의 웹 피드의 비밀 키입니다.\n비밀 키를 알고 있는 사람은 내 주시문서 목록을 읽을 수 있으니 비밀 키를 알리지 마세요.\n필요하다면 [[Special:ResetTokens|비밀 키를 재설정할 수 있습니다]].",
+       "prefs-help-watchlist-token2": "내 주시문서 목록의 웹 피드의 비밀 키입니다.\n이 키를 알고 있는 사람은 내 주시문서 목록을 읽을 수 있으니 이 키를 공유하지 마세요.\n필요하다면 [[Special:ResetTokens|이 키를 재설정할 수 있습니다]].",
        "savedprefs": "설정을 저장했습니다.",
        "savedrights": "$1의 사용자 권한이 저장되었습니다.",
        "timezonelegend": "시간대:",
        "prefs-help-email-others": "자신의 사용자 문서나 토론 문서에 있는 이메일 보내기 링크로 다른 사용자가 연락할 수 있게 할 수도 있습니다.\n이 경우에도 이메일 주소는 다른 사용자가 연락할 때 공개되지 않습니다.",
        "prefs-help-email-required": "이메일 주소가 필요합니다.",
        "prefs-info": "기본 정보",
-       "prefs-i18n": "언어 설정",
+       "prefs-i18n": "국제화",
        "prefs-signature": "서명",
        "prefs-dateformat": "날짜 형식",
        "prefs-timeoffset": "시차 설정",
-       "prefs-advancedediting": "ì\9d¼ë°\98 ì\84¤ì \95",
+       "prefs-advancedediting": "ì\9d¼ë°\98 ì\98µì\85\98",
        "prefs-editor": "편집기",
        "prefs-preview": "미리 보기",
-       "prefs-advancedrc": "ê³ ê¸\89 ì\84¤ì \95",
-       "prefs-advancedrendering": "ê³ ê¸\89 ì\84¤ì \95",
-       "prefs-advancedsearchoptions": "ê³ ê¸\89 ì\84¤ì \95",
-       "prefs-advancedwatchlist": "ê³ ê¸\89 ì\84¤ì \95",
+       "prefs-advancedrc": "ê³ ê¸\89 ì\98µì\85\98",
+       "prefs-advancedrendering": "ê³ ê¸\89 ì\98µì\85\98",
+       "prefs-advancedsearchoptions": "ê³ ê¸\89 ì\98µì\85\98",
+       "prefs-advancedwatchlist": "ê³ ê¸\89 ì\98µì\85\98",
        "prefs-displayrc": "표시 설정",
        "prefs-displaywatchlist": "표시 설정",
        "prefs-tokenwatchlist": "토큰",
        "grant-group-high-volume": "대량의 작업 수행",
        "grant-group-customization": "사용자 최적화 및 환경 설정",
        "grant-group-administration": "관리 기능 수행",
+       "grant-group-private-information": "당신에 관한 개인 데이터 접근",
        "grant-group-other": "기타 활동",
        "grant-blockusers": "사용자 차단 또는 차단 해제",
        "grant-createaccount": "계정 만들기",
        "grant-editprotected": "보호된 문서 편집하기",
        "grant-highvolume": "대용량 편집",
        "grant-oversight": "사용자 숨기기와 판 억제",
-       "grant-patrol": "페이지 검토",
+       "grant-patrol": "페이지 변경 사항 점검",
+       "grant-privateinfo": "개인 정보 접근",
        "grant-protect": "문서 보호 및 보호 해제",
        "grant-rollback": "문서의 바뀜을 되돌리기",
        "grant-sendemail": "다른 사용자에게 이메일 보내기",
        "tags-edit-success": "바뀜이 적용되었습니다.",
        "tags-edit-failure": "수정 사항이 적용될 수 없습니다: $1",
        "tags-edit-nooldid-title": "대상 판이 잘못되었습니다",
-       "tags-edit-nooldid-text": "이 기능을 수행할 특정 판을 제시하지 않았거나 해당 판이 없습니다.",
+       "tags-edit-nooldid-text": "이 기능을 수행할 대상 판을 지정하지 않았거나 해당 판이 존재하지 않습니다.",
        "tags-edit-none-selected": "추가하거나 제거할 최소 하나 이상의 태그를 선택하세요.",
        "comparepages": "문서 비교",
        "compare-page1": "첫 번째 문서",
index 7896792..0d7d482 100644 (file)
        "passwordreset-emailtitle": "{{SITENAME}} сайтындагы эсеп жазуусу жөнүндөгү маалымат",
        "passwordreset-emailelement": "Колдонуучу аты: \n$1\n\nУбактылуу сырсөз: \n$2",
        "passwordreset-emailsentemail": "Сырсөздү алмаштыруу эмейлге жөнөтүлдү.",
-       "passwordreset-emailsent-capture": "Төмөндө көрсөтүлгөн эмейлге сырсөздү алмаштыруучу кат жөнөтүлдү.",
-       "passwordreset-emailerror-capture": "Төмөндө көрсөтүлгөн дарекке сырсөздү алмаштыруу кат түзүлдү,бирок аны  {{GENDER:$2|катышуучуга}} жөнөтүү оңунан чыккан жок: $1",
        "changeemail": "E-mail даректи өзгөртүү",
        "changeemail-header": "Эл. почтанын дарегин өзгөртүү",
        "changeemail-no-info": "Бул баракка түз кайрылыш үчүн, сиз системага киришиңиз керек.",
        "post-expand-template-argument-warning": "'''Эскертүү:''' Бул барак, жок дегенде, абдан чоң көлөмдүү калыптын бир жүйөсүн камтыйт жана  жайылганда өлчөмү абдан чоң болуп кетет. \nУшул сыяктуу жүйөлөр аттатылды.",
        "post-expand-template-argument-category": "Калыптардын аттатылган жүйөлөрүн камтыган барактар",
        "parser-template-loop-warning": "Калыптарда илмек бар:[[$1]]",
-       "cantcreateaccounttitle": "Эсеп жазуусун түзүү мүмкүн эмес",
        "viewpagelogs": "Бул барактын журналдарын көрүү",
        "nohistory": "Бул барактын өзгөртүүлөр тарыхы жок",
        "currentrev": "Соңку версиясы",
        "watchlisttools-view": "Тийиштүү өзгөрүүлөрдү кароо",
        "watchlisttools-edit": "Көзөмөл тизмесин кароо жана оңдоо",
        "watchlisttools-raw": "Жетиле элек көзөмөл тизмени оңдоо",
-       "signature": "[[{{ns:колдонуучу}}:$1|$2]] ([[{{ns:колдонуучу_баарлашуу}}:$1|баарлашуу]])",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|баарлашуу]])",
        "duplicate-defaultsort": "'''Эскертүү:''' \"$2\" белгиленген ылгоочу ачкыч \"$1\" мурунку белгиленген ылгоочу ачкычты жокко чыгарат.",
        "version": "Версия",
        "version-extensions": "Орнотулган кеңейтүүлөр",
index 995237c..594636e 100644 (file)
        "nocookiesnew": "De Benotzerkont gouf ugeluecht, awer Dir sidd net ageloggt.\n{{SITENAME}} brauch fir dës Funktioun Cookien.\nDir hutt d'Cookien desaktivéiert.\nAktivéiert déi w.e.g. a loggt Iech da mat Ärem neie Benotzernomm a mat dem respektive Passwuert an.",
        "nocookieslogin": "{{SITENAME}} benotzt Cookië beim Umelle vun de Benotzer.\nDir hutt Cookien ausgeschalt.\nAktivéiert d'Cookien w.e.g. a versicht et nach eng Kéier.",
        "nocookiesfornew": "De Benotzerkont gouf net ugeluecht, well mir seng Quell net bestëmme konnten.\nVergewëssert Iech datt Dir Cookien zouloosst, luet dës Säit nei a probéiert nach emol.",
+       "createacct-loginerror": "De Benotzerkont gouf ugeluecht, mä Dir konnt net automatesch ageloggt ginn. [[Special:UserLogin|Loggt Iech w.e.g. manuell an]].",
        "noname": "Dir hutt kee gëltege Benotzernumm uginn.",
        "loginsuccesstitle": "Ageloggt",
        "loginsuccess": "'''Dir sidd elo als \"$1\" op {{SITENAME}} ugemellt.'''",
        "botpasswords-label-cancel": "Ofbriechen",
        "botpasswords-label-delete": "Läschen",
        "botpasswords-label-resetpassword": "D'Passwuert zrécksetzen",
+       "botpasswords-help-grants": "All Berechtegung gëtt Zougang op déi Benotzerrechter déi e Benotzerkont schonn huet. Kuckt d'[[Special:ListGrants|Tabell vun de Berechtigunge]] fir méi Informatiounen.",
        "botpasswords-label-restrictions": "Limite fir d'Benotzen:",
        "botpasswords-label-grants-column": "Accordéiert",
        "botpasswords-bad-appid": "Den Numm vum Bot \"$1\" ass net valabel.",
        "botpasswords-insert-failed": "De Botnumm \"$1\" konnt net dobäigesat ginn. Gouf e schonn derbäigesat?",
+       "botpasswords-created-title": "Botpasswuert ugeluecht",
        "botpasswords-created-body": "D'Botpasswuert fir de Bot-Numm \"$1\" vum Benotzer ''$2'' gouf ugeluecht.",
        "botpasswords-updated-title": "Botpasswuert aktualiséiert",
        "botpasswords-updated-body": "D'Botpasswuert fir de Bot-Numm \"$1\" vum Benotzer ''$2'' gouf aktualiséiert.",
        "botpasswords-deleted-title": "Botpasswuert geläscht",
        "botpasswords-deleted-body": "D'Botpasswuert fir de Bot-Numm \"$1\" vum Benotzer ''$2'' gouf geläscht.",
+       "botpasswords-newpassword": "Dat neit Passwuert fir sech mat <strong>$1</strong> anzeloggen ass <strong>$2</strong>.\n<em>Versuergt dat fir sech spéider dorop ze referéieren.</em>",
        "botpasswords-not-exist": "De Benotzer \"$1\" huet kee Botpasswuert mam Numm \"$2\".",
        "resetpass_forbidden": "Passwierder kënnen net geännert ginn.",
        "resetpass_forbidden-reason": "Passwierder kënnen net geännert ginn: $1",
        "right-createpage": "Säiten uleeën (déi keng Diskussiounssäite sinn)",
        "right-createtalk": "Diskussiounssäiten uleeën",
        "right-createaccount": "Nei Benotzerkonten uleeën",
+       "right-autocreateaccount": "Automatesch alogge mat engem externe Benotzerkont",
        "right-minoredit": "Ännerungen als kleng markéieren",
        "right-move": "Säite réckelen",
        "right-move-subpages": "Säiten zesumme mat hiren Ënnersäite réckelen",
        "grant-group-high-volume": "Massenaktivitéiten ausféieren",
        "grant-group-customization": "Upassungen an Astellungen",
        "grant-group-administration": "Administrativ Aktioune maachen",
+       "grant-group-private-information": "Op perséinlech Date vun Iech zougräifen",
        "grant-group-other": "Verschidden Aktivitéiten",
        "grant-blockusers": "Benotzer spären an d'Spären ophiewen",
        "grant-createaccount": "Benotzerkonten opmaachen",
        "grant-highvolume": "Massenännerungen",
        "grant-oversight": "Benotzer verstoppen a Versioune läschen",
        "grant-patrol": "Ännerungen op Säiten kontrolléieren",
+       "grant-privateinfo": "Op perséinlech Informatiounen zougräifen",
        "grant-protect": "Säite spären an entspären",
        "grant-rollback": "Ännerungen op Säiten zrécksetzen",
        "grant-sendemail": "Anere Benotzer E-Maile schécken",
index 893d69c..459c47b 100644 (file)
        "grant-group-high-volume": "Вршење на активности од голем обем",
        "grant-group-customization": "Прилагодувања и поставки",
        "grant-group-administration": "Вршење на административни дејства",
+       "grant-group-private-information": "Пристап до лични податоци за вас",
        "grant-group-other": "Разни активности",
        "grant-blockusers": "Блокирање и одблокирање корисници",
        "grant-createaccount": "Правење сметки",
        "grant-highvolume": "Високообемно уредување",
        "grant-oversight": "Скривање на корисници и преработки",
        "grant-patrol": "Патрола на измени во страници",
+       "grant-privateinfo": "Пристап до лични информации",
        "grant-protect": "Заштита на незаштитени страници",
        "grant-rollback": "Отповикување на измени во страници",
        "grant-sendemail": "Испраќање на е-пошта до други корисници",
index ebb6a50..d8bf6e3 100644 (file)
        "passwordreset-emailelement": "सदस्यनाव: \n$1\n\nअस्थायी परवलीचा शब्द: \n$2",
        "passwordreset-emailsentemail": "जर हा विपत्रपत्ता आपल्या खात्याशी संलग्न असेल तर, परवलीच्या शब्दाच्या पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात येईल.",
        "passwordreset-emailsentusername": "जर या सदस्यनावाशी संलग्न विपत्रपत्ता असेल तर, परवलीचा शब्द पुनर्स्थापनाबाबत विपत्र पाठविल्या जाईल.",
-       "passwordreset-emailsent-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात आले आहे जे खाली दर्शविण्यात आले आहे.",
-       "passwordreset-emailerror-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र निर्माण करण्यात आले, जे खाली दर्शविण्यात आले आहे.परंतु,{{GENDER:$2|सदस्य}}ला पाठविणे असफल झाले: $1",
        "changeemail": "विपत्रपत्ता बदला किंवा हटवा",
        "changeemail-header": "आपला विपत्रपत्ता बदलण्यास हे आवेदन पूर्ण करा.जर आपणास आपल्या खात्याशी संलग्न कोणताही विपत्रपत्ता हटवायचा असेल तर,आवेदन सादर करण्यापूर्वी, नविन विपत्रपत्त्यासाठी असलेली जागा कोरी ठेवा.",
-       "changeemail-passwordrequired": "हे बदल नक्की करण्यासाठी आपणास आपला परवलीचा शब्द टाकावा लागेल.",
        "changeemail-no-info": "हे पान थेट बघण्यासठी तुम्हाला सनोंद-प्रवेशित असावे लागेल.",
        "changeemail-oldemail": "सध्याचा ईमेल पत्ता :",
        "changeemail-newemail": "नवा ईमेल पत्ता:",
        "undo-nochange": "असे दिसते कि हे संपादन पूर्ववत केल्या गेले आहे.",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|चर्चा]])यांची आवृत्ती $1 परतवली.",
        "undo-summary-username-hidden": "अज्ञात सदस्याची $1 आवृत्ती परतवा",
-       "cantcreateaccounttitle": "खाते उघडू शकत नाही",
        "cantcreateaccount-text": "('''$1''')या आंतरजाल अंकपत्त्याकडूनच्या खाते निर्मितीस [[User:$3|$3]]ने अटकाव केला आहे.\n\n$3ने ''$2'' कारण दिले आहे.",
        "cantcreateaccount-range-text": "<strong>$1</strong>आवाक्यातील आंतरजाल अंकपत्ते,ज्यात आपल्या (<strong>$4</strong>) या अंकपत्त्याचा समावेश आहे, [[User:$3|$3]] ने त्यांच्या खाते निर्मितीस प्रतिबंध केला आहे.\n\n$3 ने <em>$2</em>कारण दिले आहे.",
        "viewpagelogs": "या पानाच्या नोंदी पहा",
        "undeletedrevisions": "{{PLURAL:$1|1 आवर्तन|$1 आवर्तने}} पुनर्स्थापित",
        "undeletedrevisions-files": "{{PLURAL:$1|1 आवर्तन|$1 आवर्तने}}आणि {{PLURAL:$2|1 संचिका|$2 संचिका}} पुनर्स्थापित",
        "undeletedfiles": "{{PLURAL:$1|1 संचिका|$1 संचिका}} पुनर्स्थापित",
-       "cannotundelete": "उलटवणे फसले:$1",
+       "cannotundelete": "à¤\95ाहà¥\80 à¤\95िà¤\82वा à¤¸à¤°à¥\8dवà¤\9a à¤\89लà¤\9fवणà¥\87 à¤«à¤¸à¤²à¥\87:$1",
        "undeletedpage": "<strong>$1ला पुनर्स्थापित केले</strong>\n\nअलिकडिल वगळलेल्या आणि पुनर्स्थापितांच्या नोंदीकरिता [[Special:Log/delete|वगळल्याच्या नोंदी]] पहा .",
        "undelete-header": "अलीकडील वगळलेल्या पानांकरिता [[Special:Log/delete|वगळलेल्या नोंदी]] पहा.",
        "undelete-search-title": "वगळलेली पाने शोधा",
        "sp-contributions-newbies-sub": "नवशिक्यांसाठी",
        "sp-contributions-newbies-title": "नवीन खात्यांसाठी सदस्य योगदान",
        "sp-contributions-blocklog": "रोध नोंदी",
-       "sp-contributions-suppresslog": "सदस्य योगदानाचे दमन केले",
-       "sp-contributions-deleted": "वगळलेली सदस्य संपादने",
+       "sp-contributions-suppresslog": "{{GENDER:$1|सदस्य}} योगदानाचे दमन केले",
+       "sp-contributions-deleted": "वगळलेली {{GENDER:$1|सदस्य}} संपादने",
        "sp-contributions-uploads": "अपभारणे",
        "sp-contributions-logs": "नोंदी",
        "sp-contributions-talk": "चर्चा",
index c756f47..b13f5f9 100644 (file)
        "action-applychangetags": "appreca tag pe' tramente ca se fanno 'e cagnamiente vuoste",
        "action-changetags": "azzecca o lèva tag a caso dint'a verziune nnividuale e riggistre 'e log",
        "action-deletechangetags": "scancellare 'e tag d' 'o database",
+       "action-purge": "agghiuorna sta paggena",
        "nchanges": "$1 {{PLURAL:$1|cagnamiento|cagnamiente}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|'a ll'urdema visita}}",
        "enhancedrc-history": "cronologgia",
        "uploadstash-errclear": "'A pulezzia d' 'e file scassaje.",
        "uploadstash-refresh": "Agghiuorna l'elenco d' 'e file",
        "uploadstash-thumbnail": "vide miniatura",
+       "uploadstash-exception": "Nun s'è pututo sarvà 'a càrreca dint' 'a stash ($1): \"$2\".",
        "invalid-chunk-offset": "Distanza d' 'a parte nun valida",
        "img-auth-accessdenied": "Acciesso negato",
        "img-auth-nopathinfo": "PATH_INFO mancante.\n'O server nun è mpustato pe' passà sta nfurmazione.\nPuò darse ca, essenno basato ncopp'a CGI, nun putesse suppurtà img_auth.\nVide https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
        "watchnologin": "Acciesso nun affettuato",
        "addwatch": "Miette dint' 'a l'elenco 'e paggene cuntrullate",
        "addedwatchtext": "'A paggena \"[[:$1]]\" e 'a paggena 'e chiacchiera è stata azzeccata dint'a l'elenco 'e [[Special:Watchlist|paggene cuntrullate]].",
+       "addedwatchtext-talk": "\"[[:$1]]\" e 'a paggena 'e chiacchiera suòccia è stata azzeccata dint'a l'elenco 'e [[Special:Watchlist|paggene cuntrullate]] vuosto.",
        "addedwatchtext-short": "Chista paggena \"$1\" è stata azzeccata a l'elenco 'e paggene cuntrullate.",
        "removewatch": "Leva 'a l'elenco 'e paggene cuntrullate",
-       "removedwatchtext": "\"[[:$1]]\" 'e 'a paggena 'e chiacchiera soja so' state scancellata 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
+       "removedwatchtext": "\"[[:$1]]\" e 'a paggena 'e chiacchiera soja so' state scancellate 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
+       "removedwatchtext-talk": "\"[[:$1]]\" e 'a paggena 'e chiacchiera soja so' state luvate 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
        "removedwatchtext-short": "Chista paggena \"$1\" è stata luvata a l'elenco 'e paggene cuntrullate.",
        "watch": "Secuta",
        "watchthispage": "Tiene d'uocchio sta paggena",
index f50e348..a4514e7 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "Objeto vazio",
        "content-json-empty-array": "Array vazia",
+       "deprecated-self-close-category": "Páginas com etiquetas HTML de autofechamento não válidas",
        "duplicate-args-warning": "<strong> Aviso: </strong> [[:$1]] está chamando [[:$2]] com mais de um valor para o parâmetro \"$3\". Será utilizado apenas o último valor fornecido.",
        "duplicate-args-category": "Páginas com argumentos de predefinições duplicados",
        "duplicate-args-category-desc": "A pagina contem modelos que usam argumentos duplicados, como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ou <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
index 4db45fc..8108c7a 100644 (file)
        "content-model-css": "CSS",
        "content-json-empty-object": "Objeto vazio",
        "content-json-empty-array": "Matriz vazia",
+       "deprecated-self-close-category": "Páginas com etiquetas HTML de autofechamento não válidas",
        "duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] chama [[:$2]] com mais de um valor para o parâmetro \"$3\". Somente o último valor fornecido será utilizado.",
        "duplicate-args-category": "Páginas com argumentos de predefinições duplicados",
        "duplicate-args-category-desc": "A página contém campos de predefinições que utilizam duplicatas de argumentos, tais como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ou <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "grant-group-high-volume": "Realizar actividades em grande quantidade",
        "grant-group-customization": "Personalização e preferências",
        "grant-group-administration": "Executar acções administrativas",
+       "grant-group-private-information": "Aceder aos seus dados privados",
        "grant-group-other": "Actividade diversa",
        "grant-blockusers": "Bloquear e desbloquear utilizadores",
        "grant-createaccount": "Criar contas",
index 2f315e8..6a3d604 100644 (file)
        "right-applychangetags": "{{doc-right|applychangetags}}",
        "right-changetags": "{{doc-right|changetags}}",
        "right-deletechangetags": "{{doc-right|deletechangetags}}",
-       "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}",
+       "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-privateinfo}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}",
        "grant-group-page-interaction": "{{Related|grant-group}}",
        "grant-group-file-interaction": "{{Related|grant-group}}",
        "grant-group-watchlist-interaction": "{{Related|grant-group}}",
        "grant-group-high-volume": "{{Related|Grant-group}}",
        "grant-group-customization": "{{Related|Grant-group}}",
        "grant-group-administration": "{{Related|Grant-group}}",
+       "grant-group-private-information": "{{Related|Grant-group}}",
        "grant-group-other": "{{Related|Grant-group}}",
        "grant-blockusers": "Name for grant \"blockusers\".\n{{Related|Grant}}",
-       "grant-createaccount": "Name for grant \"createaccount\".\n{{Related|grant}}",
-       "grant-createeditmovepage": "Name for grant \"createeditmovepage\".\n{{Related|grant}}",
-       "grant-delete": "Name for grant \"delete\".\n{{Related|grant}}",
-       "grant-editinterface": "Name for grant \"editinterface\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|grant}}",
-       "grant-editmycssjs": "Name for grant \"editmycssjs\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|grant}}",
-       "grant-editmyoptions": "Name for grant \"editmyoptions\".\n{{Related|grant}}",
-       "grant-editmywatchlist": "Name for grant \"editmywatchlist\".\n{{Related|grant}}\n{{Identical|Edit your watchlist}}",
-       "grant-editpage": "Name for grant \"editpage\".\n{{Related|grant}}",
-       "grant-editprotected": "Name for grant \"editprotected\".\n{{Related|grant}}",
-       "grant-highvolume": "Name for grant \"highvolume\".\n{{Related|grant}}",
-       "grant-oversight": "Name for grant \"oversight\".\n{{Related|grant}}",
-       "grant-patrol": "Name for grant \"patrol\".\n{{Related|grant}}",
-       "grant-protect": "Name for grant \"protect\".\n{{Related|grant}}",
-       "grant-rollback": "Name for grant \"rollback\".\n{{Related|grant}}",
-       "grant-sendemail": "Name for grant \"sendemail\".\n{{Related|grant}}",
-       "grant-uploadeditmovefile": "Name for grant \"uploadeditmovefile\".\n{{Related|grant}}",
-       "grant-uploadfile": "Name for grant \"uploadfile\".\n{{Related|grant}}\n{{Identical|Upload new file}}",
-       "grant-basic": "Name for grant \"basic\".\n{{Related|grant}}",
-       "grant-viewdeleted": "Name for grant \"viewdeleted\".\n{{Related|grant}}",
-       "grant-viewmywatchlist": "Name for grant \"viewmywatchlist\".\n{{Related|grant}}\n{{Identical|View your watchlist}}",
+       "grant-createaccount": "Name for grant \"createaccount\".\n{{Related|Grant}}",
+       "grant-createeditmovepage": "Name for grant \"createeditmovepage\".\n{{Related|Grant}}",
+       "grant-delete": "Name for grant \"delete\".\n{{Related|Grant}}",
+       "grant-editinterface": "Name for grant \"editinterface\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}",
+       "grant-editmycssjs": "Name for grant \"editmycssjs\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}",
+       "grant-editmyoptions": "Name for grant \"editmyoptions\".\n{{Related|Grant}}",
+       "grant-editmywatchlist": "Name for grant \"editmywatchlist\".\n{{Related|Grant}}\n{{Identical|Edit your watchlist}}",
+       "grant-editpage": "Name for grant \"editpage\".\n{{Related|Grant}}",
+       "grant-editprotected": "Name for grant \"editprotected\".\n{{Related|Grant}}",
+       "grant-highvolume": "Name for grant \"highvolume\".\n{{Related|Grant}}",
+       "grant-oversight": "Name for grant \"oversight\".\n{{Related|Grant}}",
+       "grant-patrol": "Name for grant \"patrol\".\n{{Related|Grant}}",
+       "grant-privateinfo": "Name for grant \"privateinfo\".\n{{Related|Grant}}",
+       "grant-protect": "Name for grant \"protect\".\n{{Related|Grant}}",
+       "grant-rollback": "Name for grant \"rollback\".\n{{Related|Grant}}",
+       "grant-sendemail": "Name for grant \"sendemail\".\n{{Related|Grant}}",
+       "grant-uploadeditmovefile": "Name for grant \"uploadeditmovefile\".\n{{Related|Grant}}",
+       "grant-uploadfile": "Name for grant \"uploadfile\".\n{{Related|Grant}}\n{{Identical|Upload new file}}",
+       "grant-basic": "Name for grant \"basic\".\n{{Related|Grant}}",
+       "grant-viewdeleted": "Name for grant \"viewdeleted\".\n{{Related|Grant}}",
+       "grant-viewmywatchlist": "Name for grant \"viewmywatchlist\".\n{{Related|Grant}}\n{{Identical|View your watchlist}}",
        "newuserlogpage": "{{doc-logpage}}\n\nPart of the \"Newuserlog\" extension. It is both the title of [[Special:Log/newusers]] and the link you can see in [[Special:RecentChanges]].",
        "newuserlogpagetext": "Part of the \"Newuserlog\" extension. It is the description you can see on [[Special:Log/newusers]].",
        "rightslog": "{{doc-logpage}}\n\nIn [[Special:Log]]",
        "tags-active-yes": "Table cell contents if given tag is \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-no}}\n{{Identical|Yes}}",
        "tags-active-no": "Table cell contents if given tag is not \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-yes}}\n{{Identical|No}}",
        "tags-source-extension": "Table cell contents if given tag can be applied automatically by a software [[mw:Manual:Extensions|extension]].\n\nSee also:\n* {{msg-mw|Tags-source-manual}}\n* {{msg-mw|Tags-source-none}}",
-       "tags-source-manual": "Table cell contents if given tag can be applied by users or bots.\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-none}}",
+       "tags-source-manual": "\"Applied\" is not past tense, but an adjective that describes an action that sometimes happens, as in the sentence: \"(this tag is usually) applied by users and bots\".\n\nTable cell contents if given tag can be applied by users or bots.\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-none}}",
        "tags-source-none": "Table cell contents if given tag is no longer in use. (It was applied in the past, but it is currently not applied.)\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-manual}}",
        "tags-edit": "Used on [[Special:Tags]]. Verb. Used as display text on a link to create/edit a description.\n{{Identical|Edit}}",
        "tags-delete": "Used on [[Special:Tags]]. Verb. Used as display text on a link to delete a tag.\n{{Identical|Delete}}",
index f7b7c94..a5cfc82 100644 (file)
        "sp-contributions-newbies-sub": "С новых учётных записей",
        "sp-contributions-newbies-title": "Вклад с недавно созданных учётных записей",
        "sp-contributions-blocklog": "блокировки",
-       "sp-contributions-suppresslog": "удалённый вклад участника",
-       "sp-contributions-deleted": "удалённые правки",
+       "sp-contributions-suppresslog": "удалённый вклад {{GENDER:$1|участника|участницы}}",
+       "sp-contributions-deleted": "удалённые правки {{GENDER:$1|участника|участницы}}",
        "sp-contributions-uploads": "загрузки",
        "sp-contributions-logs": "журналы",
        "sp-contributions-talk": "обсуждение",
index 87c4234..ff8f1de 100644 (file)
@@ -32,7 +32,8 @@
                        "Roonyh",
                        "Matma Rex",
                        "SusithCM",
-                       "Sandaru"
+                       "Sandaru",
+                       1100100
                ]
        },
        "tog-underline": "සබැඳි යටීර කිරීම:",
        "userlogout": "නික්මීම",
        "notloggedin": "ප්‍රවිසී නැත",
        "userlogin-noaccount": "ගිණුමක් නොමැතිද?",
-       "userlogin-joinproject": "{{SITENAME}}හා එක්වන්න",
+       "userlogin-joinproject": "{{SITENAME}} හා එක්වන්න",
        "nologin": "ඔබ හට ගිණුමක් නොමැතිද? '''$1'''.",
        "nologinlink": "ගිණුමක් තනන්න",
        "createaccount": "අලුත් ගිණුමක් තනන්න",
        "passwordreset-emailtext-user": "{{SITENAME}} හි පරිශීලක $1,{{SITENAME}}($4)සඳහා මුරපදය යලි පිහිටුවීමට ඉල්ලා ඇත.\n\n$2\n\n{{PLURAL:$3|මෙම මුරපදය|මෙම මුරපද}}{{PLURAL:$5|එක් දිනකින්|දවස්$5කින්}}කල් ඉකුත් වනු ඇත.\nඔබ දැන් ඇතුළු වී නව මුරපදයක් තේරිය යුතුය.මෙම ඉල්ලීම වෙන කෙනෙකු විසින් හෝ ඔබට ඔබගේ මුල් මුරපදය මතක නම් හෝ ඔබ තව දුරටත් එය වෙනස් කිරීමට අදහස් නොකරයි නම් හෝ ඔබ මෙම පනිවිඩය නොසලකාහැර ඔබගේ පැරණි මුරපදය භාවිතා කරන්න.",
        "passwordreset-emailelement": "පරිශීලක නාමය: \n$1\n\nතාවකාලික මුරපදය: \n$2",
        "passwordreset-emailsentemail": "මුර-පදය නැවත සකස් කිරීම පිළිබඳව විද්‍යුත් තැපෑලක් යවන ලදී.",
-       "passwordreset-emailsent-capture": "මුර-පදය වෙනස් කිරීම පිළිබඳව විද්‍යුත් තැපෑලක් යවන ලදී, එය පහත දැක්වේ.",
-       "passwordreset-emailerror-capture": "සිහිකැඳවුම් ඊ-තැපෑල ජනිත කරනු ලැබූ අතර, එය පහත දැක්වේ, නමුත් එය {{GENDER:$2|}}පරිශීලකයාට යැවීම අසාර්ථක වුනි: $1",
        "changeemail": "විද්‍යුත් තැපෑල වෙනස් කරන්න හෝ ඉවත් කරන්න",
        "changeemail-header": "ගිණුම් විද්‍යුත් තැපැල් ලිපිනය වෙනස් කරන්න",
        "changeemail-no-info": "මෙම පිටුව සෘජු ලෙස සම්ප්‍රවේශය කෙරුමට පළමුව ඔබ ප්‍රවිෂ්ටව සිටිය යුතුය.",
        "undo-nochange": "මෙම සංස්කරණය දැනටමත් අතහැර දමන ලද කර ඇති බව පෙනී යයි.",
        "undo-summary": " [[Special:Contributions/$2|$2]] මගින් සිදුකල  $1 සංශෝධනය අහෝසි කරන්න ([[User talk:$2|සාකච්ඡා]])",
        "undo-summary-username-hidden": "සැඟවුණු පරිශීලකයෙක් විසින් සංශෝධනය $1 අස් කරන්න",
-       "cantcreateaccounttitle": "ගිණුම තැනිය නොහැක",
        "cantcreateaccount-text": "මෙම IP ලිපිනය ('''$1''') මගින් ගිණුම් තැනීම [[User:$3|$3]] විසින් වාරණය කොට ඇත.\n\n$3 විසින් සපයා ඇති හේතුව ''$2'' වේ",
        "cantcreateaccount-range-text": "ඔබේ IP ලිපිනය ('' '$4' '') ද ඇතුළත් වන පරාසය තුළ IP ලිපිනයන් '' '$1', '' සිට ගිණුම් තැනීම  [[User:$3|$3]] විසින් වාරණය කොට ඇත. $3 විසින් දී ඇති හේතුව '' '$2' වේ",
        "viewpagelogs": "මෙම පිටුව පිලිබඳ සටහන් නරඹන්න",
        "special-characters-title-endash": "en තේජස",
        "special-characters-title-emdash": "em තේජස",
        "special-characters-title-minus": "ඍණ ලකුණ",
-       "api-error-blacklisted": "කරුණාකර වෙනස්, විස්තරාත්මක මාතෘකාවක් තෝරන්න.",
        "randomrootpage": "අහඹු මූල පිටුව"
 }
index d1f7ea4..7ef457b 100644 (file)
        "grant-group-high-volume": "Izvajanje visokoobsežnih dejavnosti",
        "grant-group-customization": "Prilagoditve in nastavitve",
        "grant-group-administration": "Izvajanje administrativnih dejanj",
+       "grant-group-private-information": "Dostop do zasebnih podatkov o vas",
        "grant-group-other": "Druga dejavnost",
        "grant-blockusers": "Blokiranje in odblokiranje uporabnikov",
        "grant-createaccount": "Ustvarjanje računov",
        "grant-highvolume": "Visokoobsežno urejanje",
        "grant-oversight": "Skrivanje uporabnikov in zatiranje redakcij",
        "grant-patrol": "Nadzor sprememb strani",
+       "grant-privateinfo": "Dostop do zasebnih podatkov",
        "grant-protect": "Zaščita in odstranitev zaščite strani",
        "grant-rollback": "Razveljavitev sprememb strani",
        "grant-sendemail": "Pošiljanje e-pošte drugim uporabnikom",
index 4e5103f..3cea9f2 100644 (file)
        "grant-group-high-volume": "Utför aktivitet av hög volym",
        "grant-group-customization": "Anpassning och inställningar",
        "grant-group-administration": "Utför administrativa åtgärder",
+       "grant-group-private-information": "Få tillgång till privat data om dig",
        "grant-group-other": "Diverse aktivitet",
        "grant-blockusers": "Blockera och avblockera användare",
        "grant-createaccount": "Skapa konton",
        "grant-highvolume": "Hög volymsredigering",
        "grant-oversight": "Dölj användare och censurera versioner",
        "grant-patrol": "Patrullera ändringar på sidor",
+       "grant-privateinfo": "Få tillgång till privat information",
        "grant-protect": "Skydda och ta bort skydd på sidor",
        "grant-rollback": "Rulla tillbaka ändringar på sidor",
        "grant-sendemail": "Skicka e-post till andra användare",
        "lockmanager-fail-svr-release": "Kunde inte frigöra låsen på servern $1.",
        "zip-file-open-error": "Ett fel inträffade när filen öppnades för en ZIP-kontroll.",
        "zip-wrong-format": "Den angivna filen var inte en ZIP-fil.",
-       "zip-bad": "Filen är en skadad eller annars oläsbar ZIP fil.\nDen kan inte säkerhetskontrolleras ordentligt.",
-       "zip-unsupported": "Filen är en ZIP-fil som använder ZIP funktioner som inte stöds av MediaWiki.\nDen kan inte säkerhetskontrolleras ordentligt.",
+       "zip-bad": "Filen är en skadad eller annars oläsbar ZIP-fil.\nDen kan inte säkerhetskontrolleras ordentligt.",
+       "zip-unsupported": "Filen är en ZIP-fil som använder ZIP-funktioner som inte stöds av MediaWiki.\nDen kan inte säkerhetskontrolleras ordentligt.",
        "uploadstash": "Temporära lagringsytan för uppladdningar",
        "uploadstash-summary": "Denna sida ger tillgång till filer som är uppladdade (eller håller på att laddas upp) men som ännu inte är publicerade till wikin. Dessa filer är inte synliga för någon annan än den användare som laddade upp dem.",
        "uploadstash-clear": "Rensa temporärt lagrade filer",
        "apisandbox-api-disabled": "API är inaktiverat på denna webbplats.",
        "apisandbox-intro": "Använd den här sidan för att experimentera med <strong>MediaWikis API för webbtjänster</strong>.\nSe [[mw:API:Main page|API-dokumentationen]] för ytterligare detaljer kring API-användningen. Exempel: [https://www.mediawiki.org/wiki/API#A_simple_example få innehållet från en huvudsida]. Välj en handling för att se fler exempel.\n\nObservera att även om detta är en sandlåda kan handlingar du utför på denna sida påverka wikin.",
        "apisandbox-fullscreen": "Utvidga panel",
-       "apisandbox-fullscreen-tooltip": "Utvidga sandlådspanelen för att fylla webbläsarens fönster.",
+       "apisandbox-fullscreen-tooltip": "Utvidga sandlådspanelen till att fylla webbläsarens fönster.",
        "apisandbox-unfullscreen": "Visa sida",
        "apisandbox-unfullscreen-tooltip": "Förminska sandlådspanelen så MediaWikis navigeringslänkar syns.",
        "apisandbox-submit": "Utför begäran",
        "undeletehistorynoadmin": "Den här sidan har blivit raderad. Anledningen till detta anges i sammanfattningen nedan, tillsammans med uppgifter om de användare som redigerat sidan innan den raderades. Enbart administratörerna har tillgång till den raderade texten.",
        "undelete-revision": "Raderad version av $1 (från den $4 kl. $5) av $3.",
        "undeleterevision-missing": "Versionen finns inte eller är felaktig. Versionen kan ha återställts eller tagits bort från arkivet, du kan också ha följt en felaktig länk.",
-       "undeleterevision-duplicate-revid": "{{PLURAL:$1|En sidversion|$1 sidversioner}} kunde inte återställas, eftersom dess <code>rev_id</code> användes redan.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|En sidversion|$1 sidversioner}} kunde inte återställas, eftersom dess <code>rev_id</code> redan användes.",
        "undelete-nodiff": "Ingen tidigare version hittades.",
        "undeletebtn": "Återställ",
        "undeletelink": "visa/återställ",
index c2c40dc..66e7a0e 100644 (file)
@@ -85,7 +85,8 @@
                        "Imabadplayer",
                        "İnternion",
                        "Hbseren",
-                       "Kumkumuk"
+                       "Kumkumuk",
+                       "Basak"
                ]
        },
        "tog-underline": "Bağlantıların altını çiz:",
        "print": "Yazdır",
        "view": "Görüntüle",
        "view-foreign": "$1 üzerinde gör",
-       "edit": "Değiştir",
+       "edit": "Düzenle",
        "edit-local": "Yerel açıklamayı düzenle",
        "create": "Oluştur",
        "create-local": "Yerel açıklama ekle",
        "savearticle": "Sayfayı kaydet",
        "savechanges": "Değişiklikleri kaydet",
        "publishpage": "Sayfayı yayımla",
+       "publishchanges": "Değişiklikleri yayımla",
        "preview": "Önizleme",
        "showpreview": "Önizlemeyi göster",
        "showdiff": "Değişiklikleri göster",
index c601325..f81a37c 100644 (file)
        "watchthis": "Спостерігати за цією сторінкою",
        "savearticle": "Зберегти сторінку",
        "savechanges": "Зберегти зміни",
-       "publishpage": "Ð\9eпÑ\83блÑ\96кÑ\83вати сторінку",
-       "publishchanges": "Ð\9eпÑ\83блÑ\96кÑ\83вати зміни",
+       "publishpage": "Ð\97беÑ\80егти сторінку",
+       "publishchanges": "Ð\97беÑ\80егти зміни",
        "preview": "Попередній перегляд",
        "showpreview": "Попередній перегляд",
        "showdiff": "Показати зміни",
        "grant-group-high-volume": "Виконувати великий обсяг діяльності",
        "grant-group-customization": "Налаштування і переваги",
        "grant-group-administration": "Виконувати адміністративні дії",
+       "grant-group-private-information": "Доступ до приватних даних про Вас",
        "grant-group-other": "Різна діяльність",
        "grant-blockusers": "Блокувати і розблокувати користувачів",
        "grant-createaccount": "Створювати облікові записи",
        "grant-highvolume": "Редагування у великих обсягах",
        "grant-oversight": "Приховувати користувачів і версії",
        "grant-patrol": "Патрулювати зміни на сторінках",
+       "grant-privateinfo": "Доступ до приватних даних",
        "grant-protect": "Захищати і знімати захист сторінок",
        "grant-rollback": "Відкочувати зміни на сторінках",
        "grant-sendemail": "Надсилати пошту іншим користувачам",
index b00ef27..7503b40 100644 (file)
        "shortpages": "چھوٹے صفحات",
        "longpages": "طویل ترین صفحات",
        "deadendpages": "مردہ صفحات",
-       "protectedpages": "محفوظ شدہ صفحات",
+       "protectedpages": "محفوظ کردہ صفحات",
        "protectedpages-noredirect": "رجوع مکررات چھپائیں",
        "protectedpages-timestamp": "وقت کی مہر",
        "protectedpages-page": "صفحہ",
index 7b604e7..71b389f 100644 (file)
        "minoredit": "Bu kichik tahrir",
        "watchthis": "Sahifani kuzatish",
        "savearticle": "Saqla",
+       "publishpage": "Sahifani chop et",
+       "publishchanges": "Oʻzgarishlarni chop et",
        "preview": "Ko‘rib chiqish",
        "showpreview": "Koʻrib chiqish",
        "showdiff": "Kiritilgan o‘zgarishlar",
        "undo-success": "Tahrirni bekor qilish imkoniyati bor. Iltimos, solishtirish oynasini koʻrib chiqib, aynan shu oʻzgarishlarni bekor qilmoqchiligingizga ishonch hosil qiling va undan keyin «Saqla» tugmasini bosing.",
        "undo-failure": "Keyingi tahrirlar bilan chalkashib ketgani sababli, ushbu tahrirni alohida oʻzini bekor qilishni iloji yoʻq.",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|mun.]]) tomonidan qilingan $1-sonli tahrir qaytarildi",
-       "cantcreateaccounttitle": "Ro‘yxatdan o‘tib bo‘lmadi",
        "cantcreateaccount-text": "[[User:$3|$3]] ushbu IP manzil (<strong>$1</strong>) orqali ro‘yxatdan o‘tishni bloklab qo‘ygan.\n\n$3 <em>$2</em>ni sabab qilib ko‘rsatdi",
        "cantcreateaccount-range-text": "[[User:$3|$3]] <strong>$1</strong> sohaga tegishli IP manzillar, shu jumladan sizning IP manzilingiz (<strong>$4</strong>), orqali ro‘yxatdan o‘tishni bloklab qo‘ygan.\n\n$3 <em>$2</em>ni sabab qilib ko‘rsatdi",
        "viewpagelogs": "Ushbu sahifaga doir qaydlarni koʻrsat",
index 907bb21..73f4629 100644 (file)
        "otherlanguages": "Àwọn èdè míràn",
        "redirectedfrom": "(Àtúnjúwe láti $1)",
        "redirectpagesub": "Ojúewé àtúnjúwe",
-       "redirectto": "àtúnjúwe sí",
+       "redirectto": "Ã\80túnjúwe sí:",
        "lastmodifiedat": "Àtunṣe ojúewé yi gbẹ̀yìn wáyé ni ago $2, ọjọ́ọdún $1.",
        "viewcount": "A ti wo ojúewé yi ni {{PLURAL:$1|ẹ̀kan péré|iye ìgbà $1}}.",
        "protectedpage": "Ojúewé oníàbò",
        "subject": "Orí ọ̀rọ̀/àkọlé:",
        "minoredit": "Àtúnṣe kékeré nìyí",
        "watchthis": "M'ójútó ojúewé yìí",
-       "savearticle": "Ṣe àtẹ̀jáde ojú ewé",
+       "savearticle": "Ìdásí ojúewé",
        "publishpage": "Ṣàtẹ̀jáde ojú ewé",
        "publishchanges": "Ṣàtẹ̀jáde àtúnṣe",
        "preview": "Àyẹ̀wò",
        "search-external": "Àwárí lóde",
        "searchdisabled": "Ṣíṣàwárí nínú {{SITENAME}} wà ní dídálẹ́kun.\nNí báyìí ná ẹ le ṣàwárí lọ́dọ̀ Google.\nÀkíyèsí pé àwọn atọ́ka wọn fún àkóónú {{SITENAME}} le mọ́ jẹ́ tuntun.",
        "search-error": "Àṣìṣe ṣẹlẹ̀ fún ìwárí: $1",
-       "preferences": "Ã\80wá»\8dn Ã Ã yò",
+       "preferences": "Ã\80wá»\8dn Ã¬fẹÌ\81ràn",
        "mypreferences": "Àwọn ìfẹ́ràn",
        "prefs-edits": "Iye àwọn àtúnṣe:",
        "prefsnologintext2": "Ẹ jọ̀wọ́ ẹ $1 láti ṣe ìyípadà àwọn ìfẹ́ràn yín.",
        "whatlinkshere-links": "← àwọn ìjápọ̀",
        "whatlinkshere-hideredirs": "$1 àtúnjúwe",
        "whatlinkshere-hidetrans": "$1 ìkómọ́ra",
-       "whatlinkshere-hidelinks": "$1 Àwọn ìjápọ̀",
+       "whatlinkshere-hidelinks": "Ìjápọ̀ $1",
        "whatlinkshere-hideimages": "$1 àwọn ìjápọ̀ fáìlì",
        "whatlinkshere-filters": "Ajọ̀",
        "autoblockid": "Ìdínàaláraẹni #$1",
        "javascripttest-qunit-intro": "Ẹ wo [$1 ìwé aṣàlàyé ìdánwò] ní mediawiki.org.",
        "tooltip-pt-userpage": "Ojúewé oníṣe rẹ",
        "tooltip-pt-anonuserpage": "Ojúewé oníṣe fún àdírẹ́ẹ̀sì IP tí ẹ únlò láti ṣàtúnṣe",
-       "tooltip-pt-mytalk": "Ojúewé ọ̀rọ̀ rẹ",
+       "tooltip-pt-mytalk": "Ojúewé ọ̀rọ̀ {{GENDER:|rẹ}}",
        "tooltip-pt-anontalk": "Ọ̀rọ̀ nípa àtúnṣe láti àdírẹ́ẹ̀sì IP yìí",
        "tooltip-pt-preferences": "Àwọn ìfẹ́ràn rẹ",
        "tooltip-pt-watchlist": "Àkójọ àwọn ojúewé tí ẹ̀ ún mójútó bóyá wọ́nyí padà",
-       "tooltip-pt-mycontris": "Àkójọ àwọn àfikún rẹ",
+       "tooltip-pt-mycontris": "Àtójọ àwọn àfikún {{GENDER:|rẹ}}",
        "tooltip-pt-login": "A gbà yín níyànjú kí ẹwọlé, bótilẹ̀jẹ́pẹ́ kò pọndandan.",
        "tooltip-pt-logout": "Ìjáde",
        "tooltip-pt-createaccount": "Ó dára kí ẹ dá àkópamọ́ kí ẹ sì ṣe ìtẹ̀jáwọlé, ṣùgbọ́n kò pọn dandan",
        "revdelete-uname-unhid": "orúkọ oníṣe kò pamọ́",
        "revdelete-restricted": "ṣe ìmúlò ìpàlà fún àwọn olúmójútó",
        "revdelete-unrestricted": "yọ ìpàlà fún àwọn olúmójútó",
-       "logentry-move-move": "$1 ṣeyípòdà ojúewé $3 sí $4",
+       "logentry-move-move": "$1 {{GENDER:$2|ṣeyípòdà}} ojúewé $3 sí $4",
        "logentry-move-move-noredirect": "$1 ṣeyípòdà ojúewé $3 sí $4 láìfi àtúnjúwe sílẹ̀",
        "logentry-move-move_redir": "$1 ṣeyípòdà ojúewé $3 sí $4 lórí àtúnjúwe",
        "logentry-move-move_redir-noredirect": "$1 ṣeyípòdà ojúewé $3 sí $4 lórí àtúnjúwe láìfi àtúnjúwe sílẹ̀",
index 1764b36..3956e0d 100644 (file)
        "grant-group-high-volume": "执行大量活动",
        "grant-group-customization": "自定义与设置",
        "grant-group-administration": "执行管理操作",
+       "grant-group-private-information": "访问有关您的私有数据",
        "grant-group-other": "杂项活动",
        "grant-blockusers": "封禁与解封用户",
        "grant-createaccount": "创建账户",
        "grant-highvolume": "大容量编辑",
        "grant-oversight": "隐藏用户和阻止修订",
        "grant-patrol": "巡查对页面的更改",
+       "grant-privateinfo": "访问私有信息",
        "grant-protect": "保护页面和取消页面保护",
        "grant-rollback": "回退对页面的更改",
        "grant-sendemail": "给其他用户发送电子邮件",
        "revdelete-uname-unhid": "公开用户名",
        "revdelete-restricted": "应用对管理员的限制",
        "revdelete-unrestricted": "删除对管理员的限制",
-       "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},期限$5 $6",
+       "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},期限$5 $6",
        "logentry-block-unblock": "$1{{GENDER:$2|解封了}}{{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1将{{GENDER:$4|$3}}的封禁设置{{GENDER:$2|更改为}}持续时间$5 $6",
        "logentry-suppress-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},持续时间$5 $6",
        "log-action-filter-upload-upload": "新上传",
        "log-action-filter-upload-overwrite": "重新上传",
        "authmanager-authn-not-in-progress": "身份验证尚未进行,或会话数据丢失。请从头重新开始。",
-       "authmanager-authn-no-primary": "提供的证书不能被验证。",
+       "authmanager-authn-no-primary": "提供的凭据不能通过验证。",
        "authmanager-authn-no-local-user": "提供的证书没有与该wiki上的任何用户相关联。",
        "authmanager-authn-no-local-user-link": "提供的证书有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的证书链接到对应账户的选项。",
        "authmanager-authn-autocreate-failed": "所有账户的自动创建失败:$1",
index 8368aab..ab316c0 100644 (file)
@@ -907,7 +907,7 @@ abstract class Maintenance {
 
                // Description ...
                if ( $this->mDescription ) {
-                       $this->output( "\n" . $this->mDescription . "\n" );
+                       $this->output( "\n" . wordwrap( $this->mDescription, $screenWidth ) . "\n" );
                }
                $output = "\nUsage: php " . basename( $this->mSelf );
 
index 6931259..2da45ca 100644 (file)
 require_once __DIR__ . '/cleanupTable.inc';
 
 /**
- * Maintenance script to clean up broken page links when somebody turns on $wgCapitalLinks.
+ * Maintenance script to clean up broken page links when somebody turns
+ * on or off $wgCapitalLinks.
  *
  * @ingroup Maintenance
  */
 class CapsCleanup extends TableCleanup {
 
        private $user;
+       private $namespace;
 
        public function __construct() {
                parent::__construct();
@@ -47,25 +49,66 @@ class CapsCleanup extends TableCleanup {
        }
 
        public function execute() {
-               global $wgCapitalLinks;
-
-               if ( $wgCapitalLinks ) {
-                       $this->error( "\$wgCapitalLinks is on -- no need for caps links cleanup.", true );
-               }
-
                $this->user = User::newSystemUser( 'Conversion script', [ 'steal' => true ] );
 
                $this->namespace = intval( $this->getOption( 'namespace', 0 ) );
+
+               if ( MWNamespace::isCapitalized( $this->namespace ) ) {
+                       $this->output( "Will be moving pages to first letter capitalized titles" );
+                       $callback = 'processRowToUppercase';
+               } else {
+                       $this->output( "Will be moving pages to first letter lowercase titles" );
+                       $callback = 'processRowToLowercase';
+               }
+
                $this->dryrun = $this->hasOption( 'dry-run' );
 
                $this->runTable( [
                        'table' => 'page',
                        'conds' => [ 'page_namespace' => $this->namespace ],
                        'index' => 'page_id',
-                       'callback' => 'processRow' ] );
+                       'callback' => $callback ] );
        }
 
-       protected function processRow( $row ) {
+       protected function processRowToUppercase( $row ) {
+               global $wgContLang;
+
+               $current = Title::makeTitle( $row->page_namespace, $row->page_title );
+               $display = $current->getPrefixedText();
+               $lower = $row->page_title;
+               $upper = $wgContLang->ucfirst( $row->page_title );
+               if ( $upper == $lower ) {
+                       $this->output( "\"$display\" already uppercase.\n" );
+
+                       return $this->progress( 0 );
+               }
+
+               $target = Title::makeTitle( $row->page_namespace, $upper );
+               if ( $target->exists() ) {
+                       // Prefix "CapsCleanup" to bypass the conflict
+                       $target = Title::newFromText( __CLASS__ . '/' . $display );
+               }
+               $ok = $this->movePage(
+                       $current,
+                       $target,
+                       'Converting page title to first-letter uppercase',
+                       false
+               );
+               if ( $ok ) {
+                       $this->progress( 1 );
+                       if ( $row->page_namespace == $this->namespace ) {
+                               $talk = $target->getTalkPage();
+                               $row->page_namespace = $talk->getNamespace();
+                               if ( $talk->exists() ) {
+                                       return $this->processRowToUppercase( $row );
+                               }
+                       }
+               }
+
+               return $this->progress( 0 );
+       }
+
+       protected function processRowToLowercase( $row ) {
                global $wgContLang;
 
                $current = Title::makeTitle( $row->page_namespace, $row->page_title );
@@ -79,35 +122,51 @@ class CapsCleanup extends TableCleanup {
                }
 
                $target = Title::makeTitle( $row->page_namespace, $lower );
-               $targetDisplay = $target->getPrefixedText();
                if ( $target->exists() ) {
+                       $targetDisplay = $target->getPrefixedText();
                        $this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" );
 
                        return $this->progress( 0 );
                }
 
-               if ( $this->dryrun ) {
-                       $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
-                       $ok = true;
-               } else {
-                       $mp = new MovePage( $current, $target );
-                       $status = $mp->move( $this->user, 'Converting page titles to lowercase', true );
-                       $ok = $status->isOK() ? 'OK' : $status->getWikiText( false, false, 'en' );
-                       $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
-               }
+               $ok = $this->movePage( $current, $target, 'Converting page titles to lowercase', true );
                if ( $ok === true ) {
                        $this->progress( 1 );
                        if ( $row->page_namespace == $this->namespace ) {
                                $talk = $target->getTalkPage();
                                $row->page_namespace = $talk->getNamespace();
                                if ( $talk->exists() ) {
-                                       return $this->processRow( $row );
+                                       return $this->processRowToLowercase( $row );
                                }
                        }
                }
 
                return $this->progress( 0 );
        }
+
+       /**
+        * @param Title $current
+        * @param Title $target
+        * @param string $reason
+        * @param bool $createRedirect
+        * @return bool Success
+        */
+       private function movePage( Title $current, Title $target, $reason, $createRedirect ) {
+               $display = $current->getPrefixedText();
+               $targetDisplay = $target->getPrefixedText();
+
+               if ( $this->dryrun ) {
+                       $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
+                       $ok = 'OK';
+               } else {
+                       $mp = new MovePage( $current, $target );
+                       $status = $mp->move( $this->user, $reason, $createRedirect );
+                       $ok = $status->isOK() ? 'OK' : $status->getWikiText( false, false, 'en' );
+                       $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
+               }
+
+               return $ok === 'OK';
+       }
 }
 
 $maintClass = "CapsCleanup";
index bc1b34a..94b7fb4 100644 (file)
@@ -71,6 +71,9 @@ class DeprecatedInterfaceFinder extends FileAwareNodeVisitor {
         * indicating that it is a hard-deprecated interface.
         */
        public function isHardDeprecated( PhpParser\Node $node ) {
+               if ( !$node->stmts ) {
+                       return false;
+               }
                foreach ( $node->stmts as $stmt ) {
                        if (
                                $stmt instanceof PhpParser\Node\Expr\FuncCall
@@ -142,7 +145,7 @@ class FindDeprecated extends Maintenance {
                $files = $this->getFiles();
                $chunkSize = ceil( count( $files ) / 72 );
 
-               $parser = new PhpParser\Parser( new PhpParser\Lexer\Emulative );
+               $parser = ( new PhpParser\ParserFactory )->create( PhpParser\ParserFactory::PREFER_PHP7 );
                $traverser = new PhpParser\NodeTraverser;
                $finder = new DeprecatedInterfaceFinder;
                $traverser->addVisitor( $finder );
diff --git a/maintenance/hhvm/makeRepo.php b/maintenance/hhvm/makeRepo.php
new file mode 100644 (file)
index 0000000..a0fe381
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+require __DIR__ . '/../Maintenance.php';
+
+class HHVMMakeRepo extends Maintenance {
+       function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Compile PHP sources for this MediaWiki instance, ' .
+                       'and generate an HHVM bytecode file to be used with HHVM\'s ' .
+                       'RepoAuthoritative mode. The MediaWiki core installation path and ' .
+                       'all registered extensions are automatically searched for the file ' .
+                       'extensions *.php, *.inc, *.php5 and *.phtml.' );
+               $this->addOption( 'output', 'Output filename', true, true, 'o' );
+               $this->addOption( 'input-dir', 'Add an input directory. ' .
+                       'This can be specified multiple times.', false, true, 'd', true );
+               $this->addOption( 'exclude-dir', 'Directory to exclude. ' .
+                       'This can be specified multiple times.', false, true, false, true );
+               $this->addOption( 'extension', 'Extra file extension', false, true, false, true );
+               $this->addOption( 'hhvm', 'Location of HHVM binary', false, true );
+               $this->addOption( 'base-dir', 'The root of all source files. ' .
+                       'This must match hhvm.server.source_root in the server\'s configuration file. ' .
+                       'By default, the MW core install path will be used.',
+                       false, true );
+               $this->addOption( 'verbose', 'Log level 0-3', false, true, 'v' );
+       }
+
+       private static function startsWith( $subject, $search ) {
+               return substr( $subject, 0, strlen( $search ) === $search );
+       }
+
+       function execute() {
+               global $wgExtensionCredits, $IP;
+
+               $dirs = [ $IP ];
+
+               foreach ( $wgExtensionCredits as $type => $extensions ) {
+                       foreach ( $extensions as $extension ) {
+                               if ( isset( $extension['path'] )
+                                       && !self::startsWith( $extension['path'], $IP )
+                               ) {
+                                       $dirs[] = dirname( $extension['path'] );
+                               }
+                       }
+               }
+
+               $dirs = array_merge( $dirs, $this->getOption( 'input-dir', [] ) );
+               $fileExts =
+                       [
+                               'php' => true,
+                               'inc' => true,
+                               'php5' => true,
+                               'phtml' => true
+                       ] +
+                       array_flip( $this->getOption( 'extension', [] ) );
+
+               $dirs = array_unique( $dirs );
+
+               $baseDir = $this->getOption( 'base-dir', $IP );
+               $excludeDirs = array_map( 'realpath', $this->getOption( 'exclude-dir', [] ) );
+
+               if ( $baseDir !== '' && substr( $baseDir, -1 ) !== '/' ) {
+                       $baseDir .= '/';
+               }
+
+               $unfilteredFiles = [ "$IP/LocalSettings.php" ];
+               foreach ( $dirs as $dir ) {
+                       $this->appendDir( $unfilteredFiles, $dir );
+               }
+
+               $files = [];
+               foreach ( $unfilteredFiles as $file ) {
+                       $dotPos = strrpos( $file, '.' );
+                       $slashPos = strrpos( $file, '/' );
+                       if ( $dotPos === false || $slashPos === false || $dotPos < $slashPos ) {
+                               continue;
+                       }
+                       $extension = substr( $file, $dotPos + 1 );
+                       if ( !isset( $fileExts[$extension] ) ) {
+                               continue;
+                       }
+                       $canonical = realpath( $file );
+                       foreach ( $excludeDirs as $excluded ) {
+                               if ( self::startsWith( $canonical, $excluded ) ) {
+                                       continue 2;
+                               }
+                       }
+                       if ( self::startsWith( $file, $baseDir ) ) {
+                               $file = substr( $file, strlen( $baseDir ) );
+                       }
+                       $files[] = $file;
+               }
+
+               $files = array_unique( $files );
+
+               print "Found " . count( $files ) . " files in " .
+                       count( $dirs ) . " directories\n";
+
+               $tmpDir = wfTempDir() . '/mw-make-repo' . mt_rand( 0, 1<<31 );
+               if ( !mkdir( $tmpDir ) ) {
+                       $this->error( 'Unable to create temporary directory', 1 );
+               }
+               file_put_contents( "$tmpDir/file-list", implode( "\n", $files ) );
+
+               $hhvm = $this->getOption( 'hhvm', 'hhvm' );
+               $verbose = $this->getOption( 'verbose', 3 );
+               $cmd = wfEscapeShellArg(
+                       $hhvm,
+                       '--hphp',
+                   '--target', 'hhbc',
+                       '--format', 'binary',
+                       '--force', '1',
+                       '--keep-tempdir', '1',
+                       '--log', $verbose,
+                       '-v', 'AllVolatile=true',
+                       '--input-dir', $baseDir,
+                       '--input-list', "$tmpDir/file-list",
+                       '--output-dir', $tmpDir );
+               print "$cmd\n";
+               passthru( $cmd, $ret );
+               if ( $ret ) {
+                       $this->cleanupTemp( $tmpDir );
+                       $this->error( "Error: HHVM returned error code $ret", 1 );
+               }
+               if ( !rename( "$tmpDir/hhvm.hhbc", $this->getOption( 'output' ) ) ) {
+                       $this->cleanupTemp( $tmpDir );
+                       $this->error( "Error: unable to rename output file", 1 );
+               }
+               $this->cleanupTemp( $tmpDir );
+               return 0;
+       }
+
+       private function cleanupTemp( $tmpDir ) {
+               if ( file_exists( "$tmpDir/hhvm.hhbc" ) ) {
+                       unlink( "$tmpDir/hhvm.hhbc" );
+               }
+               if ( file_exists( "$tmpDir/Stats.js" ) ) {
+                       unlink( "$tmpDir/Stats.js" );
+               }
+
+               unlink( "$tmpDir/file-list" );
+               rmdir( $tmpDir );
+       }
+
+       private function appendDir( &$files, $dir ) {
+               $iter = new RecursiveIteratorIterator(
+                       new RecursiveDirectoryIterator(
+                               $dir,
+                               FilesystemIterator::UNIX_PATHS
+                       ),
+                       RecursiveIteratorIterator::LEAVES_ONLY
+               );
+               foreach ( $iter as $file => $fileInfo ) {
+                       if ( $fileInfo->isFile() ) {
+                               $files[] = $file;
+                       }
+               }
+       }
+}
+
+$maintClass = 'HHVMMakeRepo';
+require RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/hhvm/run-server b/maintenance/hhvm/run-server
new file mode 100755 (executable)
index 0000000..2d71b87
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/hhvm -f
+<?php
+
+require __DIR__ . '/../Maintenance.php';
+
+class RunHipHopServer extends Maintenance {
+       function __construct() {
+               parent::__construct();
+       }
+
+       function execute() {
+               global $IP;
+
+               passthru(
+                       'cd ' . wfEscapeShellArg( $IP ) . " && " .
+                       wfEscapeShellArg(
+                               'hhvm',
+                               '-c', __DIR__."/server.conf",
+                               '--mode=server',
+                               '--port=8080'
+                       ),
+                       $ret
+               );
+               exit( $ret );
+       }
+}
+$maintClass = 'RunHipHopServer';
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/hhvm/server.conf b/maintenance/hhvm/server.conf
new file mode 100644 (file)
index 0000000..558bdad
--- /dev/null
@@ -0,0 +1,30 @@
+Log {
+       Level = Warning
+       UseLogFile = true
+       NativeStackTrace = true
+       InjectedStackTrace = true
+}
+Debug {
+       FullBacktrace = true
+       ServerStackTrace = true
+       ServerErrorMessage = true
+       TranslateSource = true
+}
+Server {
+       EnableStaticContentCache = false
+       EnableStaticContentFromDisk = true
+       AlwaysUseRelativePath = true
+}
+VirtualHost {
+       * {
+               ServerName = localhost
+               Pattern = .
+               RewriteRules {
+                       * {
+                               pattern = ^/wiki/(.*)$
+                               to = /index.php?title=$1
+                               qsa = true
+                       }
+               }
+       }
+}
diff --git a/maintenance/hiphop/run-server b/maintenance/hiphop/run-server
deleted file mode 100755 (executable)
index 2d71b87..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/hhvm -f
-<?php
-
-require __DIR__ . '/../Maintenance.php';
-
-class RunHipHopServer extends Maintenance {
-       function __construct() {
-               parent::__construct();
-       }
-
-       function execute() {
-               global $IP;
-
-               passthru(
-                       'cd ' . wfEscapeShellArg( $IP ) . " && " .
-                       wfEscapeShellArg(
-                               'hhvm',
-                               '-c', __DIR__."/server.conf",
-                               '--mode=server',
-                               '--port=8080'
-                       ),
-                       $ret
-               );
-               exit( $ret );
-       }
-}
-$maintClass = 'RunHipHopServer';
-require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/hiphop/server.conf b/maintenance/hiphop/server.conf
deleted file mode 100644 (file)
index 558bdad..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-Log {
-       Level = Warning
-       UseLogFile = true
-       NativeStackTrace = true
-       InjectedStackTrace = true
-}
-Debug {
-       FullBacktrace = true
-       ServerStackTrace = true
-       ServerErrorMessage = true
-       TranslateSource = true
-}
-Server {
-       EnableStaticContentCache = false
-       EnableStaticContentFromDisk = true
-       AlwaysUseRelativePath = true
-}
-VirtualHost {
-       * {
-               ServerName = localhost
-               Pattern = .
-               RewriteRules {
-                       * {
-                               pattern = ^/wiki/(.*)$
-                               to = /index.php?title=$1
-                               qsa = true
-                       }
-               }
-       }
-}
index bdbb347..aea966f 100644 (file)
@@ -139,6 +139,7 @@ class RefreshImageMetadata extends Maintenance {
                        }
 
                        foreach ( $res as $row ) {
+                               // LocalFile will upgrade immediately here if obsolete
                                $file = $repo->newFileFromRow( $row );
                                if ( $file->getUpgraded() ) {
                                        // File was upgraded.
index 68e0fad..9a9d84d 100644 (file)
@@ -5,7 +5,7 @@ you can override classes used by the installer.
 You can override 3 classes:
 * LocalSettingsGenerator - generates LocalSettings.php
 * WebInstaller - web installer UI
-* CliInstaller - command line installer
+* CliInstaller - command-line installer
 
 Example override:
 
index 0d8cfd6..d2ee1cd 100644 (file)
@@ -1429,10 +1429,6 @@ return [
                'scripts' => 'resources/src/mediawiki/mediawiki.experiments.js',
                'targets' => [ 'desktop', 'mobile' ],
        ],
-       'mediawiki.raggett' => [
-               'styles' => 'resources/src/mediawiki/mediawiki.raggett.css',
-               'targets' => [ 'desktop', 'mobile' ],
-       ],
 
        /* MediaWiki Action */
 
index b31fe82..c3a287d 100644 (file)
@@ -68,6 +68,9 @@ return call_user_func( function () {
                        'es5-shim',
                        'oojs',
                        'oojs-ui-core.styles',
+                       'oojs-ui.styles.icons',
+                       'oojs-ui.styles.indicators',
+                       'oojs-ui.styles.textures',
                        'mediawiki.language',
                ],
                'targets' => [ 'desktop', 'mobile' ],
@@ -78,14 +81,6 @@ return call_user_func( function () {
                'styles' => 'resources/src/oojs-ui-local.css', // HACK, see inside the file
                'skinStyles' => $getSkinSpecific( 'core' ),
                'targets' => [ 'desktop', 'mobile' ],
-               // ResourceLoaderImageModule doesn't support 'skipFunction', so instead we set this up so that
-               // this module is skipped together with its dependencies. Nothing else depends on these modules.
-               'dependencies' => [
-                       'oojs-ui.styles.icons',
-                       'oojs-ui.styles.indicators',
-                       'oojs-ui.styles.textures',
-               ],
-               'skipFunction' => 'resources/src/oojs-ui-styles-skip.js',
        ];
 
        // Additional widgets and layouts module.
diff --git a/resources/assets/licenses/README b/resources/assets/licenses/README
new file mode 100644 (file)
index 0000000..dae2549
--- /dev/null
@@ -0,0 +1,4 @@
+These license icons are used in LocalSettings.php files that are generated by
+the installer. Although "Public domain" has been removed from the installer as
+an option, the public-domain.png image needs to remain here to support older
+installations that refer to it in LocalSettings.php.
index d2a998c..999fae0 100644 (file)
@@ -4,7 +4,8 @@
                        "Calak",
                        "Muhammed taha",
                        "Serwan",
-                       "Pirehelokan"
+                       "Pirehelokan",
+                       "Sarchia"
                ]
        },
        "ooui-toolbar-more": "زیاتر",
@@ -16,5 +17,7 @@
        "ooui-dialog-process-dismiss": "لێگەڕان",
        "ooui-dialog-process-retry": "دیسان ھەوڵ بدە",
        "ooui-dialog-process-continue": "درێژە بدە",
-       "ooui-selectfile-placeholder": "ھیچ فایلێک ھەڵنەبژێراوە"
+       "ooui-selectfile-button-select": "پەڕگەیەک دەستنیشان بکە",
+       "ooui-selectfile-placeholder": "ھیچ فایلێک ھەڵنەبژێراوە",
+       "ooui-selectfile-dragdrop-placeholder": "پەڕگەکان بخەرە ئێرە"
 }
index 30e3efa..e193fb0 100644 (file)
@@ -8,12 +8,18 @@
                        "Palnatoke",
                        "Simeondahl",
                        "Tehnix",
-                       "Macofe"
+                       "Macofe",
+                       "Peter Alberti"
                ]
        },
        "ooui-outline-control-move-down": "Flyt ned",
        "ooui-outline-control-move-up": "Flyt op",
        "ooui-toolbar-more": "Mere",
        "ooui-toolgroup-expand": "Mere",
+       "ooui-toolgroup-collapse": "Færre",
+       "ooui-dialog-message-accept": "OK",
+       "ooui-dialog-message-reject": "Afbryd",
+       "ooui-dialog-process-error": "Noget gik galt",
+       "ooui-dialog-process-retry": "Prøv igen",
        "ooui-dialog-process-continue": "Fortsæt"
 }
index 881ff67..bf6b087 100644 (file)
@@ -6,11 +6,24 @@
                        "Kghbln",
                        "Marmase",
                        "Mirzali",
-                       "Se4598"
+                       "Se4598",
+                       "Kumkumuk"
                ]
        },
        "ooui-outline-control-move-down": "Bendi bere cêr",
        "ooui-outline-control-move-up": "Bendi bere cor",
        "ooui-outline-control-remove": "Obcey wedare",
-       "ooui-toolbar-more": "Zewbi"
+       "ooui-toolbar-more": "Zewbi",
+       "ooui-toolgroup-expand": "Dehana",
+       "ooui-toolgroup-collapse": "Deha tayn",
+       "ooui-dialog-message-accept": "TEMAM",
+       "ooui-dialog-message-reject": "Bıtexelne",
+       "ooui-dialog-process-error": "Tayê çi ğelet şi...",
+       "ooui-dialog-process-dismiss": "Racın",
+       "ooui-dialog-process-retry": "Fına bıcerbın",
+       "ooui-dialog-process-continue": "Dewam ke",
+       "ooui-selectfile-button-select": "Yu dosya weçinê",
+       "ooui-selectfile-not-supported": "Dosya weçinayış desteg nêvine na",
+       "ooui-selectfile-placeholder": "Dosya nêwçineya",
+       "ooui-selectfile-dragdrop-placeholder": "Dosya tiyara ake"
 }
index 6a38d0d..b272331 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
index 72591cc..c13ac7a 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -16,7 +16,8 @@
        cursor: pointer;
        display: inline-block;
        vertical-align: middle;
-       font: inherit;
+       font-family: inherit;
+       font-size: inherit;
        line-height: normal;
        white-space: nowrap;
        -webkit-touch-callout: none;
        color: #d45353;
 }
 .oo-ui-fieldLayout-messages .oo-ui-labelWidget {
-       padding: 0;
-       line-height: 1.875em;
+       padding: 0.1em 0;
+       line-height: 1.5em;
        vertical-align: middle;
 }
 .oo-ui-actionFieldLayout {
 }
 .oo-ui-menuSelectWidget {
        position: absolute;
+       width: 100%;
+       z-index: 4;
        background-color: #ffffff;
        margin-top: -1px;
        border: 1px solid #cccccc;
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
        cursor: pointer;
 }
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-dropdownWidget:last-child {
        margin-right: 0;
 }
        max-width: 50em;
        margin-right: 0.5em;
 }
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
 .oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
        margin-right: 0;
 }
+.oo-ui-progressBarWidget {
+       max-width: 50em;
+       background-color: #ffffff;
+       border: 1px solid #cccccc;
+       border-radius: 0.25em;
+       overflow: hidden;
+}
+.oo-ui-progressBarWidget-bar {
+       height: 1em;
+       border-right: 1px solid #cccccc;
+       -webkit-transition: width 250ms ease, margin-left 250ms ease;
+          -moz-transition: width 250ms ease, margin-left 250ms ease;
+               transition: width 250ms ease, margin-left 250ms ease;
+       background-color: #cde7f4;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
+       background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+       background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
+       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+}
+.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
+       -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+          -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+               animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+       width: 40%;
+       margin-left: -10%;
+       border-left: 1px solid #a6cee1;
+}
+.oo-ui-progressBarWidget.oo-ui-widget-disabled {
+       opacity: 0.6;
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
index 23ecccd..4e8f65c 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-element-hidden {
        display: none !important;
@@ -16,7 +16,8 @@
        cursor: pointer;
        display: inline-block;
        vertical-align: middle;
-       font: inherit;
+       font-family: inherit;
+       font-size: inherit;
        line-height: normal;
        white-space: nowrap;
        -webkit-touch-callout: none;
@@ -62,6 +63,7 @@
        margin-left: 0;
 }
 .oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+       margin-right: 0.25em;
        margin-left: 0.46875em;
 }
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        background-color: #d9d9d9;
        border-color: #d9d9d9;
-       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
        background-color: #999999;
        color: #ffffff;
+       border-color: #999999;
+       z-index: 3;
+}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
        color: #347bff;
 }
 .oo-ui-fieldLayout-messages .oo-ui-labelWidget {
        display: table-cell;
-       padding: 0;
-       line-height: 1.875;
+       padding: 0.1em 0;
+       line-height: 1.5;
        vertical-align: middle;
 }
 .oo-ui-actionFieldLayout {
        border-bottom-right-radius: 2px;
        border-top-right-radius: 2px;
 }
+.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement .oo-ui-buttonElement-button:focus {
+       border-color: #347bff;
+       z-index: 3;
+}
 .oo-ui-popupWidget {
        position: absolute;
        /* @noflip */
 }
 .oo-ui-menuSelectWidget {
        position: absolute;
+       width: 100%;
+       z-index: 4;
        background-color: #ffffff;
        margin-top: -1px;
        border: 1px solid #aaaaaa;
        position: relative;
        width: 100%;
        max-width: 50em;
-       background-color: #ffffff;
        margin-right: 0.5em;
 }
 .oo-ui-dropdownWidget-handle {
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
        cursor: pointer;
 }
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-dropdownWidget:last-child {
        margin-right: 0;
 }
 .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
        margin: 0 1em;
 }
-.oo-ui-dropdownWidget:hover .oo-ui-dropdownWidget-handle {
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
+       background-color: #ffffff;
+       -webkit-transition: border-color 100ms;
+          -moz-transition: border-color 100ms;
+               transition: border-color 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled:hover .oo-ui-dropdownWidget-handle {
        border-color: #aaaaaa;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
        max-width: 50em;
        margin-right: 0.5em;
 }
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
 .oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
        margin-right: 0;
 }
+.oo-ui-progressBarWidget {
+       max-width: 50em;
+       background-color: #ffffff;
+       border: 1px solid #cccccc;
+       border-radius: 2px;
+       overflow: hidden;
+}
+.oo-ui-progressBarWidget-bar {
+       background-color: #dddddd;
+       height: 1em;
+       -webkit-transition: width 200ms, margin-left 200ms;
+          -moz-transition: width 200ms, margin-left 200ms;
+               transition: width 200ms, margin-left 200ms;
+}
+.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
+       -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+          -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+               animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+       width: 40%;
+       margin-left: -10%;
+       border-left-width: 1px;
+}
+.oo-ui-progressBarWidget.oo-ui-widget-disabled {
+       opacity: 0.6;
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+       from {
+               margin-left: -40%;
+       }
+       to {
+               margin-left: 100%;
+       }
+}
index 4d32961..cd1a3de 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
@@ -2026,6 +2026,7 @@ OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
 OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
        this.active = !!value;
        this.$element.toggleClass( 'oo-ui-buttonElement-active', this.active );
+       this.updateThemeClasses();
        return this;
 };
 
@@ -7325,6 +7326,103 @@ OO.ui.FloatingMenuSelectWidget.prototype.toggle = function ( visible ) {
        return this;
 };
 
+/**
+ * Progress bars visually display the status of an operation, such as a download,
+ * and can be either determinate or indeterminate:
+ *
+ * - **determinate** process bars show the percent of an operation that is complete.
+ *
+ * - **indeterminate** process bars use a visual display of motion to indicate that an operation
+ *   is taking place. Because the extent of an indeterminate operation is unknown, the bar does
+ *   not use percentages.
+ *
+ * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
+ *
+ *     @example
+ *     // Examples of determinate and indeterminate progress bars.
+ *     var progressBar1 = new OO.ui.ProgressBarWidget( {
+ *         progress: 33
+ *     } );
+ *     var progressBar2 = new OO.ui.ProgressBarWidget();
+ *
+ *     // Create a FieldsetLayout to layout progress bars
+ *     var fieldset = new OO.ui.FieldsetLayout;
+ *     fieldset.addItems( [
+ *        new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
+ *        new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
+ *     ] );
+ *     $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
+ *  To create a determinate progress bar, specify a number that reflects the initial percent complete.
+ *  By default, the progress bar is indeterminate.
+ */
+OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.ProgressBarWidget.parent.call( this, config );
+
+       // Properties
+       this.$bar = $( '<div>' );
+       this.progress = null;
+
+       // Initialization
+       this.setProgress( config.progress !== undefined ? config.progress : false );
+       this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
+       this.$element
+               .attr( {
+                       role: 'progressbar',
+                       'aria-valuemin': 0,
+                       'aria-valuemax': 100
+               } )
+               .addClass( 'oo-ui-progressBarWidget' )
+               .append( this.$bar );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
+
+/* Static Properties */
+
+OO.ui.ProgressBarWidget.static.tagName = 'div';
+
+/* Methods */
+
+/**
+ * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
+ *
+ * @return {number|boolean} Progress percent
+ */
+OO.ui.ProgressBarWidget.prototype.getProgress = function () {
+       return this.progress;
+};
+
+/**
+ * Set the percent of the process completed or `false` for an indeterminate process.
+ *
+ * @param {number|boolean} progress Progress percent or `false` for indeterminate
+ */
+OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
+       this.progress = progress;
+
+       if ( progress !== false ) {
+               this.$bar.css( 'width', this.progress + '%' );
+               this.$element.attr( 'aria-valuenow', this.progress );
+       } else {
+               this.$bar.css( 'width', '' );
+               this.$element.removeAttr( 'aria-valuenow' );
+       }
+       this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', progress === false );
+};
+
 /**
  * InputWidget is the base class for all input widgets, which
  * include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs},
@@ -8590,7 +8688,8 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        // Events
        this.$input.on( {
                keypress: this.onKeyPress.bind( this ),
-               blur: this.onBlur.bind( this )
+               blur: this.onBlur.bind( this ),
+               focus: this.onFocus.bind( this )
        } );
        this.$input.one( {
                focus: this.onElementAttach.bind( this )
@@ -8602,6 +8701,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
                change: 'onChange',
                disable: 'onDisable'
        } );
+       this.on( 'change', OO.ui.debounce( this.onDebouncedChange.bind( this ), 250 ) );
 
        // Initialization
        this.$element
@@ -8744,6 +8844,16 @@ OO.ui.TextInputWidget.prototype.onBlur = function () {
        this.setValidityFlag();
 };
 
+/**
+ * Handle focus events.
+ *
+ * @private
+ * @param {jQuery.Event} e Focus event
+ */
+OO.ui.TextInputWidget.prototype.onFocus = function () {
+       this.setValidityFlag( true );
+};
+
 /**
  * Handle element attach events.
  *
@@ -8765,10 +8875,19 @@ OO.ui.TextInputWidget.prototype.onElementAttach = function () {
  */
 OO.ui.TextInputWidget.prototype.onChange = function () {
        this.updateSearchIndicator();
-       this.setValidityFlag();
        this.adjustSize();
 };
 
+/**
+ * Handle debounced change events.
+ *
+ * @param {string} value
+ * @private
+ */
+OO.ui.TextInputWidget.prototype.onDebouncedChange = function () {
+       this.setValidityFlag();
+};
+
 /**
  * Handle disable events.
  *
index ff18605..ab1e9ea 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
@@ -48,9 +48,7 @@ OO.ui.MediaWikiTheme.prototype.getElementClasses = function ( element ) {
        if ( element.supports( [ 'hasFlag' ] ) ) {
                isFramed = element.supports( [ 'isFramed' ] ) && element.isFramed();
                isActive = element.supports( [ 'isActive' ] ) && element.isActive();
-               if (
-                       ( isFramed && ( isActive || element.isDisabled() || element.hasFlag( 'primary' ) ) )
-               ) {
+               if ( isFramed && ( isActive || element.isDisabled() || element.hasFlag( 'primary' ) ) ) {
                        // Button with a dark background, use white icon
                        variants.invert = true;
                } else if ( !isFramed && element.isDisabled() ) {
index d69ebfa..75fd654 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
index 5b60528..0b55308 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
        line-height: 2.1;
        padding: 0 0.4em;
 }
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #555555;
+}
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
        background-color: #eeeeee;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
-       color: #555555;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
-       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active {
        background-color: #e5e5e5;
+       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled:hover {
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:hover {
        background-color: #eeeeee;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:active {
+       background-color: #e5e5e5;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.7;
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.9;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:active {
-       background-color: #e7e7e7;
-}
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
+.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title,
+.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
        color: #cccccc;
 }
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon,
+.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
 .oo-ui-popupToolGroup {
 .oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
        margin-right: 1.75em;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
-       background-color: #eeeeee;
-}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
-       background-color: #e5e5e5;
-}
 .oo-ui-popupToolGroup-handle {
        padding: 0.3125em;
        height: 2.5em;
 .oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
        left: 0;
 }
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
+       background-color: #eeeeee;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
+       background-color: #e5e5e5;
+}
 .oo-ui-popupToolGroup-header {
        line-height: 2.6;
        margin: 0 0.6em;
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
        background-color: #eeeeee;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:active {
-       background-color: #e7e7e7;
-}
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.9;
 }
 .oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
        background-color: #eeeeee;
 }
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
-}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #dddddd;
-}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
-}
-.oo-ui-listToolGroup.oo-ui-widget-disabled {
+.oo-ui-listToolGroup.oo-ui-widget-disabled,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-accel {
        color: #cccccc;
 }
 .oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
 .oo-ui-menuToolGroup .oo-ui-tool {
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
        background-color: #eeeeee;
 }
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
-}
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
-}
-.oo-ui-menuToolGroup.oo-ui-widget-disabled {
+.oo-ui-menuToolGroup.oo-ui-widget-disabled,
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title {
        color: #cccccc;
 }
 .oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
 .oo-ui-toolbar {
index 5254c76..18fda57 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
index a267b7f..b0e87af 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 .oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement {
        display: inline-block;
 }
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
        overflow-y: hidden;
 }
        padding: 0;
        background-color: transparent;
 }
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
-       position: relative;
-}
 .oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
        position: static;
        left: 2.25em;
        margin-left: -2px;
 }
-.oo-ui-progressBarWidget {
-       max-width: 50em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
-       border-radius: 0.25em;
-       overflow: hidden;
-}
-.oo-ui-progressBarWidget-bar {
-       height: 1em;
-       border-right: 1px solid #cccccc;
-       -webkit-transition: width 250ms ease, margin-left 250ms ease;
-          -moz-transition: width 250ms ease, margin-left 250ms ease;
-               transition: width 250ms ease, margin-left 250ms ease;
-       background-color: #cde7f4;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
-       background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
-       background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
-       background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
-}
-.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
-       -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-          -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-               animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-       width: 40%;
-       margin-left: -10%;
-       border-left: 1px solid #a6cee1;
-}
-.oo-ui-progressBarWidget.oo-ui-widget-disabled {
-       opacity: 0.6;
-}
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-selectFileWidget {
        display: inline-block;
        vertical-align: middle;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
-       position: absolute;
-       top: 0;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
-       float: right;
-}
 .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
        position: absolute;
 }
 .oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
            -ms-user-select: none;
                user-select: none;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-label {
+       position: absolute;
+       top: 0;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       text-overflow: ellipsis;
+}
+.oo-ui-selectFileWidget-fileType {
+       display: none;
+}
+.oo-ui-selectFileWidget-clearButton {
+       position: absolute;
        z-index: 2;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
+       position: relative;
        cursor: default;
-       height: 5.5em;
-       padding: 0;
+       height: 8.815em;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-iconElement-icon {
        display: none;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
-       height: 5.5em;
-       width: 5.5em;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+       width: 7.815em;
        position: absolute;
-       background-size: cover;
+       top: 0.5em;
+       bottom: 0.5em;
+       left: 0.5em;
        background-position: center center;
+       background-repeat: no-repeat;
+       background-size: contain;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+       background-repeat: repeat;
        background-size: auto;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
        opacity: 0.4;
-       background-color: #cccccc;
-       height: 5.5em;
-       width: 5.5em;
+       height: 7.815em;
+       width: 7.815em;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
-       border: 0;
-       background: none;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
        display: block;
        height: 100%;
        width: auto;
-       margin-left: 5.5em;
+       margin-left: 8.815em;
+       border: 0;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       display: block;
        position: relative;
+       top: inherit;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileName {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-fileName {
        display: block;
+       padding-top: 0.5em;
        padding-right: 2.375em;
-       overflow: hidden;
-       text-overflow: ellipsis;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
-       display: block;
-       float: none;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       position: absolute;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-clearButton {
        right: 0.5em;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
        display: block;
-       margin: 0.7em;
+       margin: 2.2em 1em 1em;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
        cursor: no-drop;
 }
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+       height: auto;
+}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       padding: 1em;
+}
 .oo-ui-selectFileWidget:last-child {
        margin-right: 0;
 }
        border-radius: 0.25em 0 0 0.25em;
        border-width: 1px 0 1px 1px;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       top: 0;
-       right: 0;
-       height: 2.3em;
-       margin-right: 0.775em;
-}
 .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
        top: 0;
        left: 0;
        height: 2.3em;
        margin-left: 0.3em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       top: 0;
+       right: 0;
+       height: 2.3em;
+       margin-right: 0.775em;
+}
+.oo-ui-selectFileWidget-label {
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       left: 0.5em;
+       right: 2.175em;
        line-height: 2.3em;
        margin: 0;
        overflow: hidden;
        white-space: nowrap;
-       -webkit-box-sizing: border-box;
-          -moz-box-sizing: border-box;
-               box-sizing: border-box;
        text-overflow: ellipsis;
-       left: 0.5em;
-       right: 0.5em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+.oo-ui-selectFileWidget-fileType {
        color: #888888;
+       display: block;
+       margin-top: 0.25em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-clearButton {
        top: 0;
+       right: 0;
        width: 1.875em;
        margin-right: 0;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+.oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        height: 2.3em;
 }
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
-}
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
        color: #cccccc;
 }
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
        left: 2.475em;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2.175em;
-}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 0;
-}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
        right: 4.2625em;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-clearButton {
        right: 2.0875em;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-label {
        right: 0.5em;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
        right: 2em;
 }
 .oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
        background-color: #e1f3ff;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
        background-color: #ffffff;
        border: 1px solid #aaaaaa;
-       margin-bottom: 0.5em;
        vertical-align: middle;
        border-radius: 0.25em;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        border-radius: 0.25em;
 }
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       line-height: 1.4;
+       overflow: inherit;
+       white-space: normal;
+}
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
        border-style: dashed;
 }
+.oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+       background-color: #f3f3f3;
+       color: #cccccc;
+       border-color: #dddddd;
+       text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
+       background-color: #f3f3f3;
+       color: #cccccc;
+       border-color: #dddddd;
+       text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
+}
 .oo-ui-outlineOptionWidget {
        position: relative;
        cursor: pointer;
 .oo-ui-capsuleMultiselectWidget-group {
        display: inline;
 }
-.oo-ui-capsuleMultiselectWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-capsuleMultiselectWidget-handle {
        background-color: #ffffff;
        cursor: text;
index 8145ef9..9632bac 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
 .oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement {
        display: inline-block;
 }
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
        overflow-y: hidden;
 }
 .oo-ui-buttonSelectWidget:focus {
        outline: 0;
 }
-.oo-ui-buttonSelectWidget:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
-       z-index: 2;
-}
 .oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
        border-radius: 0;
        margin-left: -1px;
        border-bottom-right-radius: 2px;
        border-top-right-radius: 2px;
 }
+.oo-ui-buttonSelectWidget.oo-ui-widget-enabled:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
+       border-color: #347bff;
+       box-shadow: inset 0 0 0 1px #347bff;
+}
 .oo-ui-buttonOptionWidget {
        display: inline-block;
        padding: 0;
 }
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
-       position: relative;
-}
 .oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
        position: static;
           -moz-transform: translateZ(0);
            -ms-transform: translateZ(0);
                transform: translateZ(0);
-       height: 2em;
        width: 3.5em;
+       min-height: 26px;
+       height: 2em;
        border: 1px solid #767676;
        border-radius: 1em;
        background-color: #ffffff;
                transition: border-color 100ms;
 }
 .oo-ui-toggleSwitchWidget-grip {
-       top: 0.35em;
+       top: 0.3125em;
        min-width: 16px;
-       width: 1.2em;
+       width: 1.25em;
        min-height: 16px;
-       height: 1.2em;
-       border-radius: 1.2em;
+       height: 1.25em;
+       border-radius: 1.25em;
        -webkit-transition: left 100ms, margin-left 100ms;
           -moz-transition: left 100ms, margin-left 100ms;
                transition: left 100ms, margin-left 100ms;
 .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled .oo-ui-toggleSwitchWidget-grip {
        background-color: #ffffff;
 }
-.oo-ui-progressBarWidget {
-       max-width: 50em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
-       border-radius: 2px;
-       overflow: hidden;
-}
-.oo-ui-progressBarWidget-bar {
-       background-color: #dddddd;
-       height: 1em;
-       -webkit-transition: width 200ms, margin-left 200ms;
-          -moz-transition: width 200ms, margin-left 200ms;
-               transition: width 200ms, margin-left 200ms;
-}
-.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
-       -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-          -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-               animation: oo-ui-progressBarWidget-slide 2s infinite linear;
-       width: 40%;
-       margin-left: -10%;
-       border-left-width: 1px;
-}
-.oo-ui-progressBarWidget.oo-ui-widget-disabled {
-       opacity: 0.6;
-}
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
-@keyframes oo-ui-progressBarWidget-slide {
-       from {
-               margin-left: -40%;
-       }
-       to {
-               margin-left: 100%;
-       }
-}
 .oo-ui-selectFileWidget {
        display: inline-block;
        vertical-align: middle;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
-       position: absolute;
-       top: 0;
-       bottom: 0;
-       left: 0;
-       right: 0;
-       text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
-       float: right;
-}
 .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
        position: absolute;
 }
 .oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
            -ms-user-select: none;
                user-select: none;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-label {
+       position: absolute;
+       top: 0;
+       bottom: 0;
+       left: 0;
+       right: 0;
+       text-overflow: ellipsis;
+}
+.oo-ui-selectFileWidget-fileType {
+       display: none;
+}
+.oo-ui-selectFileWidget-clearButton {
+       position: absolute;
        z-index: 2;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
+       position: relative;
        cursor: default;
-       height: 5.5em;
-       padding: 0;
+       height: 8.815em;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-iconElement-icon {
        display: none;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
-       height: 5.5em;
-       width: 5.5em;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+       width: 7.815em;
        position: absolute;
-       background-size: cover;
+       top: 0.5em;
+       bottom: 0.5em;
+       left: 0.5em;
        background-position: center center;
+       background-repeat: no-repeat;
+       background-size: contain;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+       background-repeat: repeat;
        background-size: auto;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
        opacity: 0.4;
-       background-color: #cccccc;
-       height: 5.5em;
-       width: 5.5em;
+       height: 7.815em;
+       width: 7.815em;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
-       border: 0;
-       background: none;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
        display: block;
        height: 100%;
        width: auto;
-       margin-left: 5.5em;
+       margin-left: 8.815em;
+       border: 0;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       display: block;
        position: relative;
+       top: inherit;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileName {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-fileName {
        display: block;
+       padding-top: 0.5em;
        padding-right: 2.375em;
-       overflow: hidden;
-       text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
-       display: block;
-       float: none;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       position: absolute;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-clearButton {
        right: 0.5em;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
        display: block;
-       margin: 0.7em;
+       margin: 2.2em 1em 1em;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
        cursor: no-drop;
 }
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+       height: auto;
+}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       padding: 1em;
+}
 .oo-ui-selectFileWidget:last-child {
        margin-right: 0;
 }
        height: 2.3em;
        margin-left: 0.5em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       top: 0;
+       right: 0;
+       height: 2.3em;
+       margin-right: 0.775em;
+}
+.oo-ui-selectFileWidget-label {
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
        display: block;
+       right: 2.375em;
        line-height: 2.3;
        margin: 0;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
-       left: 0;
-       right: 0;
        padding-left: 0.5em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+.oo-ui-selectFileWidget-fileType {
        color: #888888;
+       display: block;
+       margin-top: 0.25em;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+.oo-ui-selectFileWidget-clearButton {
        top: 0;
        right: 0;
-       height: 2.3em;
-       margin-right: 0.775em;
-}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
-       top: 0;
        min-width: 24px;
        width: 1.875em;
        margin-right: 0;
 }
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+.oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        height: 2.3em;
 }
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-       background-color: #f3f3f3;
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       cursor: default;
-}
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
-}
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
        color: #cccccc;
 }
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
        left: 2.875em;
 }
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 2.375em;
-}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
-       right: 0;
-}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
        right: 4.4625em;
        padding-left: 0;
 }
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-clearButton {
        right: 2.0875em;
 }
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
-       right: 0.5em;
-}
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
        right: 2em;
-       padding-left: 0;
 }
 .oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
        background-color: #ebf2ff;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
-       border-color: #dddddd;
-       background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
        background-color: #ffffff;
        border: 1px solid #cccccc;
-       margin-bottom: 0.5em;
        vertical-align: middle;
        overflow: hidden;
        border-radius: 2px;
 }
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        border-radius: 2px;
 }
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+       line-height: 1.4;
+       overflow: inherit;
+       white-space: normal;
+}
 .oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
        background-color: #eeeeee;
        border-style: dashed;
 }
+.oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+       background-color: #f3f3f3;
+       border-color: #dddddd;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
+       background-color: #f3f3f3;
+       color: #cccccc;
+       border-color: #dddddd;
+       text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+       opacity: 0.2;
+}
 .oo-ui-widget-disabled .oo-ui-selectFileWidget-dropLabel {
        display: none;
 }
 .oo-ui-capsuleMultiselectWidget-group {
        display: inline;
 }
-.oo-ui-capsuleMultiselectWidget > .oo-ui-menuSelectWidget {
-       z-index: 1;
-       width: 100%;
-}
 .oo-ui-capsuleMultiselectWidget-handle {
-       background-color: #ffffff;
-       cursor: text;
        min-height: 2.4em;
        margin-right: 0.5em;
        padding: 0.15em 0.25em;
        top: 0;
        margin: 0.3em;
 }
-.oo-ui-capsuleMultiselectWidget:hover .oo-ui-capsuleMultiselectWidget-handle {
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
+       background-color: #ffffff;
+       cursor: text;
+       -webkit-transition: border-color 100ms;
+          -moz-transition: border-color 100ms;
+               transition: border-color 100ms;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled:hover .oo-ui-capsuleMultiselectWidget-handle {
        border-color: #aaaaaa;
 }
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
        text-shadow: 0 1px 1px #ffffff;
        border-color: #dddddd;
        background-color: #f3f3f3;
-       cursor: default;
 }
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon,
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator {
index cd62339..7a38633 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
@@ -4770,103 +4770,6 @@ OO.ui.SelectFileWidget.prototype.setDisabled = function ( disabled ) {
        return this;
 };
 
-/**
- * Progress bars visually display the status of an operation, such as a download,
- * and can be either determinate or indeterminate:
- *
- * - **determinate** process bars show the percent of an operation that is complete.
- *
- * - **indeterminate** process bars use a visual display of motion to indicate that an operation
- *   is taking place. Because the extent of an indeterminate operation is unknown, the bar does
- *   not use percentages.
- *
- * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
- *
- *     @example
- *     // Examples of determinate and indeterminate progress bars.
- *     var progressBar1 = new OO.ui.ProgressBarWidget( {
- *         progress: 33
- *     } );
- *     var progressBar2 = new OO.ui.ProgressBarWidget();
- *
- *     // Create a FieldsetLayout to layout progress bars
- *     var fieldset = new OO.ui.FieldsetLayout;
- *     fieldset.addItems( [
- *        new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
- *        new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
- *     ] );
- *     $( 'body' ).append( fieldset.$element );
- *
- * @class
- * @extends OO.ui.Widget
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
- *  To create a determinate progress bar, specify a number that reflects the initial percent complete.
- *  By default, the progress bar is indeterminate.
- */
-OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Parent constructor
-       OO.ui.ProgressBarWidget.parent.call( this, config );
-
-       // Properties
-       this.$bar = $( '<div>' );
-       this.progress = null;
-
-       // Initialization
-       this.setProgress( config.progress !== undefined ? config.progress : false );
-       this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
-       this.$element
-               .attr( {
-                       role: 'progressbar',
-                       'aria-valuemin': 0,
-                       'aria-valuemax': 100
-               } )
-               .addClass( 'oo-ui-progressBarWidget' )
-               .append( this.$bar );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
-
-/* Static Properties */
-
-OO.ui.ProgressBarWidget.static.tagName = 'div';
-
-/* Methods */
-
-/**
- * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
- *
- * @return {number|boolean} Progress percent
- */
-OO.ui.ProgressBarWidget.prototype.getProgress = function () {
-       return this.progress;
-};
-
-/**
- * Set the percent of the process completed or `false` for an indeterminate process.
- *
- * @param {number|boolean} progress Progress percent or `false` for indeterminate
- */
-OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
-       this.progress = progress;
-
-       if ( progress !== false ) {
-               this.$bar.css( 'width', this.progress + '%' );
-               this.$element.attr( 'aria-valuenow', this.progress );
-       } else {
-               this.$bar.css( 'width', '' );
-               this.$element.removeAttr( 'aria-valuenow' );
-       }
-       this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', !progress );
-};
-
 /**
  * SearchWidgets combine a {@link OO.ui.TextInputWidget text input field}, where users can type a search query,
  * and a menu of search results, which is displayed beneath the query
index 55f891a..2f6c1a0 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
        width: 0;
        height: 0;
        overflow: hidden;
+       z-index: 4;
 }
 .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-active {
        width: auto;
index 0d3976f..465e17b 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
  */
 .oo-ui-window {
        background: transparent;
        width: 0;
        height: 0;
        overflow: hidden;
+       z-index: 4;
 }
 .oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-active {
        width: auto;
index 0e9dbf1..510399d 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
  */
 ( function ( OO ) {
 
index 563db00..d9cdf5a 100644 (file)
@@ -254,8 +254,9 @@ div.tleft {
        margin: .5em 1.4em 1.3em 0;
 }
 
-/* Hide elements that are marked as "empty" according to legacy Tidy rules
+/* Hide elements that are marked as "empty" according to legacy Tidy rules,
+ * except if a client script removes the mw-hide-empty-elt class from the body
  */
-.mw-empty-elt, .mw-empty-li {
+body.mw-hide-empty-elt .mw-empty-elt {
        display: none;
 }
index 9b9d324..b4edc50 100644 (file)
@@ -26,6 +26,7 @@
 
 .mw-ui-icon {
        position: relative;
+       line-height: @iconSize;
        min-height: @iconSize;
        min-width: @iconSize;
 
diff --git a/resources/src/mediawiki/mediawiki.raggett.css b/resources/src/mediawiki/mediawiki.raggett.css
deleted file mode 100644 (file)
index e69de29..0000000
index 85ded44..3b2c86e 100644 (file)
                var api, title, params,
                        imageSrc = $img.attr( 'src' );
 
+               // Reject promise if there is no thumbnail image
+               if ( $img[ 0 ] === undefined ) {
+                       return $.Deferred().reject();
+               }
+
                if ( this.imageInfoCache[ imageSrc ] === undefined ) {
                        api = new mw.Api();
                        // TODO: This supports only gallery of images
index c59f5ba..b860dbd 100644 (file)
                $links.click( function ( e ) {
                        var action, api, $link;
 
-                       // Preload the notification module for mw.notify
-                       mw.loader.load( 'mediawiki.notification' );
-
                        action = mwUriGetAction( this.href );
 
                        if ( action !== 'watch' && action !== 'unwatch' ) {
 
                        updateWatchLink( $link, action, 'loading' );
 
+                       // Preload the notification module for mw.notify
+                       mw.loader.load( 'mediawiki.notification' );
+
                        api = new mw.Api();
 
                        api[ action ]( title )
diff --git a/resources/src/oojs-ui-styles-skip.js b/resources/src/oojs-ui-styles-skip.js
deleted file mode 100644 (file)
index 57c905a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * Skip function for OOjs UI PHP style modules.
- *
- * The `<meta name="X-OOUI-PHP" />` is added to pages by OutputPage::enableOOUI().
- *
- * Looking for elements in the DOM might be expensive, but it's probably better than double-loading
- * 200 KB of CSS with embedded images because of bug T87871.
- */
-return !!jQuery( 'meta[name="X-OOUI-PHP"]' ).length;
index 35eb153..8740e5d 100644 (file)
@@ -23,12 +23,12 @@ mw-vagrant-guest:
   mediawiki_url: http://127.0.0.1/wiki/
 
 beta:
-  mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/
+  mediawiki_url: https://en.wikipedia.beta.wmflabs.org/wiki/
   mediawiki_user: Selenium_user
   # mediawiki_password: SET THIS IN THE ENVIRONMENT!
 
 test2:
-  mediawiki_url: http://test2.wikipedia.org/wiki/
+  mediawiki_url: https://test2.wikipedia.org/wiki/
   mediawiki_user: Selenium_user
   # mediawiki_password: SET THIS IN THE ENVIRONMENT!
 
index 9b85d3f..e965e2d 100644 (file)
@@ -174,10 +174,6 @@ class ParserTest {
                        echo "Warning: tidy is not installed, skipping some tests\n";
                }
 
-               if ( !extension_loaded( 'gd' ) ) {
-                       echo "Warning: GD extension is not present, thumbnailing tests will probably fail\n";
-               }
-
                $this->hooks = [];
                $this->functionHooks = [];
                $this->transparentHooks = [];
index 99b9029..788d304 100644 (file)
@@ -3087,7 +3087,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
                $expected = [
                        $rememberReq,
-                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
                        $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
                        $makeReq( "required2", AuthenticationRequest::PRIMARY_REQUIRED ),
                        $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
@@ -3107,10 +3107,10 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
                $expected = [
                        $rememberReq,
-                       $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
-                       $makeReq( "required", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
+                       $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
                        $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
-                       $makeReq( "foo", AuthenticationRequest::REQUIRED ),
+                       $makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
                        $makeReq( "bar", AuthenticationRequest::REQUIRED ),
                        $makeReq( "baz", AuthenticationRequest::REQUIRED ),
                ];
index bb9050f..6168182 100644 (file)
@@ -376,8 +376,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $content = new WikitextContent( 'test text' );
                $ok = ContentHandler::runLegacyHooks(
                        'testRunLegacyHooks',
-                       [ 'foo', &$content, 'bar' ],
-                       false
+                       [ 'foo', &$content, 'bar' ]
                );
 
                $this->assertTrue( $ok, "runLegacyHooks should have returned true" );
index ac83428..b4ae765 100644 (file)
@@ -102,6 +102,11 @@ class TextContentTest extends MediaWikiLangTestCase {
                                " Foo \n ",
                                ' Foo',
                        ],
+                       [
+                               # 2: newline normalization
+                               "LF\n\nCRLF\r\n\r\nCR\r\rEND",
+                               "LF\n\nCRLF\n\nCR\n\nEND",
+                       ],
                ];
        }
 
index 723f1c3..7e55a73 100644 (file)
@@ -285,8 +285,42 @@ class DatabaseTest extends MediaWikiTestCase {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
-               $db->rollback( __METHOD__ );
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
                $this->assertTrue( $called, 'Callback reached' );
        }
+
+       public function testGetScopedLock() {
+               $db = $this->db;
+
+               $db->setFlag( DBO_TRX );
+               try {
+                       $this->badLockingMethodImplicit( $db );
+               } catch ( RunTimeException $e ) {
+                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+               }
+               $db->clearFlag( DBO_TRX );
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+
+               try {
+                       $this->badLockingMethodExplicit( $db );
+               } catch ( RunTimeException $e ) {
+                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+               }
+               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+       }
+
+       private function badLockingMethodImplicit( IDatabase $db ) {
+               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+               $db->query( "SELECT 1" ); // trigger DBO_TRX
+               throw new RunTimeException( "Uh oh!" );
+       }
+
+       private function badLockingMethodExplicit( IDatabase $db ) {
+               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+               $db->begin( __METHOD__ );
+               throw new RunTimeException( "Uh oh!" );
+       }
 }
index a4871a6..f8dda6f 100644 (file)
@@ -44,6 +44,7 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
 
        /**
         * @covers ObjectFactory::getObjectFromSpec
+        * @covers ObjectFactory::expandClosures
         */
        public function testClosureExpansionEnabled() {
                $obj = ObjectFactory::getObjectFromSpec( [
@@ -80,6 +81,44 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( 'unwrapped', $obj->setterArgs[0] );
        }
 
+       /**
+        * @covers ObjectFactory::getObjectFromSpec
+        */
+       public function testGetObjectFromFactory() {
+               $args = [ 'a', 'b' ];
+               $obj = ObjectFactory::getObjectFromSpec( [
+                       'factory' => function ( $a, $b ) {
+                               return new ObjectFactoryTestFixture( $a, $b );
+                       },
+                       'args' => $args,
+               ] );
+               $this->assertSame( $args, $obj->args );
+       }
+
+       /**
+        * @covers ObjectFactory::getObjectFromSpec
+        * @expectedException InvalidArgumentException
+        */
+       public function testGetObjectFromInvalid() {
+               $args = [ 'a', 'b' ];
+               $obj = ObjectFactory::getObjectFromSpec( [
+                       // Missing 'class' or 'factory'
+                       'args' => $args,
+               ] );
+       }
+
+       /**
+        * @covers ObjectFactory::getObjectFromSpec
+        * @dataProvider provideConstructClassInstance
+        */
+       public function testGetObjectFromClass( $args ) {
+               $obj = ObjectFactory::getObjectFromSpec( [
+                       'class' => 'ObjectFactoryTestFixture',
+                       'args' => $args,
+               ] );
+               $this->assertSame( $args, $obj->args );
+       }
+
        /**
         * @covers ObjectFactory::constructClassInstance
         * @dataProvider provideConstructClassInstance
@@ -91,7 +130,7 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( $args, $obj->args );
        }
 
-       public function provideConstructClassInstance() {
+       public static function provideConstructClassInstance() {
                // These args go to 11. I thought about making 10 one louder, but 11!
                return [
                        '0 args' => [ [] ],
@@ -110,6 +149,7 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase {
        }
 
        /**
+        * @covers ObjectFactory::constructClassInstance
         * @expectedException InvalidArgumentException
         */
        public function testNamedArgs() {
index 081cb38..0b34b6f 100644 (file)
@@ -172,11 +172,17 @@ class SearchEngineTest extends MediaWikiLangTestCase {
                                        $name,
                                        $type
                                ] )->getMock();
+
                        $mockField->expects( $this->any() )->method( 'getMapping' )->willReturn( [
                                'testData' => 'test',
                                'name' => $name,
                                'type' => $type,
                        ] );
+
+                       $mockField->expects( $this->any() )
+                               ->method( 'merge' )
+                               ->willReturn( $mockField );
+
                        return $mockField;
                };
 
index 6446416..86ce53f 100644 (file)
@@ -86,6 +86,25 @@ class ResourcesTest extends MediaWikiTestCase {
                }
        }
 
+       /**
+        * Verify that all specified messages actually exist.
+        */
+       public function testMissingMessages() {
+               $data = self::getAllModules();
+               $validDeps = array_keys( $data['modules'] );
+               $lang = Language::factory( 'en' );
+
+               /** @var ResourceLoaderModule $module */
+               foreach ( $data['modules'] as $moduleName => $module ) {
+                       foreach ( $module->getMessages() as $msgKey ) {
+                               $this->assertTrue(
+                                       wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
+                                       "Message '$msgKey' required by '$moduleName' must exist"
+                               );
+                       }
+               }
+       }
+
        /**
         * Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
         * for the involved modules.
index 95f28c8..e30088d 100644 (file)
@@ -74,6 +74,7 @@ return [
                        'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.template.mustache.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
+                       'tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.html.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js
new file mode 100644 (file)
index 0000000..41d800a
--- /dev/null
@@ -0,0 +1,685 @@
+( function ( mw, $ ) {
+       QUnit.module( 'mediawiki (mw.loader)' );
+
+       mw.loader.addSource(
+               'testloader',
+               QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
+       );
+
+       /**
+        * The sync style load test (for @import). This is, in a way, also an open bug for
+        * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
+        * way to get a callback from when a stylesheet is loaded (that is, including any
+        * `@import` rules inside). To work around this, we'll have a little time loop to check
+        * if the styles apply.
+        *
+        * Note: This test originally used new Image() and onerror to get a callback
+        * when the url is loaded, but that is fragile since it doesn't monitor the
+        * same request as the css @import, and Safari 4 has issues with
+        * onerror/onload not being fired at all in weird cases like this.
+        */
+       function assertStyleAsync( assert, $element, prop, val, fn ) {
+               var styleTestStart,
+                       el = $element.get( 0 ),
+                       styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200;
+
+               function isCssImportApplied() {
+                       // Trigger reflow, repaint, redraw, whatever (cross-browser)
+                       var x = $element.css( 'height' );
+                       x = el.innerHTML;
+                       el.className = el.className;
+                       x = document.documentElement.clientHeight;
+
+                       return $element.css( prop ) === val;
+               }
+
+               function styleTestLoop() {
+                       var styleTestSince = new Date().getTime() - styleTestStart;
+                       // If it is passing or if we timed out, run the real test and stop the loop
+                       if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
+                               assert.equal( $element.css( prop ), val,
+                                       'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
+                               );
+
+                               if ( fn ) {
+                                       fn();
+                               }
+
+                               return;
+                       }
+                       // Otherwise, keep polling
+                       setTimeout( styleTestLoop );
+               }
+
+               // Start the loop
+               styleTestStart = new Date().getTime();
+               styleTestLoop();
+       }
+
+       function urlStyleTest( selector, prop, val ) {
+               return QUnit.fixurl(
+                       mw.config.get( 'wgScriptPath' ) +
+                               '/tests/qunit/data/styleTest.css.php?' +
+                               $.param( {
+                                       selector: selector,
+                                       prop: prop,
+                                       val: val
+                               } )
+               );
+       }
+
+       QUnit.test( 'Basic', 2, function ( assert ) {
+               var isAwesomeDone;
+
+               mw.loader.testCallback = function () {
+                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
+                       isAwesomeDone = true;
+               };
+
+               mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
+
+               return mw.loader.using( 'test.callback', function () {
+                       assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
+                       delete mw.loader.testCallback;
+
+               }, function () {
+                       assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
+               } );
+       } );
+
+       QUnit.test( 'Object method as module name', 2, function ( assert ) {
+               var isAwesomeDone;
+
+               mw.loader.testCallback = function () {
+                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
+                       isAwesomeDone = true;
+               };
+
+               mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
+
+               return mw.loader.using( 'hasOwnProperty', function () {
+                       assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
+                       delete mw.loader.testCallback;
+
+               }, function () {
+                       assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
+               } );
+       } );
+
+       QUnit.test( '.using( .. ) Promise', 2, function ( assert ) {
+               var isAwesomeDone;
+
+               mw.loader.testCallback = function () {
+                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
+                       isAwesomeDone = true;
+               };
+
+               mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
+
+               return mw.loader.using( 'test.promise' )
+               .done( function () {
+                       assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
+                       delete mw.loader.testCallback;
+
+               } )
+               .fail( function () {
+                       assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
+               } );
+       } );
+
+       QUnit.test( '.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
+               var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
+
+               assert.notEqual(
+                       $element.css( 'float' ),
+                       'right',
+                       'style is clear'
+               );
+
+               mw.loader.implement(
+                       'test.implement.a',
+                       function () {
+                               assert.equal(
+                                       $element.css( 'float' ),
+                                       'right',
+                                       'style is applied'
+                               );
+                       },
+                       {
+                               all: '.mw-test-implement-a { float: right; }'
+                       }
+               );
+
+               return mw.loader.using( 'test.implement.a' );
+       } );
+
+       QUnit.test( '.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
+               var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
+                       $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
+                       $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
+                       done = assert.async();
+
+               assert.notEqual(
+                       $element1.css( 'text-align' ),
+                       'center',
+                       'style is clear'
+               );
+               assert.notEqual(
+                       $element2.css( 'float' ),
+                       'left',
+                       'style is clear'
+               );
+               assert.notEqual(
+                       $element3.css( 'text-align' ),
+                       'right',
+                       'style is clear'
+               );
+
+               mw.loader.implement(
+                       'test.implement.b',
+                       function () {
+                               // Note: done() must only be called when the entire test is
+                               // complete. So, make sure that we don't start until *both*
+                               // assertStyleAsync calls have completed.
+                               var pending = 2;
+                               assertStyleAsync( assert, $element2, 'float', 'left', function () {
+                                       assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
+
+                                       pending--;
+                                       if ( pending === 0 ) {
+                                               done();
+                                       }
+                               } );
+                               assertStyleAsync( assert, $element3, 'float', 'right', function () {
+                                       assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
+
+                                       pending--;
+                                       if ( pending === 0 ) {
+                                               done();
+                                       }
+                               } );
+                       },
+                       {
+                               url: {
+                                       print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
+                                       screen: [
+                                               // bug 40834: Make sure it actually works with more than 1 stylesheet reference
+                                               urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
+                                               urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
+                                       ]
+                               }
+                       }
+               );
+
+               mw.loader.load( 'test.implement.b' );
+       } );
+
+       // Backwards compatibility
+       QUnit.test( '.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
+               var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
+
+               assert.notEqual(
+                       $element.css( 'float' ),
+                       'right',
+                       'style is clear'
+               );
+
+               mw.loader.implement(
+                       'test.implement.c',
+                       function () {
+                               assert.equal(
+                                       $element.css( 'float' ),
+                                       'right',
+                                       'style is applied'
+                               );
+                       },
+                       {
+                               all: '.mw-test-implement-c { float: right; }'
+                       }
+               );
+
+               return mw.loader.using( 'test.implement.c' );
+       } );
+
+       // Backwards compatibility
+       QUnit.test( '.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
+               var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
+                       $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
+                       done = assert.async();
+
+               assert.notEqual(
+                       $element.css( 'float' ),
+                       'right',
+                       'style is clear'
+               );
+               assert.notEqual(
+                       $element2.css( 'text-align' ),
+                       'center',
+                       'style is clear'
+               );
+
+               mw.loader.implement(
+                       'test.implement.d',
+                       function () {
+                               assertStyleAsync( assert, $element, 'float', 'right', function () {
+                                       assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
+                                       done();
+                               } );
+                       },
+                       {
+                               all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
+                               print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
+                       }
+               );
+
+               mw.loader.load( 'test.implement.d' );
+       } );
+
+       // @import (bug 31676)
+       QUnit.test( '.implement( styles has @import )', 7, function ( assert ) {
+               var isJsExecuted, $element,
+                       done = assert.async();
+
+               mw.loader.implement(
+                       'test.implement.import',
+                       function () {
+                               assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
+                               isJsExecuted = true;
+
+                               assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
+
+                               $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
+
+                               assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
+
+                               assertStyleAsync( assert, $element, 'float', 'right', function () {
+                                       assert.equal( $element.css( 'text-align' ), 'center',
+                                               'CSS styles after the @import rule are working'
+                                       );
+
+                                       done();
+                               } );
+                       },
+                       {
+                               css: [
+                                       '@import url(\''
+                                               + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
+                                               + '\');\n'
+                                               + '.mw-test-implement-import { text-align: center; }'
+                               ]
+                       },
+                       {
+                               'test-foobar': 'Hello Foobar, $1!'
+                       }
+               );
+
+               mw.loader.using( 'test.implement.import' ).always( function () {
+                       assert.strictEqual( isJsExecuted, true, 'script executed' );
+                       assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
+               } );
+       } );
+
+       QUnit.test( '.implement( dependency with styles )', 4, function ( assert ) {
+               var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
+                       $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
+
+               assert.notEqual(
+                       $element.css( 'float' ),
+                       'right',
+                       'style is clear'
+               );
+               assert.notEqual(
+                       $element2.css( 'float' ),
+                       'left',
+                       'style is clear'
+               );
+
+               mw.loader.register( [
+                       [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
+                       [ 'test.implement.e2', '0' ]
+               ] );
+
+               mw.loader.implement(
+                       'test.implement.e',
+                       function () {
+                               assert.equal(
+                                       $element.css( 'float' ),
+                                       'right',
+                                       'Depending module\'s style is applied'
+                               );
+                       },
+                       {
+                               all: '.mw-test-implement-e { float: right; }'
+                       }
+               );
+
+               mw.loader.implement(
+                       'test.implement.e2',
+                       function () {
+                               assert.equal(
+                                       $element2.css( 'float' ),
+                                       'left',
+                                       'Dependency\'s style is applied'
+                               );
+                       },
+                       {
+                               all: '.mw-test-implement-e2 { float: left; }'
+                       }
+               );
+
+               return mw.loader.using( 'test.implement.e' );
+       } );
+
+       QUnit.test( '.implement( only scripts )', 1, function ( assert ) {
+               mw.loader.implement( 'test.onlyscripts', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
+       } );
+
+       QUnit.test( '.implement( only messages )', 2, function ( assert ) {
+               assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
+
+               // jscs: disable requireCamelCaseOrUpperCaseIdentifiers
+               mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } );
+               // jscs: enable requireCamelCaseOrUpperCaseIdentifiers
+
+               return mw.loader.using( 'test.implement.msgs', function () {
+                       assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
+               }, function () {
+                       assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
+               } );
+       } );
+
+       QUnit.test( '.implement( empty )', 1, function ( assert ) {
+               mw.loader.implement( 'test.empty' );
+               assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
+       } );
+
+       QUnit.test( 'Broken indirect dependency', 4, function ( assert ) {
+               // don't emit an error event
+               this.sandbox.stub( mw, 'track' );
+
+               mw.loader.register( [
+                       [ 'test.module1', '0' ],
+                       [ 'test.module2', '0', [ 'test.module1' ] ],
+                       [ 'test.module3', '0', [ 'test.module2' ] ]
+               ] );
+               mw.loader.implement( 'test.module1', function () {
+                       throw new Error( 'expected' );
+               }, {}, {} );
+               assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
+               assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
+               assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
+
+               assert.strictEqual( mw.track.callCount, 1 );
+       } );
+
+       QUnit.test( 'Circular dependency', 1, function ( assert ) {
+               mw.loader.register( [
+                       [ 'test.circle1', '0', [ 'test.circle2' ] ],
+                       [ 'test.circle2', '0', [ 'test.circle3' ] ],
+                       [ 'test.circle3', '0', [ 'test.circle1' ] ]
+               ] );
+               assert.throws( function () {
+                       mw.loader.using( 'test.circle3' );
+               }, /Circular/, 'Detect circular dependency' );
+       } );
+
+       QUnit.test( 'Out-of-order implementation', 9, function ( assert ) {
+               mw.loader.register( [
+                       [ 'test.module4', '0' ],
+                       [ 'test.module5', '0', [ 'test.module4' ] ],
+                       [ 'test.module6', '0', [ 'test.module5' ] ]
+               ] );
+               mw.loader.implement( 'test.module4', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
+               mw.loader.implement( 'test.module6', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
+               mw.loader.implement( 'test.module5', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
+               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
+       } );
+
+       QUnit.test( 'Missing dependency', 13, function ( assert ) {
+               mw.loader.register( [
+                       [ 'test.module7', '0' ],
+                       [ 'test.module8', '0', [ 'test.module7' ] ],
+                       [ 'test.module9', '0', [ 'test.module8' ] ]
+               ] );
+               mw.loader.implement( 'test.module8', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
+               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
+               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
+               mw.loader.state( 'test.module7', 'missing' );
+               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
+               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
+               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
+               mw.loader.implement( 'test.module9', function () {} );
+               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
+               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
+               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
+               mw.loader.using(
+                       [ 'test.module7' ],
+                       function () {
+                               assert.ok( false, 'Success fired despite missing dependency' );
+                               assert.ok( true, 'QUnit expected() count dummy' );
+                       },
+                       function ( e, dependencies ) {
+                               assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
+                               assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
+                       }
+               );
+               mw.loader.using(
+                       [ 'test.module9' ],
+                       function () {
+                               assert.ok( false, 'Success fired despite missing dependency' );
+                               assert.ok( true, 'QUnit expected() count dummy' );
+                       },
+                       function ( e, dependencies ) {
+                               assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
+                               dependencies.sort();
+                               assert.deepEqual(
+                                       dependencies,
+                                       [ 'test.module7', 'test.module8', 'test.module9' ],
+                                       'Error callback called with all three modules as dependencies'
+                               );
+                       }
+               );
+       } );
+
+       QUnit.test( 'Dependency handling', 5, function ( assert ) {
+               var done = assert.async();
+               mw.loader.register( [
+                       // [module, version, dependencies, group, source]
+                       [ 'testMissing', '1', [], null, 'testloader' ],
+                       [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
+                       [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
+               ] );
+
+               function verifyModuleStates() {
+                       assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
+                       assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
+                       assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
+               }
+
+               mw.loader.using( [ 'testUsesNestedMissing' ],
+                       function () {
+                               assert.ok( false, 'Error handler should be invoked.' );
+                               assert.ok( true ); // Dummy to reach QUnit expect()
+
+                               verifyModuleStates();
+
+                               done();
+                       },
+                       function ( e, badmodules ) {
+                               assert.ok( true, 'Error handler should be invoked.' );
+                               // As soon as server spits out state('testMissing', 'missing');
+                               // it will bubble up and trigger the error callback.
+                               // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
+                               assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
+
+                               verifyModuleStates();
+
+                               done();
+                       }
+               );
+       } );
+
+       QUnit.test( 'Skip-function handling', 5, function ( assert ) {
+               mw.loader.register( [
+                       // [module, version, dependencies, group, source, skip]
+                       [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
+                       [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
+                       [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
+               ] );
+
+               function verifyModuleStates() {
+                       assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
+                       assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
+                       assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
+               }
+
+               return mw.loader.using( [ 'testUsesSkippable' ],
+                       function () {
+                               assert.ok( true, 'Success handler should be invoked.' );
+                               assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
+
+                               verifyModuleStates();
+                       },
+                       function ( e, badmodules ) {
+                               assert.ok( false, 'Error handler should not be invoked.' );
+                               assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
+
+                               verifyModuleStates();
+                       }
+               );
+       } );
+
+       QUnit.asyncTest( '.load( "//protocol-relative" ) - T32825', 2, function ( assert ) {
+               // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
+               // Test is for regressions!
+
+               // Forge a URL to the test callback script
+               var target = QUnit.fixurl(
+                       mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
+               );
+
+               // Confirm that mw.loader.load() works with protocol-relative URLs
+               target = target.replace( /https?:/, '' );
+
+               assert.equal( target.slice( 0, 2 ), '//',
+                       'URL must be relative to test relative URLs!'
+               );
+
+               // Async!
+               // The target calls QUnit.start
+               mw.loader.load( target );
+       } );
+
+       QUnit.asyncTest( '.load( "/absolute-path" )', 2, function ( assert ) {
+               // Forge a URL to the test callback script
+               var target = QUnit.fixurl(
+                       mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
+               );
+
+               // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
+               assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
+
+               // Async!
+               // The target calls QUnit.start
+               mw.loader.load( target );
+       } );
+
+       QUnit.test( 'Executing race - T112232', 2, function ( assert ) {
+               var done = false;
+
+               // The red herring schedules its CSS buffer first. In T112232, a bug in the
+               // state machine would cause the job for testRaceLoadMe to run with an earlier job.
+               mw.loader.implement(
+                       'testRaceRedHerring',
+                       function () {},
+                       { css: [ '.mw-testRaceRedHerring {}' ] }
+               );
+               mw.loader.implement(
+                       'testRaceLoadMe',
+                       function () {
+                               done = true;
+                       },
+                       { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
+               );
+
+               mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
+               return mw.loader.using( 'testRaceLoadMe', function () {
+                       assert.strictEqual( done, true, 'script ran' );
+                       assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
+               } );
+       } );
+
+       QUnit.test( 'require()', 6, function ( assert ) {
+               mw.loader.register( [
+                       [ 'test.require1', '0' ],
+                       [ 'test.require2', '0' ],
+                       [ 'test.require3', '0' ],
+                       [ 'test.require4', '0', [ 'test.require3' ] ]
+               ] );
+               mw.loader.implement( 'test.require1', function () {} );
+               mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
+                       module.exports = 1;
+               } );
+               mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
+                       module.exports = function () {
+                               return 'hello world';
+                       };
+               } );
+               mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
+                       var other = require( 'test.require3' );
+                       module.exports = {
+                               pizza: function () {
+                                       return other();
+                               }
+                       };
+               } );
+               return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
+               .then( function ( require ) {
+                       var module1, module2, module3, module4;
+
+                       module1 = require( 'test.require1' );
+                       module2 = require( 'test.require2' );
+                       module3 = require( 'test.require3' );
+                       module4 = require( 'test.require4' );
+
+                       assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
+                       assert.strictEqual( module2, 1, 'export a number' );
+                       assert.strictEqual( module3(), 'hello world', 'export a function' );
+                       assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
+                       assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
+
+                       assert.throws( function () {
+                               require( '_badmodule' );
+                       }, /is not loaded/, 'Requesting non-existent modules throws error.' );
+               } );
+       } );
+
+       QUnit.test( 'require() in debug mode', 1, function ( assert ) {
+               var path = mw.config.get( 'wgScriptPath' );
+               mw.loader.register( [
+                       [ 'test.require.define', '0' ],
+                       [ 'test.require.callback', '0', [ 'test.require.define' ] ]
+               ] );
+               mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
+               mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
+
+               return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
+                       var exported = require( 'test.require.callback' );
+                       assert.strictEqual( exported, 'Require worked.Define worked.',
+                               'module.exports worked in debug mode' );
+               }, function () {
+                       assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
+               } );
+       } );
+
+}( mediaWiki, jQuery ) );
index baec37c..ab463a9 100644 (file)
@@ -1,5 +1,5 @@
 /*jshint -W024 */
-( function ( mw, $ ) {
+( function ( mw ) {
        var specialCharactersPageName,
                // Can't mock SITENAME since jqueryMsg caches it at load
                siteName = mw.config.get( 'wgSiteName' );
                }
        } ) );
 
-       mw.loader.addSource(
-               'testloader',
-               QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
-       );
-
        QUnit.test( 'Initial check', 8, function ( assert ) {
                assert.ok( window.jQuery, 'jQuery defined' );
                assert.ok( window.$, '$ defined' );
                assert.equal( mw.msg( 'int-msg' ), 'Some Other Message', 'int is resolved' );
        } );
 
-       /**
-        * The sync style load test (for @import). This is, in a way, also an open bug for
-        * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
-        * way to get a callback from when a stylesheet is loaded (that is, including any
-        * `@import` rules inside). To work around this, we'll have a little time loop to check
-        * if the styles apply.
-        *
-        * Note: This test originally used new Image() and onerror to get a callback
-        * when the url is loaded, but that is fragile since it doesn't monitor the
-        * same request as the css @import, and Safari 4 has issues with
-        * onerror/onload not being fired at all in weird cases like this.
-        */
-       function assertStyleAsync( assert, $element, prop, val, fn ) {
-               var styleTestStart,
-                       el = $element.get( 0 ),
-                       styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200;
-
-               function isCssImportApplied() {
-                       // Trigger reflow, repaint, redraw, whatever (cross-browser)
-                       var x = $element.css( 'height' );
-                       x = el.innerHTML;
-                       el.className = el.className;
-                       x = document.documentElement.clientHeight;
-
-                       return $element.css( prop ) === val;
-               }
-
-               function styleTestLoop() {
-                       var styleTestSince = new Date().getTime() - styleTestStart;
-                       // If it is passing or if we timed out, run the real test and stop the loop
-                       if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
-                               assert.equal( $element.css( prop ), val,
-                                       'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
-                               );
-
-                               if ( fn ) {
-                                       fn();
-                               }
-
-                               return;
-                       }
-                       // Otherwise, keep polling
-                       setTimeout( styleTestLoop );
-               }
-
-               // Start the loop
-               styleTestStart = new Date().getTime();
-               styleTestLoop();
-       }
-
-       function urlStyleTest( selector, prop, val ) {
-               return QUnit.fixurl(
-                       mw.config.get( 'wgScriptPath' ) +
-                               '/tests/qunit/data/styleTest.css.php?' +
-                               $.param( {
-                                       selector: selector,
-                                       prop: prop,
-                                       val: val
-                               } )
-               );
-       }
-
-       QUnit.test( 'mw.loader', 2, function ( assert ) {
-               var isAwesomeDone;
-
-               mw.loader.testCallback = function () {
-                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
-                       isAwesomeDone = true;
-               };
-
-               mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
-
-               return mw.loader.using( 'test.callback', function () {
-                       assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
-                       delete mw.loader.testCallback;
-
-               }, function () {
-                       assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader with Object method as module name', 2, function ( assert ) {
-               var isAwesomeDone;
-
-               mw.loader.testCallback = function () {
-                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
-                       isAwesomeDone = true;
-               };
-
-               mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
-
-               return mw.loader.using( 'hasOwnProperty', function () {
-                       assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
-                       delete mw.loader.testCallback;
-
-               }, function () {
-                       assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader.using( .. ) Promise', 2, function ( assert ) {
-               var isAwesomeDone;
-
-               mw.loader.testCallback = function () {
-                       assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
-                       isAwesomeDone = true;
-               };
-
-               mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
-
-               return mw.loader.using( 'test.promise' )
-               .done( function () {
-                       assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
-                       delete mw.loader.testCallback;
-
-               } )
-               .fail( function () {
-                       assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
-               var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
-
-               assert.notEqual(
-                       $element.css( 'float' ),
-                       'right',
-                       'style is clear'
-               );
-
-               mw.loader.implement(
-                       'test.implement.a',
-                       function () {
-                               assert.equal(
-                                       $element.css( 'float' ),
-                                       'right',
-                                       'style is applied'
-                               );
-                       },
-                       {
-                               all: '.mw-test-implement-a { float: right; }'
-                       }
-               );
-
-               return mw.loader.using( 'test.implement.a' );
-       } );
-
-       QUnit.test( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
-               var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
-                       $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
-                       $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
-                       done = assert.async();
-
-               assert.notEqual(
-                       $element1.css( 'text-align' ),
-                       'center',
-                       'style is clear'
-               );
-               assert.notEqual(
-                       $element2.css( 'float' ),
-                       'left',
-                       'style is clear'
-               );
-               assert.notEqual(
-                       $element3.css( 'text-align' ),
-                       'right',
-                       'style is clear'
-               );
-
-               mw.loader.implement(
-                       'test.implement.b',
-                       function () {
-                               // Note: done() must only be called when the entire test is
-                               // complete. So, make sure that we don't start until *both*
-                               // assertStyleAsync calls have completed.
-                               var pending = 2;
-                               assertStyleAsync( assert, $element2, 'float', 'left', function () {
-                                       assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
-
-                                       pending--;
-                                       if ( pending === 0 ) {
-                                               done();
-                                       }
-                               } );
-                               assertStyleAsync( assert, $element3, 'float', 'right', function () {
-                                       assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
-
-                                       pending--;
-                                       if ( pending === 0 ) {
-                                               done();
-                                       }
-                               } );
-                       },
-                       {
-                               url: {
-                                       print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
-                                       screen: [
-                                               // bug 40834: Make sure it actually works with more than 1 stylesheet reference
-                                               urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
-                                               urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
-                                       ]
-                               }
-                       }
-               );
-
-               mw.loader.load( 'test.implement.b' );
-       } );
-
-       // Backwards compatibility
-       QUnit.test( 'mw.loader.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
-               var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
-
-               assert.notEqual(
-                       $element.css( 'float' ),
-                       'right',
-                       'style is clear'
-               );
-
-               mw.loader.implement(
-                       'test.implement.c',
-                       function () {
-                               assert.equal(
-                                       $element.css( 'float' ),
-                                       'right',
-                                       'style is applied'
-                               );
-                       },
-                       {
-                               all: '.mw-test-implement-c { float: right; }'
-                       }
-               );
-
-               return mw.loader.using( 'test.implement.c' );
-       } );
-
-       // Backwards compatibility
-       QUnit.test( 'mw.loader.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
-               var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
-                       $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
-                       done = assert.async();
-
-               assert.notEqual(
-                       $element.css( 'float' ),
-                       'right',
-                       'style is clear'
-               );
-               assert.notEqual(
-                       $element2.css( 'text-align' ),
-                       'center',
-                       'style is clear'
-               );
-
-               mw.loader.implement(
-                       'test.implement.d',
-                       function () {
-                               assertStyleAsync( assert, $element, 'float', 'right', function () {
-                                       assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
-                                       done();
-                               } );
-                       },
-                       {
-                               all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
-                               print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
-                       }
-               );
-
-               mw.loader.load( 'test.implement.d' );
-       } );
-
-       // @import (bug 31676)
-       QUnit.test( 'mw.loader.implement( styles has @import )', 7, function ( assert ) {
-               var isJsExecuted, $element,
-                       done = assert.async();
-
-               mw.loader.implement(
-                       'test.implement.import',
-                       function () {
-                               assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
-                               isJsExecuted = true;
-
-                               assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
-
-                               $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
-
-                               assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
-
-                               assertStyleAsync( assert, $element, 'float', 'right', function () {
-                                       assert.equal( $element.css( 'text-align' ), 'center',
-                                               'CSS styles after the @import rule are working'
-                                       );
-
-                                       done();
-                               } );
-                       },
-                       {
-                               css: [
-                                       '@import url(\''
-                                               + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
-                                               + '\');\n'
-                                               + '.mw-test-implement-import { text-align: center; }'
-                               ]
-                       },
-                       {
-                               'test-foobar': 'Hello Foobar, $1!'
-                       }
-               );
-
-               mw.loader.using( 'test.implement.import' ).always( function () {
-                       assert.strictEqual( isJsExecuted, true, 'script executed' );
-                       assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader.implement( dependency with styles )', 4, function ( assert ) {
-               var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
-                       $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
-
-               assert.notEqual(
-                       $element.css( 'float' ),
-                       'right',
-                       'style is clear'
-               );
-               assert.notEqual(
-                       $element2.css( 'float' ),
-                       'left',
-                       'style is clear'
-               );
-
-               mw.loader.register( [
-                       [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
-                       [ 'test.implement.e2', '0' ]
-               ] );
-
-               mw.loader.implement(
-                       'test.implement.e',
-                       function () {
-                               assert.equal(
-                                       $element.css( 'float' ),
-                                       'right',
-                                       'Depending module\'s style is applied'
-                               );
-                       },
-                       {
-                               all: '.mw-test-implement-e { float: right; }'
-                       }
-               );
-
-               mw.loader.implement(
-                       'test.implement.e2',
-                       function () {
-                               assert.equal(
-                                       $element2.css( 'float' ),
-                                       'left',
-                                       'Dependency\'s style is applied'
-                               );
-                       },
-                       {
-                               all: '.mw-test-implement-e2 { float: left; }'
-                       }
-               );
-
-               return mw.loader.using( 'test.implement.e' );
-       } );
-
-       QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) {
-               mw.loader.implement( 'test.onlyscripts', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
-       } );
-
-       QUnit.test( 'mw.loader.implement( only messages )', 2, function ( assert ) {
-               assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
-
-               // jscs: disable requireCamelCaseOrUpperCaseIdentifiers
-               mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } );
-               // jscs: enable requireCamelCaseOrUpperCaseIdentifiers
-
-               return mw.loader.using( 'test.implement.msgs', function () {
-                       assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
-               }, function () {
-                       assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader.implement( empty )', 1, function ( assert ) {
-               mw.loader.implement( 'test.empty' );
-               assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
-       } );
-
-       QUnit.test( 'mw.loader with broken indirect dependency', 4, function ( assert ) {
-               // don't emit an error event
-               this.sandbox.stub( mw, 'track' );
-
-               mw.loader.register( [
-                       [ 'test.module1', '0' ],
-                       [ 'test.module2', '0', [ 'test.module1' ] ],
-                       [ 'test.module3', '0', [ 'test.module2' ] ]
-               ] );
-               mw.loader.implement( 'test.module1', function () {
-                       throw new Error( 'expected' );
-               }, {}, {} );
-               assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
-               assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
-               assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
-
-               assert.strictEqual( mw.track.callCount, 1 );
-       } );
-
-       QUnit.test( 'mw.loader with circular dependency', 1, function ( assert ) {
-               mw.loader.register( [
-                       [ 'test.circle1', '0', [ 'test.circle2' ] ],
-                       [ 'test.circle2', '0', [ 'test.circle3' ] ],
-                       [ 'test.circle3', '0', [ 'test.circle1' ] ]
-               ] );
-               assert.throws( function () {
-                       mw.loader.using( 'test.circle3' );
-               }, /Circular/, 'Detect circular dependency' );
-       } );
-
-       QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) {
-               mw.loader.register( [
-                       [ 'test.module4', '0' ],
-                       [ 'test.module5', '0', [ 'test.module4' ] ],
-                       [ 'test.module6', '0', [ 'test.module5' ] ]
-               ] );
-               mw.loader.implement( 'test.module4', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
-               mw.loader.implement( 'test.module6', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
-               mw.loader.implement( 'test.module5', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
-               assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
-               assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
-       } );
-
-       QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) {
-               mw.loader.register( [
-                       [ 'test.module7', '0' ],
-                       [ 'test.module8', '0', [ 'test.module7' ] ],
-                       [ 'test.module9', '0', [ 'test.module8' ] ]
-               ] );
-               mw.loader.implement( 'test.module8', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
-               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
-               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
-               mw.loader.state( 'test.module7', 'missing' );
-               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
-               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
-               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
-               mw.loader.implement( 'test.module9', function () {} );
-               assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
-               assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
-               assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
-               mw.loader.using(
-                       [ 'test.module7' ],
-                       function () {
-                               assert.ok( false, 'Success fired despite missing dependency' );
-                               assert.ok( true, 'QUnit expected() count dummy' );
-                       },
-                       function ( e, dependencies ) {
-                               assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
-                               assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
-                       }
-               );
-               mw.loader.using(
-                       [ 'test.module9' ],
-                       function () {
-                               assert.ok( false, 'Success fired despite missing dependency' );
-                               assert.ok( true, 'QUnit expected() count dummy' );
-                       },
-                       function ( e, dependencies ) {
-                               assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
-                               dependencies.sort();
-                               assert.deepEqual(
-                                       dependencies,
-                                       [ 'test.module7', 'test.module8', 'test.module9' ],
-                                       'Error callback called with all three modules as dependencies'
-                               );
-                       }
-               );
-       } );
-
-       QUnit.test( 'mw.loader dependency handling', 5, function ( assert ) {
-               var done = assert.async();
-               mw.loader.register( [
-                       // [module, version, dependencies, group, source]
-                       [ 'testMissing', '1', [], null, 'testloader' ],
-                       [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
-                       [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
-               ] );
-
-               function verifyModuleStates() {
-                       assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
-                       assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
-                       assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
-               }
-
-               mw.loader.using( [ 'testUsesNestedMissing' ],
-                       function () {
-                               assert.ok( false, 'Error handler should be invoked.' );
-                               assert.ok( true ); // Dummy to reach QUnit expect()
-
-                               verifyModuleStates();
-
-                               done();
-                       },
-                       function ( e, badmodules ) {
-                               assert.ok( true, 'Error handler should be invoked.' );
-                               // As soon as server spits out state('testMissing', 'missing');
-                               // it will bubble up and trigger the error callback.
-                               // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
-                               assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
-
-                               verifyModuleStates();
-
-                               done();
-                       }
-               );
-       } );
-
-       QUnit.test( 'mw.loader skin-function handling', 5, function ( assert ) {
-               mw.loader.register( [
-                       // [module, version, dependencies, group, source, skip]
-                       [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
-                       [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
-                       [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
-               ] );
-
-               function verifyModuleStates() {
-                       assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
-                       assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
-                       assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
-               }
-
-               return mw.loader.using( [ 'testUsesSkippable' ],
-                       function () {
-                               assert.ok( true, 'Success handler should be invoked.' );
-                               assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
-
-                               verifyModuleStates();
-                       },
-                       function ( e, badmodules ) {
-                               assert.ok( false, 'Error handler should not be invoked.' );
-                               assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
-
-                               verifyModuleStates();
-                       }
-               );
-       } );
-
-       QUnit.asyncTest( 'mw.loader( "//protocol-relative" ) (bug 30825)', 2, function ( assert ) {
-               // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
-               // Test is for regressions!
-
-               // Forge a URL to the test callback script
-               var target = QUnit.fixurl(
-                       mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
-               );
-
-               // Confirm that mw.loader.load() works with protocol-relative URLs
-               target = target.replace( /https?:/, '' );
-
-               assert.equal( target.slice( 0, 2 ), '//',
-                       'URL must be relative to test relative URLs!'
-               );
-
-               // Async!
-               // The target calls QUnit.start
-               mw.loader.load( target );
-       } );
-
-       QUnit.asyncTest( 'mw.loader( "/absolute-path" )', 2, function ( assert ) {
-               // Forge a URL to the test callback script
-               var target = QUnit.fixurl(
-                       mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
-               );
-
-               // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
-               assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
-
-               // Async!
-               // The target calls QUnit.start
-               mw.loader.load( target );
-       } );
-
-       QUnit.test( 'mw.loader() executing race (T112232)', 2, function ( assert ) {
-               var done = false;
-
-               // The red herring schedules its CSS buffer first. In T112232, a bug in the
-               // state machine would cause the job for testRaceLoadMe to run with an earlier job.
-               mw.loader.implement(
-                       'testRaceRedHerring',
-                       function () {},
-                       { css: [ '.mw-testRaceRedHerring {}' ] }
-               );
-               mw.loader.implement(
-                       'testRaceLoadMe',
-                       function () {
-                               done = true;
-                       },
-                       { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
-               );
-
-               mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
-               return mw.loader.using( 'testRaceLoadMe', function () {
-                       assert.strictEqual( done, true, 'script ran' );
-                       assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
-               } );
-       } );
-
        QUnit.test( 'mw.hook', 13, function ( assert ) {
                var hook, add, fire, chars, callback;
 
                );
        } );
 
-       QUnit.test( 'mw.loader require()', 6, function ( assert ) {
-               mw.loader.register( [
-                       [ 'test.require1', '0' ],
-                       [ 'test.require2', '0' ],
-                       [ 'test.require3', '0' ],
-                       [ 'test.require4', '0', [ 'test.require3' ] ]
-               ] );
-               mw.loader.implement( 'test.require1', function () {} );
-               mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
-                       module.exports = 1;
-               } );
-               mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
-                       module.exports = function () {
-                               return 'hello world';
-                       };
-               } );
-               mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
-                       var other = require( 'test.require3' );
-                       module.exports = {
-                               pizza: function () {
-                                       return other();
-                               }
-                       };
-               } );
-               return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
-               .then( function ( require ) {
-                       var module1, module2, module3, module4;
-
-                       module1 = require( 'test.require1' );
-                       module2 = require( 'test.require2' );
-                       module3 = require( 'test.require3' );
-                       module4 = require( 'test.require4' );
-
-                       assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
-                       assert.strictEqual( module2, 1, 'export a number' );
-                       assert.strictEqual( module3(), 'hello world', 'export a function' );
-                       assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
-                       assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
-
-                       assert.throws( function () {
-                               require( '_badmodule' );
-                       }, /is not loaded/, 'Requesting non-existent modules throws error.' );
-               } );
-       } );
-
-       QUnit.test( 'mw.loader require() in debug mode', 1, function ( assert ) {
-               var path = mw.config.get( 'wgScriptPath' );
-               mw.loader.register( [
-                       [ 'test.require.define', '0' ],
-                       [ 'test.require.callback', '0', [ 'test.require.define' ] ]
-               ] );
-               mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
-               mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
-
-               return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
-                       var exported = require( 'test.require.callback' );
-                       assert.strictEqual( exported, 'Require worked.Define worked.',
-                               'module.exports worked in debug mode' );
-               }, function () {
-                       assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
-               } );
-       } );
-
-}( mediaWiki, jQuery ) );
+}( mediaWiki ) );