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
=== 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 ===
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
'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',
$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.
$logEntry->setComment( $logComment );
$logid = $logEntry->insert();
$logEntry->publish( $logid );
+
+ $status->value = $logid;
}
} else {
$status = Status::newFatal( 'cannotdelete',
$status = $file->delete( $reason, $suppress, $user );
if ( $status->isOK() ) {
$dbw->commit( __METHOD__ );
+ $status->value = $deleteStatus->value; // log id
} else {
$dbw->rollback( __METHOD__ );
}
*/
/**
- * Helper tools for dealing with other locally-hosted wikis
+ * Helper tools for dealing with other wikis.
*/
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();
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
*
* @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
*/
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
'allredirects' => 'ApiQueryAllLinks',
+ 'allrevisions' => 'ApiQueryAllRevisions',
'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
--- /dev/null
+<?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';
+ }
+}
}
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' );
$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() {
$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' );
/* Perform the actual query. */
$res = $this->select( __METHOD__ );
+ $revids = array();
$titles = array();
$result = $this->getResult();
$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 );
}
if ( is_null( $resultPageSet ) ) {
/* Format the result */
$result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
+ } elseif ( $params['generaterevisions'] ) {
+ $resultPageSet->populateFromRevisionIDs( $revids );
} else {
$resultPageSet->populateFromTitles( $titles );
}
'continue' => array(
ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
+ 'generaterevisions' => false,
);
}
"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 {{x}}), 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.",
"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}}",
'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
'wgResourceLoaderLegacyModules' => self::getLegacyModules(),
- 'wgRemoteUploadTarget' => $conf->get( 'RemoteUploadTarget' ),
+ 'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
);
Hooks::run( 'ResourceLoaderGetConfigVars', array( &$vars ) );
"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.",
"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",
'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(
* @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()
),
-( 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 ) );
/**
* @covers WikiMap
+ *
+ * @group Database
*/
-
class WikiMapTest extends MediaWikiLangTestCase {
public function setUp() {
$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 ) );
}
'unknown' => array( 'xyzzy', 'xyzzy' ),
'enwiki' => array( 'en.example.org', 'enwiki' ),
'ruwiki' => array( 'ru.example.org', 'ruwiki' ),
+ 'enwiktionary (sites)' => array( 'en.wiktionary.org', 'enwiktionary' ),
);
}
'Фу',
'вар'
),
+ 'enwiktionary (sites)' => array(
+ '<a class="external" rel="nofollow" ' .
+ 'href="https://en.wiktionary.org/wiki/Kitten">Kittens!</a>',
+ 'enwiktionary',
+ 'Kitten',
+ 'Kittens!'
+ ),
);
}
'Фу',
'вар'
),
+ 'enwiktionary (sites)' => array(
+ '<a class="external" rel="nofollow" ' .
+ 'href="https://en.wiktionary.org/wiki/User:Dummy">Whatever</a>',
+ 'enwiktionary',
+ 'Dummy',
+ 'Whatever'
+ ),
);
}
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',
'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',
'mediawiki.api.upload',
'mediawiki.api.watch',
'mediawiki.ForeignApi.core',
- 'mediawiki.ForeignUpload',
- 'mediawiki.ForeignStructuredUpload',
'mediawiki.jqueryMsg',
'mediawiki.messagePoster',
'mediawiki.RegExp',
+++ /dev/null
-( 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 ) );
+++ /dev/null
-( 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 ) );