[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Import.php (source)

   1  <?php
   2  /**
   3   * MediaWiki page data importer.
   4   *
   5   * Copyright © 2003,2005 Brion Vibber <[email protected]>
   6   * https://www.mediawiki.org/
   7   *
   8   * This program is free software; you can redistribute it and/or modify
   9   * it under the terms of the GNU General Public License as published by
  10   * the Free Software Foundation; either version 2 of the License, or
  11   * (at your option) any later version.
  12   *
  13   * This program is distributed in the hope that it will be useful,
  14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16   * GNU General Public License for more details.
  17   *
  18   * You should have received a copy of the GNU General Public License along
  19   * with this program; if not, write to the Free Software Foundation, Inc.,
  20   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21   * http://www.gnu.org/copyleft/gpl.html
  22   *
  23   * @file
  24   * @ingroup SpecialPage
  25   */
  26  
  27  /**
  28   * XML file reader for the page data importer
  29   *
  30   * implements Special:Import
  31   * @ingroup SpecialPage
  32   */
  33  class WikiImporter {
  34      private $reader = null;
  35      private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback;
  36      private $mSiteInfoCallback, $mTargetNamespace, $mTargetRootPage, $mPageOutCallback;
  37      private $mNoticeCallback, $mDebug;
  38      private $mImportUploads, $mImageBasePath;
  39      private $mNoUpdates = false;
  40  
  41      /**
  42       * Creates an ImportXMLReader drawing from the source provided
  43       * @param ImportStreamSource $source
  44       */
  45  	function __construct( ImportStreamSource $source ) {
  46          $this->reader = new XMLReader();
  47  
  48          if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) {
  49              stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
  50          }
  51          $id = UploadSourceAdapter::registerSource( $source );
  52          if ( defined( 'LIBXML_PARSEHUGE' ) ) {
  53              $this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
  54          } else {
  55              $this->reader->open( "uploadsource://$id" );
  56          }
  57  
  58          // Default callbacks
  59          $this->setRevisionCallback( array( $this, "importRevision" ) );
  60          $this->setUploadCallback( array( $this, 'importUpload' ) );
  61          $this->setLogItemCallback( array( $this, 'importLogItem' ) );
  62          $this->setPageOutCallback( array( $this, 'finishImportPage' ) );
  63      }
  64  
  65      /**
  66       * @return null|XMLReader
  67       */
  68  	public function getReader() {
  69          return $this->reader;
  70      }
  71  
  72  	public function throwXmlError( $err ) {
  73          $this->debug( "FAILURE: $err" );
  74          wfDebug( "WikiImporter XML error: $err\n" );
  75      }
  76  
  77  	public function debug( $data ) {
  78          if ( $this->mDebug ) {
  79              wfDebug( "IMPORT: $data\n" );
  80          }
  81      }
  82  
  83  	public function warn( $data ) {
  84          wfDebug( "IMPORT: $data\n" );
  85      }
  86  
  87  	public function notice( $msg /*, $param, ...*/ ) {
  88          $params = func_get_args();
  89          array_shift( $params );
  90  
  91          if ( is_callable( $this->mNoticeCallback ) ) {
  92              call_user_func( $this->mNoticeCallback, $msg, $params );
  93          } else { # No ImportReporter -> CLI
  94              echo wfMessage( $msg, $params )->text() . "\n";
  95          }
  96      }
  97  
  98      /**
  99       * Set debug mode...
 100       * @param bool $debug
 101       */
 102  	function setDebug( $debug ) {
 103          $this->mDebug = $debug;
 104      }
 105  
 106      /**
 107       * Set 'no updates' mode. In this mode, the link tables will not be updated by the importer
 108       * @param bool $noupdates
 109       */
 110  	function setNoUpdates( $noupdates ) {
 111          $this->mNoUpdates = $noupdates;
 112      }
 113  
 114      /**
 115       * Set a callback that displays notice messages
 116       *
 117       * @param callable $callback
 118       * @return callable
 119       */
 120  	public function setNoticeCallback( $callback ) {
 121          return wfSetVar( $this->mNoticeCallback, $callback );
 122      }
 123  
 124      /**
 125       * Sets the action to perform as each new page in the stream is reached.
 126       * @param callable $callback
 127       * @return callable
 128       */
 129  	public function setPageCallback( $callback ) {
 130          $previous = $this->mPageCallback;
 131          $this->mPageCallback = $callback;
 132          return $previous;
 133      }
 134  
 135      /**
 136       * Sets the action to perform as each page in the stream is completed.
 137       * Callback accepts the page title (as a Title object), a second object
 138       * with the original title form (in case it's been overridden into a
 139       * local namespace), and a count of revisions.
 140       *
 141       * @param callable $callback
 142       * @return callable
 143       */
 144  	public function setPageOutCallback( $callback ) {
 145          $previous = $this->mPageOutCallback;
 146          $this->mPageOutCallback = $callback;
 147          return $previous;
 148      }
 149  
 150      /**
 151       * Sets the action to perform as each page revision is reached.
 152       * @param callable $callback
 153       * @return callable
 154       */
 155  	public function setRevisionCallback( $callback ) {
 156          $previous = $this->mRevisionCallback;
 157          $this->mRevisionCallback = $callback;
 158          return $previous;
 159      }
 160  
 161      /**
 162       * Sets the action to perform as each file upload version is reached.
 163       * @param callable $callback
 164       * @return callable
 165       */
 166  	public function setUploadCallback( $callback ) {
 167          $previous = $this->mUploadCallback;
 168          $this->mUploadCallback = $callback;
 169          return $previous;
 170      }
 171  
 172      /**
 173       * Sets the action to perform as each log item reached.
 174       * @param callable $callback
 175       * @return callable
 176       */
 177  	public function setLogItemCallback( $callback ) {
 178          $previous = $this->mLogItemCallback;
 179          $this->mLogItemCallback = $callback;
 180          return $previous;
 181      }
 182  
 183      /**
 184       * Sets the action to perform when site info is encountered
 185       * @param callable $callback
 186       * @return callable
 187       */
 188  	public function setSiteInfoCallback( $callback ) {
 189          $previous = $this->mSiteInfoCallback;
 190          $this->mSiteInfoCallback = $callback;
 191          return $previous;
 192      }
 193  
 194      /**
 195       * Set a target namespace to override the defaults
 196       * @param null|int $namespace
 197       * @return bool
 198       */
 199  	public function setTargetNamespace( $namespace ) {
 200          if ( is_null( $namespace ) ) {
 201              // Don't override namespaces
 202              $this->mTargetNamespace = null;
 203          } elseif ( $namespace >= 0 ) {
 204              // @todo FIXME: Check for validity
 205              $this->mTargetNamespace = intval( $namespace );
 206          } else {
 207              return false;
 208          }
 209      }
 210  
 211      /**
 212       * Set a target root page under which all pages are imported
 213       * @param null|string $rootpage
 214       * @return Status
 215       */
 216  	public function setTargetRootPage( $rootpage ) {
 217          $status = Status::newGood();
 218          if ( is_null( $rootpage ) ) {
 219              // No rootpage
 220              $this->mTargetRootPage = null;
 221          } elseif ( $rootpage !== '' ) {
 222              $rootpage = rtrim( $rootpage, '/' ); //avoid double slashes
 223              $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace )
 224                  ? $this->mTargetNamespace
 225                  : NS_MAIN
 226              );
 227  
 228              if ( !$title || $title->isExternal() ) {
 229                  $status->fatal( 'import-rootpage-invalid' );
 230              } else {
 231                  if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
 232                      global $wgContLang;
 233  
 234                      $displayNSText = $title->getNamespace() == NS_MAIN
 235                          ? wfMessage( 'blanknamespace' )->text()
 236                          : $wgContLang->getNsText( $title->getNamespace() );
 237                      $status->fatal( 'import-rootpage-nosubpage', $displayNSText );
 238                  } else {
 239                      // set namespace to 'all', so the namespace check in processTitle() can passed
 240                      $this->setTargetNamespace( null );
 241                      $this->mTargetRootPage = $title->getPrefixedDBkey();
 242                  }
 243              }
 244          }
 245          return $status;
 246      }
 247  
 248      /**
 249       * @param string $dir
 250       */
 251  	public function setImageBasePath( $dir ) {
 252          $this->mImageBasePath = $dir;
 253      }
 254  
 255      /**
 256       * @param bool $import
 257       */
 258  	public function setImportUploads( $import ) {
 259          $this->mImportUploads = $import;
 260      }
 261  
 262      /**
 263       * Default per-revision callback, performs the import.
 264       * @param WikiRevision $revision
 265       * @return bool
 266       */
 267  	public function importRevision( $revision ) {
 268          if ( !$revision->getContentHandler()->canBeUsedOn( $revision->getTitle() ) ) {
 269              $this->notice( 'import-error-bad-location',
 270                  $revision->getTitle()->getPrefixedText(),
 271                  $revision->getID(),
 272                  $revision->getModel(),
 273                  $revision->getFormat() );
 274  
 275              return false;
 276          }
 277  
 278          try {
 279              $dbw = wfGetDB( DB_MASTER );
 280              return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
 281          } catch ( MWContentSerializationException $ex ) {
 282              $this->notice( 'import-error-unserialize',
 283                  $revision->getTitle()->getPrefixedText(),
 284                  $revision->getID(),
 285                  $revision->getModel(),
 286                  $revision->getFormat() );
 287          }
 288  
 289          return false;
 290      }
 291  
 292      /**
 293       * Default per-revision callback, performs the import.
 294       * @param WikiRevision $revision
 295       * @return bool
 296       */
 297  	public function importLogItem( $revision ) {
 298          $dbw = wfGetDB( DB_MASTER );
 299          return $dbw->deadlockLoop( array( $revision, 'importLogItem' ) );
 300      }
 301  
 302      /**
 303       * Dummy for now...
 304       * @param WikiRevision $revision
 305       * @return bool
 306       */
 307  	public function importUpload( $revision ) {
 308          $dbw = wfGetDB( DB_MASTER );
 309          return $dbw->deadlockLoop( array( $revision, 'importUpload' ) );
 310      }
 311  
 312      /**
 313       * Mostly for hook use
 314       * @param Title $title
 315       * @param string $origTitle
 316       * @param int $revCount
 317       * @param int $sRevCount
 318       * @param array $pageInfo
 319       * @return bool
 320       */
 321  	public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
 322          $args = func_get_args();
 323          return wfRunHooks( 'AfterImportPage', $args );
 324      }
 325  
 326      /**
 327       * Alternate per-revision callback, for debugging.
 328       * @param WikiRevision $revision
 329       */
 330  	public function debugRevisionHandler( &$revision ) {
 331          $this->debug( "Got revision:" );
 332          if ( is_object( $revision->title ) ) {
 333              $this->debug( "-- Title: " . $revision->title->getPrefixedText() );
 334          } else {
 335              $this->debug( "-- Title: <invalid>" );
 336          }
 337          $this->debug( "-- User: " . $revision->user_text );
 338          $this->debug( "-- Timestamp: " . $revision->timestamp );
 339          $this->debug( "-- Comment: " . $revision->comment );
 340          $this->debug( "-- Text: " . $revision->text );
 341      }
 342  
 343      /**
 344       * Notify the callback function when a new "<page>" is reached.
 345       * @param Title $title
 346       */
 347  	function pageCallback( $title ) {
 348          if ( isset( $this->mPageCallback ) ) {
 349              call_user_func( $this->mPageCallback, $title );
 350          }
 351      }
 352  
 353      /**
 354       * Notify the callback function when a "</page>" is closed.
 355       * @param Title $title
 356       * @param Title $origTitle
 357       * @param int $revCount
 358       * @param int $sucCount Number of revisions for which callback returned true
 359       * @param array $pageInfo Associative array of page information
 360       */
 361  	private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) {
 362          if ( isset( $this->mPageOutCallback ) ) {
 363              $args = func_get_args();
 364              call_user_func_array( $this->mPageOutCallback, $args );
 365          }
 366      }
 367  
 368      /**
 369       * Notify the callback function of a revision
 370       * @param WikiRevision $revision
 371       * @return bool|mixed
 372       */
 373  	private function revisionCallback( $revision ) {
 374          if ( isset( $this->mRevisionCallback ) ) {
 375              return call_user_func_array( $this->mRevisionCallback,
 376                      array( $revision, $this ) );
 377          } else {
 378              return false;
 379          }
 380      }
 381  
 382      /**
 383       * Notify the callback function of a new log item
 384       * @param WikiRevision $revision
 385       * @return bool|mixed
 386       */
 387  	private function logItemCallback( $revision ) {
 388          if ( isset( $this->mLogItemCallback ) ) {
 389              return call_user_func_array( $this->mLogItemCallback,
 390                      array( $revision, $this ) );
 391          } else {
 392              return false;
 393          }
 394      }
 395  
 396      /**
 397       * Retrieves the contents of the named attribute of the current element.
 398       * @param string $attr The name of the attribute
 399       * @return string The value of the attribute or an empty string if it is not set in the current element.
 400       */
 401  	public function nodeAttribute( $attr ) {
 402          return $this->reader->getAttribute( $attr );
 403      }
 404  
 405      /**
 406       * Shouldn't something like this be built-in to XMLReader?
 407       * Fetches text contents of the current element, assuming
 408       * no sub-elements or such scary things.
 409       * @return string
 410       * @access private
 411       */
 412  	public function nodeContents() {
 413          if ( $this->reader->isEmptyElement ) {
 414              return "";
 415          }
 416          $buffer = "";
 417          while ( $this->reader->read() ) {
 418              switch ( $this->reader->nodeType ) {
 419              case XmlReader::TEXT:
 420              case XmlReader::SIGNIFICANT_WHITESPACE:
 421                  $buffer .= $this->reader->value;
 422                  break;
 423              case XmlReader::END_ELEMENT:
 424                  return $buffer;
 425              }
 426          }
 427  
 428          $this->reader->close();
 429          return '';
 430      }
 431  
 432      /**
 433       * Primary entry point
 434       * @throws MWException
 435       * @return bool
 436       */
 437  	public function doImport() {
 438          // Calls to reader->read need to be wrapped in calls to
 439          // libxml_disable_entity_loader() to avoid local file
 440          // inclusion attacks (bug 46932).
 441          $oldDisable = libxml_disable_entity_loader( true );
 442          $this->reader->read();
 443  
 444          if ( $this->reader->name != 'mediawiki' ) {
 445              libxml_disable_entity_loader( $oldDisable );
 446              throw new MWException( "Expected <mediawiki> tag, got " .
 447                  $this->reader->name );
 448          }
 449          $this->debug( "<mediawiki> tag is correct." );
 450  
 451          $this->debug( "Starting primary dump processing loop." );
 452  
 453          $keepReading = $this->reader->read();
 454          $skip = false;
 455          while ( $keepReading ) {
 456              $tag = $this->reader->name;
 457              $type = $this->reader->nodeType;
 458  
 459              if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
 460                  // Do nothing
 461              } elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) {
 462                  break;
 463              } elseif ( $tag == 'siteinfo' ) {
 464                  $this->handleSiteInfo();
 465              } elseif ( $tag == 'page' ) {
 466                  $this->handlePage();
 467              } elseif ( $tag == 'logitem' ) {
 468                  $this->handleLogItem();
 469              } elseif ( $tag != '#text' ) {
 470                  $this->warn( "Unhandled top-level XML tag $tag" );
 471  
 472                  $skip = true;
 473              }
 474  
 475              if ( $skip ) {
 476                  $keepReading = $this->reader->next();
 477                  $skip = false;
 478                  $this->debug( "Skip" );
 479              } else {
 480                  $keepReading = $this->reader->read();
 481              }
 482          }
 483  
 484          libxml_disable_entity_loader( $oldDisable );
 485          return true;
 486      }
 487  
 488      /**
 489       * @return bool
 490       * @throws MWException
 491       */
 492  	private function handleSiteInfo() {
 493          // Site info is useful, but not actually used for dump imports.
 494          // Includes a quick short-circuit to save performance.
 495          if ( !$this->mSiteInfoCallback ) {
 496              $this->reader->next();
 497              return true;
 498          }
 499          throw new MWException( "SiteInfo tag is not yet handled, do not set mSiteInfoCallback" );
 500      }
 501  
 502  	private function handleLogItem() {
 503          $this->debug( "Enter log item handler." );
 504          $logInfo = array();
 505  
 506          // Fields that can just be stuffed in the pageInfo object
 507          $normalFields = array( 'id', 'comment', 'type', 'action', 'timestamp',
 508                      'logtitle', 'params' );
 509  
 510          while ( $this->reader->read() ) {
 511              if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 512                      $this->reader->name == 'logitem' ) {
 513                  break;
 514              }
 515  
 516              $tag = $this->reader->name;
 517  
 518              if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array(
 519                  $this, $logInfo
 520              ) ) ) {
 521                  // Do nothing
 522              } elseif ( in_array( $tag, $normalFields ) ) {
 523                  $logInfo[$tag] = $this->nodeContents();
 524              } elseif ( $tag == 'contributor' ) {
 525                  $logInfo['contributor'] = $this->handleContributor();
 526              } elseif ( $tag != '#text' ) {
 527                  $this->warn( "Unhandled log-item XML tag $tag" );
 528              }
 529          }
 530  
 531          $this->processLogItem( $logInfo );
 532      }
 533  
 534      /**
 535       * @param array $logInfo
 536       * @return bool|mixed
 537       */
 538  	private function processLogItem( $logInfo ) {
 539          $revision = new WikiRevision;
 540  
 541          $revision->setID( $logInfo['id'] );
 542          $revision->setType( $logInfo['type'] );
 543          $revision->setAction( $logInfo['action'] );
 544          $revision->setTimestamp( $logInfo['timestamp'] );
 545          $revision->setParams( $logInfo['params'] );
 546          $revision->setTitle( Title::newFromText( $logInfo['logtitle'] ) );
 547          $revision->setNoUpdates( $this->mNoUpdates );
 548  
 549          if ( isset( $logInfo['comment'] ) ) {
 550              $revision->setComment( $logInfo['comment'] );
 551          }
 552  
 553          if ( isset( $logInfo['contributor']['ip'] ) ) {
 554              $revision->setUserIP( $logInfo['contributor']['ip'] );
 555          }
 556          if ( isset( $logInfo['contributor']['username'] ) ) {
 557              $revision->setUserName( $logInfo['contributor']['username'] );
 558          }
 559  
 560          return $this->logItemCallback( $revision );
 561      }
 562  
 563  	private function handlePage() {
 564          // Handle page data.
 565          $this->debug( "Enter page handler." );
 566          $pageInfo = array( 'revisionCount' => 0, 'successfulRevisionCount' => 0 );
 567  
 568          // Fields that can just be stuffed in the pageInfo object
 569          $normalFields = array( 'title', 'id', 'redirect', 'restrictions' );
 570  
 571          $skip = false;
 572          $badTitle = false;
 573  
 574          while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 575              if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 576                      $this->reader->name == 'page' ) {
 577                  break;
 578              }
 579  
 580              $tag = $this->reader->name;
 581  
 582              if ( $badTitle ) {
 583                  // The title is invalid, bail out of this page
 584                  $skip = true;
 585              } elseif ( !wfRunHooks( 'ImportHandlePageXMLTag', array( $this,
 586                          &$pageInfo ) ) ) {
 587                  // Do nothing
 588              } elseif ( in_array( $tag, $normalFields ) ) {
 589                  // An XML snippet:
 590                  // <page>
 591                  //     <id>123</id>
 592                  //     <title>Page</title>
 593                  //     <redirect title="NewTitle"/>
 594                  //     ...
 595                  // Because the redirect tag is built differently, we need special handling for that case.
 596                  if ( $tag == 'redirect' ) {
 597                      $pageInfo[$tag] = $this->nodeAttribute( 'title' );
 598                  } else {
 599                      $pageInfo[$tag] = $this->nodeContents();
 600                      if ( $tag == 'title' ) {
 601                          $title = $this->processTitle( $pageInfo['title'] );
 602  
 603                          if ( !$title ) {
 604                              $badTitle = true;
 605                              $skip = true;
 606                          }
 607  
 608                          $this->pageCallback( $title );
 609                          list( $pageInfo['_title'], $origTitle ) = $title;
 610                      }
 611                  }
 612              } elseif ( $tag == 'revision' ) {
 613                  $this->handleRevision( $pageInfo );
 614              } elseif ( $tag == 'upload' ) {
 615                  $this->handleUpload( $pageInfo );
 616              } elseif ( $tag != '#text' ) {
 617                  $this->warn( "Unhandled page XML tag $tag" );
 618                  $skip = true;
 619              }
 620          }
 621  
 622          $this->pageOutCallback( $pageInfo['_title'], $origTitle,
 623                      $pageInfo['revisionCount'],
 624                      $pageInfo['successfulRevisionCount'],
 625                      $pageInfo );
 626      }
 627  
 628      /**
 629       * @param array $pageInfo
 630       */
 631  	private function handleRevision( &$pageInfo ) {
 632          $this->debug( "Enter revision handler" );
 633          $revisionInfo = array();
 634  
 635          $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text' );
 636  
 637          $skip = false;
 638  
 639          while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 640              if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 641                      $this->reader->name == 'revision' ) {
 642                  break;
 643              }
 644  
 645              $tag = $this->reader->name;
 646  
 647              if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array(
 648                  $this, $pageInfo, $revisionInfo
 649              ) ) ) {
 650                  // Do nothing
 651              } elseif ( in_array( $tag, $normalFields ) ) {
 652                  $revisionInfo[$tag] = $this->nodeContents();
 653              } elseif ( $tag == 'contributor' ) {
 654                  $revisionInfo['contributor'] = $this->handleContributor();
 655              } elseif ( $tag != '#text' ) {
 656                  $this->warn( "Unhandled revision XML tag $tag" );
 657                  $skip = true;
 658              }
 659          }
 660  
 661          $pageInfo['revisionCount']++;
 662          if ( $this->processRevision( $pageInfo, $revisionInfo ) ) {
 663              $pageInfo['successfulRevisionCount']++;
 664          }
 665      }
 666  
 667      /**
 668       * @param array $pageInfo
 669       * @param array $revisionInfo
 670       * @return bool|mixed
 671       */
 672  	private function processRevision( $pageInfo, $revisionInfo ) {
 673          $revision = new WikiRevision;
 674  
 675          if ( isset( $revisionInfo['id'] ) ) {
 676              $revision->setID( $revisionInfo['id'] );
 677          }
 678          if ( isset( $revisionInfo['model'] ) ) {
 679              $revision->setModel( $revisionInfo['model'] );
 680          }
 681          if ( isset( $revisionInfo['format'] ) ) {
 682              $revision->setFormat( $revisionInfo['format'] );
 683          }
 684          $revision->setTitle( $pageInfo['_title'] );
 685  
 686          if ( isset( $revisionInfo['text'] ) ) {
 687              $handler = $revision->getContentHandler();
 688              $text = $handler->importTransform(
 689                  $revisionInfo['text'],
 690                  $revision->getFormat() );
 691  
 692              $revision->setText( $text );
 693          }
 694          if ( isset( $revisionInfo['timestamp'] ) ) {
 695              $revision->setTimestamp( $revisionInfo['timestamp'] );
 696          } else {
 697              $revision->setTimestamp( wfTimestampNow() );
 698          }
 699  
 700          if ( isset( $revisionInfo['comment'] ) ) {
 701              $revision->setComment( $revisionInfo['comment'] );
 702          }
 703  
 704          if ( isset( $revisionInfo['minor'] ) ) {
 705              $revision->setMinor( true );
 706          }
 707          if ( isset( $revisionInfo['contributor']['ip'] ) ) {
 708              $revision->setUserIP( $revisionInfo['contributor']['ip'] );
 709          }
 710          if ( isset( $revisionInfo['contributor']['username'] ) ) {
 711              $revision->setUserName( $revisionInfo['contributor']['username'] );
 712          }
 713          $revision->setNoUpdates( $this->mNoUpdates );
 714  
 715          return $this->revisionCallback( $revision );
 716      }
 717  
 718      /**
 719       * @param array $pageInfo
 720       * @return mixed
 721       */
 722  	private function handleUpload( &$pageInfo ) {
 723          $this->debug( "Enter upload handler" );
 724          $uploadInfo = array();
 725  
 726          $normalFields = array( 'timestamp', 'comment', 'filename', 'text',
 727                      'src', 'size', 'sha1base36', 'archivename', 'rel' );
 728  
 729          $skip = false;
 730  
 731          while ( $skip ? $this->reader->next() : $this->reader->read() ) {
 732              if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 733                      $this->reader->name == 'upload' ) {
 734                  break;
 735              }
 736  
 737              $tag = $this->reader->name;
 738  
 739              if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array(
 740                  $this, $pageInfo
 741              ) ) ) {
 742                  // Do nothing
 743              } elseif ( in_array( $tag, $normalFields ) ) {
 744                  $uploadInfo[$tag] = $this->nodeContents();
 745              } elseif ( $tag == 'contributor' ) {
 746                  $uploadInfo['contributor'] = $this->handleContributor();
 747              } elseif ( $tag == 'contents' ) {
 748                  $contents = $this->nodeContents();
 749                  $encoding = $this->reader->getAttribute( 'encoding' );
 750                  if ( $encoding === 'base64' ) {
 751                      $uploadInfo['fileSrc'] = $this->dumpTemp( base64_decode( $contents ) );
 752                      $uploadInfo['isTempSrc'] = true;
 753                  }
 754              } elseif ( $tag != '#text' ) {
 755                  $this->warn( "Unhandled upload XML tag $tag" );
 756                  $skip = true;
 757              }
 758          }
 759  
 760          if ( $this->mImageBasePath && isset( $uploadInfo['rel'] ) ) {
 761              $path = "{$this->mImageBasePath}/{$uploadInfo['rel']}";
 762              if ( file_exists( $path ) ) {
 763                  $uploadInfo['fileSrc'] = $path;
 764                  $uploadInfo['isTempSrc'] = false;
 765              }
 766          }
 767  
 768          if ( $this->mImportUploads ) {
 769              return $this->processUpload( $pageInfo, $uploadInfo );
 770          }
 771      }
 772  
 773      /**
 774       * @param string $contents
 775       * @return string
 776       */
 777  	private function dumpTemp( $contents ) {
 778          $filename = tempnam( wfTempDir(), 'importupload' );
 779          file_put_contents( $filename, $contents );
 780          return $filename;
 781      }
 782  
 783      /**
 784       * @param array $pageInfo
 785       * @param array $uploadInfo
 786       * @return mixed
 787       */
 788  	private function processUpload( $pageInfo, $uploadInfo ) {
 789          $revision = new WikiRevision;
 790          $text = isset( $uploadInfo['text'] ) ? $uploadInfo['text'] : '';
 791  
 792          $revision->setTitle( $pageInfo['_title'] );
 793          $revision->setID( $pageInfo['id'] );
 794          $revision->setTimestamp( $uploadInfo['timestamp'] );
 795          $revision->setText( $text );
 796          $revision->setFilename( $uploadInfo['filename'] );
 797          if ( isset( $uploadInfo['archivename'] ) ) {
 798              $revision->setArchiveName( $uploadInfo['archivename'] );
 799          }
 800          $revision->setSrc( $uploadInfo['src'] );
 801          if ( isset( $uploadInfo['fileSrc'] ) ) {
 802              $revision->setFileSrc( $uploadInfo['fileSrc'],
 803                  !empty( $uploadInfo['isTempSrc'] ) );
 804          }
 805          if ( isset( $uploadInfo['sha1base36'] ) ) {
 806              $revision->setSha1Base36( $uploadInfo['sha1base36'] );
 807          }
 808          $revision->setSize( intval( $uploadInfo['size'] ) );
 809          $revision->setComment( $uploadInfo['comment'] );
 810  
 811          if ( isset( $uploadInfo['contributor']['ip'] ) ) {
 812              $revision->setUserIP( $uploadInfo['contributor']['ip'] );
 813          }
 814          if ( isset( $uploadInfo['contributor']['username'] ) ) {
 815              $revision->setUserName( $uploadInfo['contributor']['username'] );
 816          }
 817          $revision->setNoUpdates( $this->mNoUpdates );
 818  
 819          return call_user_func( $this->mUploadCallback, $revision );
 820      }
 821  
 822      /**
 823       * @return array
 824       */
 825  	private function handleContributor() {
 826          $fields = array( 'id', 'ip', 'username' );
 827          $info = array();
 828  
 829          while ( $this->reader->read() ) {
 830              if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
 831                      $this->reader->name == 'contributor' ) {
 832                  break;
 833              }
 834  
 835              $tag = $this->reader->name;
 836  
 837              if ( in_array( $tag, $fields ) ) {
 838                  $info[$tag] = $this->nodeContents();
 839              }
 840          }
 841  
 842          return $info;
 843      }
 844  
 845      /**
 846       * @param string $text
 847       * @return array|bool
 848       */
 849  	private function processTitle( $text ) {
 850          global $wgCommandLineMode;
 851  
 852          $workTitle = $text;
 853          $origTitle = Title::newFromText( $workTitle );
 854  
 855          if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) {
 856              # makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map)
 857              # and than dbKey can begin with a lowercase char
 858              $title = Title::makeTitleSafe( $this->mTargetNamespace,
 859                  $origTitle->getDBkey() );
 860          } else {
 861              if ( !is_null( $this->mTargetRootPage ) ) {
 862                  $workTitle = $this->mTargetRootPage . '/' . $workTitle;
 863              }
 864              $title = Title::newFromText( $workTitle );
 865          }
 866  
 867          if ( is_null( $title ) ) {
 868              # Invalid page title? Ignore the page
 869              $this->notice( 'import-error-invalid', $workTitle );
 870              return false;
 871          } elseif ( $title->isExternal() ) {
 872              $this->notice( 'import-error-interwiki', $title->getPrefixedText() );
 873              return false;
 874          } elseif ( !$title->canExist() ) {
 875              $this->notice( 'import-error-special', $title->getPrefixedText() );
 876              return false;
 877          } elseif ( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) {
 878              # Do not import if the importing wiki user cannot edit this page
 879              $this->notice( 'import-error-edit', $title->getPrefixedText() );
 880              return false;
 881          } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) {
 882              # Do not import if the importing wiki user cannot create this page
 883              $this->notice( 'import-error-create', $title->getPrefixedText() );
 884              return false;
 885          }
 886  
 887          return array( $title, $origTitle );
 888      }
 889  }
 890  
 891  /** This is a horrible hack used to keep source compatibility */
 892  class UploadSourceAdapter {
 893      /** @var array */
 894      public static $sourceRegistrations = array();
 895  
 896      /** @var string */
 897      private $mSource;
 898  
 899      /** @var string */
 900      private $mBuffer;
 901  
 902      /** @var int */
 903      private $mPosition;
 904  
 905      /**
 906       * @param ImportStreamSource $source
 907       * @return string
 908       */
 909  	static function registerSource( ImportStreamSource $source ) {
 910          $id = wfRandomString();
 911  
 912          self::$sourceRegistrations[$id] = $source;
 913  
 914          return $id;
 915      }
 916  
 917      /**
 918       * @param string $path
 919       * @param string $mode
 920       * @param array $options
 921       * @param string $opened_path
 922       * @return bool
 923       */
 924  	function stream_open( $path, $mode, $options, &$opened_path ) {
 925          $url = parse_url( $path );
 926          $id = $url['host'];
 927  
 928          if ( !isset( self::$sourceRegistrations[$id] ) ) {
 929              return false;
 930          }
 931  
 932          $this->mSource = self::$sourceRegistrations[$id];
 933  
 934          return true;
 935      }
 936  
 937      /**
 938       * @param int $count
 939       * @return string
 940       */
 941  	function stream_read( $count ) {
 942          $return = '';
 943          $leave = false;
 944  
 945          while ( !$leave && !$this->mSource->atEnd() &&
 946                  strlen( $this->mBuffer ) < $count ) {
 947              $read = $this->mSource->readChunk();
 948  
 949              if ( !strlen( $read ) ) {
 950                  $leave = true;
 951              }
 952  
 953              $this->mBuffer .= $read;
 954          }
 955  
 956          if ( strlen( $this->mBuffer ) ) {
 957              $return = substr( $this->mBuffer, 0, $count );
 958              $this->mBuffer = substr( $this->mBuffer, $count );
 959          }
 960  
 961          $this->mPosition += strlen( $return );
 962  
 963          return $return;
 964      }
 965  
 966      /**
 967       * @param string $data
 968       * @return bool
 969       */
 970  	function stream_write( $data ) {
 971          return false;
 972      }
 973  
 974      /**
 975       * @return mixed
 976       */
 977  	function stream_tell() {
 978          return $this->mPosition;
 979      }
 980  
 981      /**
 982       * @return bool
 983       */
 984  	function stream_eof() {
 985          return $this->mSource->atEnd();
 986      }
 987  
 988      /**
 989       * @return array
 990       */
 991  	function url_stat() {
 992          $result = array();
 993  
 994          $result['dev'] = $result[0] = 0;
 995          $result['ino'] = $result[1] = 0;
 996          $result['mode'] = $result[2] = 0;
 997          $result['nlink'] = $result[3] = 0;
 998          $result['uid'] = $result[4] = 0;
 999          $result['gid'] = $result[5] = 0;
1000          $result['rdev'] = $result[6] = 0;
1001          $result['size'] = $result[7] = 0;
1002          $result['atime'] = $result[8] = 0;
1003          $result['mtime'] = $result[9] = 0;
1004          $result['ctime'] = $result[10] = 0;
1005          $result['blksize'] = $result[11] = 0;
1006          $result['blocks'] = $result[12] = 0;
1007  
1008          return $result;
1009      }
1010  }
1011  
1012  /**
1013   * @todo document (e.g. one-sentence class description).
1014   * @ingroup SpecialPage
1015   */
1016  class WikiRevision {
1017      /** @todo Unused? */
1018      public $importer = null;
1019  
1020      /** @var Title */
1021      public $title = null;
1022  
1023      /** @var int */
1024      public $id = 0;
1025  
1026      /** @var string */
1027      public $timestamp = "20010115000000";
1028  
1029      /**
1030       * @var int
1031       * @todo Can't find any uses. Public, because that's suspicious. Get clarity. */
1032      public $user = 0;
1033  
1034      /** @var string */
1035      public $user_text = "";
1036  
1037      /** @var string */
1038      public $model = null;
1039  
1040      /** @var string */
1041      public $format = null;
1042  
1043      /** @var string */
1044      public $text = "";
1045  
1046      /** @var int */
1047      protected $size;
1048  
1049      /** @var Content */
1050      public $content = null;
1051  
1052      /** @var ContentHandler */
1053      protected $contentHandler = null;
1054  
1055      /** @var string */
1056      public $comment = "";
1057  
1058      /** @var bool */
1059      public $minor = false;
1060  
1061      /** @var string */
1062      public $type = "";
1063  
1064      /** @var string */
1065      public $action = "";
1066  
1067      /** @var string */
1068      public $params = "";
1069  
1070      /** @var string */
1071      public $fileSrc = '';
1072  
1073      /** @var bool|string */
1074      public $sha1base36 = false;
1075  
1076      /**
1077       * @var bool
1078       * @todo Unused?
1079       */
1080      public $isTemp = false;
1081  
1082      /** @var string */
1083      public $archiveName = '';
1084  
1085      protected $filename;
1086  
1087      /** @var mixed */
1088      protected $src;
1089  
1090      /** @todo Unused? */
1091      public $fileIsTemp;
1092  
1093      /** @var bool */
1094      private $mNoUpdates = false;
1095  
1096      /**
1097       * @param Title $title
1098       * @throws MWException
1099       */
1100  	function setTitle( $title ) {
1101          if ( is_object( $title ) ) {
1102              $this->title = $title;
1103          } elseif ( is_null( $title ) ) {
1104              throw new MWException( "WikiRevision given a null title in import. "
1105                  . "You may need to adjust \$wgLegalTitleChars." );
1106          } else {
1107              throw new MWException( "WikiRevision given non-object title in import." );
1108          }
1109      }
1110  
1111      /**
1112       * @param int $id
1113       */
1114  	function setID( $id ) {
1115          $this->id = $id;
1116      }
1117  
1118      /**
1119       * @param string $ts
1120       */
1121  	function setTimestamp( $ts ) {
1122          # 2003-08-05T18:30:02Z
1123          $this->timestamp = wfTimestamp( TS_MW, $ts );
1124      }
1125  
1126      /**
1127       * @param string $user
1128       */
1129  	function setUsername( $user ) {
1130          $this->user_text = $user;
1131      }
1132  
1133      /**
1134       * @param string $ip
1135       */
1136  	function setUserIP( $ip ) {
1137          $this->user_text = $ip;
1138      }
1139  
1140      /**
1141       * @param string $model
1142       */
1143  	function setModel( $model ) {
1144          $this->model = $model;
1145      }
1146  
1147      /**
1148       * @param string $format
1149       */
1150  	function setFormat( $format ) {
1151          $this->format = $format;
1152      }
1153  
1154      /**
1155       * @param string $text
1156       */
1157  	function setText( $text ) {
1158          $this->text = $text;
1159      }
1160  
1161      /**
1162       * @param string $text
1163       */
1164  	function setComment( $text ) {
1165          $this->comment = $text;
1166      }
1167  
1168      /**
1169       * @param bool $minor
1170       */
1171  	function setMinor( $minor ) {
1172          $this->minor = (bool)$minor;
1173      }
1174  
1175      /**
1176       * @param mixed $src
1177       */
1178  	function setSrc( $src ) {
1179          $this->src = $src;
1180      }
1181  
1182      /**
1183       * @param string $src
1184       * @param bool $isTemp
1185       */
1186  	function setFileSrc( $src, $isTemp ) {
1187          $this->fileSrc = $src;
1188          $this->fileIsTemp = $isTemp;
1189      }
1190  
1191      /**
1192       * @param string $sha1base36
1193       */
1194  	function setSha1Base36( $sha1base36 ) {
1195          $this->sha1base36 = $sha1base36;
1196      }
1197  
1198      /**
1199       * @param string $filename
1200       */
1201  	function setFilename( $filename ) {
1202          $this->filename = $filename;
1203      }
1204  
1205      /**
1206       * @param string $archiveName
1207       */
1208  	function setArchiveName( $archiveName ) {
1209          $this->archiveName = $archiveName;
1210      }
1211  
1212      /**
1213       * @param int $size
1214       */
1215  	function setSize( $size ) {
1216          $this->size = intval( $size );
1217      }
1218  
1219      /**
1220       * @param string $type
1221       */
1222  	function setType( $type ) {
1223          $this->type = $type;
1224      }
1225  
1226      /**
1227       * @param string $action
1228       */
1229  	function setAction( $action ) {
1230          $this->action = $action;
1231      }
1232  
1233      /**
1234       * @param array $params
1235       */
1236  	function setParams( $params ) {
1237          $this->params = $params;
1238      }
1239  
1240      /**
1241       * @param bool $noupdates
1242       */
1243  	public function setNoUpdates( $noupdates ) {
1244          $this->mNoUpdates = $noupdates;
1245      }
1246  
1247      /**
1248       * @return Title
1249       */
1250  	function getTitle() {
1251          return $this->title;
1252      }
1253  
1254      /**
1255       * @return int
1256       */
1257  	function getID() {
1258          return $this->id;
1259      }
1260  
1261      /**
1262       * @return string
1263       */
1264  	function getTimestamp() {
1265          return $this->timestamp;
1266      }
1267  
1268      /**
1269       * @return string
1270       */
1271  	function getUser() {
1272          return $this->user_text;
1273      }
1274  
1275      /**
1276       * @return string
1277       *
1278       * @deprecated Since 1.21, use getContent() instead.
1279       */
1280  	function getText() {
1281          ContentHandler::deprecated( __METHOD__, '1.21' );
1282  
1283          return $this->text;
1284      }
1285  
1286      /**
1287       * @return ContentHandler
1288       */
1289  	function getContentHandler() {
1290          if ( is_null( $this->contentHandler ) ) {
1291              $this->contentHandler = ContentHandler::getForModelID( $this->getModel() );
1292          }
1293  
1294          return $this->contentHandler;
1295      }
1296  
1297      /**
1298       * @return Content
1299       */
1300  	function getContent() {
1301          if ( is_null( $this->content ) ) {
1302              $handler = $this->getContentHandler();
1303              $this->content = $handler->unserializeContent( $this->text, $this->getFormat() );
1304          }
1305  
1306          return $this->content;
1307      }
1308  
1309      /**
1310       * @return string
1311       */
1312  	function getModel() {
1313          if ( is_null( $this->model ) ) {
1314              $this->model = $this->getTitle()->getContentModel();
1315          }
1316  
1317          return $this->model;
1318      }
1319  
1320      /**
1321       * @return string
1322       */
1323  	function getFormat() {
1324          if ( is_null( $this->format ) ) {
1325              $this->format = $this->getContentHandler()->getDefaultFormat();
1326          }
1327  
1328          return $this->format;
1329      }
1330  
1331      /**
1332       * @return string
1333       */
1334  	function getComment() {
1335          return $this->comment;
1336      }
1337  
1338      /**
1339       * @return bool
1340       */
1341  	function getMinor() {
1342          return $this->minor;
1343      }
1344  
1345      /**
1346       * @return mixed
1347       */
1348  	function getSrc() {
1349          return $this->src;
1350      }
1351  
1352      /**
1353       * @return bool|string
1354       */
1355  	function getSha1() {
1356          if ( $this->sha1base36 ) {
1357              return wfBaseConvert( $this->sha1base36, 36, 16 );
1358          }
1359          return false;
1360      }
1361  
1362      /**
1363       * @return string
1364       */
1365  	function getFileSrc() {
1366          return $this->fileSrc;
1367      }
1368  
1369      /**
1370       * @return bool
1371       */
1372  	function isTempSrc() {
1373          return $this->isTemp;
1374      }
1375  
1376      /**
1377       * @return mixed
1378       */
1379  	function getFilename() {
1380          return $this->filename;
1381      }
1382  
1383      /**
1384       * @return string
1385       */
1386  	function getArchiveName() {
1387          return $this->archiveName;
1388      }
1389  
1390      /**
1391       * @return mixed
1392       */
1393  	function getSize() {
1394          return $this->size;
1395      }
1396  
1397      /**
1398       * @return string
1399       */
1400  	function getType() {
1401          return $this->type;
1402      }
1403  
1404      /**
1405       * @return string
1406       */
1407  	function getAction() {
1408          return $this->action;
1409      }
1410  
1411      /**
1412       * @return string
1413       */
1414  	function getParams() {
1415          return $this->params;
1416      }
1417  
1418      /**
1419       * @return bool
1420       */
1421  	function importOldRevision() {
1422          $dbw = wfGetDB( DB_MASTER );
1423  
1424          # Sneak a single revision into place
1425          $user = User::newFromName( $this->getUser() );
1426          if ( $user ) {
1427              $userId = intval( $user->getId() );
1428              $userText = $user->getName();
1429              $userObj = $user;
1430          } else {
1431              $userId = 0;
1432              $userText = $this->getUser();
1433              $userObj = new User;
1434          }
1435  
1436          // avoid memory leak...?
1437          $linkCache = LinkCache::singleton();
1438          $linkCache->clear();
1439  
1440          $page = WikiPage::factory( $this->title );
1441          $page->loadPageData( 'fromdbmaster' );
1442          if ( !$page->exists() ) {
1443              # must create the page...
1444              $pageId = $page->insertOn( $dbw );
1445              $created = true;
1446              $oldcountable = null;
1447          } else {
1448              $pageId = $page->getId();
1449              $created = false;
1450  
1451              $prior = $dbw->selectField( 'revision', '1',
1452                  array( 'rev_page' => $pageId,
1453                      'rev_timestamp' => $dbw->timestamp( $this->timestamp ),
1454                      'rev_user_text' => $userText,
1455                      'rev_comment' => $this->getComment() ),
1456                  __METHOD__
1457              );
1458              if ( $prior ) {
1459                  // @todo FIXME: This could fail slightly for multiple matches :P
1460                  wfDebug( __METHOD__ . ": skipping existing revision for [[" .
1461                      $this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
1462                  return false;
1463              }
1464              $oldcountable = $page->isCountable();
1465          }
1466  
1467          # @todo FIXME: Use original rev_id optionally (better for backups)
1468          # Insert the row
1469          $revision = new Revision( array(
1470              'title' => $this->title,
1471              'page' => $pageId,
1472              'content_model' => $this->getModel(),
1473              'content_format' => $this->getFormat(),
1474              //XXX: just set 'content' => $this->getContent()?
1475              'text' => $this->getContent()->serialize( $this->getFormat() ),
1476              'comment' => $this->getComment(),
1477              'user' => $userId,
1478              'user_text' => $userText,
1479              'timestamp' => $this->timestamp,
1480              'minor_edit' => $this->minor,
1481              ) );
1482          $revision->insertOn( $dbw );
1483          $changed = $page->updateIfNewerOn( $dbw, $revision );
1484  
1485          if ( $changed !== false && !$this->mNoUpdates ) {
1486              wfDebug( __METHOD__ . ": running updates\n" );
1487              $page->doEditUpdates(
1488                  $revision,
1489                  $userObj,
1490                  array( 'created' => $created, 'oldcountable' => $oldcountable )
1491              );
1492          }
1493  
1494          return true;
1495      }
1496  
1497  	function importLogItem() {
1498          $dbw = wfGetDB( DB_MASTER );
1499          # @todo FIXME: This will not record autoblocks
1500          if ( !$this->getTitle() ) {
1501              wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
1502                  $this->timestamp . "\n" );
1503              return;
1504          }
1505          # Check if it exists already
1506          // @todo FIXME: Use original log ID (better for backups)
1507          $prior = $dbw->selectField( 'logging', '1',
1508              array( 'log_type' => $this->getType(),
1509                  'log_action' => $this->getAction(),
1510                  'log_timestamp' => $dbw->timestamp( $this->timestamp ),
1511                  'log_namespace' => $this->getTitle()->getNamespace(),
1512                  'log_title' => $this->getTitle()->getDBkey(),
1513                  'log_comment' => $this->getComment(),
1514                  #'log_user_text' => $this->user_text,
1515                  'log_params' => $this->params ),
1516              __METHOD__
1517          );
1518          // @todo FIXME: This could fail slightly for multiple matches :P
1519          if ( $prior ) {
1520              wfDebug( __METHOD__
1521                  . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp "
1522                  . $this->timestamp . "\n" );
1523              return;
1524          }
1525          $log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
1526          $data = array(
1527              'log_id' => $log_id,
1528              'log_type' => $this->type,
1529              'log_action' => $this->action,
1530              'log_timestamp' => $dbw->timestamp( $this->timestamp ),
1531              'log_user' => User::idFromName( $this->user_text ),
1532              #'log_user_text' => $this->user_text,
1533              'log_namespace' => $this->getTitle()->getNamespace(),
1534              'log_title' => $this->getTitle()->getDBkey(),
1535              'log_comment' => $this->getComment(),
1536              'log_params' => $this->params
1537          );
1538          $dbw->insert( 'logging', $data, __METHOD__ );
1539      }
1540  
1541      /**
1542       * @return bool
1543       */
1544  	function importUpload() {
1545          # Construct a file
1546          $archiveName = $this->getArchiveName();
1547          if ( $archiveName ) {
1548              wfDebug( __METHOD__ . "Importing archived file as $archiveName\n" );
1549              $file = OldLocalFile::newFromArchiveName( $this->getTitle(),
1550                  RepoGroup::singleton()->getLocalRepo(), $archiveName );
1551          } else {
1552              $file = wfLocalFile( $this->getTitle() );
1553              wfDebug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" );
1554              if ( $file->exists() && $file->getTimestamp() > $this->getTimestamp() ) {
1555                  $archiveName = $file->getTimestamp() . '!' . $file->getName();
1556                  $file = OldLocalFile::newFromArchiveName( $this->getTitle(),
1557                      RepoGroup::singleton()->getLocalRepo(), $archiveName );
1558                  wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" );
1559              }
1560          }
1561          if ( !$file ) {
1562              wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" );
1563              return false;
1564          }
1565  
1566          # Get the file source or download if necessary
1567          $source = $this->getFileSrc();
1568          $flags = $this->isTempSrc() ? File::DELETE_SOURCE : 0;
1569          if ( !$source ) {
1570              $source = $this->downloadSource();
1571              $flags |= File::DELETE_SOURCE;
1572          }
1573          if ( !$source ) {
1574              wfDebug( __METHOD__ . ": Could not fetch remote file.\n" );
1575              return false;
1576          }
1577          $sha1 = $this->getSha1();
1578          if ( $sha1 && ( $sha1 !== sha1_file( $source ) ) ) {
1579              if ( $flags & File::DELETE_SOURCE ) {
1580                  # Broken file; delete it if it is a temporary file
1581                  unlink( $source );
1582              }
1583              wfDebug( __METHOD__ . ": Corrupt file $source.\n" );
1584              return false;
1585          }
1586  
1587          $user = User::newFromName( $this->user_text );
1588  
1589          # Do the actual upload
1590          if ( $archiveName ) {
1591              $status = $file->uploadOld( $source, $archiveName,
1592                  $this->getTimestamp(), $this->getComment(), $user, $flags );
1593          } else {
1594              $status = $file->upload( $source, $this->getComment(), $this->getComment(),
1595                  $flags, false, $this->getTimestamp(), $user );
1596          }
1597  
1598          if ( $status->isGood() ) {
1599              wfDebug( __METHOD__ . ": Successful\n" );
1600              return true;
1601          } else {
1602              wfDebug( __METHOD__ . ': failed: ' . $status->getXml() . "\n" );
1603              return false;
1604          }
1605      }
1606  
1607      /**
1608       * @return bool|string
1609       */
1610  	function downloadSource() {
1611          global $wgEnableUploads;
1612          if ( !$wgEnableUploads ) {
1613              return false;
1614          }
1615  
1616          $tempo = tempnam( wfTempDir(), 'download' );
1617          $f = fopen( $tempo, 'wb' );
1618          if ( !$f ) {
1619              wfDebug( "IMPORT: couldn't write to temp file $tempo\n" );
1620              return false;
1621          }
1622  
1623          // @todo FIXME!
1624          $src = $this->getSrc();
1625          $data = Http::get( $src );
1626          if ( !$data ) {
1627              wfDebug( "IMPORT: couldn't fetch source $src\n" );
1628              fclose( $f );
1629              unlink( $tempo );
1630              return false;
1631          }
1632  
1633          fwrite( $f, $data );
1634          fclose( $f );
1635  
1636          return $tempo;
1637      }
1638  
1639  }
1640  
1641  /**
1642   * @todo document (e.g. one-sentence class description).
1643   * @ingroup SpecialPage
1644   */
1645  class ImportStringSource {
1646  	function __construct( $string ) {
1647          $this->mString = $string;
1648          $this->mRead = false;
1649      }
1650  
1651      /**
1652       * @return bool
1653       */
1654  	function atEnd() {
1655          return $this->mRead;
1656      }
1657  
1658      /**
1659       * @return bool|string
1660       */
1661  	function readChunk() {
1662          if ( $this->atEnd() ) {
1663              return false;
1664          }
1665          $this->mRead = true;
1666          return $this->mString;
1667      }
1668  }
1669  
1670  /**
1671   * @todo document (e.g. one-sentence class description).
1672   * @ingroup SpecialPage
1673   */
1674  class ImportStreamSource {
1675  	function __construct( $handle ) {
1676          $this->mHandle = $handle;
1677      }
1678  
1679      /**
1680       * @return bool
1681       */
1682  	function atEnd() {
1683          return feof( $this->mHandle );
1684      }
1685  
1686      /**
1687       * @return string
1688       */
1689  	function readChunk() {
1690          return fread( $this->mHandle, 32768 );
1691      }
1692  
1693      /**
1694       * @param string $filename
1695       * @return Status
1696       */
1697  	static function newFromFile( $filename ) {
1698          wfSuppressWarnings();
1699          $file = fopen( $filename, 'rt' );
1700          wfRestoreWarnings();
1701          if ( !$file ) {
1702              return Status::newFatal( "importcantopen" );
1703          }
1704          return Status::newGood( new ImportStreamSource( $file ) );
1705      }
1706  
1707      /**
1708       * @param string $fieldname
1709       * @return Status
1710       */
1711  	static function newFromUpload( $fieldname = "xmlimport" ) {
1712          $upload =& $_FILES[$fieldname];
1713  
1714          if ( $upload === null || !$upload['name'] ) {
1715              return Status::newFatal( 'importnofile' );
1716          }
1717          if ( !empty( $upload['error'] ) ) {
1718              switch ( $upload['error'] ) {
1719                  case 1:
1720                      # The uploaded file exceeds the upload_max_filesize directive in php.ini.
1721                      return Status::newFatal( 'importuploaderrorsize' );
1722                  case 2:
1723                      # The uploaded file exceeds the MAX_FILE_SIZE directive that
1724                      # was specified in the HTML form.
1725                      return Status::newFatal( 'importuploaderrorsize' );
1726                  case 3:
1727                      # The uploaded file was only partially uploaded
1728                      return Status::newFatal( 'importuploaderrorpartial' );
1729                  case 6:
1730                      # Missing a temporary folder.
1731                      return Status::newFatal( 'importuploaderrortemp' );
1732                  # case else: # Currently impossible
1733              }
1734  
1735          }
1736          $fname = $upload['tmp_name'];
1737          if ( is_uploaded_file( $fname ) ) {
1738              return ImportStreamSource::newFromFile( $fname );
1739          } else {
1740              return Status::newFatal( 'importnofile' );
1741          }
1742      }
1743  
1744      /**
1745       * @param string $url
1746       * @param string $method
1747       * @return Status
1748       */
1749  	static function newFromURL( $url, $method = 'GET' ) {
1750          wfDebug( __METHOD__ . ": opening $url\n" );
1751          # Use the standard HTTP fetch function; it times out
1752          # quicker and sorts out user-agent problems which might
1753          # otherwise prevent importing from large sites, such
1754          # as the Wikimedia cluster, etc.
1755          $data = Http::request( $method, $url, array( 'followRedirects' => true ) );
1756          if ( $data !== false ) {
1757              $file = tmpfile();
1758              fwrite( $file, $data );
1759              fflush( $file );
1760              fseek( $file, 0 );
1761              return Status::newGood( new ImportStreamSource( $file ) );
1762          } else {
1763              return Status::newFatal( 'importcantopen' );
1764          }
1765      }
1766  
1767      /**
1768       * @param string $interwiki
1769       * @param string $page
1770       * @param bool $history
1771       * @param bool $templates
1772       * @param int $pageLinkDepth
1773       * @return Status
1774       */
1775  	public static function newFromInterwiki( $interwiki, $page, $history = false,
1776          $templates = false, $pageLinkDepth = 0
1777      ) {
1778          if ( $page == '' ) {
1779              return Status::newFatal( 'import-noarticle' );
1780          }
1781          $link = Title::newFromText( "$interwiki:Special:Export/$page" );
1782          if ( is_null( $link ) || !$link->isExternal() ) {
1783              return Status::newFatal( 'importbadinterwiki' );
1784          } else {
1785              $params = array();
1786              if ( $history ) {
1787                  $params['history'] = 1;
1788              }
1789              if ( $templates ) {
1790                  $params['templates'] = 1;
1791              }
1792              if ( $pageLinkDepth ) {
1793                  $params['pagelink-depth'] = $pageLinkDepth;
1794              }
1795              $url = $link->getFullURL( $params );
1796              # For interwikis, use POST to avoid redirects.
1797              return ImportStreamSource::newFromURL( $url, "POST" );
1798          }
1799      }
1800  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1