* https://www.mediawiki.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 */ namespace MediaWiki\Specials; use Exception; use ImportReporter; use ImportStreamSource; use MediaWiki\Html\Html; use MediaWiki\HTMLForm\HTMLForm; use MediaWiki\MainConfigNames; use MediaWiki\Permissions\PermissionStatus; use MediaWiki\SpecialPage\SpecialPage; use MediaWiki\Status\Status; use PermissionsError; use UnexpectedValueException; use WikiImporterFactory; use Wikimedia\Rdbms\DBError; use Wikimedia\RequestTimeout\TimeoutException; /** * MediaWiki page data importer * * @ingroup SpecialPage */ class SpecialImport extends SpecialPage { /** @var array */ private $importSources; private WikiImporterFactory $wikiImporterFactory; public function __construct( WikiImporterFactory $wikiImporterFactory ) { parent::__construct( 'Import', 'import' ); $this->wikiImporterFactory = $wikiImporterFactory; } public function doesWrites() { return true; } /** * Execute * @param string|null $par */ public function execute( $par ) { $this->useTransactionalTimeLimit(); $this->setHeaders(); $this->outputHeader(); $this->importSources = $this->getConfig()->get( MainConfigNames::ImportSources ); // Avoid phan error by checking the type if ( !is_array( $this->importSources ) ) { throw new UnexpectedValueException( '$wgImportSources must be an array' ); } $this->getHookRunner()->onImportSources( $this->importSources ); $authority = $this->getAuthority(); $statusImport = PermissionStatus::newEmpty(); $authority->isDefinitelyAllowed( 'import', $statusImport ); $statusImportUpload = PermissionStatus::newEmpty(); $authority->isDefinitelyAllowed( 'importupload', $statusImportUpload ); // Only show an error here if the user can't import using either method. // If they can use at least one of the methods, allow access, and checks elsewhere // will ensure that we only show the form(s) they can use. $out = $this->getOutput(); if ( !$statusImport->isGood() && !$statusImportUpload->isGood() ) { // Show separate messages for each check. There isn't a good way to merge them into a single // message if the checks failed for different reasons. $out->prepareErrorPage(); $out->setPageTitleMsg( $this->msg( 'permissionserrors' ) ); $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' ); $out->addWikiTextAsInterface( Html::errorBox( $out->formatPermissionStatus( $statusImport, 'import' ) ) ); $out->addWikiTextAsInterface( Html::errorBox( $out->formatPermissionStatus( $statusImportUpload, 'importupload' ) ) ); return; } $out->addModules( 'mediawiki.misc-authed-ooui' ); $out->addModuleStyles( 'mediawiki.special.import.styles.ooui' ); $this->checkReadOnly(); $request = $this->getRequest(); if ( $request->wasPosted() && $request->getRawVal( 'action' ) == 'submit' ) { $this->doImport(); } $this->showForm(); } /** * Do the actual import */ private function doImport() { $isUpload = false; $request = $this->getRequest(); $sourceName = $request->getVal( 'source' ); $assignKnownUsers = $request->getCheck( 'assignKnownUsers' ); $logcomment = $request->getText( 'log-comment' ); $pageLinkDepth = $this->getConfig()->get( MainConfigNames::ExportMaxLinkDepth ) == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' ); $rootpage = ''; $mapping = $request->getVal( 'mapping' ); $namespace = $this->getConfig()->get( MainConfigNames::ImportTargetNamespace ); if ( $mapping === 'namespace' ) { $namespace = $request->getIntOrNull( 'namespace' ); } elseif ( $mapping === 'subpage' ) { $rootpage = $request->getText( 'rootpage' ); } $user = $this->getUser(); $authority = $this->getAuthority(); $status = PermissionStatus::newEmpty(); $fullInterwikiPrefix = null; if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { $source = Status::newFatal( 'import-token-mismatch' ); } elseif ( $sourceName === 'upload' ) { $isUpload = true; $fullInterwikiPrefix = $request->getVal( 'usernamePrefix' ); if ( $authority->authorizeAction( 'importupload', $status ) ) { $source = ImportStreamSource::newFromUpload( "xmlimport" ); } else { throw new PermissionsError( 'importupload', $status ); } } elseif ( $sourceName === 'interwiki' ) { if ( !$authority->authorizeAction( 'import', $status ) ) { throw new PermissionsError( 'import', $status ); } $interwiki = $fullInterwikiPrefix = $request->getVal( 'interwiki' ); // does this interwiki have subprojects? $hasSubprojects = array_key_exists( $interwiki, $this->importSources ); if ( !$hasSubprojects && !in_array( $interwiki, $this->importSources ) ) { $source = Status::newFatal( "import-invalid-interwiki" ); } else { $subproject = null; if ( $hasSubprojects ) { $subproject = $request->getVal( 'subproject' ); // Trim "project::" prefix added for JS if ( str_starts_with( $subproject, $interwiki . '::' ) ) { $subproject = substr( $subproject, strlen( $interwiki . '::' ) ); } $fullInterwikiPrefix .= ':' . $subproject; } if ( $hasSubprojects && !in_array( $subproject, $this->importSources[$interwiki] ) ) { $source = Status::newFatal( 'import-invalid-interwiki' ); } else { $history = $request->getCheck( 'interwikiHistory' ); $frompage = $request->getText( 'frompage' ); $includeTemplates = $request->getCheck( 'interwikiTemplates' ); $source = ImportStreamSource::newFromInterwiki( // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive $fullInterwikiPrefix, $frompage, $history, $includeTemplates, $pageLinkDepth ); } } } else { $source = Status::newFatal( "importunknownsource" ); } if ( (string)$fullInterwikiPrefix === '' ) { $source->fatal( 'importnoprefix' ); } $out = $this->getOutput(); if ( !$source->isGood() ) { $out->wrapWikiMsg( Html::errorBox( '$1' ), [ 'importfailed', $source->getWikiText( false, false, $this->getLanguage() ), count( $source->getMessages() ) ] ); } else { $importer = $this->wikiImporterFactory->getWikiImporter( $source->value, $this->getAuthority() ); if ( $namespace !== null ) { $importer->setTargetNamespace( $namespace ); } elseif ( $rootpage !== null ) { $statusRootPage = $importer->setTargetRootPage( $rootpage ); if ( !$statusRootPage->isGood() ) { $out->wrapWikiMsg( Html::errorBox( '$1' ), [ 'import-options-wrong', $statusRootPage->getWikiText( false, false, $this->getLanguage() ), count( $statusRootPage->getMessages() ) ] ); return; } } // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive $importer->setUsernamePrefix( $fullInterwikiPrefix, $assignKnownUsers ); $out->addWikiMsg( "importstart" ); $reporter = new ImportReporter( $importer, $isUpload, // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive $fullInterwikiPrefix, $logcomment, $this->getContext() ); $exception = false; $reporter->open(); try { $importer->doImport(); } catch ( DBError | TimeoutException $e ) { // Re-throw exceptions which are not safe to catch (T383933). throw $e; } catch ( Exception $e ) { $exception = $e; } finally { $result = $reporter->close(); } if ( $exception ) { # No source or XML parse error $out->wrapWikiMsg( Html::errorBox( '$1' ), [ 'importfailed', wfEscapeWikiText( $exception->getMessage() ), 1 ] ); } elseif ( !$result->isGood() ) { # Zero revisions $out->wrapWikiMsg( Html::errorBox( '$1' ), [ 'importfailed', $result->getWikiText( false, false, $this->getLanguage() ), count( $result->getMessages() ) ] ); } else { # Success! $out->addWikiMsg( 'importsuccess' ); } $out->addHTML( '