Merge "Proper namespace handling for WikiImporter"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 5 Jan 2015 22:40:15 +0000 (22:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 5 Jan 2015 22:40:15 +0000 (22:40 +0000)
1  2 
autoload.php
docs/hooks.txt
includes/Import.php
includes/specials/SpecialImport.php

diff --combined autoload.php
@@@ -44,7 -44,6 +44,7 @@@ $wgAutoloadLocalClasses = array
        'ApiFormatXmlRsd' => __DIR__ . '/includes/api/ApiRsd.php',
        'ApiFormatYaml' => __DIR__ . '/includes/api/ApiFormatYaml.php',
        'ApiHelp' => __DIR__ . '/includes/api/ApiHelp.php',
 +      'ApiHelpParamValueMessage' => __DIR__ . '/includes/api/ApiHelpParamValueMessage.php',
        'ApiImageRotate' => __DIR__ . '/includes/api/ApiImageRotate.php',
        'ApiImport' => __DIR__ . '/includes/api/ApiImport.php',
        'ApiImportReporter' => __DIR__ . '/includes/api/ApiImport.php',
        'ApiRollback' => __DIR__ . '/includes/api/ApiRollback.php',
        'ApiRsd' => __DIR__ . '/includes/api/ApiRsd.php',
        'ApiSetNotificationTimestamp' => __DIR__ . '/includes/api/ApiSetNotificationTimestamp.php',
 +      'ApiStashEdit' => __DIR__ . '/includes/api/ApiStashEdit.php',
        'ApiTokens' => __DIR__ . '/includes/api/ApiTokens.php',
        'ApiUnblock' => __DIR__ . '/includes/api/ApiUnblock.php',
        'ApiUndelete' => __DIR__ . '/includes/api/ApiUndelete.php',
        'ChangesListSpecialPage' => __DIR__ . '/includes/specialpage/ChangesListSpecialPage.php',
        'ChannelFeed' => __DIR__ . '/includes/Feed.php',
        'CheckBadRedirects' => __DIR__ . '/maintenance/checkBadRedirects.php',
 +      'CheckComposerLockUpToDate' => __DIR__ . '/maintenance/checkComposerLockUpToDate.php',
        'CheckExtensionsCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc',
        'CheckImages' => __DIR__ . '/maintenance/checkImages.php',
        'CheckLanguageCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc',
        'CompareParserCache' => __DIR__ . '/maintenance/compareParserCache.php',
        'CompareParsers' => __DIR__ . '/maintenance/compareParsers.php',
        'ComposerHookHandler' => __DIR__ . '/includes/composer/ComposerHookHandler.php',
 +      'ComposerJson' => __DIR__ . '/includes/libs/composer/ComposerJson.php',
 +      'ComposerLock' => __DIR__ . '/includes/libs/composer/ComposerLock.php',
        'ComposerPackageModifier' => __DIR__ . '/includes/composer/ComposerPackageModifier.php',
        'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php',
        'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php',
        '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',
        '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',
        'MWLoggerLegacyLogger' => __DIR__ . '/includes/debug/logger/legacy/Logger.php',
        'MWLoggerLegacySpi' => __DIR__ . '/includes/debug/logger/legacy/Spi.php',
        'MWLoggerMonologHandler' => __DIR__ . '/includes/debug/logger/monolog/Handler.php',
 +      'MWLoggerMonologLegacyFormatter' => __DIR__ . '/includes/debug/logger/monolog/LegacyFormatter.php',
        'MWLoggerMonologProcessor' => __DIR__ . '/includes/debug/logger/monolog/Processor.php',
 +      'MWLoggerMonologSamplingHandler' => __DIR__ . '/includes/debug/logger/monolog/SamplingHandler.php',
        'MWLoggerMonologSpi' => __DIR__ . '/includes/debug/logger/monolog/Spi.php',
        'MWLoggerNullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MWLoggerSpi' => __DIR__ . '/includes/debug/logger/Spi.php',
        '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',
        'RebuildLocalisationCache' => __DIR__ . '/maintenance/rebuildLocalisationCache.php',
        'RebuildMessages' => __DIR__ . '/maintenance/rebuildmessages.php',
        'RebuildRecentchanges' => __DIR__ . '/maintenance/rebuildrecentchanges.php',
 +      'RebuildSitesCache' => __DIR__ . '/maintenance/rebuildSitesCache.php',
        'RebuildTextIndex' => __DIR__ . '/maintenance/rebuildtextindex.php',
        'RecentChange' => __DIR__ . '/includes/changes/RecentChange.php',
        'RecompressTracked' => __DIR__ . '/maintenance/storage/recompressTracked.php',
        'RefreshLinksJob' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob.php',
        'RefreshLinksJob2' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob2.php',
        'RegexlikeReplacer' => __DIR__ . '/includes/utils/StringUtils.php',
 +      'RemoveInvalidEmails' => __DIR__ . '/maintenance/removeInvalidEmails.php',
        'RemoveUnusedAccounts' => __DIR__ . '/maintenance/removeUnusedAccounts.php',
        'RenameDbPrefix' => __DIR__ . '/maintenance/renameDbPrefix.php',
        'RenderAction' => __DIR__ . '/includes/actions/RenderAction.php',
        'ResourceLoaderFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFileModule.php',
        'ResourceLoaderFilePageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFilePageModule.php',
        'ResourceLoaderFilePath' => __DIR__ . '/includes/resourceloader/ResourceLoaderFilePath.php',
 +      'ResourceLoaderImage' => __DIR__ . '/includes/resourceloader/ResourceLoaderImage.php',
 +      'ResourceLoaderImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderImageModule.php',
        'ResourceLoaderLanguageDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageDataModule.php',
        'ResourceLoaderLanguageNamesModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
        'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
        'ResourceLoaderSkinModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSkinModule.php',
        'ResourceLoaderStartUpModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderStartUpModule.php',
        'ResourceLoaderUserCSSPrefsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
 +      'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php',
        'ResourceLoaderUserGroupsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserGroupsModule.php',
        'ResourceLoaderUserModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserModule.php',
        'ResourceLoaderUserOptionsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserOptionsModule.php',
        'SearchResultSet' => __DIR__ . '/includes/search/SearchResultSet.php',
        'SearchSqlite' => __DIR__ . '/includes/search/SearchSqlite.php',
        'SearchUpdate' => __DIR__ . '/includes/deferred/SearchUpdate.php',
 +      'SectionProfileCallback' => __DIR__ . '/includes/profiler/SectionProfiler.php',
        'SectionProfiler' => __DIR__ . '/includes/profiler/SectionProfiler.php',
        'SevenZipStream' => __DIR__ . '/maintenance/7zip.inc',
        'ShiConverter' => __DIR__ . '/languages/classes/LanguageShi.php',
        'SiteArray' => __DIR__ . '/includes/site/SiteList.php',
        'SiteConfiguration' => __DIR__ . '/includes/SiteConfiguration.php',
        'SiteList' => __DIR__ . '/includes/site/SiteList.php',
 +      'SiteListFileCache' => __DIR__ . '/includes/site/SiteListFileCache.php',
 +      'SiteListFileCacheBuilder' => __DIR__ . '/includes/site/SiteListFileCacheBuilder.php',
        'SiteObject' => __DIR__ . '/includes/site/Site.php',
        'SiteSQLStore' => __DIR__ . '/includes/site/SiteSQLStore.php',
        'SiteStats' => __DIR__ . '/includes/SiteStats.php',
        'SiteStatsInit' => __DIR__ . '/includes/SiteStats.php',
        'SiteStatsUpdate' => __DIR__ . '/includes/deferred/SiteStatsUpdate.php',
        'SiteStore' => __DIR__ . '/includes/site/SiteStore.php',
 -      'Sites' => __DIR__ . '/includes/site/SiteSQLStore.php',
        'Skin' => __DIR__ . '/includes/skins/Skin.php',
        'SkinApi' => __DIR__ . '/includes/skins/SkinApi.php',
        'SkinApiTemplate' => __DIR__ . '/includes/skins/SkinApiTemplate.php',
        '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',
        'TraditionalImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php',
        'TransactionProfiler' => __DIR__ . '/includes/profiler/TransactionProfiler.php',
        'TransformParameterError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
 +      'TransformTooBigImageAreaError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'TransformationalImageHandler' => __DIR__ . '/includes/media/TransformationalImageHandler.php',
        'UDPRCFeedEngine' => __DIR__ . '/includes/rcfeed/UDPRCFeedEngine.php',
 +      'UDPTransport' => __DIR__ . '/includes/libs/UDPTransport.php',
        'UIDGenerator' => __DIR__ . '/includes/utils/UIDGenerator.php',
        'UcdXmlReader' => __DIR__ . '/maintenance/language/generateCollationData.php',
        'UncategorizedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedcategories.php',
        'ZhConverter' => __DIR__ . '/languages/classes/LanguageZh.php',
        'ZipDirectoryReader' => __DIR__ . '/includes/utils/ZipDirectoryReader.php',
        'ZipDirectoryReaderError' => __DIR__ . '/includes/utils/ZipDirectoryReader.php',
 -      'lessc' => __DIR__ . '/includes/libs/lessc.inc.php',
 -      'lessc_formatter_classic' => __DIR__ . '/includes/libs/lessc.inc.php',
 -      'lessc_formatter_compressed' => __DIR__ . '/includes/libs/lessc.inc.php',
 -      'lessc_formatter_lessjs' => __DIR__ . '/includes/libs/lessc.inc.php',
 -      'lessc_parser' => __DIR__ . '/includes/libs/lessc.inc.php',
        'profile_point' => __DIR__ . '/profileinfo.php',
  );
