MediaWiki  master
XmlDumpWriter.php
Go to the documentation of this file.
1 <?php
34 use Wikimedia\Assert\Assert;
35 use Wikimedia\IPUtils;
36 
41 
43  public const WRITE_CONTENT = 0;
44 
46  public const WRITE_STUB = 1;
47 
52  private const WRITE_STUB_DELETED = 2;
53 
58  public static $supportedSchemas = [
61  ];
62 
68  private $schemaVersion;
69 
75  private $currentTitle = null;
76 
80  private $contentMode;
81 
83  private $hookRunner;
84 
91  public function __construct(
92  $contentMode = self::WRITE_CONTENT,
94  ) {
95  Assert::parameter(
96  in_array( $contentMode, [ self::WRITE_CONTENT, self::WRITE_STUB ], true ),
97  '$contentMode',
98  'must be one of the following constants: WRITE_CONTENT or WRITE_STUB.'
99  );
100 
101  Assert::parameter(
102  in_array( $schemaVersion, self::$supportedSchemas, true ),
103  '$schemaVersion',
104  'must be one of the following schema versions: '
105  . implode( ',', self::$supportedSchemas )
106  );
107 
108  $this->contentMode = $contentMode;
109  $this->schemaVersion = $schemaVersion;
110  $this->hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
111  }
112 
123  public function openStream() {
124  $ver = $this->schemaVersion;
125  return Xml::element( 'mediawiki', [
126  'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
127  'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
128  /*
129  * When a new version of the schema is created, it needs staging on mediawiki.org.
130  * This requires a change in the operations/mediawiki-config git repo.
131  *
132  * Create a changeset like https://gerrit.wikimedia.org/r/#/c/149643/ in which
133  * you copy in the new xsd file.
134  *
135  * After it is reviewed, merged and deployed (sync-docroot), the index.html needs purging.
136  * echo "https://www.mediawiki.org/xml/index.html" | mwscript purgeList.php --wiki=aawiki
137  */
138  'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
139  "http://www.mediawiki.org/xml/export-$ver.xsd",
140  'version' => $ver,
141  'xml:lang' => MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() ],
142  null ) .
143  "\n" .
144  $this->siteInfo();
145  }
146 
150  private function siteInfo() {
151  $info = [
152  $this->sitename(),
153  $this->dbname(),
154  $this->homelink(),
155  $this->generator(),
156  $this->caseSetting(),
157  $this->namespaces() ];
158  return " <siteinfo>\n " .
159  implode( "\n ", $info ) .
160  "\n </siteinfo>\n";
161  }
162 
166  private function sitename() {
167  $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
168  MainConfigNames::Sitename );
169  return Xml::element( 'sitename', [], $sitename );
170  }
171 
175  private function dbname() {
176  $dbname = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::DBname );
177  return Xml::element( 'dbname', [], $dbname );
178  }
179 
183  private function generator() {
184  return Xml::element( 'generator', [], 'MediaWiki ' . MW_VERSION );
185  }
186 
190  private function homelink() {
191  return Xml::element( 'base', [], Title::newMainPage()->getCanonicalURL() );
192  }
193 
197  private function caseSetting() {
198  $capitalLinks = MediaWikiServices::getInstance()->getMainConfig()->get(
199  MainConfigNames::CapitalLinks );
200  // "case-insensitive" option is reserved for future
201  $sensitivity = $capitalLinks ? 'first-letter' : 'case-sensitive';
202  return Xml::element( 'case', [], $sensitivity );
203  }
204 
208  private function namespaces() {
209  $spaces = "<namespaces>\n";
210  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
211  foreach (
212  MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces()
213  as $ns => $title
214  ) {
215  $spaces .= ' ' .
216  Xml::element( 'namespace',
217  [
218  'key' => $ns,
219  'case' => $nsInfo->isCapitalized( $ns )
220  ? 'first-letter' : 'case-sensitive',
221  ], $title ) . "\n";
222  }
223  $spaces .= " </namespaces>";
224  return $spaces;
225  }
226 
233  public function closeStream() {
234  return "</mediawiki>\n";
235  }
236 
244  public function openPage( $row ) {
245  $out = " <page>\n";
246  $this->currentTitle = Title::newFromRow( $row );
247  $canonicalTitle = self::canonicalTitle( $this->currentTitle );
248  $out .= ' ' . Xml::elementClean( 'title', [], $canonicalTitle ) . "\n";
249  $out .= ' ' . Xml::element( 'ns', [], strval( $row->page_namespace ) ) . "\n";
250  $out .= ' ' . Xml::element( 'id', [], strval( $row->page_id ) ) . "\n";
251  if ( $row->page_is_redirect ) {
252  $services = MediaWikiServices::getInstance();
253  $page = $services->getWikiPageFactory()->newFromTitle( $this->currentTitle );
254  $redirectStore = $services->getRedirectStore();
255  $redirect = $this->invokeLenient(
256  static function () use ( $page, $redirectStore ) {
257  return $redirectStore->getRedirectTarget( $page );
258  },
259  'Failed to get redirect target of page ' . $page->getId()
260  );
261  if ( $redirect instanceof Title && $redirect->isValidRedirectTarget() ) {
262  $out .= ' ';
263  $out .= Xml::element( 'redirect', [ 'title' => self::canonicalTitle( $redirect ) ] );
264  $out .= "\n";
265  }
266  }
267  $this->hookRunner->onXmlDumpWriterOpenPage( $this, $out, $row, $this->currentTitle );
268 
269  return $out;
270  }
271 
278  public function closePage() {
279  if ( $this->currentTitle !== null ) {
280  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
281  // In rare cases, link cache has the same key for some pages which
282  // might be read as part of the same batch. T220424 and T220316
283  $linkCache->clearLink( $this->currentTitle );
284  }
285  return " </page>\n";
286  }
287 
291  private function getRevisionStore() {
292  return MediaWikiServices::getInstance()->getRevisionStore();
293  }
294 
298  private function getBlobStore() {
299  // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
300  return MediaWikiServices::getInstance()->getBlobStore();
301  }
302 
314  private function invokeLenient( $callback, $warning ) {
315  try {
316  return $callback();
317  } catch ( SuppressedDataException $ex ) {
318  return null;
319  } catch ( Exception $ex ) {
320  if ( $ex instanceof MWException || $ex instanceof RuntimeException ||
321  $ex instanceof InvalidArgumentException ) {
322  MWDebug::warning( $warning . ': ' . $ex->getMessage() );
323  return null;
324  } else {
325  throw $ex;
326  }
327  }
328  }
329 
341  public function writeRevision( $row, $slotRows = null ) {
342  $rev = $this->getRevisionStore()->newRevisionFromRowAndSlots(
343  $row,
344  $slotRows,
345  0,
346  $this->currentTitle
347  );
348 
349  $out = " <revision>\n";
350  $out .= " " . Xml::element( 'id', null, strval( $rev->getId() ) ) . "\n";
351 
352  if ( $rev->getParentId() ) {
353  $out .= " " . Xml::element( 'parentid', null, strval( $rev->getParentId() ) ) . "\n";
354  }
355 
356  $out .= $this->writeTimestamp( $rev->getTimestamp() );
357 
358  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
359  $out .= " " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
360  } else {
361  // empty values get written out as uid 0, see T224221
362  $user = $rev->getUser();
363  $out .= $this->writeContributor(
364  $user ? $user->getId() : 0,
365  $user ? $user->getName() : ''
366  );
367  }
368 
369  if ( $rev->isMinor() ) {
370  $out .= " <minor/>\n";
371  }
372  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
373  $out .= " " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
374  } else {
375  if ( $rev->getComment()->text != '' ) {
376  $out .= " "
377  . Xml::elementClean( 'comment', [], strval( $rev->getComment()->text ) )
378  . "\n";
379  }
380  }
381 
382  $contentMode = $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ? self::WRITE_STUB_DELETED
384 
385  $slots = $rev->getSlots()->getSlots();
386 
387  // use predictable order, put main slot first
388  ksort( $slots );
389  $out .= $this->writeSlot( $slots[SlotRecord::MAIN], $contentMode );
390 
391  foreach ( $slots as $role => $slot ) {
392  if ( $role === SlotRecord::MAIN ) {
393  continue;
394  }
395  $out .= $this->writeSlot( $slot, $contentMode );
396  }
397 
398  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
399  $out .= " <sha1/>\n";
400  } else {
401  $sha1 = $this->invokeLenient(
402  static function () use ( $rev ) {
403  return $rev->getSha1();
404  },
405  'failed to determine sha1 for revision ' . $rev->getId()
406  );
407  $out .= " " . Xml::element( 'sha1', null, strval( $sha1 ) ) . "\n";
408  }
409 
410  // Avoid PHP 7.1 warning from passing $this by reference
411  $writer = $this;
412  $text = '';
413  if ( $contentMode === self::WRITE_CONTENT ) {
415  $content = $this->invokeLenient(
416  static function () use ( $rev ) {
417  return $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
418  },
419  'Failed to load main slot content of revision ' . $rev->getId()
420  );
421 
422  $text = $content ? $content->serialize() : '';
423  }
424  $this->hookRunner->onXmlDumpWriterWriteRevision( $writer, $out, $row, $text, $rev );
425 
426  $out .= " </revision>\n";
427 
428  return $out;
429  }
430 
437  private function writeSlot( SlotRecord $slot, $contentMode ) {
438  $isMain = $slot->getRole() === SlotRecord::MAIN;
439  $isV11 = $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11;
440 
441  if ( !$isV11 && !$isMain ) {
442  // ignore extra slots
443  return '';
444  }
445 
446  $out = '';
447  $indent = ' ';
448 
449  if ( !$isMain ) {
450  // non-main slots are wrapped into an additional element.
451  $out .= ' ' . Xml::openElement( 'content' ) . "\n";
452  $indent .= ' ';
453  $out .= $indent . Xml::element( 'role', null, strval( $slot->getRole() ) ) . "\n";
454  }
455 
456  if ( $isV11 ) {
457  $out .= $indent . Xml::element( 'origin', null, strval( $slot->getOrigin() ) ) . "\n";
458  }
459 
460  $contentModel = $slot->getModel();
461  $contentHandler = MediaWikiServices::getInstance()
462  ->getContentHandlerFactory()
463  ->getContentHandler( $contentModel );
464  $contentFormat = $contentHandler->getDefaultFormat();
465 
466  // XXX: The content format is only relevant when actually outputting serialized content.
467  // It should probably be an attribute on the text tag.
468  $out .= $indent . Xml::element( 'model', null, strval( $contentModel ) ) . "\n";
469  $out .= $indent . Xml::element( 'format', null, strval( $contentFormat ) ) . "\n";
470 
471  $textAttributes = [
472  'bytes' => $this->invokeLenient(
473  static function () use ( $slot ) {
474  return $slot->getSize();
475  },
476  'failed to determine size for slot ' . $slot->getRole() . ' of revision '
477  . $slot->getRevision()
478  ) ?: '0'
479  ];
480 
481  if ( $isV11 ) {
482  $textAttributes['sha1'] = $this->invokeLenient(
483  static function () use ( $slot ) {
484  return $slot->getSha1();
485  },
486  'failed to determine sha1 for slot ' . $slot->getRole() . ' of revision '
487  . $slot->getRevision()
488  ) ?: '';
489  }
490 
491  if ( $contentMode === self::WRITE_CONTENT ) {
492  $content = $this->invokeLenient(
493  static function () use ( $slot ) {
494  return $slot->getContent();
495  },
496  'failed to load content for slot ' . $slot->getRole() . ' of revision '
497  . $slot->getRevision()
498  );
499 
500  if ( $content === null ) {
501  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
502  } else {
503  $out .= $this->writeText( $content, $textAttributes, $indent );
504  }
505  } elseif ( $contentMode === self::WRITE_STUB_DELETED ) {
506  // write <text> placeholder tag
507  $textAttributes['deleted'] = 'deleted';
508  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
509  } else {
510  // write <text> stub tag
511  if ( $isV11 ) {
512  $textAttributes['location'] = $slot->getAddress();
513  }
514 
515  if ( $isMain ) {
516  // Output the numerical text ID if possible, for backwards compatibility.
517  // Note that this is currently the ONLY reason we have a BlobStore here at all.
518  // When removing this line, check whether the BlobStore has become unused.
519  try {
520  // NOTE: this will only work for addresses of the form "tt:12345".
521  // If we want to support other kinds of addresses in the future,
522  // we will have to silently ignore failures here.
523  // For now, this fails for "tt:0", which is present in the WMF production
524  // database as of July 2019, due to data corruption.
525  $textId = $this->getBlobStore()->getTextIdFromAddress( $slot->getAddress() );
526  } catch ( InvalidArgumentException $ex ) {
527  MWDebug::warning( 'Bad content address for slot ' . $slot->getRole()
528  . ' of revision ' . $slot->getRevision() . ': ' . $ex->getMessage() );
529  $textId = 0;
530  }
531 
532  if ( is_int( $textId ) ) {
533  $textAttributes['id'] = $textId;
534  }
535  }
536 
537  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
538  }
539 
540  if ( !$isMain ) {
541  $out .= ' ' . Xml::closeElement( 'content' ) . "\n";
542  }
543 
544  return $out;
545  }
546 
554  private function writeText( Content $content, $textAttributes, $indent ) {
555  $out = '';
556 
557  $contentHandler = $content->getContentHandler();
558  $contentFormat = $contentHandler->getDefaultFormat();
559 
560  if ( $content instanceof TextContent ) {
561  // HACK: For text based models, bypass the serialization step. This allows extensions (like Flow)
562  // that use incompatible combinations of serialization format and content model.
563  $data = $content->getNativeData();
564  } else {
565  $data = $content->serialize( $contentFormat );
566  }
567 
568  $data = $contentHandler->exportTransform( $data, $contentFormat );
569  $textAttributes['bytes'] = $size = strlen( $data ); // make sure to use the actual size
570  $textAttributes['xml:space'] = 'preserve';
571  $out .= $indent . Xml::elementClean( 'text', $textAttributes, strval( $data ) ) . "\n";
572 
573  return $out;
574  }
575 
583  public function writeLogItem( $row ) {
584  $out = " <logitem>\n";
585  $out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
586 
587  $out .= $this->writeTimestamp( $row->log_timestamp, " " );
588 
589  if ( $row->log_deleted & LogPage::DELETED_USER ) {
590  $out .= " " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
591  } else {
592  $out .= $this->writeContributor( $row->actor_user, $row->actor_name, " " );
593  }
594 
595  if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
596  $out .= " " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
597  } else {
598  $comment = CommentStore::getStore()->getComment( 'log_comment', $row )->text;
599  if ( $comment != '' ) {
600  $out .= " " . Xml::elementClean( 'comment', null, strval( $comment ) ) . "\n";
601  }
602  }
603 
604  $out .= " " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
605  $out .= " " . Xml::element( 'action', null, strval( $row->log_action ) ) . "\n";
606 
607  if ( $row->log_deleted & LogPage::DELETED_ACTION ) {
608  $out .= " " . Xml::element( 'text', [ 'deleted' => 'deleted' ] ) . "\n";
609  } else {
610  $title = Title::makeTitle( $row->log_namespace, $row->log_title );
611  $out .= " " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
612  $out .= " " . Xml::elementClean( 'params',
613  [ 'xml:space' => 'preserve' ],
614  strval( $row->log_params ) ) . "\n";
615  }
616 
617  $out .= " </logitem>\n";
618 
619  return $out;
620  }
621 
627  public function writeTimestamp( $timestamp, $indent = " " ) {
628  $ts = wfTimestamp( TS_ISO_8601, $timestamp );
629  return $indent . Xml::element( 'timestamp', null, $ts ) . "\n";
630  }
631 
638  public function writeContributor( $id, $text, $indent = " " ) {
639  $out = $indent . "<contributor>\n";
640  if ( $id || !IPUtils::isValid( $text ) ) {
641  $out .= $indent . " " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
642  $out .= $indent . " " . Xml::element( 'id', null, strval( $id ) ) . "\n";
643  } else {
644  $out .= $indent . " " . Xml::elementClean( 'ip', null, strval( $text ) ) . "\n";
645  }
646  $out .= $indent . "</contributor>\n";
647  return $out;
648  }
649 
656  public function writeUploads( $row, $dumpContents = false ) {
657  if ( $row->page_namespace == NS_FILE ) {
658  $img = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
659  ->newFile( $row->page_title );
660  if ( $img && $img->exists() ) {
661  $out = '';
662  foreach ( array_reverse( $img->getHistory() ) as $ver ) {
663  $out .= $this->writeUpload( $ver, $dumpContents );
664  }
665  $out .= $this->writeUpload( $img, $dumpContents );
666  return $out;
667  }
668  }
669  return '';
670  }
671 
677  private function writeUpload( $file, $dumpContents = false ) {
678  if ( $file->isOld() ) {
680  '@phan-var OldLocalFile $file';
681  $archiveName = " " .
682  Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
683  } else {
684  $archiveName = '';
685  }
686  if ( $dumpContents ) {
687  $be = $file->getRepo()->getBackend();
688  # Dump file as base64
689  # Uses only XML-safe characters, so does not need escaping
690  # @todo Too bad this loads the contents into memory (script might swap)
691  $contents = ' <contents encoding="base64">' .
692  chunk_split( base64_encode(
693  $be->getFileContents( [ 'src' => $file->getPath() ] ) ) ) .
694  " </contents>\n";
695  } else {
696  $contents = '';
697  }
698  $uploader = $file->getUploader( File::FOR_PUBLIC );
699  if ( $uploader ) {
700  $uploader = $this->writeContributor( $uploader->getId(), $uploader->getName() );
701  } else {
702  $uploader = Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
703  }
704  $comment = $file->getDescription( File::FOR_PUBLIC );
705  if ( ( $comment ?? '' ) !== '' ) {
706  $comment = Xml::elementClean( 'comment', null, $comment );
707  } else {
708  $comment = Xml::element( 'comment', [ 'deleted' => 'deleted' ] );
709  }
710  return " <upload>\n" .
711  $this->writeTimestamp( $file->getTimestamp() ) .
712  $uploader .
713  " " . $comment . "\n" .
714  " " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
715  $archiveName .
716  " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" .
717  " " . Xml::element( 'size', null, (string)( $file->getSize() ?: 0 ) ) . "\n" .
718  " " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
719  " " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
720  $contents .
721  " </upload>\n";
722  }
723 
734  public static function canonicalTitle( Title $title ) {
735  if ( $title->isExternal() ) {
736  return $title->getPrefixedText();
737  }
738 
739  $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
740  getFormattedNsText( $title->getNamespace() );
741 
742  // @todo Emit some kind of warning to the user if $title->getNamespace() !==
743  // NS_MAIN and $prefix === '' (viz. pages in an unregistered namespace)
744 
745  if ( $prefix !== '' ) {
746  $prefix .= ':';
747  }
748 
749  return $prefix . $title->getText();
750  }
751 }
const NS_FILE
Definition: Defines.php:70
const MW_VERSION
The running version of MediaWiki.
Definition: Defines.php:36
const XML_DUMP_SCHEMA_VERSION_11
Definition: Defines.php:316
const XML_DUMP_SCHEMA_VERSION_10
Definition: Defines.php:315
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getStore()
const FOR_PUBLIC
Definition: File.php:88
const DELETED_USER
Definition: LogPage.php:42
const DELETED_COMMENT
Definition: LogPage.php:41
const DELETED_ACTION
Definition: LogPage.php:40
static warning( $msg, $callerOffset=1, $level=E_USER_NOTICE, $log='auto')
Adds a warning entry to the log.
Definition: MWDebug.php:183
MediaWiki exception.
Definition: MWException.php:29
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:561
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Page revision base class.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
getContent()
Returns the Content of the given slot.
Definition: SlotRecord.php:317
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:507
getSha1()
Returns the content size.
Definition: SlotRecord.php:556
getSize()
Returns the content size.
Definition: SlotRecord.php:540
getAddress()
Returns the address of this slot's content.
Definition: SlotRecord.php:517
getModel()
Returns the content model.
Definition: SlotRecord.php:584
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
Definition: SlotRecord.php:423
getRevision()
Returns the ID of the revision this slot is associated with.
Definition: SlotRecord.php:414
Exception raised in response to an audience check when attempting to access suppressed information wi...
Service for storing and loading Content objects.
Content object implementation for representing flat text.
Definition: TextContent.php:40
Represents a title within MediaWiki.
Definition: Title.php:49
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:700
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:3803
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:573
closeStream()
Closes the output stream with the closing root element.
__construct( $contentMode=self::WRITE_CONTENT, $schemaVersion=XML_DUMP_SCHEMA_VERSION_11)
static string[] $supportedSchemas
the schema versions supported for output @final
const WRITE_STUB_DELETED
Only output subs for revision content, indicating that the content has been deleted/suppressed.
static canonicalTitle(Title $title)
Return prefixed text form of title, but using the content language's canonical namespace.
int $contentMode
Whether to output revision content or just stubs.
const WRITE_STUB
Only output subs for revision content.
string $schemaVersion
which schema version the generated XML should comply to.
writeUpload( $file, $dumpContents=false)
invokeLenient( $callback, $warning)
Invokes the given callback, catching and logging any storage related exceptions.
HookRunner $hookRunner
writeLogItem( $row)
Dumps a "<logitem>" section on the output stream, with data filled in from the given database row.
writeTimestamp( $timestamp, $indent=" ")
const WRITE_CONTENT
Output serialized revision content.
writeUploads( $row, $dumpContents=false)
Warning! This data is potentially inconsistent.
closePage()
Closes a "<page>" section on the output stream.
openStream()
Opens the XML output stream's root "<mediawiki>" element.
writeRevision( $row, $slotRows=null)
Dumps a "<revision>" section on the output stream, with data filled in from the given database row.
openPage( $row)
Opens a "<page>" section on the output stream, with data from the given database row.
writeText(Content $content, $textAttributes, $indent)
Title null $currentTitle
Title of the currently processed page.
writeSlot(SlotRecord $slot, $contentMode)
writeContributor( $id, $text, $indent=" ")
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:121
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:112
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:43
static elementClean( $element, $attribs=[], $contents='')
Format an XML element as with self::element(), but run text through the content language's normalize(...
Definition: Xml.php:94
Base interface for content objects.
Definition: Content.php:35
$content
Definition: router.php:76
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42