Merge "Split out UserInputWidgets from mediawiki.widgets"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 7 Oct 2015 18:12:39 +0000 (18:12 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 7 Oct 2015 18:12:39 +0000 (18:12 +0000)
22 files changed:
RELEASE-NOTES-1.27
autoload.php
includes/DefaultSettings.php
includes/FileDeleteForm.php
includes/WikiMap.php
includes/api/ApiErrorFormatter.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllRevisions.php [new file with mode: 0644]
includes/api/ApiQueryFileRepoInfo.php
includes/api/ApiQueryRecentChanges.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/resourceloader/ResourceLoaderStartUpModule.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.ForeignUpload.js
tests/phpunit/includes/WikiMapTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.ForeignStructuredUpload.test.js [deleted file]
tests/qunit/suites/resources/mediawiki/mediawiki.ForeignUpload.test.js [deleted file]

index 2310cdb..c96eed2 100644 (file)
@@ -16,6 +16,7 @@ production.
   1000 for the latter) are now hard-coded.
 * $wgDebugDumpSqlLength was removed (deprecated in 1.24).
 * $wgDebugDBTransactions was removed (deprecated in 1.20).
+* $wgRemoteUploadTarget (added in 1.26) removed, replaced by $wgForeignUploadTargets
 
 === New features in 1.27 ===
 * $wgDataCenterId and $wgDataCenterRoles where added, which will serve as
@@ -38,6 +39,8 @@ production.
 === Bug fixes in 1.27 ===
 
 === Action API changes in 1.27 ===
+* Added list=allrevisions.
+* generator=recentchanges now has the option to generate revids.
 
 === Action API internal changes in 1.27 ===
 
@@ -47,10 +50,8 @@ MediaWiki supports over 350 languages. Many localisations are updated
 regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Bugzilla reports.
 
-
 === Other changes in 1.27 ===
 
-
 == Compatibility ==
 
 MediaWiki 1.27 requires PHP 5.3.3 or later. There is experimental support for
