Proper namespace handling for WikiImporter
authorThis, that and the other <at.light@live.com.au>
Wed, 10 Dec 2014 11:24:47 +0000 (22:24 +1100)
committerThis, that and the other <at.light@live.com.au>
Wed, 10 Dec 2014 11:24:47 +0000 (22:24 +1100)
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 <siteinfo> and <ns> 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

19 files changed:
autoload.php
docs/hooks.txt
includes/Import.php
includes/specials/SpecialImport.php
includes/title/ForeignTitle.php [new file with mode: 0644]
includes/title/ForeignTitleFactory.php [new file with mode: 0644]
includes/title/ImportTitleFactory.php [new file with mode: 0644]
includes/title/NaiveForeignTitleFactory.php [new file with mode: 0644]
includes/title/NaiveImportTitleFactory.php [new file with mode: 0644]
includes/title/NamespaceAwareForeignTitleFactory.php [new file with mode: 0644]
includes/title/NamespaceImportTitleFactory.php [new file with mode: 0644]
includes/title/SubpageImportTitleFactory.php [new file with mode: 0644]
tests/phpunit/includes/ImportTest.php
tests/phpunit/includes/title/ForeignTitleTest.php [new file with mode: 0644]
tests/phpunit/includes/title/NaiveForeignTitleFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/title/NaiveImportTitleFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/title/NamespaceImportTitleFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/title/SubpageImportTitleFactoryTest.php [new file with mode: 0644]

index 5239edc..1c28696 100644 (file)
@@ -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',
index 7ec6ff5..813b587 100644 (file)
@@ -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
index 4eb8e97..5922c69 100644 (file)
  */
 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 "<page>" is reached.
         * @param Title $title
@@ -361,12 +397,13 @@ class WikiImporter {
        /**
         * Notify the callback function when a "</page>" 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 );
        }
 }
 
index 0831c20..701e206 100644 (file)
@@ -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 (file)
index 0000000..ed96d17
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+/**
+ * A structure to hold the title of a page on a foreign MediaWiki installation
+ *
+ * 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
+ * @author This, that and the other
+ */
+
+/**
+ * A simple, immutable structure to hold the title of a page on a foreign
+ * MediaWiki installation.
+ */
+class ForeignTitle {
+       /**
+        * @var int|null
+        * Null if we don't know the namespace ID (e.g. interwiki links)
+        */
+       protected $namespaceId;
+       /** @var string */
+       protected $namespaceName;
+       /** @var string */
+       protected $pageName;
+
+       /**
+        * Creates a new ForeignTitle object.
+        *
+        * @param int|null $namespaceId Null if the namespace ID is unknown (e.g.
+        * interwiki links)
+        * @param string $namespaceName
+        * @param string $pageName
+        */
+       public function __construct( $namespaceId, $namespaceName, $pageName ) {
+               if ( is_null( $namespaceId ) ) {
+                       $this->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 (file)
index 0000000..427afdf
--- /dev/null
@@ -0,0 +1,36 @@
+<?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 parser that translates page titles into ForeignTitle objects.
+ */
+interface ForeignTitleFactory {
+       /**
+        * 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 <title> 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 );
+}
diff --git a/includes/title/ImportTitleFactory.php b/includes/title/ImportTitleFactory.php
new file mode 100644 (file)
index 0000000..629616d
--- /dev/null
@@ -0,0 +1,36 @@
+<?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+
+ */
+
+/**
+ * Represents an object that can convert page titles on a foreign wiki
+ * (ForeignTitle objects) into page titles on the local wiki (Title objects).
+ */
+interface 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 );
+}
diff --git a/includes/title/NaiveForeignTitleFactory.php b/includes/title/NaiveForeignTitleFactory.php
new file mode 100644 (file)
index 0000000..6c8bcc0
--- /dev/null
@@ -0,0 +1,71 @@
+<?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 parser that translates page titles on a foreign wiki into ForeignTitle
+ * objects, with no knowledge of the namespace setup on the foreign site.
+ */
+class NaiveForeignTitleFactory implements ForeignTitleFactory {
+       /**
+        * 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 <title> and <ns> 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 <siteinfo> 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 <ns> 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 (file)
index 0000000..43c662e
--- /dev/null
@@ -0,0 +1,65 @@
+<?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), using a default namespace
+ * mapping.
+ *
+ * For built-in namespaces (0 <= ID < 100), we try to find a local namespace
+ * with the same namespace ID as the foreign page. If no such namespace exists,
+ * or the namespace ID is unknown or > 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 (file)
index 0000000..bf97e2c
--- /dev/null
@@ -0,0 +1,134 @@
+<?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 parser that translates page titles on a foreign wiki into ForeignTitle
+ * objects, using information about the namespace setup on the foreign site.
+ */
+class NamespaceAwareForeignTitleFactory implements ForeignTitleFactory {
+       /**
+        * @var array
+        */
+       protected $foreignNamespaces;
+       /**
+        * @var array
+        */
+       private $foreignNamespacesFlipped;
+
+       /**
+        * Normalizes an array name for $foreignNamespacesFlipped.
+        * @param string $name
+        * @return string
+        */
+       private function normalizeNamespaceName( $name ) {
+               return strtolower( str_replace( ' ', '_', $name ) );
+       }
+
+       /**
+        * @param array|null $foreignNamespaces An array 'id' => '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 <title> 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 (file)
index 0000000..0c1d0c4
--- /dev/null
@@ -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 (file)
index 0000000..b0be7af
--- /dev/null
@@ -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() );
+       }
+}
index 678c89b..ea753e8 100644 (file)
@@ -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 (file)
index 0000000..04af871
--- /dev/null
@@ -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 (file)
index 0000000..5d613db
--- /dev/null
@@ -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 (file)
index 0000000..a46698a
--- /dev/null
@@ -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 (file)
index 0000000..4d68ab4
--- /dev/null
@@ -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 (file)
index 0000000..f0ffdb3
--- /dev/null
@@ -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 (file)
index 0000000..71c9c70
--- /dev/null
@@ -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 );
+       }
+}