diff --combined docs/hooks.txt
@@@ -66,7 -66,7 +66,7 @@@ email notification when an article is s
                # code to actually show the article goes here
  
                if ($wgNotifyArticle) {
 -                      wfNotifyArticleShow($article));
 +                      wfNotifyArticleShow($article);
                }
        }
  
@@@ -314,7 -314,7 +314,7 @@@ $output: The OutputPage object where ou
  
  '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
@@@ -407,19 -407,6 +407,19 @@@ $module: ApiBase Module objec
  &$help: Array of HTML strings to be joined for the output.
  $options: Array Options passed to ApiHelp::getHelp
  
 +'ApiOpenSearchSuggest': Called when constructing the OpenSearch results. Hooks
 +can alter or append to the array.
 +&$results: array with integer keys to associative arrays. Keys in associative
 +array:
 +  - title: Title object.
 +  - redirect from: Title or null.
 +  - extract: Description for this result.
 +  - extract trimmed: If truthy, the extract will not be trimmed to
 +    $wgOpenSearchDescriptionLength.
 +  - image: Thumbnail for this result. Value is an array with subkeys 'source'
 +    (url), 'width', 'height', 'alt', 'align'.
 +  - url: Url for the given title.
 +
  'APIQueryAfterExecute': After calling the execute() method of an
  action=query submodule. Use this to extend core API modules.
  &$module: Module object
