[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |