From 569c3e862b3277b6f2a4223ddd9b19468f4f4c74 Mon Sep 17 00:00:00 2001 From: "This, that and the other" Date: Wed, 23 Jul 2014 17:45:51 +1000 Subject: [PATCH] Allow two-tier setup of transwiki import sources There has been some demand, particularly in the Wikimedia cluster, for the ability to import from any wiki of a cluster. For this to occur, the transwiki import user interface needs a bit of a rethink. This patch replaces the existing single dropdown with a pair of dropdowns: the first to select the wiki project, and the second to select the subproject (e.g. a Wikipedia/Wiktionary language, or a Wikia site). The second one is optional (to support single-wiki sites like Meta, or for backwards compatibility with existing setups). $wgImportSources is now treated as a mixed array/associated array (see comment in DefaultSettings.php). Existing configurations will still work but will receive no new functionality. The non-JavaScript fallback is not pretty, but (a) it works, (b) I don't see an easy way to make it nicer, and (c) wiki sysops should probably be using a JavaScript-enabled browser for admin actions like importing... The intention is to alter the WMF configuration to automatically populate $wgImportSources with all public cluster wikis. I'm not exactly sure how this will be set up, but this patch is an important first step. I expect some non-WMF users of MediaWiki will find it helpful as well. Change-Id: Icdb655500c1ae5374dc7a9f4d99e6738b2269b14 --- RELEASE-NOTES-1.24 | 3 + includes/DefaultSettings.php | 11 +++ includes/specials/SpecialImport.php | 99 +++++++++++++++---- languages/i18n/en.json | 3 +- languages/i18n/qqq.json | 3 +- resources/Resources.php | 3 + .../mediawiki.special.import.js | 35 +++++++ 7 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 resources/src/mediawiki.special/mediawiki.special.import.js diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index d5446ae9f6..0f1419ecda 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -57,6 +57,9 @@ production. there is a maintenance script wrapOldPassword.php that can wrap all passwords in PBKDF2 (or the hashing algorithm of your choice) if you don't want to wait for your users to log in. +* $wgImportSources can now either be a regular array, or an associative map + specifying subprojects on the interwiki map of the target wiki, or a mix of + the two. Existing configurations will still work. === New features in 1.24 === * Added a new hook, "WhatLinksHereProps", to allow extensions to annotate diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 12e3357596..fd26cfe5d3 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -5995,6 +5995,17 @@ $wgShowCreditsIfMax = true; * Special:Import (for sysops). Since complete page history can be imported, * these should be 'trusted'. * + * This can either be a regular array, or an associative map specifying + * subprojects on the interwiki map of the target wiki, or a mix of the two, + * e.g. + * @code + * $wgImportSources = array( + * 'wikipedia' => array( 'cs', 'en', 'fr', 'zh' ), + * 'wikispecies', + * 'wikia' => array( 'animanga', 'brickipedia', 'desserts' ), + * ); + * @endcode + * * If a user has the 'import' permission but not the 'importupload' permission, * they will only be able to run imports through this transwiki interface. */ diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 2a3ab6450f..1b45315fc4 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -31,6 +31,8 @@ */ class SpecialImport extends SpecialPage { private $interwiki = false; + private $subproject; + private $fullInterwikiPrefix; private $namespace; private $rootpage = ''; private $frompage = ''; @@ -55,6 +57,8 @@ class SpecialImport extends SpecialPage { $this->setHeaders(); $this->outputHeader(); + $this->getOutput()->addModules( 'mediawiki.special.import' ); + $user = $this->getUser(); if ( !$user->isAllowedAny( 'import', 'importupload' ) ) { throw new PermissionsError( 'import' ); @@ -116,19 +120,30 @@ class SpecialImport extends SpecialPage { if ( !$user->isAllowed( 'import' ) ) { throw new PermissionsError( 'import' ); } - $this->interwiki = $request->getVal( 'interwiki' ); - if ( !in_array( $this->interwiki, $this->getConfig()->get( 'ImportSources' ) ) ) { + $this->interwiki = $this->fullInterwikiPrefix = $request->getVal( 'interwiki' ); + // does this interwiki have subprojects? + $importSources = $this->getConfig()->get( 'ImportSources' ); + $hasSubprojects = array_key_exists( $this->interwiki, $importSources ); + if ( !$hasSubprojects && !in_array( $this->interwiki, $importSources ) ) { $source = Status::newFatal( "import-invalid-interwiki" ); } else { - $this->history = $request->getCheck( 'interwikiHistory' ); - $this->frompage = $request->getText( "frompage" ); - $this->includeTemplates = $request->getCheck( 'interwikiTemplates' ); - $source = ImportStreamSource::newFromInterwiki( - $this->interwiki, - $this->frompage, - $this->history, - $this->includeTemplates, - $this->pageLinkDepth ); + if ( $hasSubprojects ) { + $this->subproject = $request->getVal( 'subproject' ); + $this->fullInterwikiPrefix .= ':' . $request->getVal( 'subproject' ); + } + if ( $hasSubprojects && !in_array( $this->subproject, $importSources[$this->interwiki] ) ) { + $source = Status::newFatal( "import-invalid-interwiki" ); + } else { + $this->history = $request->getCheck( 'interwikiHistory' ); + $this->frompage = $request->getText( "frompage" ); + $this->includeTemplates = $request->getCheck( 'interwikiTemplates' ); + $source = ImportStreamSource::newFromInterwiki( + $this->fullInterwikiPrefix, + $this->frompage, + $this->history, + $this->includeTemplates, + $this->pageLinkDepth ); + } } } else { $source = Status::newFatal( "importunknownsource" ); @@ -166,7 +181,7 @@ class SpecialImport extends SpecialPage { $reporter = new ImportReporter( $importer, $isUpload, - $this->interwiki, + $this->fullInterwikiPrefix, $this->logcomment ); $reporter->setContext( $this->getContext() ); @@ -299,7 +314,7 @@ class SpecialImport extends SpecialPage { Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) . " " . - Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) . + Xml::label( $this->msg( 'import-interwiki-sourcewiki' )->text(), 'interwiki' ) . " " . Xml::openElement( @@ -308,13 +323,63 @@ class SpecialImport extends SpecialPage { ) ); - foreach ( $importSources as $prefix ) { - $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : ''; - $out->addHTML( Xml::option( $prefix, $prefix, $selected ) ); + $needSubprojectField = false; + foreach ( $importSources as $key => $value ) { + if ( is_int( $key ) ) { + $key = $value; + } else if ( $value !== $key ) { + $needSubprojectField = true; + } + + $attribs = array( + 'value' => $key, + ); + if ( is_array( $value ) ) { + $attribs['data-subprojects'] = implode( ' ', $value ); + } + if ( $this->interwiki === $key ) { + $attribs['selected'] = 'selected'; + } + $out->addHTML( Html::element( 'option', $attribs, $key ) ); + } + + $out->addHTML( + Xml::closeElement( 'select' ) + ); + + if ( $needSubprojectField ) { + $out->addHTML( + Xml::openElement( + 'select', + array( 'name' => 'subproject', 'id' => 'subproject' ) + ) + ); + + $subprojectsToAdd = array(); + foreach ( $importSources as $key => $value ) { + if ( is_array( $value ) ) { + $subprojectsToAdd = array_merge( $subprojectsToAdd, $value ); + } + } + $subprojectsToAdd = array_unique( $subprojectsToAdd ); + sort( $subprojectsToAdd ); + foreach ( $subprojectsToAdd as $subproject ) { + $out->addHTML( Xml::option( $subproject, $subproject, $this->subproject === $subproject ) ); + } + + $out->addHTML( + Xml::closeElement( 'select' ) + ); } $out->addHTML( - Xml::closeElement( 'select' ) . + " + + + " . + Xml::label( $this->msg( 'import-interwiki-sourcepage' )->text(), 'frompage' ) . + " + " . Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) . " diff --git a/languages/i18n/en.json b/languages/i18n/en.json index a43a7421a0..70c503bbf6 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2277,7 +2277,8 @@ "import-summary": "", "importinterwiki": "Transwiki import", "import-interwiki-text": "Select a wiki and page title to import.\nRevision dates and editors' names will be preserved.\nAll transwiki import actions are logged at the [[Special:Log/import|import log]].", - "import-interwiki-source": "Source wiki/page:", + "import-interwiki-sourcewiki": "Source wiki:", + "import-interwiki-sourcepage": "Source page:", "import-interwiki-history": "Copy all history revisions for this page", "import-interwiki-templates": "Include all templates", "import-interwiki-submit": "Import", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 74e295432b..3c3f727d5b 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2439,7 +2439,8 @@ "import-summary": "{{doc-specialpagesummary|import}}", "importinterwiki": "Used as legend for the Import form in [[Special:Import]].", "import-interwiki-text": "Used as summary for the Import form in [[Special:Import]].", - "import-interwiki-source": "Used as label for input box in [[Special:Import]].", + "import-interwiki-sourcewiki": "Used as label for dropdown box in [[Special:Import]].", + "import-interwiki-sourcepage": "Used as label for input box in [[Special:Import]].", "import-interwiki-history": "This is an option on [[Special:Import]]. Usually, when unchecked, only the first version of a page is imported. When you check the option, all versions are imported. This is important often to check for licensing reasons.\n\nSee also:\n* {{msg-mw|Import-interwiki-templates}}\n* {{msg-mw|Import-interwiki-namespace}}\n* {{msg-mw|Import-comment}}\n* {{msg-mw|Import-interwiki-rootpage}}\n* {{msg-mw|Import-interwiki-submit}}", "import-interwiki-templates": "Used as label for the checkbox in [[Special:Import]].\n\nSee also:\n* {{msg-mw|Import-interwiki-history}}\n* {{msg-mw|Import-interwiki-namespace}}\n* {{msg-mw|Import-comment}}\n* {{msg-mw|Import-interwiki-rootpage}}\n* {{msg-mw|Import-interwiki-submit}}", "import-interwiki-submit": "Used as Submit button text in [[Special:Import]].\n\nSee also:\n* {{msg-mw|Import-interwiki-history}}\n* {{msg-mw|Import-interwiki-templates}}\n* {{msg-mw|Import-interwiki-namespace}}\n* {{msg-mw|Import-comment}}\n* {{msg-mw|Import-interwiki-rootpage}}\n{{Identical|Import}}", diff --git a/resources/Resources.php b/resources/Resources.php index 4a1c86b972..3a74026688 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1251,6 +1251,9 @@ return array( 'mediawiki.special.changeslist.enhanced' => array( 'styles' => 'resources/src/mediawiki.special/mediawiki.special.changeslist.enhanced.css', ), + 'mediawiki.special.import' => array( + 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.import.js', + ), 'mediawiki.special.movePage' => array( 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.movePage.js', 'dependencies' => 'jquery.byteLimit', diff --git a/resources/src/mediawiki.special/mediawiki.special.import.js b/resources/src/mediawiki.special/mediawiki.special.import.js new file mode 100644 index 0000000000..a9a985ebd4 --- /dev/null +++ b/resources/src/mediawiki.special/mediawiki.special.import.js @@ -0,0 +1,35 @@ +/*! + * JavaScript for Special:Import + */ +( function ( $ ) { + function updateImportSubprojectList() { + var $projectField = $( '#mw-import-table-interwiki #interwiki' ), + $subprojectField = $projectField.parent().find( '#subproject' ), + $selected = $projectField.find( ':selected' ), + oldValue = $subprojectField.val(), + option, options; + + if ( $selected.attr( 'data-subprojects' ) ) { + options = $.map( $selected.attr( 'data-subprojects' ).split( ' ' ), function ( el ) { + option = document.createElement( 'option' ); + option.appendChild( document.createTextNode( el ) ); + option.setAttribute( 'value', el ); + if ( oldValue === el ) { + option.setAttribute( 'selected', 'selected' ); + } + return option; + } ); + $subprojectField.show().empty().append( options ); + } else { + $subprojectField.hide(); + } + } + + $( function () { + var $projectField = $( '#mw-import-table-interwiki #interwiki' ); + if ( $projectField.length ) { + $projectField.change( updateImportSubprojectList ); + updateImportSubprojectList(); + } + } ); +}( jQuery ) ); -- 2.20.1