@@@ -777,10 -764,12 +777,10 @@@ $out: OutputPage objec
  'BeforeParserFetchFileAndTitle': Before an image is rendered by Parser.
  $parser: Parser object
  $nt: the image title
 -&$options: array of options to RepoGroup::findFile
 +&$options: array of options to RepoGroup::findFile. If it contains 'broken'
 +  as a key then the file will appear as a broken thumbnail.
  &$descQuery: query string to add to thumbnail URL
  
 -FIXME: Where does the below sentence fit in?
 -If 'broken' is a key in $options then the file will appear as a broken thumbnail.
 -
  'BeforeParserFetchTemplateAndtitle': Before a template is fetched by Parser.
  $parser: Parser object
  $title: title of the template
@@@ -1047,8 -1036,7 +1047,8 @@@ This may be triggered by the EditPage o
  Use the $status object to indicate whether the edit should be allowed, and to provide
  a reason for disallowing it. Return false to abort the edit, and true to continue.
  Returning true if $status->isOK() returns false means "don't save but continue user
 -interaction", e.g. show the edit form.
 +interaction", e.g. show the edit form. $status->apiHookResult can be set to an array
 +to be returned by api.php action=edit. This is used to deliver captchas.
  $context: object implementing the IContextSource interface.
  $content: content of the edit box, as a Content object.
  $status: Status object to represent errors, etc.
@@@ -1253,15 -1241,15 +1253,15 @@@ $reason: reaso
  'FormatAutocomments': When an autocomment is formatted by the Linker.
   &$comment: Reference to the accumulated comment. Initially null, when set the
     default code will be skipped.
 - $pre: Initial part of the parsed comment before the call to the hook.
 + $pre: Boolean, true if there is text before this autocomment
   $auto: The extracted part of the parsed comment before the call to the hook.
 - $post: The final part of the parsed comment before the call to the hook.
 + $post: Boolean, true if there is text after this autocomment
   $title: An optional title object used to links to sections. Can be null.
   $local: Boolean indicating whether section links should refer to local page.
  
  'GalleryGetModes': Get list of classes that can render different modes of a
   gallery
 -$modeArray: An associative array mapping mode names to classes that implement
 +&$modeArray: An associative array mapping mode names to classes that implement
   that mode. It is expected all registered classes are a subclass of
   ImageGalleryBase.
  
