MediaWiki  master
XmlDumpWriter.php
Go to the documentation of this file.
1 <?php
37 use Wikimedia\Assert\Assert;
38 use Wikimedia\IPUtils;
39 
44 
46  public const WRITE_CONTENT = 0;
47 
49  public const WRITE_STUB = 1;
50 
55  private const WRITE_STUB_DELETED = 2;
56 
61  public static $supportedSchemas = [
64  ];
65 
71  private $schemaVersion;
72 
78  private $currentTitle = null;
79 
83  private $contentMode;
84 
86  private $hookRunner;
87 
89  private $commentStore;
90 
99  public function __construct(
100  $contentMode = self::WRITE_CONTENT,
101  $schemaVersion = XML_DUMP_SCHEMA_VERSION_11,
102  ?HookContainer $hookContainer = null,
103  ?CommentStore $commentStore = null
104  ) {
105  Assert::parameter(
106  in_array( $contentMode, [ self::WRITE_CONTENT, self::WRITE_STUB ], true ),
107  '$contentMode',
108  'must be one of the following constants: WRITE_CONTENT or WRITE_STUB.'
109  );
110 
111  Assert::parameter(
112  in_array( $schemaVersion, self::$supportedSchemas, true ),
113  '$schemaVersion',
114  'must be one of the following schema versions: '
115  . implode( ',', self::$supportedSchemas )
116  );
117 
118  $this->contentMode = $contentMode;
119  $this->schemaVersion = $schemaVersion;
120  $this->hookRunner = new HookRunner(
121  $hookContainer ?? MediaWikiServices::getInstance()->getHookContainer()
122  );
123  $this->commentStore = $commentStore ?? MediaWikiServices::getInstance()->getCommentStore();
124  }
125 
136  public function openStream() {
137  $ver = $this->schemaVersion;
138  return Xml::element( 'mediawiki', [
139  'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
140  'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
141  /*
142  * When a new version of the schema is created, it needs staging on mediawiki.org.
143  * This requires a change in the operations/mediawiki-config git repo.
144  *
145  * Create a changeset like https://gerrit.wikimedia.org/r/#/c/149643/ in which
146  * you copy in the new xsd file.
147  *
148  * After it is reviewed, merged and deployed (sync-docroot), the index.html needs purging.
149  * echo "https://www.mediawiki.org/xml/index.html" | mwscript purgeList.php --wiki=aawiki
150  */
151  'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
152  "http://www.mediawiki.org/xml/export-$ver.xsd",
153  'version' => $ver,
154  'xml:lang' => MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() ],
155  null ) .
156  "\n" .
157  $this->siteInfo();
158  }
159 
163  private function siteInfo() {
164  $info = [
165  $this->sitename(),
166  $this->dbname(),
167  $this->homelink(),
168  $this->generator(),
169  $this->caseSetting(),
170  $this->namespaces() ];
171  return " <siteinfo>\n " .
172  implode( "\n ", $info ) .
173  "\n </siteinfo>\n";
174  }
175 
179  private function sitename() {
180  $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
181  MainConfigNames::Sitename );
182  return Xml::element( 'sitename', [], $sitename );
183  }
184 
188  private function dbname() {
189  $dbname = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::DBname );
190  return Xml::element( 'dbname', [], $dbname );
191  }
192 
196  private function generator() {
197  return Xml::element( 'generator', [], 'MediaWiki ' . MW_VERSION );
198  }
199 
203  private function homelink() {
204  return Xml::element( 'base', [], Title::newMainPage()->getCanonicalURL() );
205  }
206 
210  private function caseSetting() {
211  $capitalLinks = MediaWikiServices::getInstance()->getMainConfig()->get(
212  MainConfigNames::CapitalLinks );
213  // "case-insensitive" option is reserved for future
214  $sensitivity = $capitalLinks ? 'first-letter' : 'case-sensitive';
215  return Xml::element( 'case', [], $sensitivity );
216  }
217 
221  private function namespaces() {
222  $spaces = "<namespaces>\n";
223  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
224  foreach (
225  MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces()
226  as $ns => $title
227  ) {
228  $spaces .= ' ' .
229  Xml::element( 'namespace',
230  [
231  'key' => $ns,
232  'case' => $nsInfo->isCapitalized( $ns )
233  ? 'first-letter' : 'case-sensitive',
234  ], $title ) . "\n";
235  }
236  $spaces .= " </namespaces>";
237  return $spaces;
238  }
239 
246  public function closeStream() {
247  return "</mediawiki>\n";
248  }
249 
257  public function openPage( $row ) {
258  $out = " <page>\n";
259  $this->currentTitle = Title::newFromRow( $row );
260  $canonicalTitle = self::canonicalTitle( $this->currentTitle );
261  $out .= ' ' . Xml::elementClean( 'title', [], $canonicalTitle ) . "\n";
262  $out .= ' ' . Xml::element( 'ns', [], strval( $row->page_namespace ) ) . "\n";
263  $out .= ' ' . Xml::element( 'id', [], strval( $row->page_id ) ) . "\n";
264  if ( $row->page_is_redirect ) {
265  $services = MediaWikiServices::getInstance();
266  $page = $services->getWikiPageFactory()->newFromTitle( $this->currentTitle );
267  $redirectStore = $services->getRedirectStore();
268  $redirect = $this->invokeLenient(
269  static function () use ( $page, $redirectStore ) {
270  return $redirectStore->getRedirectTarget( $page );
271  },
272  'Failed to get redirect target of page ' . $page->getId()
273  );
274  if ( $redirect instanceof Title && $redirect->isValidRedirectTarget() ) {
275  $out .= ' ';
276  $out .= Xml::element( 'redirect', [ 'title' => self::canonicalTitle( $redirect ) ] );
277  $out .= "\n";
278  }
279  }
280  $this->hookRunner->onXmlDumpWriterOpenPage( $this, $out, $row, $this->currentTitle );
281 
282  return $out;
283  }
284 
291  public function closePage() {
292  if ( $this->currentTitle !== null ) {
293  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
294  // In rare cases, link cache has the same key for some pages which
295  // might be read as part of the same batch. T220424 and T220316
296  $linkCache->clearLink( $this->currentTitle );
297  }
298  return " </page>\n";
299  }
300 
304  private function getRevisionStore() {
305  return MediaWikiServices::getInstance()->getRevisionStore();
306  }
307 
311  private function getBlobStore() {
312  // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
313  return MediaWikiServices::getInstance()->getBlobStore();
314  }
315 
327  private function invokeLenient( $callback, $warning ) {
328  try {
329  return $callback();
330  } catch ( SuppressedDataException $ex ) {
331  return null;
332  } catch ( Exception $ex ) {
333  if ( $ex instanceof MWException || $ex instanceof RuntimeException ||
334  $ex instanceof InvalidArgumentException ) {
335  MWDebug::warning( $warning . ': ' . $ex->getMessage() );
336  return null;
337  } else {
338  throw $ex;
339  }
340  }
341  }
342 
354  public function writeRevision( $row, $slotRows = null ) {
355  $rev = $this->getRevisionStore()->newRevisionFromRowAndSlots(
356  $row,
357  $slotRows,
358  0,
359  $this->currentTitle
360  );
361 
362  $out = " <revision>\n";
363  $out .= " " . Xml::element( 'id', null, strval( $rev->getId() ) ) . "\n";
364 
365  if ( $rev->getParentId() ) {
366  $out .= " " . Xml::element( 'parentid', null, strval( $rev->getParentId() ) ) . "\n";
367  }
368 
369  $out .= $this->writeTimestamp( $rev->getTimestamp() );
370 
371  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
372  $out .= " " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
373  } else {
374  // empty values get written out as uid 0, see T224221
375  $user = $rev->getUser();
376  $out .= $this->writeContributor(
377  $user ? $user->getId() : 0,
378  $user ? $user->getName() : ''
379  );
380  }
381 
382  if ( $rev->isMinor() ) {
383  $out .= " <minor/>\n";
384  }
385  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
386  $out .= " " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
387  } else {
388  if ( $rev->getComment()->text != '' ) {
389  $out .= " "
390  . Xml::elementClean( 'comment', [], strval( $rev->getComment()->text ) )
391  . "\n";
392  }
393  }
394 
395  $contentMode = $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ? self::WRITE_STUB_DELETED
396  : $this->contentMode;
397 
398  $slots = $rev->getSlots()->getSlots();
399 
400  // use predictable order, put main slot first
401  ksort( $slots );
402  $out .= $this->writeSlot( $slots[SlotRecord::MAIN], $contentMode );
403 
404  foreach ( $slots as $role => $slot ) {
405  if ( $role === SlotRecord::MAIN ) {
406  continue;
407  }
408  $out .= $this->writeSlot( $slot, $contentMode );
409  }
410 
411  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
412  $out .= " <sha1/>\n";
413  } else {
414  $sha1 = $this->invokeLenient(
415  static function () use ( $rev ) {
416  return $rev->getSha1();
417  },
418  'failed to determine sha1 for revision ' . $rev->getId()
419  );
420  $out .= " " . Xml::element( 'sha1', null, strval( $sha1 ) ) . "\n";
421  }
422 
423  $text = '';
424  if ( $contentMode === self::WRITE_CONTENT ) {
426  $content = $this->invokeLenient(
427  static function () use ( $rev ) {
428  return $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
429  },
430  'Failed to load main slot content of revision ' . $rev->getId()
431  );
432 
433  $text = $content ? $content->serialize() : '';
434  }
435  $this->hookRunner->onXmlDumpWriterWriteRevision( $this, $out, $row, $text, $rev );
436 
437  $out .= " </revision>\n";
438 
439  return $out;
440  }
441 
448  private function writeSlot( SlotRecord $slot, $contentMode ) {
449  $isMain = $slot->getRole() === SlotRecord::MAIN;
450  $isV11 = $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11;
451 
452  if ( !$isV11 && !$isMain ) {
453  // ignore extra slots
454  return '';
455  }
456 
457  $out = '';
458  $indent = ' ';
459 
460  if ( !$isMain ) {
461  // non-main slots are wrapped into an additional element.
462  $out .= ' ' . Xml::openElement( 'content' ) . "\n";
463  $indent .= ' ';
464  $out .= $indent . Xml::element( 'role', null, strval( $slot->getRole() ) ) . "\n";
465  }
466 
467  if ( $isV11 ) {
468  $out .= $indent . Xml::element( 'origin', null, strval( $slot->getOrigin() ) ) . "\n";
469  }
470 
471  $contentModel = $slot->getModel();
472  $contentHandler = MediaWikiServices::getInstance()
473  ->getContentHandlerFactory()
474  ->getContentHandler( $contentModel );
475  $contentFormat = $contentHandler->getDefaultFormat();
476 
477  // XXX: The content format is only relevant when actually outputting serialized content.
478  // It should probably be an attribute on the text tag.
479  $out .= $indent . Xml::element( 'model', null, strval( $contentModel ) ) . "\n";
480  $out .= $indent . Xml::element( 'format', null, strval( $contentFormat ) ) . "\n";
481 
482  $textAttributes = [
483  'bytes' => $this->invokeLenient(
484  static function () use ( $slot ) {
485  return $slot->getSize();
486  },
487  'failed to determine size for slot ' . $slot->getRole() . ' of revision '
488  . $slot->getRevision()
489  ) ?: '0'
490  ];
491 
492  if ( $isV11 ) {
493  $textAttributes['sha1'] = $this->invokeLenient(
494  static function () use ( $slot ) {
495  return $slot->getSha1();
496  },
497  'failed to determine sha1 for slot ' . $slot->getRole() . ' of revision '
498  . $slot->getRevision()
499  ) ?: '';
500  }
501 
502  if ( $contentMode === self::WRITE_CONTENT ) {
503  $content = $this->invokeLenient(
504  static function () use ( $slot ) {
505  return $slot->getContent();
506  },
507  'failed to load content for slot ' . $slot->getRole() . ' of revision '
508  . $slot->getRevision()
509  );
510 
511  if ( $content === null ) {
512  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
513  } else {
514  $out .= $this->writeText( $content, $textAttributes, $indent );
515  }
516  } elseif ( $contentMode === self::WRITE_STUB_DELETED ) {
517  // write <text> placeholder tag
518  $textAttributes['deleted'] = 'deleted';
519  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
520  } else {
521  // write <text> stub tag
522  if ( $isV11 ) {
523  $textAttributes['location'] = $slot->getAddress();
524  }
525 
526  if ( $isMain ) {
527  // Output the numerical text ID if possible, for backwards compatibility.
528  // Note that this is currently the ONLY reason we have a BlobStore here at all.
529  // When removing this line, check whether the BlobStore has become unused.
530  try {
531  // NOTE: this will only work for addresses of the form "tt:12345".
532  // If we want to support other kinds of addresses in the future,
533  // we will have to silently ignore failures here.
534  // For now, this fails for "tt:0", which is present in the WMF production
535  // database as of July 2019, due to data corruption.
536  $textId = $this->getBlobStore()->getTextIdFromAddress( $slot->getAddress() );
537  } catch ( InvalidArgumentException $ex ) {
538  MWDebug::warning( 'Bad content address for slot ' . $slot->getRole()
539  . ' of revision ' . $slot->getRevision() . ': ' . $ex->getMessage() );
540  $textId = 0;
541  }
542 
543  if ( is_int( $textId ) ) {
544  $textAttributes['id'] = $textId;
545  }
546  }
547 
548  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
549  }
550 
551  if ( !$isMain ) {
552  $out .= ' ' . Xml::closeElement( 'content' ) . "\n";
553  }
554 
555  return $out;
556  }
557 
565  private function writeText( Content $content, $textAttributes, $indent ) {
566  $contentHandler = $content->getContentHandler();
567  $contentFormat = $contentHandler->getDefaultFormat();
568 
569  if ( $content instanceof TextContent ) {
570  // HACK: For text based models, bypass the serialization step. This allows extensions (like Flow)
571  // that use incompatible combinations of serialization format and content model.
572  $data = $content->getText();
573  } else {
574  $data = $content->serialize( $contentFormat );
575  }
576 
577  $data = $contentHandler->exportTransform( $data, $contentFormat );
578  // make sure to use the actual size
579  $textAttributes['bytes'] = strlen( $data );
580  $textAttributes['xml:space'] = 'preserve';
581  return $indent . Xml::elementClean( 'text', $textAttributes, strval( $data ) ) . "\n";
582  }
583 
591  public function writeLogItem( $row ) {
592  $out = " <logitem>\n";
593  $out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
594 
595  $out .= $this->writeTimestamp( $row->log_timestamp, " " );
596 
597  if ( $row->log_deleted & LogPage::DELETED_USER ) {
598  $out .= " " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
599  } else {
600  $out .= $this->writeContributor( $row->actor_user, $row->actor_name, " " );
601  }
602 
603  if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
604  $out .= " " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
605  } else {
606  $comment = $this->commentStore->getComment( 'log_comment', $row )->text;
607  if ( $comment != '' ) {
608  $out .= " " . Xml::elementClean( 'comment', null, strval( $comment ) ) . "\n";
609  }
610  }
611 
612  $out .= " " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
613  $out .= " " . Xml::element( 'action', null, strval( $row->log_action ) ) . "\n";
614 
615  if ( $row->log_deleted & LogPage::DELETED_ACTION ) {
616  $out .= " " . Xml::element( 'text', [ 'deleted' => 'deleted' ] ) . "\n";
617  } else {
618  $title = Title::makeTitle( $row->log_namespace, $row->log_title );
619  $out .= " " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
620  $out .= " " . Xml::elementClean( 'params',
621  [ 'xml:space' => 'preserve' ],
622  strval( $row->log_params ) ) . "\n";
623  }
624 
625  $out .= " </logitem>\n";
626 
627  return $out;
628  }
629 
635  public function writeTimestamp( $timestamp, $indent = " " ) {
636  $ts = wfTimestamp( TS_ISO_8601, $timestamp );
637  return $indent . Xml::element( 'timestamp', null, $ts ) . "\n";
638  }
639 
646  public function writeContributor( $id, $text, $indent = " " ) {
647  $out = $indent . "<contributor>\n";
648  if ( $id || !IPUtils::isValid( $text ) ) {
649  $out .= $indent . " " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
650  $out .= $indent . " " . Xml::element( 'id', null, strval( $id ) ) . "\n";
651  } else {
652  $out .= $indent . " " . Xml::elementClean( 'ip', null, strval( $text ) ) . "\n";
653  }
654  $out .= $indent . "</contributor>\n";
655  return $out;
656  }
657 
664  public function writeUploads( $row, $dumpContents = false ) {
665  if ( $row->page_namespace == NS_FILE ) {
666  $img = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
667  ->newFile( $row->page_title );
668  if ( $img && $img->exists() ) {
669  $out = '';
670  foreach ( array_reverse( $img->getHistory() ) as $ver ) {
671  $out .= $this->writeUpload( $ver, $dumpContents );
672  }
673  $out .= $this->writeUpload( $img, $dumpContents );
674  return $out;
675  }
676  }
677  return '';
678  }
679 
685  private function writeUpload( $file, $dumpContents = false ) {
686  if ( $file->isOld() ) {
688  '@phan-var OldLocalFile $file';
689  $archiveName = " " .
690  Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
691  } else {
692  $archiveName = '';
693  }
694  if ( $dumpContents ) {
695  $be = $file->getRepo()->getBackend();
696  # Dump file as base64
697  # Uses only XML-safe characters, so does not need escaping
698  # @todo Too bad this loads the contents into memory (script might swap)
699  $contents = ' <contents encoding="base64">' .
700  chunk_split( base64_encode(
701  $be->getFileContents( [ 'src' => $file->getPath() ] ) ) ) .
702  " </contents>\n";
703  } else {
704  $contents = '';
705  }
706  $uploader = $file->getUploader( File::FOR_PUBLIC );
707  if ( $uploader ) {
708  $uploader = $this->writeContributor( $uploader->getId(), $uploader->getName() );
709  } else {
710  $uploader = Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
711  }
712  $comment = $file->getDescription( File::FOR_PUBLIC );
713  if ( ( $comment ?? '' ) !== '' ) {
714  $comment = Xml::elementClean( 'comment', null, $comment );
715  } else {
716  $comment = Xml::element( 'comment', [ 'deleted' => 'deleted' ] );
717  }
718  return " <upload>\n" .
719  $this->writeTimestamp( $file->getTimestamp() ) .
720  $uploader .
721  " " . $comment . "\n" .
722  " " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
723  $archiveName .
724  " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" .
725  " " . Xml::element( 'size', null, (string)( $file->getSize() ?: 0 ) ) . "\n" .
726  " " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
727  " " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
728  $contents .
729  " </upload>\n";
730  }
731 
742  public static function canonicalTitle( Title $title ) {
743  if ( $title->isExternal() ) {
744  return $title->getPrefixedText();
745  }
746 
747  $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
748  getFormattedNsText( $title->getNamespace() );
749 
750  // @todo Emit some kind of warning to the user if $title->getNamespace() !==
751  // NS_MAIN and $prefix === '' (viz. pages in an unregistered namespace)
752 
753  if ( $prefix !== '' ) {
754  $prefix .= ':';
755  }
756 
757  return $prefix . $title->getText();
758  }
759 }
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:315
const XML_DUMP_SCHEMA_VERSION_10
Definition: Defines.php:314
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
const FOR_PUBLIC
Definition: File.php:90
const DELETED_USER
Definition: LogPage.php:46
const DELETED_COMMENT
Definition: LogPage.php:45
const DELETED_ACTION
Definition: LogPage.php:44
static warning( $msg, $callerOffset=1, $level=E_USER_NOTICE, $log='auto')
Adds a warning entry to the log.
Definition: MWDebug.php:187
MediaWiki exception.
Definition: MWException.php:33
Handle database storage of comments such as edit summaries and log reasons.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
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:319
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:509
getSha1()
Returns the content size.
Definition: SlotRecord.php:558
getSize()
Returns the content size.
Definition: SlotRecord.php:542
getAddress()
Returns the address of this slot's content.
Definition: SlotRecord.php:519
getModel()
Returns the content model.
Definition: SlotRecord.php:586
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
Definition: SlotRecord.php:425
getRevision()
Returns the ID of the revision this slot is associated with.
Definition: SlotRecord.php:416
Exception raised in response to an audience check when attempting to access suppressed information wi...
Service for storing and loading Content objects representing revision data blobs.
Represents a title within MediaWiki.
Definition: Title.php:76
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:3549
isExternal()
Is this Title interwiki?
Definition: Title.php:948
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1058
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:1031
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1885
Content object implementation for representing flat text.
Definition: TextContent.php:41
closeStream()
Closes the output stream with the closing root element.
static string[] $supportedSchemas
the schema versions supported for output @final
static canonicalTitle(Title $title)
Return prefixed text form of title, but using the content language's canonical namespace.
const WRITE_STUB
Only output subs for revision content.
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.
__construct( $contentMode=self::WRITE_CONTENT, $schemaVersion=XML_DUMP_SCHEMA_VERSION_11, ?HookContainer $hookContainer=null, ?CommentStore $commentStore=null)
writeContributor( $id, $text, $indent=" ")
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:124
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:115
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:50
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:97
Base interface for representing page content.
Definition: Content.php:39
$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