index 5060f87..a2f432f 100644 (file)
@@ -74,6 +74,7 @@ $wgAutoloadLocalClasses = array(
        'ApiQueryAllLinks' => __DIR__ . '/includes/api/ApiQueryAllLinks.php',
        'ApiQueryAllMessages' => __DIR__ . '/includes/api/ApiQueryAllMessages.php',
        'ApiQueryAllPages' => __DIR__ . '/includes/api/ApiQueryAllPages.php',
+       'ApiQueryAllRevisions' => __DIR__ . '/includes/api/ApiQueryAllRevisions.php',
        'ApiQueryAllUsers' => __DIR__ . '/includes/api/ApiQueryAllUsers.php',
        'ApiQueryBacklinks' => __DIR__ . '/includes/api/ApiQueryBacklinks.php',
        'ApiQueryBacklinksprop' => __DIR__ . '/includes/api/ApiQueryBacklinksprop.php',
index 3153afb..95baa56 100644 (file)
@@ -526,12 +526,11 @@ $wgForeignFileRepos = array();
 $wgUseInstantCommons = false;
 
 /**
- * Name of the remote repository to which users will be allowed to upload
- * files in their editors. Used to find a set of message names to describe
- * the legal requirements for uploading to that wiki, and suggestions for
- * when those requirements are not met.
+ * Array of foreign file repos (set in $wgForeignFileRepos above) that
+ * are allowable upload targets. These wikis must have some method of
+ * authentication (i.e. CentralAuth), and be CORS-enabled for this wiki.
  */
-$wgRemoteUploadTarget = 'default';
+$wgForeignUploadTargets = array();
 
 /**
  * File backend structure configuration.
index bcd6db2..5e7f5b2 100644 (file)
@@ -180,6 +180,8 @@ class FileDeleteForm {
                                $logEntry->setComment( $logComment );
                                $logid = $logEntry->insert();
                                $logEntry->publish( $logid );
+
+                               $status->value = $logid;
                        }
                } else {
                        $status = Status::newFatal( 'cannotdelete',
@@ -197,6 +199,7 @@ class FileDeleteForm {
                                        $status = $file->delete( $reason, $suppress, $user );
                                        if ( $status->isOK() ) {
                                                $dbw->commit( __METHOD__ );
+                                               $status->value = $deleteStatus->value; // log id
                                        } else {
                                                $dbw->rollback( __METHOD__ );
                                        }
index 6215af1..325831e 100644 (file)
@@ -21,7 +21,7 @@
  */
 
 /**
- * Helper tools for dealing with other locally-hosted wikis
+ * Helper tools for dealing with other wikis.
  */
 class WikiMap {
 
@@ -32,6 +32,20 @@ class WikiMap {
         * @return WikiReference|null WikiReference object or null if the wiki was not found
         */
        public static function getWiki( $wikiID ) {
+               $wikiReference = self::getWikiReferenceFromWgConf( $wikiID );
+               if ( $wikiReference ) {
+                       return $wikiReference;
+               }
+
+               // Try sites, if $wgConf failed
+               return self::getWikiWikiReferenceFromSites( $wikiID );
+       }
+
+       /**
+        * @param string $wikiID
+        * @return WikiReference|null WikiReference object or null if the wiki was not found
+        */
+       private static function getWikiReferenceFromWgConf( $wikiID ) {
                global $wgConf;
 
                $wgConf->loadFullData();
@@ -54,6 +68,42 @@ class WikiMap {
                return new WikiReference( $canonicalServer, $path, $server );
        }
 
+       /**
+        * @param string $wikiID
+        * @return WikiReference|null WikiReference object or null if the wiki was not found
+        */
+       private static function getWikiWikiReferenceFromSites( $wikiID ) {
+               static $siteStore = null;
+               if ( !$siteStore ) {
+                       // Replace once T114471 got fixed and don't do the caching here.
+                       $siteStore = SiteSQLStore::newInstance();
+               }
+
+               $site = $siteStore->getSite( $wikiID );
+
+               if ( !$site instanceof MediaWikiSite ) {
+                       // Abort if not a MediaWikiSite, as this is about Wikis
+                       return null;
+               }
+
+               $urlParts = wfParseUrl( $site->getPageUrl() );
+               if ( $urlParts === false || !isset( $urlParts['path'] ) || !isset( $urlParts['host'] ) ) {
+                       // We can't create a meaningful WikiReference without URLs
+                       return null;
+               }
+
+               // XXX: Check whether path contains a $1?
+               $path = $urlParts['path'];
+               if ( isset( $urlParts['query'] ) ) {
+                       $path .= '?' . $urlParts['query'];
+               }
+
+               $canonicalServer = isset( $urlParts['scheme'] ) ? $urlParts['scheme'] : 'http';
+               $canonicalServer .= '://' . $urlParts['host'];
+
+               return new WikiReference( $canonicalServer, $path );
+       }
+
        /**
         * Convenience to get the wiki's display name
         *
index 9414329..37cb19d 100644 (file)
@@ -232,7 +232,10 @@ class ApiErrorFormatter {
  * @deprecated Only for backwards compatibility, do not use
  * @ingroup API
  */
+// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
 class ApiErrorFormatter_BackCompat extends ApiErrorFormatter {
+       // @codingStandardsIgnoreEnd
+
        /**
         * @param ApiResult $result Into which data will be added
         */
index 3609a11..902bca7 100644 (file)
@@ -76,6 +76,7 @@ class ApiQuery extends ApiBase {
                'alllinks' => 'ApiQueryAllLinks',
                'allpages' => 'ApiQueryAllPages',
                'allredirects' => 'ApiQueryAllLinks',
+               'allrevisions' => 'ApiQueryAllRevisions',
                'alltransclusions' => 'ApiQueryAllLinks',
                'allusers' => 'ApiQueryAllUsers',
                'backlinks' => 'ApiQueryBacklinks',
diff --git a/includes/api/ApiQueryAllRevisions.php b/includes/api/ApiQueryAllRevisions.php
new file mode 100644 (file)
index 0000000..e853cdc
--- /dev/null
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Created on Sep 27, 2015
+ *
+ * Copyright © 2015 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Query module to enumerate all revisions.
+ *
+ * @ingroup API
+ * @since 1.27
+ */
+class ApiQueryAllRevisions extends ApiQueryRevisionsBase {
+
+       public function __construct( ApiQuery $query, $moduleName ) {
+               parent::__construct( $query, $moduleName, 'arv' );
+       }
+
+       /**
+        * @param ApiPageSet $resultPageSet
+        * @return void
+        */
+       protected function run( ApiPageSet $resultPageSet = null ) {
+               $db = $this->getDB();
+               $params = $this->extractRequestParams( false );
+
+               $result = $this->getResult();
+
+               $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
+
+               // Namespace check is likely to be desired, but can't be done
+               // efficiently in SQL.
+               $miser_ns = null;
+               $needPageTable = false;
+               if ( $params['namespace'] !== null ) {
+                       $params['namespace'] = array_unique( $params['namespace'] );
+                       sort( $params['namespace'] );
+                       if ( $params['namespace'] != MWNamespace::getValidNamespaces() ) {
+                               $needPageTable = true;
+                               if ( $this->getConfig()->get( 'MiserMode' ) ) {
+                                       $miser_ns = $params['namespace'];
+                               } else {
+                                       $this->addWhere( array( 'page_namespace' => $params['namespace'] ) );
+                               }
+                       }
+               }
+
+               $this->addTables( 'revision' );
+               if ( $resultPageSet === null ) {
+                       $this->parseParameters( $params );
+                       $this->addTables( 'page' );
+                       $this->addJoinConds(
+                               array( 'page' => array( 'INNER JOIN', array( 'rev_page = page_id' ) ) )
+                       );
+                       $this->addFields( Revision::selectFields() );
+                       $this->addFields( Revision::selectPageFields() );
+
+                       // Review this depeneding on the outcome of T113901
+                       $this->addOption( 'STRAIGHT_JOIN' );
+               } else {
+                       $this->limit = $this->getParameter( 'limit' ) ?: 10;
+                       $this->addFields( array( 'rev_timestamp', 'rev_id' ) );
+                       if ( $params['generatetitles'] ) {
+                               $this->addFields( array( 'rev_page' ) );
+                       }
+
+                       if ( $needPageTable ) {
+                               $this->addTables( 'page' );
+                               $this->addJoinConds(
+                                       array( 'page' => array( 'INNER JOIN', array( 'rev_page = page_id' ) ) )
+                               );
+                               $this->addFieldsIf( array( 'page_namespace' ), (bool)$miser_ns );
+
+                               // Review this depeneding on the outcome of T113901
+                               $this->addOption( 'STRAIGHT_JOIN' );
+                       }
+               }
+
+               if ( $this->fld_tags ) {
+                       $this->addTables( 'tag_summary' );
+                       $this->addJoinConds(
+                               array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) )
+                       );
+                       $this->addFields( 'ts_tags' );
+               }
+
+               if ( $this->fetchContent ) {
+                       $this->addTables( 'text' );
+                       $this->addJoinConds(
+                               array( 'text' => array( 'INNER JOIN', array( 'rev_text_id=old_id' ) ) )
+                       );
+                       $this->addFields( 'old_id' );
+                       $this->addFields( Revision::selectTextFields() );
+               }
+
+               if ( $params['user'] !== null ) {
+                       $id = User::idFromName( $params['user'] );
+                       if ( $id ) {
+                               $this->addWhereFld( 'rev_user', $id );
+                       } else {
+                               $this->addWhereFld( 'rev_user_text', $params['user'] );
+                       }
+               } elseif ( $params['excludeuser'] !== null ) {
+                       $id = User::idFromName( $params['excludeuser'] );
+                       if ( $id ) {
+                               $this->addWhere( 'rev_user != ' . $id );
+                       } else {
+                               $this->addWhere( 'rev_user_text != ' . $db->addQuotes( $params['excludeuser'] ) );
+                       }
+               }
+
+               if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
+                       // Paranoia: avoid brute force searches (bug 17342)
+                       if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
+                               $bitmask = Revision::DELETED_USER;
+                       } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+                               $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+                       } else {
+                               $bitmask = 0;
+                       }
+                       if ( $bitmask ) {
+                               $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
+                       }
+               }
+
+               $dir = $params['dir'];
+
+               if ( $params['continue'] !== null ) {
+                       $op = ( $dir == 'newer' ? '>' : '<' );
+                       $cont = explode( '|', $params['continue'] );
+                       $this->dieContinueUsageIf( count( $cont ) != 2 );
+                       $ts = $db->addQuotes( $db->timestamp( $cont[0] ) );
+                       $rev_id = (int)$cont[1];
+                       $this->dieContinueUsageIf( strval( $rev_id ) !== $cont[1] );
+                       $this->addWhere( "rev_timestamp $op $ts OR " .
+                               "(rev_timestamp = $ts AND " .
+                               "rev_id $op= $rev_id)" );
+               }
+
+               $this->addOption( 'LIMIT', $this->limit + 1 );
+
+               $sort = ( $dir == 'newer' ? '' : ' DESC' );
+               $orderby = array();
+               // Targeting index rev_timestamp, user_timestamp, or usertext_timestamp
+               // But 'user' is always constant for the latter two, so it doesn't matter here.
+               $orderby[] = "rev_timestamp $sort";
+               $orderby[] = "rev_id $sort";
+               $this->addOption( 'ORDER BY', $orderby );
+
+               $res = $this->select( __METHOD__ );
+               $pageMap = array(); // Maps rev_page to array index
+               $count = 0;
+               $nextIndex = 0;
+               $generated = array();
+               foreach ( $res as $row ) {
+                       if ( ++$count > $this->limit ) {
+                               // We've had enough
+                               $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
+                               break;
+                       }
+
+                       // Miser mode namespace check
+                       if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) {
+                               continue;
+                       }
+
+                       if ( $resultPageSet !== null ) {
+                               if ( $params['generatetitles'] ) {
+                                       $generated[$row->rev_page] = $row->rev_page;
+                               } else {
+                                       $generated[] = $row->rev_id;
+                               }
+                       } else {
+                               $revision = Revision::newFromRow( $row );
+                               $rev = $this->extractRevisionInfo( $revision, $row );
+
+                               if ( !isset( $pageMap[$row->rev_page] ) ) {
+                                       $index = $nextIndex++;
+                                       $pageMap[$row->rev_page] = $index;
+                                       $title = $revision->getTitle();
+                                       $a = array(
+                                               'pageid' => $title->getArticleID(),
+                                               'revisions' => array( $rev ),
+                                       );
+                                       ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
+                                       ApiQueryBase::addTitleInfo( $a, $title );
+                                       $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a );
+                               } else {
+                                       $index = $pageMap[$row->rev_page];
+                                       $fit = $result->addValue(
+                                               array( 'query', $this->getModuleName(), $index, 'revisions' ),
+                                               null, $rev );
+                               }
+                               if ( !$fit ) {
+                                       $this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
+                                       break;
+                               }
+                       }
+               }
+
+               if ( $resultPageSet !== null ) {
+                       if ( $params['generatetitles'] ) {
+                               $resultPageSet->populateFromPageIDs( $generated );
+                       } else {
+                               $resultPageSet->populateFromRevisionIDs( $generated );
+                       }
+               } else {
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
+               }
+       }
+
+       public function getAllowedParams() {
+               $ret = parent::getAllowedParams() + array(
+                       'user' => array(
+                               ApiBase::PARAM_TYPE => 'user',
+                       ),
+                       'namespace' => array(
+                               ApiBase::PARAM_ISMULTI => true,
+                               ApiBase::PARAM_TYPE => 'namespace',
+                               ApiBase::PARAM_DFLT => null,
+                       ),
+                       'start' => array(
+                               ApiBase::PARAM_TYPE => 'timestamp',
+                       ),
+                       'end' => array(
+                               ApiBase::PARAM_TYPE => 'timestamp',
+                       ),
+                       'dir' => array(
+                               ApiBase::PARAM_TYPE => array(
+                                       'newer',
+                                       'older'
+                               ),
+                               ApiBase::PARAM_DFLT => 'older',
+                               ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+                       ),
+                       'excludeuser' => array(
+                               ApiBase::PARAM_TYPE => 'user',
+                       ),
+                       'continue' => array(
+                               ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+                       ),
+                       'generatetitles' => array(
+                               ApiBase::PARAM_DFLT => false,
+                       ),
+               );
+
+               if ( $this->getConfig()->get( 'MiserMode' ) ) {
+                       $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+                               'api-help-param-limited-in-miser-mode',
+                       );
+               }
+
+               return $ret;
+       }
+
+       protected function getExamplesMessages() {
+               return array(
+                       'action=query&list=allrevisions&arvuser=Example&arvlimit=50'
+                               => 'apihelp-query+allrevisions-example-user',
+                       'action=query&list=allrevisions&arvdir=newer&arvlimit=50'
+                               => 'apihelp-query+allrevisions-example-ns-main',
+               );
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Allrevisions';
+       }
+}
index 057b011..12b9893 100644 (file)
@@ -41,18 +41,26 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
        }
 
        public function execute() {
+               $conf = $this->getConfig();
+
                $params = $this->extractRequestParams();
                $props = array_flip( $params['prop'] );
 
                $repos = array();
 
                $repoGroup = $this->getInitialisedRepoGroup();
+               $foreignTargets = $conf->get( 'ForeignUploadTargets' );
+
+               $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$repos, $props, $foreignTargets ) {
+                       $repoProps = $repo->getInfo();
+                       $repoProps['canUpload'] = in_array( $repoProps['name'], $foreignTargets );
 
-               $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$repos, $props ) {
-                       $repos[] = array_intersect_key( $repo->getInfo(), $props );
+                       $repos[] = array_intersect_key( $repoProps, $props );
                } );
 
-               $repos[] = array_intersect_key( $repoGroup->getLocalRepo()->getInfo(), $props );
+               $localInfo = $repoGroup->getLocalRepo()->getInfo();
+               $localInfo['canUpload'] = $conf->get( 'EnableUploads' );
+               $repos[] = array_intersect_key( $localInfo, $props );
 
                $result = $this->getResult();
                ApiResult::setIndexedTagName( $repos, 'repo' );
@@ -85,10 +93,14 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
                        $props = array_merge( $props, array_keys( $repo->getInfo() ) );
                } );
 
-               return array_values( array_unique( array_merge(
+               $propValues = array_values( array_unique( array_merge(
                        $props,
                        array_keys( $repoGroup->getLocalRepo()->getInfo() )
                ) ) );
+
+               $propValues[] = 'canUpload';
+
+               return $propValues;
        }
 
        protected function getExamplesMessages() {
index f579062..ed0a2a7 100644 (file)
@@ -297,6 +297,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
                                || isset( $show['!redirect'] );
                }
+               $this->addFieldsIf( array( 'rc_this_oldid' ),
+                       $resultPageSet && $params['generaterevisions'] );
 
                if ( $this->fld_tags ) {
                        $this->addTables( 'tag_summary' );
@@ -366,6 +368,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                /* Perform the actual query. */
                $res = $this->select( __METHOD__ );
 
+               $revids = array();
                $titles = array();
 
                $result = $this->getResult();
@@ -389,6 +392,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                        $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
                                        break;
                                }
+                       } elseif ( $params['generaterevisions'] ) {
+                               $revid = (int)$row->rc_this_oldid;
+                               if ( $revid > 0 ) {
+                                       $revids[] = $revid;
+                               }
                        } else {
                                $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
                        }
@@ -397,6 +405,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                if ( is_null( $resultPageSet ) ) {
                        /* Format the result */
                        $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
+               } elseif ( $params['generaterevisions'] ) {
+                       $resultPageSet->populateFromRevisionIDs( $revids );
                } else {
                        $resultPageSet->populateFromTitles( $titles );
                }
@@ -681,6 +691,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        'continue' => array(
                                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
                        ),
+                       'generaterevisions' => false,
                );
        }
 
