From 1fe98feab0f7b3164c7b4ee483b80a8dacf06173 Mon Sep 17 00:00:00 2001 From: "This, that and the other" Date: Tue, 27 Jan 2015 20:01:04 +1100 Subject: [PATCH] Make import destination UI more intuitive and clearer Previously there were two fields: Destination namespace, and Destination root page. They were both optional, and the "root page" one in particular was a bit mysterious until you tried it out. In addition, there was a strange interaction when you set both fields (I still don't quite understand what used to happen in this case). Now, there is a set of three clearly described radio buttons, allowing the user to select whether to import pages into their automatically chosen locations, into a single namespace, or as subpages of a given page. These correspond to the three ImportTitleFactory classes available in MediaWiki. See https://phabricator.wikimedia.org/M28 for a screenshot. The logic of WikiImporter#setTargetNamespace is tweaked slightly to remove the interaction between target namespace and target root page, since only one of these options can now be set. Similarly, the API's import module is modified in the same way. Bug: T17908 Change-Id: I11521260a88a7f4a95fbdb71ac50bcf7b4fe5cd1 --- RELEASE-NOTES-1.26 | 2 + includes/Import.php | 9 +- includes/api/ApiImport.php | 3 +- includes/api/i18n/en.json | 4 +- includes/specials/SpecialImport.php | 141 ++++++++++++++++++---------- languages/i18n/en.json | 5 +- languages/i18n/qqq.json | 5 +- 7 files changed, 105 insertions(+), 64 deletions(-) diff --git a/RELEASE-NOTES-1.26 b/RELEASE-NOTES-1.26 index eddfe30895..9426635060 100644 --- a/RELEASE-NOTES-1.26 +++ b/RELEASE-NOTES-1.26 @@ -21,6 +21,8 @@ production. === Action API changes in 1.26 === * API action=query&list=tags: The displayname can now be boolean false if the tag is meant to be hidden from user interfaces. +* action=import no longer allows both the namespace= and rootpage= parameters + to be set. If they are both set, the value of rootpage= will be ignored. === Action API internal changes in 1.26 === diff --git a/includes/Import.php b/includes/Import.php index 1e0f8e2f9c..c2fae30952 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -34,7 +34,7 @@ class WikiImporter { private $reader = null; private $foreignNamespaces = null; private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback; - private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback; + private $mSiteInfoCallback, $mPageOutCallback; private $mNoticeCallback, $mDebug; private $mImportUploads, $mImageBasePath; private $mNoUpdates = false; @@ -240,7 +240,6 @@ class WikiImporter { public function setTargetNamespace( $namespace ) { if ( is_null( $namespace ) ) { // Don't override namespaces - $this->mTargetNamespace = null; $this->setImportTitleFactory( new NaiveImportTitleFactory() ); return true; } elseif ( @@ -248,7 +247,6 @@ class WikiImporter { MWNamespace::exists( intval( $namespace ) ) ) { $namespace = intval( $namespace ); - $this->mTargetNamespace = $namespace; $this->setImportTitleFactory( new NamespaceImportTitleFactory( $namespace ) ); return true; } else { @@ -268,10 +266,7 @@ class WikiImporter { $this->setImportTitleFactory( new NaiveImportTitleFactory() ); } elseif ( $rootpage !== '' ) { $rootpage = rtrim( $rootpage, '/' ); //avoid double slashes - $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) - ? $this->mTargetNamespace - : NS_MAIN - ); + $title = Title::newFromText( $rootpage ); if ( !$title || $title->isExternal() ) { $status->fatal( 'import-rootpage-invalid' ); diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php index 2e87d22da0..40cf6e29ce 100644 --- a/includes/api/ApiImport.php +++ b/includes/api/ApiImport.php @@ -63,8 +63,7 @@ class ApiImport extends ApiBase { $importer = new WikiImporter( $source->value, $this->getConfig() ); if ( isset( $params['namespace'] ) ) { $importer->setTargetNamespace( $params['namespace'] ); - } - if ( isset( $params['rootpage'] ) ) { + } elseif ( isset( $params['rootpage'] ) ) { $statusRootPage = $importer->setTargetRootPage( $params['rootpage'] ); if ( !$statusRootPage->isGood() ) { $this->dieStatus( $statusRootPage ); diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 615d60a9cd..88eaf7ce57 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -196,8 +196,8 @@ "apihelp-import-param-interwikipage": "For interwiki imports: page to import.", "apihelp-import-param-fullhistory": "For interwiki imports: import the full history, not just the current version.", "apihelp-import-param-templates": "For interwiki imports: import all included templates as well.", - "apihelp-import-param-namespace": "For interwiki imports: import to this namespace.", - "apihelp-import-param-rootpage": "Import as subpage of this page.", + "apihelp-import-param-namespace": "Import to this namespace. Overrides the $1rootpage parameter.", + "apihelp-import-param-rootpage": "Import as subpage of this page. Ignored if the $1namespace parameter is provided.", "apihelp-import-example-import": "Import [[meta:Help:Parserfunctions]] to namespace 100 with full history.", "apihelp-login-description": "Log in and get authentication cookies.\n\nIn the event of a successful log-in, the needed cookies will be included in the HTTP response headers. In the event of a failed log-in, further attempts may be throttled to limit automated password guessing attacks.", diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index af86964772..8124f10115 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -34,6 +34,7 @@ class SpecialImport extends SpecialPage { private $interwiki = false; private $subproject; private $fullInterwikiPrefix; + private $mapping = 'default'; private $namespace; private $rootpage = ''; private $frompage = ''; @@ -101,26 +102,33 @@ class SpecialImport extends SpecialPage { private function doImport() { $isUpload = false; $request = $this->getRequest(); - $this->namespace = $request->getIntOrNull( 'namespace' ); $this->sourceName = $request->getVal( "source" ); $this->logcomment = $request->getText( 'log-comment' ); $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' ); - $this->rootpage = $request->getText( 'rootpage' ); + + $this->mapping = $request->getVal( 'mapping' ); + if ( $this->mapping === 'namespace' ) { + $this->namespace = $request->getIntOrNull( 'namespace' ); + } elseif ( $this->mapping === 'subpage' ) { + $this->rootpage = $request->getText( 'rootpage' ); + } else { + $this->mapping = 'default'; + } $user = $this->getUser(); if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) { $source = Status::newFatal( 'import-token-mismatch' ); - } elseif ( $this->sourceName == 'upload' ) { + } elseif ( $this->sourceName === 'upload' ) { $isUpload = true; if ( $user->isAllowed( 'importupload' ) ) { $source = ImportStreamSource::newFromUpload( "xmlimport" ); } else { throw new PermissionsError( 'importupload' ); } - } elseif ( $this->sourceName == "interwiki" ) { + } elseif ( $this->sourceName === 'interwiki' ) { if ( !$user->isAllowed( 'import' ) ) { throw new PermissionsError( 'import' ); } @@ -163,8 +171,7 @@ class SpecialImport extends SpecialPage { $importer = new WikiImporter( $source->value, $this->getConfig() ); if ( !is_null( $this->namespace ) ) { $importer->setTargetNamespace( $this->namespace ); - } - if ( !is_null( $this->rootpage ) ) { + } elseif ( !is_null( $this->rootpage ) ) { $statusRootPage = $importer->setTargetRootPage( $this->rootpage ); if ( !$statusRootPage->isGood() ) { $out->wrapWikiMsg( @@ -219,6 +226,79 @@ class SpecialImport extends SpecialPage { } } + private function getMappingFormPart( $sourceName ) { + $isSameSourceAsBefore = ( $this->sourceName === $sourceName ); + $defaultNamespace = $this->getConfig()->get( 'ImportTargetNamespace' ); + return " + + + " . + Xml::radioLabel( + $this->msg( 'import-mapping-default' )->text(), + 'mapping', + 'default', + // mw-import-mapping-interwiki-default, mw-import-mapping-upload-default + "mw-import-mapping-$sourceName-default", + ( $isSameSourceAsBefore ? + ( $this->mapping === 'default' ) : + is_null( $defaultNamespace ) ) + ) . + " + + + + + " . + Xml::radioLabel( + $this->msg( 'import-mapping-namespace' )->text(), + 'mapping', + 'namespace', + // mw-import-mapping-interwiki-namespace, mw-import-mapping-upload-namespace + "mw-import-mapping-$sourceName-namespace", + ( $isSameSourceAsBefore ? + ( $this->mapping === 'namespace' ) : + !is_null( $defaultNamespace ) ) + ) . ' ' . + Html::namespaceSelector( + array( + 'selected' => ( $isSameSourceAsBefore ? + $this->namespace : + ( $defaultNamespace || '' ) ), + ), array( + 'name' => "namespace", + // mw-import-namespace-interwiki, mw-import-namespace-upload + 'id' => "mw-import-namespace-$sourceName", + 'class' => 'namespaceselector', + ) + ) . + " + + + + + " . + Xml::radioLabel( + $this->msg( 'import-mapping-subpage' )->text(), + 'mapping', + 'subpage', + // mw-import-mapping-interwiki-subpage, mw-import-mapping-upload-subpage + "mw-import-mapping-$sourceName-subpage", + ( $isSameSourceAsBefore ? ( $this->mapping === 'subpage' ) : '' ) + ) . ' ' . + Xml::input( 'rootpage', 50, + ( $isSameSourceAsBefore ? $this->rootpage : '' ), + array( + // Should be "mw-import-rootpage-...", but we keep this inaccurate + // ID for legacy reasons + // mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload + 'id' => "mw-interwiki-rootpage-$sourceName", + 'type' => 'text' + ) + ) . ' ' . + " + "; + } + private function showForm() { $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ); $user = $this->getUser(); @@ -226,6 +306,7 @@ class SpecialImport extends SpecialPage { $importSources = $this->getConfig()->get( 'ImportSources' ); if ( $user->isAllowed( 'importupload' ) ) { + $mappingSelection = $this->getMappingFormPart( 'upload' ); $out->addHTML( Xml::fieldset( $this->msg( 'import-upload' )->text() ) . Xml::openElement( @@ -255,22 +336,11 @@ class SpecialImport extends SpecialPage { " " . Xml::input( 'log-comment', 50, - ( $this->sourceName == 'upload' ? $this->logcomment : '' ), + ( $this->sourceName === 'upload' ? $this->logcomment : '' ), array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' . " - - " . - Xml::label( - $this->msg( 'import-interwiki-rootpage' )->text(), - 'mw-interwiki-rootpage-upload' - ) . - " - " . - Xml::input( 'rootpage', 50, $this->rootpage, - array( 'id' => 'mw-interwiki-rootpage-upload', 'type' => 'text' ) ) . ' ' . - " - + $mappingSelection " . @@ -301,6 +371,7 @@ class SpecialImport extends SpecialPage { " "; } + $mappingSelection = $this->getMappingFormPart( 'interwiki' ); $out->addHTML( Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) . @@ -413,45 +484,17 @@ class SpecialImport extends SpecialPage { " $importDepth - - " . - Xml::label( $this->msg( 'import-interwiki-namespace' )->text(), 'namespace' ) . - " - " . - Html::namespaceSelector( - array( - 'selected' => $this->namespace, - 'all' => '', - ), array( - 'name' => 'namespace', - 'id' => 'namespace', - 'class' => 'namespaceselector', - ) - ) . - " - " . Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) . " " . Xml::input( 'log-comment', 50, - ( $this->sourceName == 'interwiki' ? $this->logcomment : '' ), + ( $this->sourceName === 'interwiki' ? $this->logcomment : '' ), array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' . " - - " . - Xml::label( - $this->msg( 'import-interwiki-rootpage' )->text(), - 'mw-interwiki-rootpage-interwiki' - ) . - " - " . - Xml::input( 'rootpage', 50, $this->rootpage, - array( 'id' => 'mw-interwiki-rootpage-interwiki', 'type' => 'text' ) ) . ' ' . - " - + $mappingSelection diff --git a/languages/i18n/en.json b/languages/i18n/en.json index b8cda248e5..8afed1a44c 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2314,8 +2314,9 @@ "import-interwiki-history": "Copy all history revisions for this page", "import-interwiki-templates": "Include all templates", "import-interwiki-submit": "Import", - "import-interwiki-namespace": "Destination namespace:", - "import-interwiki-rootpage": "Destination root page (optional):", + "import-mapping-default": "Import to default locations", + "import-mapping-namespace": "Import to a namespace:", + "import-mapping-subpage": "Import as subpages of the following page:", "import-upload-filename": "Filename:", "import-comment": "Comment:", "importtext": "Please export the file from the source wiki using the [[Special:Export|export utility]].\nSave it to your computer and upload it here.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 4ee5f25618..f4d1e40819 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2482,8 +2482,9 @@ "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}}", - "import-interwiki-namespace": "Used as label in Import form on [[Special:Import]].\n\nSee also:\n* {{msg-mw|Import-interwiki-history}}\n* {{msg-mw|Import-interwiki-templates}}\n* {{msg-mw|Import-comment}}\n* {{msg-mw|Import-interwiki-rootpage}}\n* {{msg-mw|Import-interwiki-submit}}", - "import-interwiki-rootpage": "Used on [[Special:Import]] as label.\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-submit}}", + "import-mapping-default": "Used as label for the first of three radio buttons in Import form on [[Special:Import]].\n\nSee also:\n* {{msg-mw|Import-mapping-namespace}}\n* {{msg-mw|Import-mapping-subpage}}", + "import-mapping-namespace": "Used as label for the second of three radio buttons in Import form on [[Special:Import]]. The radio button is followed by a drop-down list from which the user can select a namespace.\n\nSee also:\n* {{msg-mw|Import-mapping-default}}\n* {{msg-mw|Import-mapping-subpage}}", + "import-mapping-subpage": "Used as label for the third of three radio buttons in Import form on [[Special:Import]]. The radio button is followed by a text box in which the user can type a page name. The imported pages will be created as subpages of the entered page name.\n\nSee also:\n* {{msg-mw|Import-mapping-default}}\n* {{msg-mw|Import-mapping-namespace}}", "import-upload-filename": "Used on [[Special:Import]] as label for upload of an XML file containing the pages to import.\n{{Identical|Filename}}", "import-comment": "Used as label for input box 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-interwiki-rootpage}}\n* {{msg-mw|Import-interwiki-submit}}\n{{Identical|Comment}}", "importtext": "Used in the Import form on [[Special:Import]].", -- 2.20.1