29use UnexpectedValueException;
83 private $revisionStore;
92 private $commentStore;
128 $this->commentStore = $commentStore;
132 self::schemaVersion(),
139 $this->hookRunner =
new HookRunner( $hookContainer );
140 $this->revisionStore = $revisionStore;
141 $this->titleParser = $titleParser;
150 $this->writer =
new XmlDumpWriter( $this->text, $schemaVersion );
161 $this->sink =&
$sink;
165 $output = $this->writer->openStream();
166 $this->sink->writeOpenStream( $output );
170 $output = $this->writer->closeStream();
171 $this->sink->writeCloseStream( $output );
193 $condition =
'rev_page >= ' . intval( $start );
195 $condition .=
' AND rev_page < ' . intval( $end );
198 $condition =
'page_id >= ' . intval( $start );
200 $condition .=
' AND page_id < ' . intval( $end );
203 $this->
dumpFrom( $condition, $orderRevs );
214 $condition =
'rev_id >= ' . intval( $start );
216 $condition .=
' AND rev_id < ' . intval( $end );
224 ' AND page_title=' . $this->db->addQuotes( $page->
getDBkey() ) );
232 $link = $this->titleParser->parseTitle( $name );
234 'page_namespace=' . $link->getNamespace() .
235 ' AND page_title=' . $this->db->addQuotes( $link->getDBkey() ) );
237 throw new RuntimeException(
"Can't export invalid title" );
245 foreach ( $names as $name ) {
259 $condition =
'log_id >= ' . intval( $start );
261 $condition .=
' AND log_id < ' . intval( $end );
274 $this->author_list =
"<contributors>";
277 $res = $this->revisionStore->newSelectQueryBuilder( $this->db )
280 ->where( $this->db->bitAnd(
'rev_deleted', RevisionRecord::DELETED_USER ) .
' = 0' )
282 ->caller( __METHOD__ )->fetchResultSet();
284 foreach ( $res as $row ) {
285 $this->author_list .=
"<contributor>" .
287 htmlspecialchars( $row->rev_user_text ) .
290 ( (int)$row->rev_user ) .
294 $this->author_list .=
"</contributors>";
301 protected function dumpFrom( $cond =
'', $orderRevs =
false ) {
302 if ( is_int( $this->history ) && ( $this->history & self::LOGS ) ) {
315 $hideLogs = LogEventsList::getExcludeClause( $this->db );
317 $where[] = $hideLogs;
319 # Add on any caller specified conditions
324 $commentQuery = $this->commentStore->getJoin(
'log_comment' );
328 $result = $this->db->newSelectQueryBuilder()
330 'log_id',
'log_type',
'log_action',
'log_timestamp',
'log_namespace',
331 'log_title',
'log_params',
'log_deleted',
'actor_user',
'actor_name'
334 ->join(
'actor',
null,
'actor_id=log_actor' )
336 ->andWhere( $this->db->expr(
'log_id',
'>', intval( $lastLogId ) ) )
337 ->orderBy(
'log_id' )
338 ->useIndex( [
'logging' =>
'PRIMARY' ] )
339 ->limit( self::BATCH_SIZE )
340 ->queryInfo( $commentQuery )
341 ->caller( __METHOD__ )
344 if ( !$result->numRows() ) {
349 $this->reloadDBConfig();
358 $revQuery = $this->revisionStore->getQueryInfo( [
'page' ] );
359 $slotQuery = $this->revisionStore->getSlotsQueryInfo( [
'content' ] );
366 $tables = array_merge( [
'page' ], array_diff( $revQuery[
'tables'], [
'page' ] ) );
367 $tables = array_merge( $tables, array_diff( $slotQuery[
'tables'], $tables ) );
368 $join = $revQuery[
'joins'] + [
369 'revision' => $revQuery[
'joins'][
'page'],
370 'slots' => [
'JOIN', [
'slot_revision_id = rev_id' ] ],
371 'content' => [
'JOIN', [
'content_id = slot_content_id' ] ],
373 unset( $join[
'page'] );
375 $fields = array_merge( $revQuery[
'fields'], $slotQuery[
'fields'] );
377 if ( $this->text != self::STUB ) {
378 $fields[
'_load_content'] =
'1';
382 if ( $cond !==
'' ) {
385 $opts = [
'ORDER BY' => [
'rev_page ASC',
'rev_id ASC' ] ];
386 $opts[
'USE INDEX'] = [];
389 if ( is_array( $this->history ) ) {
390 # Time offset/limit for all pages/history...
392 if ( $this->history[
'dir'] ==
'asc' ) {
393 $opts[
'ORDER BY'] =
'rev_timestamp ASC';
396 $opts[
'ORDER BY'] =
'rev_timestamp DESC';
399 if ( !empty( $this->history[
'offset'] ) ) {
400 $conds[] =
"rev_timestamp $op " .
401 $this->db->addQuotes( $this->db->timestamp( $this->history[
'offset'] ) );
404 if ( !empty( $this->history[
'limit'] ) ) {
405 $maxRowCount = intval( $this->history[
'limit'] );
407 } elseif ( $this->history & self::FULL ) {
408 # Full history dumps...
409 # query optimization for history stub dumps
410 if ( $this->text == self::STUB ) {
411 $opts[] =
'STRAIGHT_JOIN';
412 unset( $join[
'revision'] );
413 $join[
'page'] = [
'JOIN',
'rev_page=page_id' ];
415 } elseif ( $this->history & self::CURRENT ) {
416 # Latest revision dumps...
417 if ( $this->list_authors && $cond !=
'' ) {
420 $join[
'revision'] = [
'JOIN',
'page_id=rev_page AND page_latest=rev_id' ];
421 $opts[
'ORDER BY' ] = [
'page_id ASC' ];
422 } elseif ( $this->history & self::STABLE ) {
423 # "Stable" revision dumps...
424 # Default JOIN, to be overridden...
425 $join[
'revision'] = [
'JOIN',
'page_id=rev_page AND page_latest=rev_id' ];
426 # One, and only one hook should set this, and return false
427 if ( $this->hookRunner->onWikiExporter__dumpStableQuery( $tables, $opts, $join ) ) {
428 throw new LogicException( __METHOD__ .
" given invalid history dump type." );
430 } elseif ( $this->history & self::RANGE ) {
431 # Dump of revisions within a specified range. Condition already set in revsByRange().
433 # Unknown history specification parameter?
434 throw new UnexpectedValueException( __METHOD__ .
" given invalid history dump type." );
445 $this->hookRunner->onModifyExportQuery(
446 $this->db, $tables, $cond, $opts, $join, $conds );
450 if ( !empty( $maxRowCount ) && $rowCount + self::BATCH_SIZE > $maxRowCount ) {
451 $opts[
'LIMIT'] = $maxRowCount - $rowCount;
455 # Do the query and process any results, remembering max ids for the next iteration.
456 $result = $this->db->newSelectQueryBuilder()
460 ->andWhere( $this->db->expr(
'rev_page',
'>', intval( $revPage ) )->orExpr(
461 $this->db->expr(
'rev_page',
'=', intval( $revPage ) )->and(
'rev_id', $op, intval( $revId ) )
463 ->caller( __METHOD__ )
467 if ( $result->numRows() > 0 ) {
469 $rowCount += $result->numRows();
470 $revPage = $lastRow->rev_page;
471 $revId = $lastRow->rev_id;
477 if ( $done && $lastRow ) {
482 $this->reloadDBConfig();
507 $revRow = $slotRows[0];
509 if ( $this->limitNamespaces &&
510 !in_array( $revRow->page_namespace, $this->limitNamespaces ) ) {
515 if ( $lastRow ===
null ||
516 $lastRow->page_namespace !== $revRow->page_namespace ||
517 $lastRow->page_title !== $revRow->page_title ) {
518 if ( $lastRow !==
null ) {
520 if ( $this->dumpUploads ) {
521 $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
523 $output .= $this->writer->closePage();
524 $this->sink->writeClosePage( $output );
526 $output = $this->writer->openPage( $revRow );
527 $this->sink->writeOpenPage( $revRow, $output );
530 $output = $this->writer->writeRevision( $revRow, $slotRows );
531 $this->sink->writeRevision( $revRow, $output );
533 MWDebug::warning(
'Problem encountered retrieving rev and slot metadata for'
534 .
' revision ' . $revRow->rev_id .
': ' . $ex->getMessage() );
540 throw new LogicException(
'Error while processing a stream of slot rows' );
561 $slotRows[] = $carry;
568 while ( $row = $results->fetchObject() ) {
569 if ( $prev && $prev->rev_id !== $row->rev_id ) {
587 if ( $this->dumpUploads ) {
588 $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
591 $output .= $this->writer->closePage();
592 $this->sink->writeClosePage( $output );
600 foreach ( $resultset as $row ) {
601 $output = $this->writer->writeLogItem( $row );
602 $this->sink->writeLogItem( $row, $output );
604 return $row->log_id ??
null;
613 private function reloadDBConfig() {
620class_alias( WikiExporter::class,
'WikiExporter' );
A class containing constants representing the names of configuration variables.
const XmlDumpSchemaVersion
Name constant for the XmlDumpSchemaVersion setting, for use with Config::get()
Interface for objects (potentially) representing an editable wiki page.