index 30182c1..5127f47 100644 (file)
        "apihelp-query+allredirects-example-unique-generator": "Gets all target pages, marking the missing ones.",
        "apihelp-query+allredirects-example-generator": "Gets pages containing the redirects.",
 
+       "apihelp-query+allrevisions-description": "List all revisions.",
+       "apihelp-query+allrevisions-param-start": "The timestamp to start enumerating from.",
+       "apihelp-query+allrevisions-param-end": "The timestamp to stop enumerating at.",
+       "apihelp-query+allrevisions-param-user": "Only list revisions by this user.",
+       "apihelp-query+allrevisions-param-excludeuser": "Don't list revisions by this user.",
+       "apihelp-query+allrevisions-param-namespace": "Only list pages in this namespace.",
+       "apihelp-query+allrevisions-param-generatetitles": "When being used as a generator, generate titles rather than revision IDs.",
+       "apihelp-query+allrevisions-example-user": "List the last 50 contributions by user <kbd>Example</kbd>.",
+       "apihelp-query+allrevisions-example-ns-main": "List the first 50 revisions in the main namespace.",
+
        "apihelp-query+alltransclusions-description": "List all transclusions (pages embedded using &#123;&#123;x&#125;&#125;), including non-existing.",
        "apihelp-query+alltransclusions-param-from": "The title of the transclusion to start enumerating from.",
        "apihelp-query+alltransclusions-param-to": "The title of the transclusion to stop enumerating at.",
        "apihelp-query+recentchanges-param-limit": "How many total changes to return.",
        "apihelp-query+recentchanges-param-type": "Which types of changes to show.",
        "apihelp-query+recentchanges-param-toponly": "Only list changes which are the latest revision.",
+       "apihelp-query+recentchanges-param-generaterevisions": "When being used as a generator, generate revision IDs rather than titles. Recent change entries without associated revision IDs (e.g. most log entries) will generate nothing.",
        "apihelp-query+recentchanges-example-simple": "List recent changes.",
        "apihelp-query+recentchanges-example-generator": "Get page info about recent unpatrolled changes.",
 
index b55faf4..745aa90 100644 (file)
        "apihelp-query+allredirects-example-unique": "{{doc-apihelp-example|query+allredirects}}",
        "apihelp-query+allredirects-example-unique-generator": "{{doc-apihelp-example|query+allredirects}}",
        "apihelp-query+allredirects-example-generator": "{{doc-apihelp-example|query+allredirects}}",