@@@ -1461,7 -1449,7 +1461,7 @@@ $page: ImagePage objec
  'ImgAuthBeforeStream': executed before file is streamed to user, but only when
  using img_auth.php.
  &$title: the Title object of the file as it would appear for the upload page
 -&$path: the original file and path name when img_auth was invoked by the the web
 +&$path: the original file and path name when img_auth was invoked by the web
    server
  &$name: the name only component of the file
  &$result: The location to pass back results of the hook routine (only used if
@@@ -2166,7 -2154,6 +2166,7 @@@ $ns : array of int namespace keys to se
  $search : search term (not guaranteed to be conveniently normalized)
  $limit : maximum number of results to return
  &$results : out param: array of page names (strings)
 +$offset : number of results to offset from the beginning
  
  'PrefixSearchExtractNamespace': Called if core was not able to extract a
  namespace from the search string so that extensions can attempt it.
@@@ -2424,7 -2411,7 +2424,7 @@@ after variants have been added
  'SkinTemplateOutputPageBeforeExec': Before SkinTemplate::outputPage() starts
  page output.
  &$sktemplate: SkinTemplate object
 -&$tpl: Template engine object
 +&$tpl: QuickTemplate engine object
  
  'SkinTemplatePreventOtherActiveTabs': Use this to prevent showing active tabs.
  $sktemplate: SkinTemplate object
@@@ -2692,8 -2679,7 +2692,8 @@@ that can be applied
  $title: The title in question.
  &$types: The types of protection available.
  
 -'TitleIsCssOrJsPage': Called when determining if a page is a CSS or JS page.
 +'TitleIsCssOrJsPage': DEPRECATED! Use ContentHandlerDefaultModelFor instead.
 +Called when determining if a page is a CSS or JS page.
  $title: Title object that is being checked
  $result: Boolean; whether MediaWiki currently thinks this is a CSS/JS page.
    Hooks may change this value to override the return value of
@@@ -2714,8 -2700,7 +2714,8 @@@ $result: Boolean; whether MediaWiki cur
    Hooks may change this value to override the return value of
    Title::isMovable().
  
 -'TitleIsWikitextPage': Called when determining if a page is a wikitext or should
 +'TitleIsWikitextPage': DEPRECATED! Use ContentHandlerDefaultModelFor instead.
 +Called when determining if a page is a wikitext or should
  be handled by separate handler (via ArticleViewCustom).
  $title: Title object that is being checked
  $result: Boolean; whether MediaWiki currently thinks this is a wikitext page.
@@@ -2897,7 -2882,7 +2897,7 @@@ $user: User objec
  &$timestamp: timestamp, change this to override local email authentication
    timestamp
  
 -'UserGetImplicitGroups': Called in User::getImplicitGroups().
 +'UserGetImplicitGroups': DEPRECATED, called in User::getImplicitGroups().
  &$groups: List of implicit (automatically-assigned) groups
  
  'UserGetLanguageObject': Called when getting user's interface language object.
diff --combined includes/Import.php
   */
  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 +71,8 @@@
                $this->setUploadCallback( array( $this, 'importUpload' ) );
                $this->setLogItemCallback( array( $this, 'importLogItem' ) );
                $this->setPageOutCallback( array( $this, 'finishImportPage' ) );
+               $this->importTitleFactory = new NaiveImportTitleFactory();
        }
  
        /**
                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
                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;
                }
                $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 )
                                                : $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 ) );
                                }
                        }
                }
        /**
         * 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 );
 +              return Hooks::run( 'AfterImportPage', $args );
        }
  
        /**
                $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
        /**
         * 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 );
                $buffer = "";
                while ( $this->reader->read() ) {
                        switch ( $this->reader->nodeType ) {
 -                      case XmlReader::TEXT:
 -                      case XmlReader::SIGNIFICANT_WHITESPACE:
 +                      case XMLReader::TEXT:
 +                      case XMLReader::SIGNIFICANT_WHITESPACE:
                                $buffer .= $this->reader->value;
                                break;
 -                      case XmlReader::END_ELEMENT:
 +                      case XMLReader::END_ELEMENT:
                                return $buffer;
                        }
                }
                        $tag = $this->reader->name;
                        $type = $this->reader->nodeType;
  
 -                      if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
 +                      if ( !Hooks::run( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
                                // Do nothing
 -                      } elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) {
 +                      } elseif ( $tag == 'mediawiki' && $type == XMLReader::END_ELEMENT ) {
                                break;
                        } elseif ( $tag == 'siteinfo' ) {
                                $this->handleSiteInfo();
                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() {
                                        'logtitle', 'params' );
  
                while ( $this->reader->read() ) {
 -                      if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 +                      if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
                                        $this->reader->name == 'logitem' ) {
                                break;
                        }
  
                        $tag = $this->reader->name;
  
 -                      if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array(
 +                      if ( !Hooks::run( 'ImportHandleLogItemXMLTag', array(
                                $this, $logInfo
                        ) ) ) {
                                // Do nothing
                $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;
  
                while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 -                      if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 +                      if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
                                        $this->reader->name == 'page' ) {
                                break;
                        }
  
+                       $skip = false;
                        $tag = $this->reader->name;
  
                        if ( $badTitle ) {
                                // The title is invalid, bail out of this page
                                $skip = true;
 -                      } elseif ( !wfRunHooks( 'ImportHandlePageXMLTag', array( $this,
 +                      } elseif ( !Hooks::run( 'ImportHandlePageXMLTag', array( $this,
                                                &$pageInfo ) ) ) {
                                // Do nothing
                        } elseif ( in_array( $tag, $normalFields ) ) {
                                        $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 );
                $skip = false;
  
                while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 -                      if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 +                      if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
                                        $this->reader->name == 'revision' ) {
                                break;
                        }
  
                        $tag = $this->reader->name;
  
 -                      if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array(
 +                      if ( !Hooks::run( 'ImportHandleRevisionXMLTag', array(
                                $this, $pageInfo, $revisionInfo
                        ) ) ) {
                                // Do nothing
                $skip = false;
  
                while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 -                      if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 +                      if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
                                        $this->reader->name == 'upload' ) {
                                break;
                        }
  
                        $tag = $this->reader->name;
  
 -                      if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array(
 +                      if ( !Hooks::run( 'ImportHandleUploadXMLTag', array(
                                $this, $pageInfo
                        ) ) ) {
                                // Do nothing
                $info = array();
  
                while ( $this->reader->read() ) {
 -                      if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 +                      if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
                                        $this->reader->name == 'contributor' ) {
                                break;
                        }
  
        /**
         * @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() );
                        return false;
                }
  
-               return array( $title, $origTitle );
+               return array( $title, $foreignTitle );
        }
  }
  
@@@ -1652,10 -1709,7 +1709,10 @@@ class WikiRevision 
  }
  
  /**
 - * @todo document (e.g. one-sentence class description).
 + * Used for importing XML dumps where the content of the dump is in a string.
 + * This class is ineffecient, and should only be used for small dumps.
 + * For larger dumps, ImportStreamSource should be used instead.
 + *
   * @ingroup SpecialPage
   */
  class ImportStringSource {
  }
  
  /**
 - * @todo document (e.g. one-sentence class description).
 + * Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
   * @ingroup SpecialPage
   */
  class ImportStreamSource {
@@@ -47,20 -47,17 +47,20 @@@ class SpecialImport extends SpecialPag
         */
        public function __construct() {
                parent::__construct( 'Import', 'import' );
 -              $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
        }
  
        /**
         * Execute
         * @param string|null $par
 +       * @throws PermissionsError
 +       * @throws ReadOnlyError
         */
        function execute( $par ) {
                $this->setHeaders();
                $this->outputHeader();
  
 +              $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
 +
                $this->getOutput()->addModules( 'mediawiki.special.import' );
  
                $user = $this->getUser();
@@@ -521,13 -518,14 +521,14 @@@ class ImportReporter extends ContextSou
  
        /**
         * @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 );
  
                                $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 ) {
                                $page = WikiPage::factory( $title );
                                # Update page record
                                $page->updateRevisionOn( $dbw, $nullRevision );
 -                              wfRunHooks(
 +                              Hooks::run(
                                        'NewRevisionFromEditComplete',
                                        array( $page, $nullRevision, $latest, $this->getUser() )
                                );