From 37b4cd5da2c3adfd0207ad74a2212fe7292341ca Mon Sep 17 00:00:00 2001 From: "This, that and the other" Date: Wed, 10 Dec 2014 22:24:47 +1100 Subject: [PATCH] Proper namespace handling for WikiImporter Up until now, the import backend has tried to resolve titles in the XML data using the regular Title class. This is a disastrous idea, as local namespace names often do not match foreign namespace titles. There is enough metadata present in XML dumps generated by modern MW versions for the target namespace ID and name to be reliably determined. This metadata is contained in the and tags, which (unbelievably enough) was totally ignored by WikiImporter until now. Fallbacks are provided for older XML dump versions which may be missing some or all of this metadata. The ForeignTitle class is introduced. This is intended specifically for the resolution of titles on foreign wikis. In the future, an InterwikiTitle class could be added, which would inherit ForeignTitle and add members for the interwiki prefix and fragment. Factory classes to generate ForeignTitle objects from string data, and Title objects from ForeignTitle objects, are also added. The 'AfterImportPage' hook has been modified so the second argument is a ForeignTitle object instead of a Title (the documentation was wrong, it was never a string). LiquidThreads, SMW and FacetedSearch all use this hook but none of them use the $origTitle parameter. Bug: T32723 Bug: T42192 Change-Id: Iaa58e1b9fd7287cdf999cef6a6f3bb63cd2a4778 --- autoload.php | 8 + docs/hooks.txt | 2 +- includes/Import.php | 157 ++++++++++++------ includes/specials/SpecialImport.php | 7 +- includes/title/ForeignTitle.php | 117 +++++++++++++ includes/title/ForeignTitleFactory.php | 36 ++++ includes/title/ImportTitleFactory.php | 36 ++++ includes/title/NaiveForeignTitleFactory.php | 71 ++++++++ includes/title/NaiveImportTitleFactory.php | 65 ++++++++ .../NamespaceAwareForeignTitleFactory.php | 134 +++++++++++++++ .../title/NamespaceImportTitleFactory.php | 52 ++++++ includes/title/SubpageImportTitleFactory.php | 55 ++++++ tests/phpunit/includes/ImportTest.php | 58 ++++++- .../includes/title/ForeignTitleTest.php | 104 ++++++++++++ .../title/NaiveForeignTitleFactoryTest.php | 92 ++++++++++ .../title/NaiveImportTitleFactoryTest.php | 90 ++++++++++ .../NamespaceAwareForeignTitleFactoryTest.php | 90 ++++++++++ .../title/NamespaceImportTitleFactoryTest.php | 78 +++++++++ .../title/SubpageImportTitleFactoryTest.php | 87 ++++++++++ 19 files changed, 1284 insertions(+), 55 deletions(-) create mode 100644 includes/title/ForeignTitle.php create mode 100644 includes/title/ForeignTitleFactory.php create mode 100644 includes/title/ImportTitleFactory.php create mode 100644 includes/title/NaiveForeignTitleFactory.php create mode 100644 includes/title/NaiveImportTitleFactory.php create mode 100644 includes/title/NamespaceAwareForeignTitleFactory.php create mode 100644 includes/title/NamespaceImportTitleFactory.php create mode 100644 includes/title/SubpageImportTitleFactory.php create mode 100644 tests/phpunit/includes/title/ForeignTitleTest.php create mode 100644 tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php create mode 100644 tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php diff --git a/autoload.php b/autoload.php index 5239edcacc..1c28696171 100644 --- a/autoload.php +++ b/autoload.php @@ -426,6 +426,8 @@ $wgAutoloadLocalClasses = array( 'ForeignDBFile' => __DIR__ . '/includes/filerepo/file/ForeignDBFile.php', 'ForeignDBRepo' => __DIR__ . '/includes/filerepo/ForeignDBRepo.php', 'ForeignDBViaLBRepo' => __DIR__ . '/includes/filerepo/ForeignDBViaLBRepo.php', + 'ForeignTitle' => __DIR__ . '/includes/title/ForeignTitle.php', + 'ForeignTitleFactory' => __DIR__ . '/includes/title/ForeignTitleFactory.php', 'ForkController' => __DIR__ . '/includes/ForkController.php', 'FormAction' => __DIR__ . '/includes/actions/FormAction.php', 'FormOptions' => __DIR__ . '/includes/FormOptions.php', @@ -525,6 +527,7 @@ $wgAutoloadLocalClasses = array( 'ImportSiteScripts' => __DIR__ . '/maintenance/importSiteScripts.php', 'ImportStreamSource' => __DIR__ . '/includes/Import.php', 'ImportStringSource' => __DIR__ . '/includes/Import.php', + 'ImportTitleFactory' => __DIR__ . '/includes/title/ImportTitleFactory.php', 'IncludableSpecialPage' => __DIR__ . '/includes/specialpage/IncludableSpecialPage.php', 'IndexPager' => __DIR__ . '/includes/pager/IndexPager.php', 'InfoAction' => __DIR__ . '/includes/actions/InfoAction.php', @@ -761,7 +764,11 @@ $wgAutoloadLocalClasses = array( 'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php', 'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php', 'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php', + 'NaiveForeignTitleFactory' => __DIR__ . '/includes/title/NaiveForeignTitleFactory.php', + 'NaiveImportTitleFactory' => __DIR__ . '/includes/title/NaiveImportTitleFactory.php', + 'NamespaceAwareForeignTitleFactory' => __DIR__ . '/includes/title/NamespaceAwareForeignTitleFactory.php', 'NamespaceConflictChecker' => __DIR__ . '/maintenance/namespaceDupes.php', + 'NamespaceImportTitleFactory' => __DIR__ . '/includes/title/NamespaceImportTitleFactory.php', 'NewFilesPager' => __DIR__ . '/includes/specials/SpecialNewimages.php', 'NewPagesPager' => __DIR__ . '/includes/specials/SpecialNewpages.php', 'NewUsersLogFormatter' => __DIR__ . '/includes/logging/NewUsersLogFormatter.php', @@ -1133,6 +1140,7 @@ $wgAutoloadLocalClasses = array( 'StubObject' => __DIR__ . '/includes/StubObject.php', 'StubUserLang' => __DIR__ . '/includes/StubObject.php', 'SubmitAction' => __DIR__ . '/includes/actions/SubmitAction.php', + 'SubpageImportTitleFactory' => __DIR__ . '/includes/title/SubpageImportTitleFactory.php', 'SvgHandler' => __DIR__ . '/includes/media/SVG.php', 'SwiftFileBackend' => __DIR__ . '/includes/filebackend/SwiftFileBackend.php', 'SwiftFileBackendDirList' => __DIR__ . '/includes/filebackend/SwiftFileBackend.php', diff --git a/docs/hooks.txt b/docs/hooks.txt index 7ec6ff5c7b..813b587e55 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -314,7 +314,7 @@ $output: The OutputPage object where output() was called 'AfterImportPage': When a page import is completed. $title: Title under which the revisions were imported -$origTitle: Title provided by the XML file +$foreignTitle: ForeignTitle object based on data provided by the XML file $revCount: Number of revisions in the XML file $sRevCount: Number of successfully imported revisions $pageInfo: associative array of page information diff --git a/includes/Import.php b/includes/Import.php index 4eb8e97488..5922c69336 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -32,13 +32,16 @@ */ class WikiImporter { private $reader = null; + private $foreignNamespaces = null; private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback; - private $mSiteInfoCallback, $mTargetNamespace, $mTargetRootPage, $mPageOutCallback; + private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback; private $mNoticeCallback, $mDebug; private $mImportUploads, $mImageBasePath; private $mNoUpdates = false; /** @var Config */ private $config; + /** @var ImportTitleFactory */ + private $importTitleFactory; /** * Creates an ImportXMLReader drawing from the source provided @@ -68,6 +71,8 @@ class WikiImporter { $this->setUploadCallback( array( $this, 'importUpload' ) ); $this->setLogItemCallback( array( $this, 'importLogItem' ) ); $this->setPageOutCallback( array( $this, 'finishImportPage' ) ); + + $this->importTitleFactory = new NaiveImportTitleFactory(); } /** @@ -199,6 +204,15 @@ class WikiImporter { return $previous; } + /** + * Sets the factory object to use to convert ForeignTitle objects into local + * Title objects + * @param ImportTitleFactory $factory + */ + public function setImportTitleFactory( $factory ) { + $this->importTitleFactory = $factory; + } + /** * Set a target namespace to override the defaults * @param null|int $namespace @@ -208,9 +222,16 @@ class WikiImporter { if ( is_null( $namespace ) ) { // Don't override namespaces $this->mTargetNamespace = null; - } elseif ( $namespace >= 0 ) { - // @todo FIXME: Check for validity - $this->mTargetNamespace = intval( $namespace ); + $this->setImportTitleFactory( new NaiveImportTitleFactory() ); + return true; + } elseif ( + $namespace >= 0 && + MWNamespace::exists( intval( $namespace ) ) + ) { + $namespace = intval( $namespace ); + $this->mTargetNamespace = $namespace; + $this->setImportTitleFactory( new NamespaceImportTitleFactory( $namespace ) ); + return true; } else { return false; } @@ -225,7 +246,7 @@ class WikiImporter { $status = Status::newGood(); if ( is_null( $rootpage ) ) { // No rootpage - $this->mTargetRootPage = null; + $this->setImportTitleFactory( new NaiveImportTitleFactory() ); } elseif ( $rootpage !== '' ) { $rootpage = rtrim( $rootpage, '/' ); //avoid double slashes $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) @@ -244,9 +265,9 @@ class WikiImporter { : $wgContLang->getNsText( $title->getNamespace() ); $status->fatal( 'import-rootpage-nosubpage', $displayNSText ); } else { - // set namespace to 'all', so the namespace check in processTitle() can passed + // set namespace to 'all', so the namespace check in processTitle() can pass $this->setTargetNamespace( null ); - $this->mTargetRootPage = $title->getPrefixedDBkey(); + $this->setImportTitleFactory( new SubpageImportTitleFactory( $title ) ); } } } @@ -320,13 +341,14 @@ class WikiImporter { /** * Mostly for hook use * @param Title $title - * @param string $origTitle + * @param ForeignTitle $foreignTitle * @param int $revCount * @param int $sRevCount * @param array $pageInfo * @return bool */ - public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) { + public function finishImportPage( $title, $foreignTitle, $revCount, + $sRevCount, $pageInfo ) { $args = func_get_args(); return wfRunHooks( 'AfterImportPage', $args ); } @@ -348,6 +370,20 @@ class WikiImporter { $this->debug( "-- Text: " . $revision->text ); } + /** + * Notify the callback function of site info + * @param array $siteInfo + * @return bool|mixed + */ + private function siteInfoCallback( $siteInfo ) { + if ( isset( $this->mSiteInfoCallback ) ) { + return call_user_func_array( $this->mSiteInfoCallback, + array( $siteInfo, $this ) ); + } else { + return false; + } + } + /** * Notify the callback function when a new "" is reached. * @param Title $title @@ -361,12 +397,13 @@ class WikiImporter { /** * Notify the callback function when a "" is closed. * @param Title $title - * @param Title $origTitle + * @param ForeignTitle $foreignTitle * @param int $revCount * @param int $sucCount Number of revisions for which callback returned true * @param array $pageInfo Associative array of page information */ - private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) { + private function pageOutCallback( $title, $foreignTitle, $revCount, + $sucCount, $pageInfo ) { if ( isset( $this->mPageOutCallback ) ) { $args = func_get_args(); call_user_func_array( $this->mPageOutCallback, $args ); @@ -493,18 +530,31 @@ class WikiImporter { return true; } - /** - * @return bool - * @throws MWException - */ private function handleSiteInfo() { - // Site info is useful, but not actually used for dump imports. - // Includes a quick short-circuit to save performance. - if ( !$this->mSiteInfoCallback ) { - $this->reader->next(); - return true; + $this->debug( "Enter site info handler." ); + $siteInfo = array(); + + // Fields that can just be stuffed in the siteInfo object + $normalFields = array( 'sitename', 'base', 'generator', 'case' ); + + while ( $this->reader->read() ) { + if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + $this->reader->name == 'siteinfo' ) { + break; + } + + $tag = $this->reader->name; + + if ( $tag == 'namespace' ) { + $this->foreignNamespaces[ $this->nodeAttribute( 'key' ) ] = + $this->nodeContents(); + } elseif ( in_array( $tag, $normalFields ) ) { + $siteInfo[$tag] = $this->nodeContents(); + } } - throw new MWException( "SiteInfo tag is not yet handled, do not set mSiteInfoCallback" ); + + $siteInfo['_namespaces'] = $this->foreignNamespaces; + $this->siteInfoCallback( $siteInfo ); } private function handleLogItem() { @@ -574,7 +624,7 @@ class WikiImporter { $pageInfo = array( 'revisionCount' => 0, 'successfulRevisionCount' => 0 ); // Fields that can just be stuffed in the pageInfo object - $normalFields = array( 'title', 'id', 'redirect', 'restrictions' ); + $normalFields = array( 'title', 'ns', 'id', 'redirect', 'restrictions' ); $skip = false; $badTitle = false; @@ -585,6 +635,8 @@ class WikiImporter { break; } + $skip = false; + $tag = $this->reader->name; if ( $badTitle ) { @@ -605,29 +657,35 @@ class WikiImporter { $pageInfo[$tag] = $this->nodeAttribute( 'title' ); } else { $pageInfo[$tag] = $this->nodeContents(); - if ( $tag == 'title' ) { - $title = $this->processTitle( $pageInfo['title'] ); + } + } elseif ( $tag == 'revision' || $tag == 'upload' ) { + if ( !isset( $title ) ) { + $title = $this->processTitle( $pageInfo['title'], + isset( $pageInfo['ns'] ) ? $pageInfo['ns'] : null ); + + if ( !$title ) { + $badTitle = true; + $skip = true; + } - if ( !$title ) { - $badTitle = true; - $skip = true; - } + $this->pageCallback( $title ); + list( $pageInfo['_title'], $foreignTitle ) = $title; + } - $this->pageCallback( $title ); - list( $pageInfo['_title'], $origTitle ) = $title; + if ( $title ) { + if ( $tag == 'revision' ) { + $this->handleRevision( $pageInfo ); + } else { + $this->handleUpload( $pageInfo ); } } - } elseif ( $tag == 'revision' ) { - $this->handleRevision( $pageInfo ); - } elseif ( $tag == 'upload' ) { - $this->handleUpload( $pageInfo ); } elseif ( $tag != '#text' ) { $this->warn( "Unhandled page XML tag $tag" ); $skip = true; } } - $this->pageOutCallback( $pageInfo['_title'], $origTitle, + $this->pageOutCallback( $pageInfo['_title'], $foreignTitle, $pageInfo['revisionCount'], $pageInfo['successfulRevisionCount'], $pageInfo ); @@ -852,28 +910,27 @@ class WikiImporter { /** * @param string $text + * @param string|null $ns * @return array|bool */ - private function processTitle( $text ) { - $workTitle = $text; - $origTitle = Title::newFromText( $workTitle ); - - if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) { - # makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map) - # and than dbKey can begin with a lowercase char - $title = Title::makeTitleSafe( $this->mTargetNamespace, - $origTitle->getDBkey() ); + private function processTitle( $text, $ns = null ) { + if ( is_null( $this->foreignNamespaces ) ) { + $foreignTitleFactory = new NaiveForeignTitleFactory(); } else { - if ( !is_null( $this->mTargetRootPage ) ) { - $workTitle = $this->mTargetRootPage . '/' . $workTitle; - } - $title = Title::newFromText( $workTitle ); + $foreignTitleFactory = new NamespaceAwareForeignTitleFactory( + $this->foreignNamespaces ); } + $foreignTitle = $foreignTitleFactory->createForeignTitle( $text, + intval( $ns ) ); + + $title = $this->importTitleFactory->createTitleFromForeignTitle( + $foreignTitle ); + $commandLineMode = $this->config->get( 'CommandLineMode' ); if ( is_null( $title ) ) { # Invalid page title? Ignore the page - $this->notice( 'import-error-invalid', $workTitle ); + $this->notice( 'import-error-invalid', $foreignTitle->getFullText() ); return false; } elseif ( $title->isExternal() ) { $this->notice( 'import-error-interwiki', $title->getPrefixedText() ); @@ -891,7 +948,7 @@ class WikiImporter { return false; } - return array( $title, $origTitle ); + return array( $title, $foreignTitle ); } } diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 0831c208ce..701e206e11 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -518,13 +518,14 @@ class ImportReporter extends ContextSource { /** * @param Title $title - * @param Title $origTitle + * @param ForeignTitle $foreignTitle * @param int $revisionCount * @param int $successCount * @param array $pageInfo * @return void */ - function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) { + function reportPage( $title, $foreignTitle, $revisionCount, + $successCount, $pageInfo ) { $args = func_get_args(); call_user_func_array( $this->mOriginalPageOutCallback, $args ); @@ -553,7 +554,7 @@ class ImportReporter extends ContextSource { $log->addEntry( 'upload', $title, $detail, array(), $this->getUser() ); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . - $origTitle->getPrefixedText() . ']]'; + $foreignTitle->getFullText() . ']]'; $detail = $this->msg( 'import-logentry-interwiki-detail' )->numParams( $successCount )->params( $interwiki )->inContentLanguage()->text(); if ( $this->reason ) { diff --git a/includes/title/ForeignTitle.php b/includes/title/ForeignTitle.php new file mode 100644 index 0000000000..ed96d17cde --- /dev/null +++ b/includes/title/ForeignTitle.php @@ -0,0 +1,117 @@ +namespaceId = null; + } else { + $this->namespaceId = intval( $namespaceId ); + } + $this->namespaceName = str_replace( ' ', '_', $namespaceName ); + $this->pageName = str_replace( ' ', '_', $pageName ); + } + + /** + * Do we know the namespace ID of the page on the foreign wiki? + * @return bool + */ + public function isNamespaceIdKnown() { + return !is_null( $this->namespaceId ); + } + + /** + * @return int + * @throws MWException If isNamespaceIdKnown() is false, it does not make + * sense to call this function. + */ + public function getNamespaceId() { + if ( is_null( $this->namespaceId ) ) { + throw new MWException( + "Attempted to call getNamespaceId when the namespace ID is not known" ); + } + return $this->namespaceId; + } + + /** @return string */ + public function getNamespaceName() { + return $this->namespaceName; + } + + /** @return string */ + public function getText() { + return $this->pageName; + } + + /** @return string */ + public function getFullText() { + $result = ''; + if ( $this->namespaceName ) { + $result .= $this->namespaceName . ':'; + } + $result .= $this->pageName; + return $result; + } + + /** + * Returns a string representation of the title, for logging. This is purely + * informative and must not be used programmatically. Use the appropriate + * ImportTitleFactory to generate the correct string representation for a + * given use. + * + * @return string + */ + public function __toString() { + $name = ''; + if ( $this->isNamespaceIdKnown() ) { + $name .= '{ns' . $this->namespaceId . '}'; + } else { + $name .= '{ns??}'; + } + $name .= $this->namespaceName . ':' . $this->pageName; + + return $name; + } +} diff --git a/includes/title/ForeignTitleFactory.php b/includes/title/ForeignTitleFactory.php new file mode 100644 index 0000000000..427afdf3eb --- /dev/null +++ b/includes/title/ForeignTitleFactory.php @@ -0,0 +1,36 @@ + and attributes found in an XML dump. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ); +} diff --git a/includes/title/ImportTitleFactory.php b/includes/title/ImportTitleFactory.php new file mode 100644 index 0000000000..629616d8c0 --- /dev/null +++ b/includes/title/ImportTitleFactory.php @@ -0,0 +1,36 @@ + and attributes found in an XML dump. + * + * Although exported XML dumps have contained a map of namespace IDs to names + * since MW 1.5, the importer used to completely ignore the tag + * before MW 1.25. It is therefore possible that custom XML dumps (i.e. not + * generated by Special:Export) have been created without this metadata. + * As a result, this code falls back to using namespace data for the local + * wiki (similar to buggy pre-1.25 behaviour) if $ns is not supplied. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ) { + $pieces = explode( ':', $title, 2 ); + + global $wgContLang; + + // Can we assume that the part of the page title before the colon is a + // namespace name? + // + // XML export schema version 0.5 and earlier (MW 1.18 and earlier) does not + // contain a tag, so we need to be able to handle that case. + // + // If we know the namespace ID, we assume a non-zero namespace ID means + // the ':' sets off a valid namespace name. If we don't know the namespace + // ID, we fall back to using the local wiki's namespace names to resolve + // this -- better than nothing, and mimics the old crappy behavior + $isNamespacePartValid = is_null( $ns ) ? + ( $wgContLang->getNsIndex( $pieces[0] ) !== false ) : + $ns != 0; + + if ( count( $pieces ) === 2 && $isNamespacePartValid ) { + list( $namespaceName, $pageName ) = $pieces; + } else { + $namespaceName = ''; + $pageName = $title; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } +} diff --git a/includes/title/NaiveImportTitleFactory.php b/includes/title/NaiveImportTitleFactory.php new file mode 100644 index 0000000000..43c662e71d --- /dev/null +++ b/includes/title/NaiveImportTitleFactory.php @@ -0,0 +1,65 @@ + 100, we look for a local namespace with + * a matching namespace name. If that can't be found, we dump the page in the + * main namespace as a last resort. + */ +class NaiveImportTitleFactory implements ImportTitleFactory { + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + global $wgContLang; + + if ( $foreignTitle->isNamespaceIdKnown() ) { + $foreignNs = $foreignTitle->getNamespaceId(); + + // For built-in namespaces (0 <= ID < 100), we try to find a local NS with + // the same namespace ID + if ( $foreignNs < 100 && MWNamespace::exists( $foreignNs ) ) { + return Title::makeTitleSafe( $foreignNs, $foreignTitle->getText() ); + } + } + + // Do we have a local namespace by the same name as the foreign + // namespace? + $targetNs = $wgContLang->getNsIndex( $foreignTitle->getNamespaceName() ); + if ( $targetNs !== false ) { + return Title::makeTitleSafe( $targetNs, $foreignTitle->getText() ); + } + + // Otherwise, just fall back to main namespace + return Title::makeTitleSafe( 0, $foreignTitle->getFullText() ); + } +} diff --git a/includes/title/NamespaceAwareForeignTitleFactory.php b/includes/title/NamespaceAwareForeignTitleFactory.php new file mode 100644 index 0000000000..bf97e2cdf2 --- /dev/null +++ b/includes/title/NamespaceAwareForeignTitleFactory.php @@ -0,0 +1,134 @@ + 'name' which contains + * the complete namespace setup of the foreign wiki. Such data could be + * obtained from siteinfo/namespaces in an XML dump file, or by an action API + * query such as api.php?action=query&meta=siteinfo&siprop=namespaces. If + * this data is unavailable, use NaiveForeignTitleFactory instead. + */ + public function __construct( $foreignNamespaces ) { + $this->foreignNamespaces = $foreignNamespaces; + if ( !is_null( $foreignNamespaces ) ) { + $this->foreignNamespacesFlipped = array(); + foreach ( $foreignNamespaces as $id => $name ) { + $newKey = self::normalizeNamespaceName( $name ); + $this->foreignNamespacesFlipped[$newKey] = $id; + } + } + } + + /** + * Creates a ForeignTitle object based on the page title, and optionally the + * namespace ID, of a page on a foreign wiki. These values could be, for + * example, the and <ns> attributes found in an XML dump. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ) { + // Export schema version 0.5 and earlier (MW 1.18 and earlier) does not + // contain a <ns> tag, so we need to be able to handle that case. + if ( is_null( $ns ) ) { + return self::parseTitleNoNs( $title ); + } else { + return self::parseTitleWithNs( $title, $ns ); + } + } + + /** + * Helper function to parse the title when the namespace ID is not specified. + * + * @param string $title + * @return ForeignTitle + */ + protected function parseTitleNoNs( $title ) { + $pieces = explode( ':', $title, 2 ); + $key = self::normalizeNamespaceName( $pieces[0] ); + + // Does the part before the colon match a known namespace? Check the + // foreign namespaces + $isNamespacePartValid = isset( $this->foreignNamespacesFlipped[$key] ); + + if ( count( $pieces ) === 2 && $isNamespacePartValid ) { + list( $namespaceName, $pageName ) = $pieces; + $ns = $this->foreignNamespacesFlipped[$key]; + } else { + $namespaceName = ''; + $pageName = $title; + $ns = 0; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } + + /** + * Helper function to parse the title when the namespace value is known. + * + * @param string $title + * @param int $ns + * @return ForeignTitle + */ + protected function parseTitleWithNs( $title, $ns ) { + $pieces = explode( ':', $title, 2 ); + + if ( isset( $this->foreignNamespaces[$ns] ) ) { + $namespaceName = $this->foreignNamespaces[$ns]; + } else { + $namespaceName = $ns == '0' ? '' : $pieces[0]; + } + + // We assume that the portion of the page title before the colon is the + // namespace name, except in the case of namespace 0 + if ( $ns != '0' ) { + $pageName = $pieces[1]; + } else { + $pageName = $title; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } +} diff --git a/includes/title/NamespaceImportTitleFactory.php b/includes/title/NamespaceImportTitleFactory.php new file mode 100644 index 0000000000..0c1d0c40c7 --- /dev/null +++ b/includes/title/NamespaceImportTitleFactory.php @@ -0,0 +1,52 @@ +<?php +/** + * 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 + * @license GPL 2+ + */ + +/** + * A class to convert page titles on a foreign wiki (ForeignTitle objects) into + * page titles on the local wiki (Title objects), placing all pages in a fixed + * local namespace. + */ +class NamespaceImportTitleFactory implements ImportTitleFactory { + /** @var int */ + protected $ns; + + /** + * @param int $ns The namespace to use for all pages + */ + public function __construct( $ns ) { + if ( !MWNamespace::exists( $ns ) ) { + throw new MWException( "Namespace $ns doesn't exist on this wiki" ); + } + $this->ns = $ns; + } + + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + return Title::makeTitleSafe( $this->ns, $foreignTitle->getText() ); + } +} diff --git a/includes/title/SubpageImportTitleFactory.php b/includes/title/SubpageImportTitleFactory.php new file mode 100644 index 0000000000..b0be7afaa1 --- /dev/null +++ b/includes/title/SubpageImportTitleFactory.php @@ -0,0 +1,55 @@ +<?php +/** + * 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 + * @license GPL 2+ + */ + +/** + * A class to convert page titles on a foreign wiki (ForeignTitle objects) into + * page titles on the local wiki (Title objects), placing all pages as subpages + * of a given root page. + */ +class SubpageImportTitleFactory implements ImportTitleFactory { + /** @var Title */ + protected $rootPage; + + /** + * @param Title $rootPage The root page under which all pages should be + * created + */ + public function __construct( Title $rootPage ) { + if ( !MWNamespace::hasSubpages( $rootPage->getNamespace() ) ) { + throw new MWException( "The root page you specified, $rootPage, is in a " . + "namespace where subpages are not allowed" ); + } + $this->rootPage = $rootPage; + } + + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + return Title::newFromText( $this->rootPage->getPrefixedDBkey() . '/' . + $foreignTitle->getFullText() ); + } +} diff --git a/tests/phpunit/includes/ImportTest.php b/tests/phpunit/includes/ImportTest.php index 678c89bfa9..ea753e815f 100644 --- a/tests/phpunit/includes/ImportTest.php +++ b/tests/phpunit/includes/ImportTest.php @@ -28,7 +28,8 @@ class ImportTest extends MediaWikiLangTestCase { $source = $this->getInputStreamSource( $xml ); $redirect = null; - $callback = function ( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) use ( &$redirect ) { + $callback = function ( Title $title, ForeignTitle $foreignTitle, $revCount, + $sRevCount, $pageInfo ) use ( &$redirect ) { if ( array_key_exists( 'redirect', $pageInfo ) ) { $redirect = $pageInfo['redirect']; } @@ -98,4 +99,59 @@ EOF ); } + /** + * @covers WikiImporter::handleSiteInfo + * @dataProvider getSiteInfoXML + * @param string $xml + * @param array|null $namespaces + */ + public function testSiteInfoContainsNamespaces( $xml, $namespaces ) { + $source = $this->getInputStreamSource( $xml ); + + $importNamespaces = null; + $callback = function ( array $siteinfo, $innerImporter ) use ( &$importNamespaces ) { + $importNamespaces = $siteinfo['_namespaces']; + }; + + $importer = new WikiImporter( $source, ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) ); + $importer->setSiteInfoCallback( $callback ); + $importer->doImport(); + + $this->assertEquals( $importNamespaces, $namespaces ); + } + + public function getSiteInfoXML() { + return array( + array( + <<< EOF +<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.10/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd" version="0.10" xml:lang="en"> + <siteinfo> + <namespaces> + <namespace key="-2" case="first-letter">Media</namespace> + <namespace key="-1" case="first-letter">Special</namespace> + <namespace key="0" case="first-letter" /> + <namespace key="1" case="first-letter">Talk</namespace> + <namespace key="2" case="first-letter">User</namespace> + <namespace key="3" case="first-letter">User talk</namespace> + <namespace key="100" case="first-letter">Portal</namespace> + <namespace key="101" case="first-letter">Portal talk</namespace> + </namespaces> + </siteinfo> +</mediawiki> +EOF + , + array( + '-2' => 'Media', + '-1' => 'Special', + '0' => '', + '1' => 'Talk', + '2' => 'User', + '3' => 'User talk', + '100' => 'Portal', + '101' => 'Portal talk', + ) + ), + ); + } + } diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php new file mode 100644 index 0000000000..04af8718d7 --- /dev/null +++ b/tests/phpunit/includes/title/ForeignTitleTest.php @@ -0,0 +1,104 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers ForeignTitle + * + * @group Title + */ +class ForeignTitleTest extends MediaWikiTestCase { + + public function basicProvider() { + return array( + array( + new ForeignTitle( 20, 'Contributor', 'JohnDoe' ), + 20, 'Contributor', 'JohnDoe' + ), + array( + new ForeignTitle( '1', 'Discussion', 'Capital' ), + 1, 'Discussion', 'Capital' + ), + array( + new ForeignTitle( 0, '', 'MainNamespace' ), + 0, '', 'MainNamespace' + ), + array( + new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ), + 4, 'Some_ns', 'Article_title_with_spaces' + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $title, $expectedId, $expectedName, + $expectedText ) { + + $this->assertEquals( true, $title->isNamespaceIdKnown() ); + $this->assertEquals( $expectedId, $title->getNamespaceId() ); + $this->assertEquals( $expectedName, $title->getNamespaceName() ); + $this->assertEquals( $expectedText, $title->getText() ); + } + + public function testUnknownNamespaceCheck( ) { + $title = new ForeignTitle( null, 'this', 'that' ); + + $this->assertEquals( false, $title->isNamespaceIdKnown() ); + $this->assertEquals( 'this', $title->getNamespaceName() ); + $this->assertEquals( 'that', $title->getText() ); + } + + public function testUnknownNamespaceError( ) { + $this->setExpectedException( 'MWException' ); + $title = new ForeignTitle( null, 'this', 'that' ); + $title->getNamespaceId(); + } + + public function fullTextProvider() { + return array( + array( + new ForeignTitle( 20, 'Contributor', 'JohnDoe' ), + 'Contributor:JohnDoe' + ), + array( + new ForeignTitle( '1', 'Discussion', 'Capital' ), + 'Discussion:Capital' + ), + array( + new ForeignTitle( 0, '', 'MainNamespace' ), + 'MainNamespace' + ), + array( + new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ), + 'Some_ns:Article_title_with_spaces' + ), + ); + } + + /** + * @dataProvider fullTextProvider + */ + public function testFullText( ForeignTitle $title, $fullText ) { + $this->assertEquals( $fullText, $title->getFullText() ); + } +} diff --git a/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php new file mode 100644 index 0000000000..5d613db3dc --- /dev/null +++ b/tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php @@ -0,0 +1,92 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers NaiveForeignTitleFactory + * + * @group Title + */ +class NaiveForeignTitleFactoryTest extends MediaWikiTestCase { + + public function basicProvider() { + return array( + array( + 'MainNamespaceArticle', 0, + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + ), + array( + 'MainNamespaceArticle', null, + new ForeignTitle( null, '', 'MainNamespaceArticle' ), + ), + array( + 'Talk:Nice_talk', 1, + new ForeignTitle( 1, 'Talk', 'Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 0, + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 9000, // non-existent local namespace ID + new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 4, // existing local namespace ID + new ForeignTitle( 4, 'Bogus', 'Nice_talk' ), + ), + array( + 'Talk:Extra:Nice_talk', 1, + new ForeignTitle( 1, 'Talk', 'Extra:Nice_talk' ), + ), + array( + 'Talk:Extra:Nice_talk', null, + new ForeignTitle( null, 'Talk', 'Extra:Nice_talk' ), + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) { + $factory = new NaiveForeignTitleFactory(); + $testTitle = $factory->createForeignTitle( $title, $ns ); + + $this->assertEquals( $testTitle->isNamespaceIdKnown(), + $foreignTitle->isNamespaceIdKnown() ); + + if ( + $testTitle->isNamespaceIdKnown() && + $foreignTitle->isNamespaceIdKnown() + ) { + $this->assertEquals( $testTitle->getNamespaceId(), + $foreignTitle->getNamespaceId() ); + } + + $this->assertEquals( $testTitle->getNamespaceName(), + $foreignTitle->getNamespaceName() ); + $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); + + $this->assertEquals( str_replace( ' ', '_', $title ), + $foreignTitle->getFullText() ); + } +} diff --git a/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php b/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php new file mode 100644 index 0000000000..a46698af5a --- /dev/null +++ b/tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php @@ -0,0 +1,90 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers NaiveImportTitleFactory + * + * @group Title + */ +class NaiveImportTitleFactoryTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + 'wgExtraNamespaces' => array( 100 => 'Portal' ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( null, '', 'MainNamespaceArticle' ), + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + Title::newFromText( 'Talk:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + Title::newFromText( 'Bogus:Nice_talk' ) + ), + array( + new ForeignTitle( 100, 'Bogus', 'Nice_talk' ), + Title::newFromText( 'Bogus:Nice_talk' ) // not Portal:Nice_talk + ), + array( + new ForeignTitle( 1, 'Bogus', 'Nice_talk' ), + Title::newFromText( 'Talk:Nice_talk' ) // not Bogus:Nice_talk + ), + array( + new ForeignTitle( 100, 'Portal', 'Nice_talk' ), + Title::newFromText( 'Portal:Nice_talk' ) + ), + array( + new ForeignTitle( 724, 'Portal', 'Nice_talk' ), + Title::newFromText( 'Portal:Nice_talk' ) + ), + array( + new ForeignTitle( 2, 'Portal', 'Nice_talk' ), + Title::newFromText( 'User:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, Title $title ) { + $factory = new NaiveImportTitleFactory(); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $title->equals( $testTitle ) ); + } +} diff --git a/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php new file mode 100644 index 0000000000..4d68ab416b --- /dev/null +++ b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php @@ -0,0 +1,90 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers NamespaceAwareForeignTitleFactory + * + * @group Title + */ +class NamespaceAwareForeignTitleFactoryTest extends MediaWikiTestCase { + + public function basicProvider() { + return array( + array( + 'MainNamespaceArticle', 0, + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + ), + array( + 'MainNamespaceArticle', null, + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + ), + array( + 'Talk:Nice_talk', 1, + new ForeignTitle( 1, 'Talk', 'Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 0, + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', null, + new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 4, + new ForeignTitle( 4, 'Bogus', 'Nice_talk' ), + ), + array( + 'Bogus:Nice_talk', 1, + new ForeignTitle( 1, 'Talk', 'Nice_talk' ), + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) { + + $foreignNamespaces = array( + 0 => '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus' + ); + + $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces ); + $testTitle = $factory->createForeignTitle( $title, $ns ); + + $this->assertEquals( $testTitle->isNamespaceIdKnown(), + $foreignTitle->isNamespaceIdKnown() ); + + if ( + $testTitle->isNamespaceIdKnown() && + $foreignTitle->isNamespaceIdKnown() + ) { + $this->assertEquals( $testTitle->getNamespaceId(), + $foreignTitle->getNamespaceId() ); + } + + $this->assertEquals( $testTitle->getNamespaceName(), + $foreignTitle->getNamespaceName() ); + $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); + } +} diff --git a/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php new file mode 100644 index 0000000000..f0ffdb3f57 --- /dev/null +++ b/tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php @@ -0,0 +1,78 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers NamespaceImportTitleFactory + * + * @group Title + */ +class NamespaceImportTitleFactoryTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + 0, + Title::newFromText( 'MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + 2, + Title::newFromText( 'User:MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + 0, + Title::newFromText( 'Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + 0, + Title::newFromText( 'Bogus:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + 2, + Title::newFromText( 'User:Bogus:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, $ns, Title $title ) { + $factory = new NamespaceImportTitleFactory( $ns ); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $title->equals( $testTitle ) ); + } +} diff --git a/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php b/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php new file mode 100644 index 0000000000..71c9c7018c --- /dev/null +++ b/tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php @@ -0,0 +1,87 @@ +<?php +/** + * 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 + * @license GPL 2+ + * @author This, that and the other + */ + +/** + * @covers SubpageImportTitleFactory + * + * @group Title + */ +class SubpageImportTitleFactoryTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + 'wgNamespacesWithSubpages' => array( 0 => false, 2 => true ), + ) ); + } + + public function basicProvider() { + return array( + array( + new ForeignTitle( 0, '', 'MainNamespaceArticle' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/MainNamespaceArticle' ) + ), + array( + new ForeignTitle( 1, 'Discussion', 'Nice_talk' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/Discussion:Nice_talk' ) + ), + array( + new ForeignTitle( 0, '', 'Bogus:Nice_talk' ), + Title::newFromText( 'User:Graham' ), + Title::newFromText( 'User:Graham/Bogus:Nice_talk' ) + ), + ); + } + + /** + * @dataProvider basicProvider + */ + public function testBasic( ForeignTitle $foreignTitle, Title $rootPage, + Title $title ) { + + $factory = new SubpageImportTitleFactory( $rootPage ); + $testTitle = $factory->createTitleFromForeignTitle( $foreignTitle ); + + $this->assertTrue( $testTitle->equals( $title ) ); + } + + public function failureProvider() { + return array( + array( + Title::newFromText( 'Graham' ), + ), + ); + } + + /** + * @dataProvider failureProvider + */ + public function testFailures( Title $rootPage ) { + $this->setExpectedException( 'MWException' ); + new SubpageImportTitleFactory( $rootPage ); + } +} -- 2.20.1