+       "apihelp-query+allrevisions-description": "{{apihelp-description|query+allrevisions}}",
+       "apihelp-query+allrevisions-param-end": "{{apihelp-param|query+allrevisions|end}}",
+       "apihelp-query+allrevisions-param-excludeuser": "{{apihelp-param|query+allrevisions|excludeuser}}",
+       "apihelp-query+allrevisions-param-generatetitles": "{{apihelp-param|query+allrevisions|generatetitles}}",
+       "apihelp-query+allrevisions-param-namespace": "{{apihelp-param|query+allrevisions|namespace}}",
+       "apihelp-query+allrevisions-param-start": "{{apihelp-param|query+allrevisions|start}}",
+       "apihelp-query+allrevisions-param-user": "{{apihelp-param|query+allrevisions|user}}",
+       "apihelp-query+allrevisions-example-ns-main": "{{apihelp-example|query+allrevisions}}",
+       "apihelp-query+allrevisions-example-user": "{{apihelp-example|query+allrevisions}}",
        "apihelp-query+alltransclusions-description": "{{doc-apihelp-description|query+alltransclusions}}",
        "apihelp-query+alltransclusions-param-from": "{{doc-apihelp-param|query+alltransclusions|from}}",
        "apihelp-query+alltransclusions-param-to": "{{doc-apihelp-param|query+alltransclusions|to}}",
        "apihelp-query+recentchanges-param-limit": "{{doc-apihelp-param|query+recentchanges|limit}}",
        "apihelp-query+recentchanges-param-type": "{{doc-apihelp-param|query+recentchanges|type}}",
        "apihelp-query+recentchanges-param-toponly": "{{doc-apihelp-param|query+recentchanges|toponly}}",
+       "apihelp-query+recentchanges-param-generaterevisions": "{{doc-apihelp-param|query+recentchanges|generaterevisions}}",
        "apihelp-query+recentchanges-example-simple": "{{doc-apihelp-example|query+recentchanges}}",
        "apihelp-query+recentchanges-example-generator": "{{doc-apihelp-example|query+recentchanges}}",
        "apihelp-query+redirects-description": "{{doc-apihelp-description|query+redirects}}",
index 1857d23..eabafbd 100644 (file)
@@ -102,7 +102,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
                        'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
                        'wgResourceLoaderLegacyModules' => self::getLegacyModules(),
-                       'wgRemoteUploadTarget' => $conf->get( 'RemoteUploadTarget' ),
+                       'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
                );
 
                Hooks::run( 'ResourceLoaderGetConfigVars', array( &$vars ) );
index 2005ee8..25781d1 100644 (file)
        "foreign-structured-upload-form-label-own-work": "This is my own work",
        "foreign-structured-upload-form-label-infoform-categories": "Categories",
        "foreign-structured-upload-form-label-infoform-date": "Date",
+       "foreign-structured-upload-form-label-own-work-message-local": "I confirm that I am uploading this file following the terms of service and licensing policies on {{SITENAME}}.",
+       "foreign-structured-upload-form-label-not-own-work-message-local": "If you are not able to upload this file under the policies of {{SITENAME}}, please close this dialog and try another method.",
+       "foreign-structured-upload-form-label-not-own-work-local-local": "You may also want to try [[Special:Upload|the default upload page]].",
        "foreign-structured-upload-form-label-own-work-message-default": "I understand that I am uploading this file to a shared repository. I confirm that I am doing so following the terms of service and licensing policies there.",
        "foreign-structured-upload-form-label-not-own-work-message-default": "If you are not able to upload this file under the policies of the shared repository, please close this dialog and try another method.",
        "foreign-structured-upload-form-label-not-own-work-local-default": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if this file can be uploaded there under their policies.",
-       "foreign-structured-upload-form-label-own-work-message-wikimediacommons": "I attest that I own the copyright on this file, and agree to irrevocably release this file to Wikimedia Commons under the [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] license, and I agree to the [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use].",
-       "foreign-structured-upload-form-label-not-own-work-message-wikimediacommons": "If you do not own the copyright on this file, or you wish to release it under a different license, consider using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].",
-       "foreign-structured-upload-form-label-not-own-work-local-wikimediacommons": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if the site allows the upload of this file under their policies.",
+       "foreign-structured-upload-form-label-own-work-message-shared": "I attest that I own the copyright on this file, and agree to irrevocably release this file to Wikimedia Commons under the [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] license, and I agree to the [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use].",
+       "foreign-structured-upload-form-label-not-own-work-message-shared": "If you do not own the copyright on this file, or you wish to release it under a different license, consider using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].",
+       "foreign-structured-upload-form-label-not-own-work-local-shared": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if the site allows the upload of this file under their policies.",
        "backend-fail-stream": "Could not stream file \"$1\".",
        "backend-fail-backup": "Could not backup file \"$1\".",
        "backend-fail-notexists": "The file $1 does not exist.",
index 3893577..e04b0bf 100644 (file)
        "foreign-structured-upload-form-label-own-work": "Label for own work toggle",
        "foreign-structured-upload-form-label-infoform-categories": "Label for category selector input\n{{Identical|Category}}",
        "foreign-structured-upload-form-label-infoform-date": "Label for date input\n{{Identical|Date}}",
+       "foreign-structured-upload-form-label-own-work-message-local": "Message shown by local when a user affirms that they are allowed to upload a file to the local wiki.",
+       "foreign-structured-upload-form-label-not-own-work-message-local": "Message shown by local when a user cannot upload a file to the local wiki.",
+       "foreign-structured-upload-form-label-not-own-work-local-local": "Suggests uploading a file via Special:Upload instead of using whatever method they're currently using.",
        "foreign-structured-upload-form-label-own-work-message-default": "Message shown by default when a user affirms that they are allowed to upload a file to a remote wiki.",
        "foreign-structured-upload-form-label-not-own-work-message-default": "Message shown by default when a user cannot upload a file to a remote wiki.",
