[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Export.php (source)

   1  <?php
   2  /**
   3   * Base classes for dumps and export
   4   *
   5   * Copyright © 2003, 2005, 2006 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   */
  25  
  26  /**
  27   * @defgroup Dump Dump
  28   */
  29  
  30  /**
  31   * @ingroup SpecialPage Dump
  32   */
  33  class WikiExporter {
  34      /** @var bool Return distinct author list (when not returning full history) */
  35      public $list_authors = false;
  36  
  37      /** @var bool */
  38      public $dumpUploads = false;
  39  
  40      /** @var bool */
  41      public $dumpUploadFileContents = false;
  42  
  43      /** @var string */
  44      public $author_list = "";
  45  
  46      const FULL = 1;
  47      const CURRENT = 2;
  48      const STABLE = 4; // extension defined
  49      const LOGS = 8;
  50      const RANGE = 16;
  51  
  52      const BUFFER = 0;
  53      const STREAM = 1;
  54  
  55      const TEXT = 0;
  56      const STUB = 1;
  57  
  58      /** @var int */
  59      public $buffer;
  60  
  61      /** @var int */
  62      public $text;
  63  
  64      /** @var DumpOutput */
  65      public $sink;
  66  
  67      /**
  68       * Returns the export schema version.
  69       * @return string
  70       */
  71  	public static function schemaVersion() {
  72          return "0.9";
  73      }
  74  
  75      /**
  76       * If using WikiExporter::STREAM to stream a large amount of data,
  77       * provide a database connection which is not managed by
  78       * LoadBalancer to read from: some history blob types will
  79       * make additional queries to pull source data while the
  80       * main query is still running.
  81       *
  82       * @param DatabaseBase $db
  83       * @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
  84       *   WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
  85       *   - offset: non-inclusive offset at which to start the query
  86       *   - limit: maximum number of rows to return
  87       *   - dir: "asc" or "desc" timestamp order
  88       * @param int $buffer One of WikiExporter::BUFFER or WikiExporter::STREAM
  89       * @param int $text One of WikiExporter::TEXT or WikiExporter::STUB
  90       */
  91  	function __construct( $db, $history = WikiExporter::CURRENT,
  92              $buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
  93          $this->db = $db;
  94          $this->history = $history;
  95          $this->buffer = $buffer;
  96          $this->writer = new XmlDumpWriter();
  97          $this->sink = new DumpOutput();
  98          $this->text = $text;
  99      }
 100  
 101      /**
 102       * Set the DumpOutput or DumpFilter object which will receive
 103       * various row objects and XML output for filtering. Filters
 104       * can be chained or used as callbacks.
 105       *
 106       * @param DumpOutput $sink
 107       */
 108  	public function setOutputSink( &$sink ) {
 109          $this->sink =& $sink;
 110      }
 111  
 112  	public function openStream() {
 113          $output = $this->writer->openStream();
 114          $this->sink->writeOpenStream( $output );
 115      }
 116  
 117  	public function closeStream() {
 118          $output = $this->writer->closeStream();
 119          $this->sink->writeCloseStream( $output );
 120      }
 121  
 122      /**
 123       * Dumps a series of page and revision records for all pages
 124       * in the database, either including complete history or only
 125       * the most recent version.
 126       */
 127  	public function allPages() {
 128          $this->dumpFrom( '' );
 129      }
 130  
 131      /**
 132       * Dumps a series of page and revision records for those pages
 133       * in the database falling within the page_id range given.
 134       * @param int $start Inclusive lower limit (this id is included)
 135       * @param int $end Exclusive upper limit (this id is not included)
 136       *   If 0, no upper limit.
 137       */
 138  	public function pagesByRange( $start, $end ) {
 139          $condition = 'page_id >= ' . intval( $start );
 140          if ( $end ) {
 141              $condition .= ' AND page_id < ' . intval( $end );
 142          }
 143          $this->dumpFrom( $condition );
 144      }
 145  
 146      /**
 147       * Dumps a series of page and revision records for those pages
 148       * in the database with revisions falling within the rev_id range given.
 149       * @param int $start Inclusive lower limit (this id is included)
 150       * @param int $end Exclusive upper limit (this id is not included)
 151       *   If 0, no upper limit.
 152       */
 153  	public function revsByRange( $start, $end ) {
 154          $condition = 'rev_id >= ' . intval( $start );
 155          if ( $end ) {
 156              $condition .= ' AND rev_id < ' . intval( $end );
 157          }
 158          $this->dumpFrom( $condition );
 159      }
 160  
 161      /**
 162       * @param Title $title
 163       */
 164  	public function pageByTitle( $title ) {
 165          $this->dumpFrom(
 166              'page_namespace=' . $title->getNamespace() .
 167              ' AND page_title=' . $this->db->addQuotes( $title->getDBkey() ) );
 168      }
 169  
 170      /**
 171       * @param string $name
 172       * @throws MWException
 173       */
 174  	public function pageByName( $name ) {
 175          $title = Title::newFromText( $name );
 176          if ( is_null( $title ) ) {
 177              throw new MWException( "Can't export invalid title" );
 178          } else {
 179              $this->pageByTitle( $title );
 180          }
 181      }
 182  
 183      /**
 184       * @param array $names
 185       */
 186  	public function pagesByName( $names ) {
 187          foreach ( $names as $name ) {
 188              $this->pageByName( $name );
 189          }
 190      }
 191  
 192  	public function allLogs() {
 193          $this->dumpFrom( '' );
 194      }
 195  
 196      /**
 197       * @param int $start
 198       * @param int $end
 199       */
 200  	public function logsByRange( $start, $end ) {
 201          $condition = 'log_id >= ' . intval( $start );
 202          if ( $end ) {
 203              $condition .= ' AND log_id < ' . intval( $end );
 204          }
 205          $this->dumpFrom( $condition );
 206      }
 207  
 208      /**
 209       * Generates the distinct list of authors of an article
 210       * Not called by default (depends on $this->list_authors)
 211       * Can be set by Special:Export when not exporting whole history
 212       *
 213       * @param array $cond
 214       */
 215  	protected function do_list_authors( $cond ) {
 216          wfProfileIn( __METHOD__ );
 217          $this->author_list = "<contributors>";
 218          // rev_deleted
 219  
 220          $res = $this->db->select(
 221              array( 'page', 'revision' ),
 222              array( 'DISTINCT rev_user_text', 'rev_user' ),
 223              array(
 224                  $this->db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0',
 225                  $cond,
 226                  'page_id = rev_id',
 227              ),
 228              __METHOD__
 229          );
 230  
 231          foreach ( $res as $row ) {
 232              $this->author_list .= "<contributor>" .
 233                  "<username>" .
 234                  htmlentities( $row->rev_user_text ) .
 235                  "</username>" .
 236                  "<id>" .
 237                  $row->rev_user .
 238                  "</id>" .
 239                  "</contributor>";
 240          }
 241          $this->author_list .= "</contributors>";
 242          wfProfileOut( __METHOD__ );
 243      }
 244  
 245      /**
 246       * @param string $cond
 247       * @throws MWException
 248       * @throws Exception
 249       */
 250  	protected function dumpFrom( $cond = '' ) {
 251          wfProfileIn( __METHOD__ );
 252          # For logging dumps...
 253          if ( $this->history & self::LOGS ) {
 254              $where = array( 'user_id = log_user' );
 255              # Hide private logs
 256              $hideLogs = LogEventsList::getExcludeClause( $this->db );
 257              if ( $hideLogs ) {
 258                  $where[] = $hideLogs;
 259              }
 260              # Add on any caller specified conditions
 261              if ( $cond ) {
 262                  $where[] = $cond;
 263              }
 264              # Get logging table name for logging.* clause
 265              $logging = $this->db->tableName( 'logging' );
 266  
 267              if ( $this->buffer == WikiExporter::STREAM ) {
 268                  $prev = $this->db->bufferResults( false );
 269              }
 270              $result = null; // Assuring $result is not undefined, if exception occurs early
 271              try {
 272                  $result = $this->db->select( array( 'logging', 'user' ),
 273                      array( "{$logging}.*", 'user_name' ), // grab the user name
 274                      $where,
 275                      __METHOD__,
 276                      array( 'ORDER BY' => 'log_id', 'USE INDEX' => array( 'logging' => 'PRIMARY' ) )
 277                  );
 278                  $this->outputLogStream( $result );
 279                  if ( $this->buffer == WikiExporter::STREAM ) {
 280                      $this->db->bufferResults( $prev );
 281                  }
 282              } catch ( Exception $e ) {
 283                  // Throwing the exception does not reliably free the resultset, and
 284                  // would also leave the connection in unbuffered mode.
 285  
 286                  // Freeing result
 287                  try {
 288                      if ( $result ) {
 289                          $result->free();
 290                      }
 291                  } catch ( Exception $e2 ) {
 292                      // Already in panic mode -> ignoring $e2 as $e has
 293                      // higher priority
 294                  }
 295  
 296                  // Putting database back in previous buffer mode
 297                  try {
 298                      if ( $this->buffer == WikiExporter::STREAM ) {
 299                          $this->db->bufferResults( $prev );
 300                      }
 301                  } catch ( Exception $e2 ) {
 302                      // Already in panic mode -> ignoring $e2 as $e has
 303                      // higher priority
 304                  }
 305  
 306                  // Inform caller about problem
 307                  wfProfileOut( __METHOD__ );
 308                  throw $e;
 309              }
 310          # For page dumps...
 311          } else {
 312              $tables = array( 'page', 'revision' );
 313              $opts = array( 'ORDER BY' => 'page_id ASC' );
 314              $opts['USE INDEX'] = array();
 315              $join = array();
 316              if ( is_array( $this->history ) ) {
 317                  # Time offset/limit for all pages/history...
 318                  $revJoin = 'page_id=rev_page';
 319                  # Set time order
 320                  if ( $this->history['dir'] == 'asc' ) {
 321                      $op = '>';
 322                      $opts['ORDER BY'] = 'rev_timestamp ASC';
 323                  } else {
 324                      $op = '<';
 325                      $opts['ORDER BY'] = 'rev_timestamp DESC';
 326                  }
 327                  # Set offset
 328                  if ( !empty( $this->history['offset'] ) ) {
 329                      $revJoin .= " AND rev_timestamp $op " .
 330                          $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
 331                  }
 332                  $join['revision'] = array( 'INNER JOIN', $revJoin );
 333                  # Set query limit
 334                  if ( !empty( $this->history['limit'] ) ) {
 335                      $opts['LIMIT'] = intval( $this->history['limit'] );
 336                  }
 337              } elseif ( $this->history & WikiExporter::FULL ) {
 338                  # Full history dumps...
 339                  $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
 340              } elseif ( $this->history & WikiExporter::CURRENT ) {
 341                  # Latest revision dumps...
 342                  if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
 343                      $this->do_list_authors( $cond );
 344                  }
 345                  $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
 346              } elseif ( $this->history & WikiExporter::STABLE ) {
 347                  # "Stable" revision dumps...
 348                  # Default JOIN, to be overridden...
 349                  $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
 350                  # One, and only one hook should set this, and return false
 351                  if ( wfRunHooks( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) {
 352                      wfProfileOut( __METHOD__ );
 353                      throw new MWException( __METHOD__ . " given invalid history dump type." );
 354                  }
 355              } elseif ( $this->history & WikiExporter::RANGE ) {
 356                  # Dump of revisions within a specified range
 357                  $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
 358                  $opts['ORDER BY'] = array( 'rev_page ASC', 'rev_id ASC' );
 359              } else {
 360                  # Unknown history specification parameter?
 361                  wfProfileOut( __METHOD__ );
 362                  throw new MWException( __METHOD__ . " given invalid history dump type." );
 363              }
 364              # Query optimization hacks
 365              if ( $cond == '' ) {
 366                  $opts[] = 'STRAIGHT_JOIN';
 367                  $opts['USE INDEX']['page'] = 'PRIMARY';
 368              }
 369              # Build text join options
 370              if ( $this->text != WikiExporter::STUB ) { // 1-pass
 371                  $tables[] = 'text';
 372                  $join['text'] = array( 'INNER JOIN', 'rev_text_id=old_id' );
 373              }
 374  
 375              if ( $this->buffer == WikiExporter::STREAM ) {
 376                  $prev = $this->db->bufferResults( false );
 377              }
 378  
 379              $result = null; // Assuring $result is not undefined, if exception occurs early
 380              try {
 381                  wfRunHooks( 'ModifyExportQuery',
 382                          array( $this->db, &$tables, &$cond, &$opts, &$join ) );
 383  
 384                  # Do the query!
 385                  $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
 386                  # Output dump results
 387                  $this->outputPageStream( $result );
 388  
 389                  if ( $this->buffer == WikiExporter::STREAM ) {
 390                      $this->db->bufferResults( $prev );
 391                  }
 392              } catch ( Exception $e ) {
 393                  // Throwing the exception does not reliably free the resultset, and
 394                  // would also leave the connection in unbuffered mode.
 395  
 396                  // Freeing result
 397                  try {
 398                      if ( $result ) {
 399                          $result->free();
 400                      }
 401                  } catch ( Exception $e2 ) {
 402                      // Already in panic mode -> ignoring $e2 as $e has
 403                      // higher priority
 404                  }
 405  
 406                  // Putting database back in previous buffer mode
 407                  try {
 408                      if ( $this->buffer == WikiExporter::STREAM ) {
 409                          $this->db->bufferResults( $prev );
 410                      }
 411                  } catch ( Exception $e2 ) {
 412                      // Already in panic mode -> ignoring $e2 as $e has
 413                      // higher priority
 414                  }
 415  
 416                  // Inform caller about problem
 417                  throw $e;
 418              }
 419          }
 420          wfProfileOut( __METHOD__ );
 421      }
 422  
 423      /**
 424       * Runs through a query result set dumping page and revision records.
 425       * The result set should be sorted/grouped by page to avoid duplicate
 426       * page records in the output.
 427       *
 428       * Should be safe for
 429       * streaming (non-buffered) queries, as long as it was made on a
 430       * separate database connection not managed by LoadBalancer; some
 431       * blob storage types will make queries to pull source data.
 432       *
 433       * @param ResultWrapper $resultset
 434       */
 435  	protected function outputPageStream( $resultset ) {
 436          $last = null;
 437          foreach ( $resultset as $row ) {
 438              if ( $last === null ||
 439                  $last->page_namespace != $row->page_namespace ||
 440                  $last->page_title != $row->page_title ) {
 441                  if ( $last !== null ) {
 442                      $output = '';
 443                      if ( $this->dumpUploads ) {
 444                          $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
 445                      }
 446                      $output .= $this->writer->closePage();
 447                      $this->sink->writeClosePage( $output );
 448                  }
 449                  $output = $this->writer->openPage( $row );
 450                  $this->sink->writeOpenPage( $row, $output );
 451                  $last = $row;
 452              }
 453              $output = $this->writer->writeRevision( $row );
 454              $this->sink->writeRevision( $row, $output );
 455          }
 456          if ( $last !== null ) {
 457              $output = '';
 458              if ( $this->dumpUploads ) {
 459                  $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
 460              }
 461              $output .= $this->author_list;
 462              $output .= $this->writer->closePage();
 463              $this->sink->writeClosePage( $output );
 464          }
 465      }
 466  
 467      /**
 468       * @param ResultWrapper $resultset
 469       */
 470  	protected function outputLogStream( $resultset ) {
 471          foreach ( $resultset as $row ) {
 472              $output = $this->writer->writeLogItem( $row );
 473              $this->sink->writeLogItem( $row, $output );
 474          }
 475      }
 476  }
 477  
 478  /**
 479   * @ingroup Dump
 480   */
 481  class XmlDumpWriter {
 482      /**
 483       * Returns the export schema version.
 484       * @deprecated since 1.20; use WikiExporter::schemaVersion() instead
 485       * @return string
 486       */
 487  	function schemaVersion() {
 488          wfDeprecated( __METHOD__, '1.20' );
 489          return WikiExporter::schemaVersion();
 490      }
 491  
 492      /**
 493       * Opens the XML output stream's root "<mediawiki>" element.
 494       * This does not include an xml directive, so is safe to include
 495       * as a subelement in a larger XML stream. Namespace and XML Schema
 496       * references are included.
 497       *
 498       * Output will be encoded in UTF-8.
 499       *
 500       * @return string
 501       */
 502  	function openStream() {
 503          global $wgLanguageCode;
 504          $ver = WikiExporter::schemaVersion();
 505          return Xml::element( 'mediawiki', array(
 506              'xmlns'              => "http://www.mediawiki.org/xml/export-$ver/",
 507              'xmlns:xsi'          => "http://www.w3.org/2001/XMLSchema-instance",
 508              /*
 509               * When a new version of the schema is created, it needs staging on mediawiki.org.
 510               * This requires a change in the operations/mediawiki-config git repo.
 511               *
 512               * Create a changeset like https://gerrit.wikimedia.org/r/#/c/149643/ in which
 513               * you copy in the new xsd file.
 514               *
 515               * After it is reviewed, merged and deployed (sync-docroot), the index.html needs purging.
 516               * echo "http://www.mediawiki.org/xml/index.html" | mwscript purgeList.php --wiki=aawiki
 517               */
 518              'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
 519                  "http://www.mediawiki.org/xml/export-$ver.xsd",
 520              'version'            => $ver,
 521              'xml:lang'           => $wgLanguageCode ),
 522              null ) .
 523              "\n" .
 524              $this->siteInfo();
 525      }
 526  
 527      /**
 528       * @return string
 529       */
 530  	function siteInfo() {
 531          $info = array(
 532              $this->sitename(),
 533              $this->dbname(),
 534              $this->homelink(),
 535              $this->generator(),
 536              $this->caseSetting(),
 537              $this->namespaces() );
 538          return "  <siteinfo>\n    " .
 539              implode( "\n    ", $info ) .
 540              "\n  </siteinfo>\n";
 541      }
 542  
 543      /**
 544       * @return string
 545       */
 546  	function sitename() {
 547          global $wgSitename;
 548          return Xml::element( 'sitename', array(), $wgSitename );
 549      }
 550  
 551      /**
 552       * @return string
 553       */
 554  	function dbname() {
 555          global $wgDBname;
 556          return Xml::element( 'dbname', array(), $wgDBname );
 557      }
 558  
 559      /**
 560       * @return string
 561       */
 562  	function generator() {
 563          global $wgVersion;
 564          return Xml::element( 'generator', array(), "MediaWiki $wgVersion" );
 565      }
 566  
 567      /**
 568       * @return string
 569       */
 570  	function homelink() {
 571          return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalURL() );
 572      }
 573  
 574      /**
 575       * @return string
 576       */
 577  	function caseSetting() {
 578          global $wgCapitalLinks;
 579          // "case-insensitive" option is reserved for future
 580          $sensitivity = $wgCapitalLinks ? 'first-letter' : 'case-sensitive';
 581          return Xml::element( 'case', array(), $sensitivity );
 582      }
 583  
 584      /**
 585       * @return string
 586       */
 587  	function namespaces() {
 588          global $wgContLang;
 589          $spaces = "<namespaces>\n";
 590          foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
 591              $spaces .= '      ' .
 592                  Xml::element( 'namespace',
 593                      array(
 594                          'key' => $ns,
 595                          'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
 596                      ), $title ) . "\n";
 597          }
 598          $spaces .= "    </namespaces>";
 599          return $spaces;
 600      }
 601  
 602      /**
 603       * Closes the output stream with the closing root element.
 604       * Call when finished dumping things.
 605       *
 606       * @return string
 607       */
 608  	function closeStream() {
 609          return "</mediawiki>\n";
 610      }
 611  
 612      /**
 613       * Opens a "<page>" section on the output stream, with data
 614       * from the given database row.
 615       *
 616       * @param object $row
 617       * @return string
 618       */
 619  	public function openPage( $row ) {
 620          $out = "  <page>\n";
 621          $title = Title::makeTitle( $row->page_namespace, $row->page_title );
 622          $out .= '    ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
 623          $out .= '    ' . Xml::element( 'ns', array(), strval( $row->page_namespace ) ) . "\n";
 624          $out .= '    ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n";
 625          if ( $row->page_is_redirect ) {
 626              $page = WikiPage::factory( $title );
 627              $redirect = $page->getRedirectTarget();
 628              if ( $redirect instanceof Title && $redirect->isValidRedirectTarget() ) {
 629                  $out .= '    ';
 630                  $out .= Xml::element( 'redirect', array( 'title' => self::canonicalTitle( $redirect ) ) );
 631                  $out .= "\n";
 632              }
 633          }
 634  
 635          if ( $row->page_restrictions != '' ) {
 636              $out .= '    ' . Xml::element( 'restrictions', array(),
 637                  strval( $row->page_restrictions ) ) . "\n";
 638          }
 639  
 640          wfRunHooks( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) );
 641  
 642          return $out;
 643      }
 644  
 645      /**
 646       * Closes a "<page>" section on the output stream.
 647       *
 648       * @access private
 649       * @return string
 650       */
 651  	function closePage() {
 652          return "  </page>\n";
 653      }
 654  
 655      /**
 656       * Dumps a "<revision>" section on the output stream, with
 657       * data filled in from the given database row.
 658       *
 659       * @param object $row
 660       * @return string
 661       * @access private
 662       */
 663  	function writeRevision( $row ) {
 664          wfProfileIn( __METHOD__ );
 665  
 666          $out = "    <revision>\n";
 667          $out .= "      " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
 668          if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
 669              $out .= "      " . Xml::element( 'parentid', null, strval( $row->rev_parent_id ) ) . "\n";
 670          }
 671  
 672          $out .= $this->writeTimestamp( $row->rev_timestamp );
 673  
 674          if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_USER ) ) {
 675              $out .= "      " . Xml::element( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
 676          } else {
 677              $out .= $this->writeContributor( $row->rev_user, $row->rev_user_text );
 678          }
 679  
 680          if ( isset( $row->rev_minor_edit ) && $row->rev_minor_edit ) {
 681              $out .= "      <minor/>\n";
 682          }
 683          if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_COMMENT ) ) {
 684              $out .= "      " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
 685          } elseif ( $row->rev_comment != '' ) {
 686              $out .= "      " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
 687          }
 688  
 689          if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
 690              $content_model = strval( $row->rev_content_model );
 691          } else {
 692              // probably using $wgContentHandlerUseDB = false;
 693              $title = Title::makeTitle( $row->page_namespace, $row->page_title );
 694              $content_model = ContentHandler::getDefaultModelFor( $title );
 695          }
 696  
 697          $content_handler = ContentHandler::getForModelID( $content_model );
 698  
 699          if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
 700              $content_format = strval( $row->rev_content_format );
 701          } else {
 702              // probably using $wgContentHandlerUseDB = false;
 703              $content_format = $content_handler->getDefaultFormat();
 704          }
 705  
 706          $text = '';
 707          if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
 708              $out .= "      " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
 709          } elseif ( isset( $row->old_text ) ) {
 710              // Raw text from the database may have invalid chars
 711              $text = strval( Revision::getRevisionText( $row ) );
 712              $text = $content_handler->exportTransform( $text, $content_format );
 713              $out .= "      " . Xml::elementClean( 'text',
 714                  array( 'xml:space' => 'preserve', 'bytes' => intval( $row->rev_len ) ),
 715                  strval( $text ) ) . "\n";
 716          } else {
 717              // Stub output
 718              $out .= "      " . Xml::element( 'text',
 719                  array( 'id' => $row->rev_text_id, 'bytes' => intval( $row->rev_len ) ),
 720                  "" ) . "\n";
 721          }
 722  
 723          if ( isset( $row->rev_sha1 )
 724              && $row->rev_sha1
 725              && !( $row->rev_deleted & Revision::DELETED_TEXT )
 726          ) {
 727              $out .= "      " . Xml::element( 'sha1', null, strval( $row->rev_sha1 ) ) . "\n";
 728          } else {
 729              $out .= "      <sha1/>\n";
 730          }
 731  
 732          $out .= "      " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
 733          $out .= "      " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
 734  
 735          wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
 736  
 737          $out .= "    </revision>\n";
 738  
 739          wfProfileOut( __METHOD__ );
 740          return $out;
 741      }
 742  
 743      /**
 744       * Dumps a "<logitem>" section on the output stream, with
 745       * data filled in from the given database row.
 746       *
 747       * @param object $row
 748       * @return string
 749       * @access private
 750       */
 751  	function writeLogItem( $row ) {
 752          wfProfileIn( __METHOD__ );
 753  
 754          $out = "  <logitem>\n";
 755          $out .= "    " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
 756  
 757          $out .= $this->writeTimestamp( $row->log_timestamp, "    " );
 758  
 759          if ( $row->log_deleted & LogPage::DELETED_USER ) {
 760              $out .= "    " . Xml::element( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
 761          } else {
 762              $out .= $this->writeContributor( $row->log_user, $row->user_name, "    " );
 763          }
 764  
 765          if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
 766              $out .= "    " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
 767          } elseif ( $row->log_comment != '' ) {
 768              $out .= "    " . Xml::elementClean( 'comment', null, strval( $row->log_comment ) ) . "\n";
 769          }
 770  
 771          $out .= "    " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
 772          $out .= "    " . Xml::element( 'action', null, strval( $row->log_action ) ) . "\n";
 773  
 774          if ( $row->log_deleted & LogPage::DELETED_ACTION ) {
 775              $out .= "    " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
 776          } else {
 777              $title = Title::makeTitle( $row->log_namespace, $row->log_title );
 778              $out .= "    " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
 779              $out .= "    " . Xml::elementClean( 'params',
 780                  array( 'xml:space' => 'preserve' ),
 781                  strval( $row->log_params ) ) . "\n";
 782          }
 783  
 784          $out .= "  </logitem>\n";
 785  
 786          wfProfileOut( __METHOD__ );
 787          return $out;
 788      }
 789  
 790      /**
 791       * @param string $timestamp
 792       * @param string $indent Default to six spaces
 793       * @return string
 794       */
 795  	function writeTimestamp( $timestamp, $indent = "      " ) {
 796          $ts = wfTimestamp( TS_ISO_8601, $timestamp );
 797          return $indent . Xml::element( 'timestamp', null, $ts ) . "\n";
 798      }
 799  
 800      /**
 801       * @param int $id
 802       * @param string $text
 803       * @param string $indent Default to six spaces
 804       * @return string
 805       */
 806  	function writeContributor( $id, $text, $indent = "      " ) {
 807          $out = $indent . "<contributor>\n";
 808          if ( $id || !IP::isValid( $text ) ) {
 809              $out .= $indent . "  " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
 810              $out .= $indent . "  " . Xml::element( 'id', null, strval( $id ) ) . "\n";
 811          } else {
 812              $out .= $indent . "  " . Xml::elementClean( 'ip', null, strval( $text ) ) . "\n";
 813          }
 814          $out .= $indent . "</contributor>\n";
 815          return $out;
 816      }
 817  
 818      /**
 819       * Warning! This data is potentially inconsistent. :(
 820       * @param object $row
 821       * @param bool $dumpContents
 822       * @return string
 823       */
 824  	function writeUploads( $row, $dumpContents = false ) {
 825          if ( $row->page_namespace == NS_FILE ) {
 826              $img = wfLocalFile( $row->page_title );
 827              if ( $img && $img->exists() ) {
 828                  $out = '';
 829                  foreach ( array_reverse( $img->getHistory() ) as $ver ) {
 830                      $out .= $this->writeUpload( $ver, $dumpContents );
 831                  }
 832                  $out .= $this->writeUpload( $img, $dumpContents );
 833                  return $out;
 834              }
 835          }
 836          return '';
 837      }
 838  
 839      /**
 840       * @param File $file
 841       * @param bool $dumpContents
 842       * @return string
 843       */
 844  	function writeUpload( $file, $dumpContents = false ) {
 845          if ( $file->isOld() ) {
 846              $archiveName = "      " .
 847                  Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
 848          } else {
 849              $archiveName = '';
 850          }
 851          if ( $dumpContents ) {
 852              $be = $file->getRepo()->getBackend();
 853              # Dump file as base64
 854              # Uses only XML-safe characters, so does not need escaping
 855              # @todo Too bad this loads the contents into memory (script might swap)
 856              $contents = '      <contents encoding="base64">' .
 857                  chunk_split( base64_encode(
 858                      $be->getFileContents( array( 'src' => $file->getPath() ) ) ) ) .
 859                  "      </contents>\n";
 860          } else {
 861              $contents = '';
 862          }
 863          if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
 864              $comment = Xml::element( 'comment', array( 'deleted' => 'deleted' ) );
 865          } else {
 866              $comment = Xml::elementClean( 'comment', null, $file->getDescription() );
 867          }
 868          return "    <upload>\n" .
 869              $this->writeTimestamp( $file->getTimestamp() ) .
 870              $this->writeContributor( $file->getUser( 'id' ), $file->getUser( 'text' ) ) .
 871              "      " . $comment . "\n" .
 872              "      " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
 873              $archiveName .
 874              "      " . Xml::element( 'src', null, $file->getCanonicalURL() ) . "\n" .
 875              "      " . Xml::element( 'size', null, $file->getSize() ) . "\n" .
 876              "      " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
 877              "      " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
 878              $contents .
 879              "    </upload>\n";
 880      }
 881  
 882      /**
 883       * Return prefixed text form of title, but using the content language's
 884       * canonical namespace. This skips any special-casing such as gendered
 885       * user namespaces -- which while useful, are not yet listed in the
 886       * XML "<siteinfo>" data so are unsafe in export.
 887       *
 888       * @param Title $title
 889       * @return string
 890       * @since 1.18
 891       */
 892  	public static function canonicalTitle( Title $title ) {
 893          if ( $title->isExternal() ) {
 894              return $title->getPrefixedText();
 895          }
 896  
 897          global $wgContLang;
 898          $prefix = str_replace( '_', ' ', $wgContLang->getNsText( $title->getNamespace() ) );
 899  
 900          if ( $prefix !== '' ) {
 901              $prefix .= ':';
 902          }
 903  
 904          return $prefix . $title->getText();
 905      }
 906  }
 907  
 908  /**
 909   * Base class for output stream; prints to stdout or buffer or wherever.
 910   * @ingroup Dump
 911   */
 912  class DumpOutput {
 913  
 914      /**
 915       * @param string $string
 916       */
 917  	function writeOpenStream( $string ) {
 918          $this->write( $string );
 919      }
 920  
 921      /**
 922       * @param string $string
 923       */
 924  	function writeCloseStream( $string ) {
 925          $this->write( $string );
 926      }
 927  
 928      /**
 929       * @param object $page
 930       * @param string $string
 931       */
 932  	function writeOpenPage( $page, $string ) {
 933          $this->write( $string );
 934      }
 935  
 936      /**
 937       * @param string $string
 938       */
 939  	function writeClosePage( $string ) {
 940          $this->write( $string );
 941      }
 942  
 943      /**
 944       * @param object $rev
 945       * @param string $string
 946       */
 947  	function writeRevision( $rev, $string ) {
 948          $this->write( $string );
 949      }
 950  
 951      /**
 952       * @param object $rev
 953       * @param string $string
 954       */
 955  	function writeLogItem( $rev, $string ) {
 956          $this->write( $string );
 957      }
 958  
 959      /**
 960       * Override to write to a different stream type.
 961       * @param string $string
 962       * @return bool
 963       */
 964  	function write( $string ) {
 965          print $string;
 966      }
 967  
 968      /**
 969       * Close the old file, move it to a specified name,
 970       * and reopen new file with the old name. Use this
 971       * for writing out a file in multiple pieces
 972       * at specified checkpoints (e.g. every n hours).
 973       * @param string|array $newname File name. May be a string or an array with one element
 974       */
 975  	function closeRenameAndReopen( $newname ) {
 976      }
 977  
 978      /**
 979       * Close the old file, and move it to a specified name.
 980       * Use this for the last piece of a file written out
 981       * at specified checkpoints (e.g. every n hours).
 982       * @param string|array $newname File name. May be a string or an array with one element
 983       * @param bool $open If true, a new file with the old filename will be opened
 984       *   again for writing (default: false)
 985       */
 986  	function closeAndRename( $newname, $open = false ) {
 987      }
 988  
 989      /**
 990       * Returns the name of the file or files which are
 991       * being written to, if there are any.
 992       * @return null
 993       */
 994  	function getFilenames() {
 995          return null;
 996      }
 997  }
 998  
 999  /**
1000   * Stream outputter to send data to a file.
1001   * @ingroup Dump
1002   */
1003  class DumpFileOutput extends DumpOutput {
1004      protected $handle = false, $filename;
1005  
1006      /**
1007       * @param string $file
1008       */
1009  	function __construct( $file ) {
1010          $this->handle = fopen( $file, "wt" );
1011          $this->filename = $file;
1012      }
1013  
1014      /**
1015       * @param string $string
1016       */
1017  	function writeCloseStream( $string ) {
1018          parent::writeCloseStream( $string );
1019          if ( $this->handle ) {
1020              fclose( $this->handle );
1021              $this->handle = false;
1022          }
1023      }
1024  
1025      /**
1026       * @param string $string
1027       */
1028  	function write( $string ) {
1029          fputs( $this->handle, $string );
1030      }
1031  
1032      /**
1033       * @param string $newname
1034       */
1035  	function closeRenameAndReopen( $newname ) {
1036          $this->closeAndRename( $newname, true );
1037      }
1038  
1039      /**
1040       * @param string $newname
1041       * @throws MWException
1042       */
1043  	function renameOrException( $newname ) {
1044              if ( !rename( $this->filename, $newname ) ) {
1045                  throw new MWException( __METHOD__ . ": rename of file {$this->filename} to $newname failed\n" );
1046              }
1047      }
1048  
1049      /**
1050       * @param array $newname
1051       * @return string
1052       * @throws MWException
1053       */
1054  	function checkRenameArgCount( $newname ) {
1055          if ( is_array( $newname ) ) {
1056              if ( count( $newname ) > 1 ) {
1057                  throw new MWException( __METHOD__ . ": passed multiple arguments for rename of single file\n" );
1058              } else {
1059                  $newname = $newname[0];
1060              }
1061          }
1062          return $newname;
1063      }
1064  
1065      /**
1066       * @param string $newname
1067       * @param bool $open
1068       */
1069  	function closeAndRename( $newname, $open = false ) {
1070          $newname = $this->checkRenameArgCount( $newname );
1071          if ( $newname ) {
1072              if ( $this->handle ) {
1073                  fclose( $this->handle );
1074                  $this->handle = false;
1075              }
1076              $this->renameOrException( $newname );
1077              if ( $open ) {
1078                  $this->handle = fopen( $this->filename, "wt" );
1079              }
1080          }
1081      }
1082  
1083      /**
1084       * @return string|null
1085       */
1086  	function getFilenames() {
1087          return $this->filename;
1088      }
1089  }
1090  
1091  /**
1092   * Stream outputter to send data to a file via some filter program.
1093   * Even if compression is available in a library, using a separate
1094   * program can allow us to make use of a multi-processor system.
1095   * @ingroup Dump
1096   */
1097  class DumpPipeOutput extends DumpFileOutput {
1098      protected $command, $filename;
1099      protected $procOpenResource = false;
1100  
1101      /**
1102       * @param string $command
1103       * @param string $file
1104       */
1105  	function __construct( $command, $file = null ) {
1106          if ( !is_null( $file ) ) {
1107              $command .= " > " . wfEscapeShellArg( $file );
1108          }
1109  
1110          $this->startCommand( $command );
1111          $this->command = $command;
1112          $this->filename = $file;
1113      }
1114  
1115      /**
1116       * @param string $string
1117       */
1118  	function writeCloseStream( $string ) {
1119          parent::writeCloseStream( $string );
1120          if ( $this->procOpenResource ) {
1121              proc_close( $this->procOpenResource );
1122              $this->procOpenResource = false;
1123          }
1124      }
1125  
1126      /**
1127       * @param string $command
1128       */
1129  	function startCommand( $command ) {
1130          $spec = array(
1131              0 => array( "pipe", "r" ),
1132          );
1133          $pipes = array();
1134          $this->procOpenResource = proc_open( $command, $spec, $pipes );
1135          $this->handle = $pipes[0];
1136      }
1137  
1138      /**
1139       * @param string $newname
1140       */
1141  	function closeRenameAndReopen( $newname ) {
1142          $this->closeAndRename( $newname, true );
1143      }
1144  
1145      /**
1146       * @param string $newname
1147       * @param bool $open
1148       */
1149  	function closeAndRename( $newname, $open = false ) {
1150          $newname = $this->checkRenameArgCount( $newname );
1151          if ( $newname ) {
1152              if ( $this->handle ) {
1153                  fclose( $this->handle );
1154                  $this->handle = false;
1155              }
1156              if ( $this->procOpenResource ) {
1157                  proc_close( $this->procOpenResource );
1158                  $this->procOpenResource = false;
1159              }
1160              $this->renameOrException( $newname );
1161              if ( $open ) {
1162                  $command = $this->command;
1163                  $command .= " > " . wfEscapeShellArg( $this->filename );
1164                  $this->startCommand( $command );
1165              }
1166          }
1167      }
1168  }
1169  
1170  /**
1171   * Sends dump output via the gzip compressor.
1172   * @ingroup Dump
1173   */
1174  class DumpGZipOutput extends DumpPipeOutput {
1175      /**
1176       * @param string $file
1177       */
1178  	function __construct( $file ) {
1179          parent::__construct( "gzip", $file );
1180      }
1181  }
1182  
1183  /**
1184   * Sends dump output via the bgzip2 compressor.
1185   * @ingroup Dump
1186   */
1187  class DumpBZip2Output extends DumpPipeOutput {
1188      /**
1189       * @param string $file
1190       */
1191  	function __construct( $file ) {
1192          parent::__construct( "bzip2", $file );
1193      }
1194  }
1195  
1196  /**
1197   * Sends dump output via the p7zip compressor.
1198   * @ingroup Dump
1199   */
1200  class Dump7ZipOutput extends DumpPipeOutput {
1201      /**
1202       * @param string $file
1203       */
1204  	function __construct( $file ) {
1205          $command = $this->setup7zCommand( $file );
1206          parent::__construct( $command );
1207          $this->filename = $file;
1208      }
1209  
1210      /**
1211       * @param string $file
1212       * @return string
1213       */
1214  	function setup7zCommand( $file ) {
1215          $command = "7za a -bd -si " . wfEscapeShellArg( $file );
1216          // Suppress annoying useless crap from p7zip
1217          // Unfortunately this could suppress real error messages too
1218          $command .= ' >' . wfGetNull() . ' 2>&1';
1219          return $command;
1220      }
1221  
1222      /**
1223       * @param string $newname
1224       * @param bool $open
1225       */
1226  	function closeAndRename( $newname, $open = false ) {
1227          $newname = $this->checkRenameArgCount( $newname );
1228          if ( $newname ) {
1229              fclose( $this->handle );
1230              proc_close( $this->procOpenResource );
1231              $this->renameOrException( $newname );
1232              if ( $open ) {
1233                  $command = $this->setup7zCommand( $this->filename );
1234                  $this->startCommand( $command );
1235              }
1236          }
1237      }
1238  }
1239  
1240  /**
1241   * Dump output filter class.
1242   * This just does output filtering and streaming; XML formatting is done
1243   * higher up, so be careful in what you do.
1244   * @ingroup Dump
1245   */
1246  class DumpFilter {
1247      /**
1248       * @var DumpOutput
1249       * FIXME will need to be made protected whenever legacy code
1250       * is updated.
1251       */
1252      public $sink;
1253  
1254      /**
1255       * @var bool
1256       */
1257      protected $sendingThisPage;
1258  
1259      /**
1260       * @param DumpOutput $sink
1261       */
1262  	function __construct( &$sink ) {
1263          $this->sink =& $sink;
1264      }
1265  
1266      /**
1267       * @param string $string
1268       */
1269  	function writeOpenStream( $string ) {
1270          $this->sink->writeOpenStream( $string );
1271      }
1272  
1273      /**
1274       * @param string $string
1275       */
1276  	function writeCloseStream( $string ) {
1277          $this->sink->writeCloseStream( $string );
1278      }
1279  
1280      /**
1281       * @param object $page
1282       * @param string $string
1283       */
1284  	function writeOpenPage( $page, $string ) {
1285          $this->sendingThisPage = $this->pass( $page, $string );
1286          if ( $this->sendingThisPage ) {
1287              $this->sink->writeOpenPage( $page, $string );
1288          }
1289      }
1290  
1291      /**
1292       * @param string $string
1293       */
1294  	function writeClosePage( $string ) {
1295          if ( $this->sendingThisPage ) {
1296              $this->sink->writeClosePage( $string );
1297              $this->sendingThisPage = false;
1298          }
1299      }
1300  
1301      /**
1302       * @param object $rev
1303       * @param string $string
1304       */
1305  	function writeRevision( $rev, $string ) {
1306          if ( $this->sendingThisPage ) {
1307              $this->sink->writeRevision( $rev, $string );
1308          }
1309      }
1310  
1311      /**
1312       * @param object $rev
1313       * @param string $string
1314       */
1315  	function writeLogItem( $rev, $string ) {
1316          $this->sink->writeRevision( $rev, $string );
1317      }
1318  
1319      /**
1320       * @param string $newname
1321       */
1322  	function closeRenameAndReopen( $newname ) {
1323          $this->sink->closeRenameAndReopen( $newname );
1324      }
1325  
1326      /**
1327       * @param string $newname
1328       * @param bool $open
1329       */
1330  	function closeAndRename( $newname, $open = false ) {
1331          $this->sink->closeAndRename( $newname, $open );
1332      }
1333  
1334      /**
1335       * @return array
1336       */
1337  	function getFilenames() {
1338          return $this->sink->getFilenames();
1339      }
1340  
1341      /**
1342       * Override for page-based filter types.
1343       * @param object $page
1344       * @return bool
1345       */
1346  	function pass( $page ) {
1347          return true;
1348      }
1349  }
1350  
1351  /**
1352   * Simple dump output filter to exclude all talk pages.
1353   * @ingroup Dump
1354   */
1355  class DumpNotalkFilter extends DumpFilter {
1356      /**
1357       * @param object $page
1358       * @return bool
1359       */
1360  	function pass( $page ) {
1361          return !MWNamespace::isTalk( $page->page_namespace );
1362      }
1363  }
1364  
1365  /**
1366   * Dump output filter to include or exclude pages in a given set of namespaces.
1367   * @ingroup Dump
1368   */
1369  class DumpNamespaceFilter extends DumpFilter {
1370      /** @var bool */
1371      public $invert = false;
1372  
1373      /** @var array */
1374      public $namespaces = array();
1375  
1376      /**
1377       * @param DumpOutput $sink
1378       * @param array $param
1379       * @throws MWException
1380       */
1381  	function __construct( &$sink, $param ) {
1382          parent::__construct( $sink );
1383  
1384          $constants = array(
1385              "NS_MAIN"           => NS_MAIN,
1386              "NS_TALK"           => NS_TALK,
1387              "NS_USER"           => NS_USER,
1388              "NS_USER_TALK"      => NS_USER_TALK,
1389              "NS_PROJECT"        => NS_PROJECT,
1390              "NS_PROJECT_TALK"   => NS_PROJECT_TALK,
1391              "NS_FILE"           => NS_FILE,
1392              "NS_FILE_TALK"      => NS_FILE_TALK,
1393              "NS_IMAGE"          => NS_IMAGE, // NS_IMAGE is an alias for NS_FILE
1394              "NS_IMAGE_TALK"     => NS_IMAGE_TALK,
1395              "NS_MEDIAWIKI"      => NS_MEDIAWIKI,
1396              "NS_MEDIAWIKI_TALK" => NS_MEDIAWIKI_TALK,
1397              "NS_TEMPLATE"       => NS_TEMPLATE,
1398              "NS_TEMPLATE_TALK"  => NS_TEMPLATE_TALK,
1399              "NS_HELP"           => NS_HELP,
1400              "NS_HELP_TALK"      => NS_HELP_TALK,
1401              "NS_CATEGORY"       => NS_CATEGORY,
1402              "NS_CATEGORY_TALK"  => NS_CATEGORY_TALK );
1403  
1404          if ( $param { 0 } == '!' ) {
1405              $this->invert = true;
1406              $param = substr( $param, 1 );
1407          }
1408  
1409          foreach ( explode( ',', $param ) as $key ) {
1410              $key = trim( $key );
1411              if ( isset( $constants[$key] ) ) {
1412                  $ns = $constants[$key];
1413                  $this->namespaces[$ns] = true;
1414              } elseif ( is_numeric( $key ) ) {
1415                  $ns = intval( $key );
1416                  $this->namespaces[$ns] = true;
1417              } else {
1418                  throw new MWException( "Unrecognized namespace key '$key'\n" );
1419              }
1420          }
1421      }
1422  
1423      /**
1424       * @param object $page
1425       * @return bool
1426       */
1427  	function pass( $page ) {
1428          $match = isset( $this->namespaces[$page->page_namespace] );
1429          return $this->invert xor $match;
1430      }
1431  }
1432  
1433  /**
1434   * Dump output filter to include only the last revision in each page sequence.
1435   * @ingroup Dump
1436   */
1437  class DumpLatestFilter extends DumpFilter {
1438      public $page;
1439  
1440      public $pageString;
1441  
1442      public $rev;
1443  
1444      public $revString;
1445  
1446      /**
1447       * @param object $page
1448       * @param string $string
1449       */
1450  	function writeOpenPage( $page, $string ) {
1451          $this->page = $page;
1452          $this->pageString = $string;
1453      }
1454  
1455      /**
1456       * @param string $string
1457       */
1458  	function writeClosePage( $string ) {
1459          if ( $this->rev ) {
1460              $this->sink->writeOpenPage( $this->page, $this->pageString );
1461              $this->sink->writeRevision( $this->rev, $this->revString );
1462              $this->sink->writeClosePage( $string );
1463          }
1464          $this->rev = null;
1465          $this->revString = null;
1466          $this->page = null;
1467          $this->pageString = null;
1468      }
1469  
1470      /**
1471       * @param object $rev
1472       * @param string $string
1473       */
1474  	function writeRevision( $rev, $string ) {
1475          if ( $rev->rev_id == $this->page->page_latest ) {
1476              $this->rev = $rev;
1477              $this->revString = $string;
1478          }
1479      }
1480  }
1481  
1482  /**
1483   * Base class for output stream; prints to stdout or buffer or wherever.
1484   * @ingroup Dump
1485   */
1486  class DumpMultiWriter {
1487  
1488      /**
1489       * @param array $sinks
1490       */
1491  	function __construct( $sinks ) {
1492          $this->sinks = $sinks;
1493          $this->count = count( $sinks );
1494      }
1495  
1496      /**
1497       * @param string $string
1498       */
1499  	function writeOpenStream( $string ) {
1500          for ( $i = 0; $i < $this->count; $i++ ) {
1501              $this->sinks[$i]->writeOpenStream( $string );
1502          }
1503      }
1504  
1505      /**
1506       * @param string $string
1507       */
1508  	function writeCloseStream( $string ) {
1509          for ( $i = 0; $i < $this->count; $i++ ) {
1510              $this->sinks[$i]->writeCloseStream( $string );
1511          }
1512      }
1513  
1514      /**
1515       * @param object $page
1516       * @param string $string
1517       */
1518  	function writeOpenPage( $page, $string ) {
1519          for ( $i = 0; $i < $this->count; $i++ ) {
1520              $this->sinks[$i]->writeOpenPage( $page, $string );
1521          }
1522      }
1523  
1524      /**
1525       * @param string $string
1526       */
1527  	function writeClosePage( $string ) {
1528          for ( $i = 0; $i < $this->count; $i++ ) {
1529              $this->sinks[$i]->writeClosePage( $string );
1530          }
1531      }
1532  
1533      /**
1534       * @param object $rev
1535       * @param string $string
1536       */
1537  	function writeRevision( $rev, $string ) {
1538          for ( $i = 0; $i < $this->count; $i++ ) {
1539              $this->sinks[$i]->writeRevision( $rev, $string );
1540          }
1541      }
1542  
1543      /**
1544       * @param array $newnames
1545       */
1546  	function closeRenameAndReopen( $newnames ) {
1547          $this->closeAndRename( $newnames, true );
1548      }
1549  
1550      /**
1551       * @param array $newnames
1552       * @param bool $open
1553       */
1554  	function closeAndRename( $newnames, $open = false ) {
1555          for ( $i = 0; $i < $this->count; $i++ ) {
1556              $this->sinks[$i]->closeAndRename( $newnames[$i], $open );
1557          }
1558      }
1559  
1560      /**
1561       * @return array
1562       */
1563  	function getFilenames() {
1564          $filenames = array();
1565          for ( $i = 0; $i < $this->count; $i++ ) {
1566              $filenames[] = $this->sinks[$i]->getFilenames();
1567          }
1568          return $filenames;
1569      }
1570  }


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