-       "foreign-structured-upload-form-label-not-own-work-local-default": "Suggests uploading a file locally instead of to a remote wiki. $1 is the name of the local wiki.",
-       "foreign-structured-upload-form-label-own-work-message-wikimediacommons": "Legal message to show when the work is made by the uploader.",
-       "foreign-structured-upload-form-label-not-own-work-message-wikimediacommons": "Message to show when the work isn't owned by the uploader.",
-       "foreign-structured-upload-form-label-not-own-work-local-wikimediacommons": "Message suggesting the user might want to upload a file locally instead of to Wikimedia Commons. $1 is the name of the local wiki.",
+       "foreign-structured-upload-form-label-not-own-work-local-default": "Suggests uploading a file locally instead of to a remote wiki.",
+       "foreign-structured-upload-form-label-own-work-message-shared": "Legal message to show when the work is made by the uploader.",
+       "foreign-structured-upload-form-label-not-own-work-message-shared": "Message to show when the work isn't owned by the uploader.",
+       "foreign-structured-upload-form-label-not-own-work-local-shared": "Message suggesting the user might want to upload a file locally instead of to Wikimedia Commons. $1 is the name of the local wiki.",
        "backend-fail-stream": "Parameters:\n* $1 - a filename",
        "backend-fail-backup": "Parameters:\n* $1 - a filename",
        "backend-fail-notexists": "Parameters:\n* $1 - a filename",
index f0b7cd5..c635f0e 100644 (file)
@@ -1255,9 +1255,12 @@ return array(
                        'foreign-structured-upload-form-label-own-work-message-default',
                        'foreign-structured-upload-form-label-not-own-work-message-default',
                        'foreign-structured-upload-form-label-not-own-work-local-default',
-                       'foreign-structured-upload-form-label-own-work-message-wikimediacommons',
-                       'foreign-structured-upload-form-label-not-own-work-message-wikimediacommons',
-                       'foreign-structured-upload-form-label-not-own-work-local-wikimediacommons',
+                       'foreign-structured-upload-form-label-own-work-message-shared',
+                       'foreign-structured-upload-form-label-not-own-work-message-shared',
+                       'foreign-structured-upload-form-label-not-own-work-local-shared',
+                       'foreign-structured-upload-form-label-own-work-message-local',
+                       'foreign-structured-upload-form-label-not-own-work-message-local',
+                       'foreign-structured-upload-form-label-not-own-work-local-local',
                ),
        ),
        'mediawiki.toc' => array(
index 3051d52..825e738 100644 (file)
@@ -19,7 +19,7 @@
         * @uses mw.ForeignStructuredUpload
         * @extends mw.Upload.BookletLayout
         * @cfg {string} [targetHost] Used to set up the target wiki.
-        *     If nothing is passed, the {@link mw.ForeignUpload#property-targetHost default} is used.
+        *     If nothing is passed, the {@link mw.ForeignUpload#property-target default} is used.
         */
        mw.ForeignStructuredUpload.BookletLayout = function ( config ) {
                config = config || {};
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
                var fieldset,
-                       target = mw.config.get( 'wgRemoteUploadTarget' ),
+                       targets = mw.config.get( 'wgForeignUploadTargets' ),
+                       // Default to using local, but try to use a configured target.
+                       // TODO allow finer configuration of this somehow?
+                       target = ( targets && targets.length ) ? targets[ 0 ] : 'local',
                        $ownWorkMessage = $( '<p>' ).html(
                                mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target ).parse()
                        ),
index 0929661..38c9106 100644 (file)
@@ -1,4 +1,4 @@
-( function ( mw, OO ) {
+( function ( mw, OO, $ ) {
        /**
         * @class mw.ForeignUpload
         * @extends mw.Upload
         * Subclassed to upload to a foreign API, with no other goodies. Use
         * this for a generic foreign image repository on your wiki farm.
         *
-        * Note you can provide the {@link #targetHost targetHost} or not - if the first argument is
+        * Note you can provide the {@link #target target} or not - if the first argument is
         * an object, we assume you want the default, and treat it as apiconfig
         * instead.
         *
         * @constructor
-        * @param {string} [targetHost="commons.wikimedia.org"] Used to set up the target
+        * @param {string} [target] Used to set up the target
         *     wiki. If not remote, this class behaves identically to mw.Upload (unless further subclassed)
+        *     Use the same names as set in $wgForeignFileRepos for this. Also,
+        *     make sure there is an entry in the $wgForeignUploadTargets array
+        *     set to "true" for this name.
         * @param {Object} [apiconfig] Passed to the constructor of mw.ForeignApi or mw.Api, as needed.
         */
-       function ForeignUpload( targetHost, apiconfig ) {
-               var api;
+       function ForeignUpload( target, apiconfig ) {
+               var api, upload = this;
 
-               if ( typeof targetHost === 'object' ) {
-                       // targetHost probably wasn't passed in, it must
+               if ( typeof target === 'object' ) {
+                       // target probably wasn't passed in, it must
                        // be apiconfig
-                       apiconfig = targetHost;
-               } else {
-                       // targetHost is a useful string, set it here
-                       this.targetHost = targetHost || this.targetHost;
+                       apiconfig = target;
+                       target = undefined;
                }
 
-               if ( location.host !== this.targetHost ) {
-                       api = new mw.ForeignApi(
-                               location.protocol + '//' + this.targetHost + '/w/api.php',
-                               apiconfig
-                       );
+               this.target = target;
+
+               // Now we have several different options.
+               // If the local wiki is the target, then we can skip a bunch of steps
+               // and just return an mw.Api object, because we don't need any special
+               // configuration for that.
+               // However, if the target is a remote wiki, we must check the API
+               // to confirm that the target is one that this site is configured to
+               // support.
+               if ( this.target === 'local' ) {
+                       // We'll ignore the CORS and centralauth stuff if the target is
+                       // the local wiki.
+                       this.apiPromise = $.Deferred().resolve( new mw.Api( apiconfig ) );
                } else {
-                       // We'll ignore the CORS and centralauth stuff if we're on Commons already
-                       api = new mw.Api( apiconfig );
+                       api = new mw.Api();
+                       this.apiPromise = api.get( {
+                               action: 'query',
+                               meta: 'filerepoinfo',
+                               friprop: [ 'name', 'scriptDirUrl', 'canUpload' ]
+                       } ).then( function ( data ) {
+                               var i, repo,
+                                       repos = data.query.repos;
+
+                               // First pass - try to find the passed-in target and check
+                               // that it's configured for uploads.
+                               for ( i in repos ) {
+                                       repo = repos[ i ];
+
+                                       // Skip repos that are not our target, or if they
+                                       // are the target, cannot be uploaded to.
+                                       if ( repo.name === upload.target && repo.canUpload ) {
+                                               return new mw.ForeignApi(
+                                                       repo.scriptDirUrl + '/api.php',
+                                                       apiconfig
+                                               );
+                                       }
+                               }
+
+                               // Second pass - none of the configured repos were our
+                               // passed-in target, just look for the first one that would
+                               // work.
+                               for ( i in repos ) {
+                                       repo = repos[ i ];
+
+                                       if ( repo.canUpload ) {
+                                               return new mw.ForeignApi(
+                                                       repo.scriptDirUrl + '/api.php',
+                                                       apiconfig
+                                               );
+                                       }
+                               }
+
+                               // No luck finding the correct foreign repo, default to local.
+                               return $.Deferred().resolve( new mw.Api( apiconfig ) );
+                       } );
                }
 
-               mw.Upload.call( this, api );
+               // Build the upload object without an API - this class overrides the
+               // actual API call methods to wait for the apiPromise to resolve
+               // before continuing.
+               mw.Upload.call( this, null );
        }
 
        OO.inheritClass( ForeignUpload, mw.Upload );
 
        /**
-        * @property targetHost
+        * @property {string} target
         * Used to specify the target repository of the upload.
         *
-        * You could override this to point at something that isn't Commons,
-        * but be sure it has the correct templates and is CORS and CentralAuth
-        * ready.
+        * If you set this to something that isn't 'local', you must be sure to
+        * add that target to $wgForeignUploadTargets in LocalSettings, and the
+        * repository must be set up to use CORS and CentralAuth.
+        *
+        * Most wikis use "shared" to refer to Wikimedia Commons, we assume that
+        * in this class and in the messages linked to it.
+        */
+
+       /**
+        * Override from mw.Upload to make sure the API info is found and allowed
+        */
+       ForeignUpload.prototype.upload = function () {
+               var upload = this;
+               return this.apiPromise.then( function ( api ) {
+                       upload.api = api;
+                       return mw.Upload.prototype.upload.call( upload );
+               } );
+       };
+
+       /**
+        * Override from mw.Upload to make sure the API info is found and allowed
         */
-       ForeignUpload.prototype.targetHost = 'commons.wikimedia.org';
+       ForeignUpload.prototype.uploadToStash = function () {
+               var upload = this;
+               return this.apiPromise.then( function ( api ) {
+                       upload.api = api;
+                       return mw.Upload.prototype.uploadToStash.call( upload );
+               } );
+       };
 
        mw.ForeignUpload = ForeignUpload;
-}( mediaWiki, OO ) );
+}( mediaWiki, OO, jQuery ) );
index 08ba41d..e86559e 100644 (file)
@@ -2,8 +2,9 @@
 
 /**
  * @covers WikiMap
+ *
+ * @group Database
  */
-
 class WikiMapTest extends MediaWikiLangTestCase {
 
        public function setUp() {
@@ -24,23 +25,40 @@ class WikiMapTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( array(
                        'wgConf' => $conf,
                ) );
+
+               TestSites::insertIntoDb();
        }
 
        public function provideGetWiki() {
+               // As provided by $wgConf
                $enwiki = new WikiReference( 'http://en.example.org', '/w/$1' );
                $ruwiki = new WikiReference( '//ru.example.org', '/wiki/$1' );
 
+               // Created from site objects
+               $nlwiki = new WikiReference( 'https://nl.wikipedia.org', '/wiki/$1' );
+               // enwiktionary doesn't have an interwiki id, thus this falls back to minor = lang code
+               $enwiktionary = new WikiReference( 'https://en.wiktionary.org', '/wiki/$1' );
+
                return array(
-                       'unknown' => array( false, 'xyzzy' ),
-                       'enwiki' => array( $enwiki, 'enwiki' ),
-                       'ruwiki' => array( $ruwiki, 'ruwiki' ),
+                       'unknown' => array( null, 'xyzzy' ),
+                       'enwiki (wgConf)' => array( $enwiki, 'enwiki' ),
+                       'ruwiki (wgConf)' => array( $ruwiki, 'ruwiki' ),
+                       'nlwiki (sites)' => array( $nlwiki, 'nlwiki', false ),
+                       'enwiktionary (sites)' => array( $enwiktionary, 'enwiktionary', false ),
+                       'non MediaWiki site' => array( null, 'spam', false ),
                );
        }
 
        /**
         * @dataProvider provideGetWiki
         */
-       public function testGetWiki( $expected, $wikiId ) {
+       public function testGetWiki( $expected, $wikiId, $useWgConf = true ) {
+               if ( !$useWgConf ) {
+                       $this->setMwGlobals( array(
+                               'wgConf' => new SiteConfiguration(),
+                       ) );
+               }
+
                $this->assertEquals( $expected, WikiMap::getWiki( $wikiId ) );
        }
 
@@ -49,6 +67,7 @@ class WikiMapTest extends MediaWikiLangTestCase {
                        'unknown' => array( 'xyzzy', 'xyzzy' ),
                        'enwiki' => array( 'en.example.org', 'enwiki' ),
                        'ruwiki' => array( 'ru.example.org', 'ruwiki' ),
+                       'enwiktionary (sites)' => array( 'en.wiktionary.org', 'enwiktionary' ),
                );
        }
 
@@ -75,6 +94,13 @@ class WikiMapTest extends MediaWikiLangTestCase {
                                'Фу',
                                'вар'
                        ),
+                       'enwiktionary (sites)' => array(
+                               '<a class="external" rel="nofollow" ' .
+                                       'href="https://en.wiktionary.org/wiki/Kitten">Kittens!</a>',
+                               'enwiktionary',
+                               'Kitten',
+                               'Kittens!'
+                       ),
                );
        }
 
@@ -104,6 +130,13 @@ class WikiMapTest extends MediaWikiLangTestCase {
                                'Фу',
                                'вар'
                        ),
+                       'enwiktionary (sites)' => array(
+                               '<a class="external" rel="nofollow" ' .
+                                       'href="https://en.wiktionary.org/wiki/User:Dummy">Whatever</a>',
+                               'enwiktionary',
+                               'Dummy',
+                               'Whatever'
+                       ),
                );
        }
 
@@ -118,6 +151,11 @@ class WikiMapTest extends MediaWikiLangTestCase {
                return array(
                        'unknown' => array( false, 'xyzzy', 'Foo' ),
                        'enwiki' => array( 'http://en.example.org/w/Foo', 'enwiki', 'Foo' ),
+                       'enwiktionary (sites)' => array(
+                               'https://en.wiktionary.org/wiki/Testme',
+                               'enwiktionary',
+                               'Testme'
+                       ),
                        'ruwiki with fragment' => array(
                                '//ru.example.org/wiki/%D0%A4%D1%83#%D0%B2%D0%B0%D1%80',
                                'ruwiki',
index 0a29af5..f9ddcf2 100644 (file)
@@ -71,8 +71,6 @@ return array(
                        'tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
-                       'tests/qunit/suites/resources/mediawiki/mediawiki.ForeignUpload.test.js',
-                       'tests/qunit/suites/resources/mediawiki/mediawiki.ForeignStructuredUpload.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js',
@@ -116,8 +114,6 @@ return array(
                        'mediawiki.api.upload',
                        'mediawiki.api.watch',
                        'mediawiki.ForeignApi.core',
-                       'mediawiki.ForeignUpload',
-                       'mediawiki.ForeignStructuredUpload',
                        'mediawiki.jqueryMsg',
                        'mediawiki.messagePoster',
                        'mediawiki.RegExp',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.ForeignStructuredUpload.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.ForeignStructuredUpload.test.js
deleted file mode 100644 (file)
index 3007416..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-( function ( mw ) {
-       QUnit.module( 'mediawiki.ForeignStructuredUpload', QUnit.newMwEnvironment( {} ) );
-
-       QUnit.test( 'Constructor check', function ( assert ) {
-               QUnit.expect( 3 );
-               var upload = new mw.ForeignStructuredUpload();
-
-               assert.ok( upload, 'The ForeignUpload constructor is working.' );
-               assert.ok( upload.descriptions, 'The descriptions array was initialized properly' );
-               assert.ok( upload.categories, 'The categories array was initialized properly' );
-       } );
-
-       QUnit.test( 'getText', function ( assert ) {
-               QUnit.expect( 1 );
-
-               var upload = new mw.ForeignStructuredUpload();
-
-               // Set basic information
-               upload.addDescription( 'en', 'Test description one two three' );
-               upload.addDescription( 'en-x-piglatin', 'Esttay escriptionday unway ootay eethray' );
-               upload.setDate( '1776-07-04' );
-               upload.addCategories( [ 'Test 1', 'Test 2' ] );
-               upload.addCategories( [ 'Test 3' ] );
-
-               // Fake the user
-               this.sandbox.stub( upload, 'getUser' ).returns( 'Test user' );
-
-               assert.strictEqual( upload.getText().trim(), '{{Information\n|description={{en|Test description one two three}}\n{{en-x-piglatin|Esttay escriptionday unway ootay eethray}}\n|date=1776-07-04\n|source=Test user\n|author=Test user\n}}\n\n\n\n[[Category:Test 1]]\n[[Category:Test 2]]\n[[Category:Test 3]]' );
-       } );
-}( mediaWiki ) );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.ForeignUpload.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.ForeignUpload.test.js
deleted file mode 100644 (file)
index 98b9678..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-( function ( mw ) {
-       QUnit.module( 'mediawiki.ForeignUpload', QUnit.newMwEnvironment( {} ) );
-
-       QUnit.test( 'Constructor check', function ( assert ) {
-               QUnit.expect( 3 );
-               var upload = new mw.ForeignUpload();
-
-               assert.ok( upload, 'The ForeignUpload constructor is working.' );
-               assert.strictEqual( upload.targetHost, 'commons.wikimedia.org', 'Default target host is correct' );
-               assert.ok( upload.api instanceof mw.ForeignApi, 'API is correctly configured to point at a foreign wiki.' );
-       } );
-}( mediaWiki ) );