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,
93  $schemaVersion = XML_DUMP_SCHEMA_VERSION_11
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
383  : $this->contentMode;
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  $text = '';
411  if ( $contentMode === self::WRITE_CONTENT ) {
413  $content = $this->invokeLenient(
414  static function () use ( $rev ) {
415  return $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
416  },
417  'Failed to load main slot content of revision ' . $rev->getId()
418  );
419 
420  $text = $content ? $content->serialize() : '';
421  }
422  $this->hookRunner->onXmlDumpWriterWriteRevision( $this, $out, $row, $text, $rev );
423 
424  $out .= " </revision>\n";
425 
426  return $out;
427  }
428 
435  private function writeSlot( SlotRecord $slot, $contentMode ) {
436  $isMain = $slot->getRole() === SlotRecord::MAIN;
437  $isV11 = $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11;
438 
439  if ( !$isV11 && !$isMain ) {
440  // ignore extra slots
441  return '';
442  }
443 
444  $out = '';
445  $indent = ' ';
446 
447  if ( !$isMain ) {
448  // non-main slots are wrapped into an additional element.
449  $out .= ' ' . Xml::openElement( 'content' ) . "\n";
450  $indent .= ' ';
451  $out .= $indent . Xml::element( 'role', null, strval( $slot->getRole() ) ) . "\n";
452  }
453 
454  if ( $isV11 ) {
455  $out .= $indent . Xml::element( 'origin', null, strval( $slot->getOrigin() ) ) . "\n";
456  }
457 
458  $contentModel = $slot->getModel();
459  $contentHandler = MediaWikiServices::getInstance()
460  ->getContentHandlerFactory()
461  ->getContentHandler( $contentModel );
462  $contentFormat = $contentHandler->getDefaultFormat();
463 
464  // XXX: The content format is only relevant when actually outputting serialized content.
465  // It should probably be an attribute on the text tag.
466  $out .= $indent . Xml::element( 'model', null, strval( $contentModel ) ) . "\n";
467  $out .= $indent . Xml::element( 'format', null, strval( $contentFormat ) ) . "\n";
468 
469  $textAttributes = [
470  'bytes' => $this->invokeLenient(
471  static function () use ( $slot ) {
472  return $slot->getSize();
473  },
474  'failed to determine size for slot ' . $slot->getRole() . ' of revision '
475  . $slot->getRevision()
476  ) ?: '0'
477  ];
478 
479  if ( $isV11 ) {
480  $textAttributes['sha1'] = $this->invokeLenient(
481  static function () use ( $slot ) {
482  return $slot->getSha1();
483  },
484  'failed to determine sha1 for slot ' . $slot->getRole() . ' of revision '
485  . $slot->getRevision()
486  ) ?: '';
487  }
488 
489  if ( $contentMode === self::WRITE_CONTENT ) {
490  $content = $this->invokeLenient(
491  static function () use ( $slot ) {
492  return $slot->getContent();
493  },
494  'failed to load content for slot ' . $slot->getRole() . ' of revision '
495  . $slot->getRevision()
496  );
497 
498  if ( $content === null ) {
499  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
500  } else {
501  $out .= $this->writeText( $content, $textAttributes, $indent );
502  }
503  } elseif ( $contentMode === self::WRITE_STUB_DELETED ) {
504  // write <text> placeholder tag
505  $textAttributes['deleted'] = 'deleted';
506  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
507  } else {
508  // write <text> stub tag
509  if ( $isV11 ) {
510  $textAttributes['location'] = $slot->getAddress();
511  }
512 
513  if ( $isMain ) {
514  // Output the numerical text ID if possible, for backwards compatibility.
515  // Note that this is currently the ONLY reason we have a BlobStore here at all.
516  // When removing this line, check whether the BlobStore has become unused.
517  try {
518  // NOTE: this will only work for addresses of the form "tt:12345".
519  // If we want to support other kinds of addresses in the future,
520  // we will have to silently ignore failures here.
521  // For now, this fails for "tt:0", which is present in the WMF production
522  // database as of July 2019, due to data corruption.
523  $textId = $this->getBlobStore()->getTextIdFromAddress( $slot->getAddress() );
524  } catch ( InvalidArgumentException $ex ) {
525  MWDebug::warning( 'Bad content address for slot ' . $slot->getRole()
526  . ' of revision ' . $slot->getRevision() . ': ' . $ex->getMessage() );
527  $textId = 0;
528  }
529 
530  if ( is_int( $textId ) ) {
531  $textAttributes['id'] = $textId;
532  }
533  }
534 
535  $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
536  }
537 
538  if ( !$isMain ) {
539  $out .= ' ' . Xml::closeElement( 'content' ) . "\n";
540  }
541 
542  return $out;
543  }
544 
552  private function writeText( Content $content, $textAttributes, $indent ) {
553  $out = '';
554 
555  $contentHandler = $content->getContentHandler();
556  $contentFormat = $contentHandler->getDefaultFormat();
557 
558  if ( $content instanceof TextContent ) {
559  // HACK: For text based models, bypass the serialization step. This allows extensions (like Flow)
560  // that use incompatible combinations of serialization format and content model.
561  $data = $content->getNativeData();
562  } else {
563  $data = $content->serialize( $contentFormat );
564  }
565 
566  $data = $contentHandler->exportTransform( $data, $contentFormat );
567  $textAttributes['bytes'] = strlen( $data ); // make sure to use the actual size
568  $textAttributes['xml:space'] = 'preserve';
569  $out .= $indent . Xml::elementClean( 'text', $textAttributes, strval( $data ) ) . "\n";
570 
571  return $out;
572  }
573 
581  public function writeLogItem( $row ) {
582  $out = " <logitem>\n";
583  $out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
584 
585  $out .= $this->writeTimestamp( $row->log_timestamp, " " );
586 
587  if ( $row->log_deleted & LogPage::DELETED_USER ) {
588  $out .= " " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
589  } else {
590  $out .= $this->writeContributor( $row->actor_user, $row->actor_name, " " );
591  }
592 
593  if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
594  $out .= " " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
595  } else {
596  $comment = CommentStore::getStore()->getComment( 'log_comment', $row )->text;
597  if ( $comment != '' ) {
598  $out .= " " . Xml::elementClean( 'comment', null, strval( $comment ) ) . "\n";
599  }
600  }
601 
602  $out .= " " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
603  $out .= " " . Xml::element( 'action', null, strval( $row->log_action ) ) . "\n";
604 
605  if ( $row->log_deleted & LogPage::DELETED_ACTION ) {
606  $out .= " " . Xml::element( 'text', [ 'deleted' => 'deleted' ] ) . "\n";
607  } else {
608  $title = Title::makeTitle( $row->log_namespace, $row->log_title );
609  $out .= " " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
610  $out .= " " . Xml::elementClean( 'params',
611  [ 'xml:space' => 'preserve' ],
612  strval( $row->log_params ) ) . "\n";
613  }
614 
615  $out .= " </logitem>\n";
616 
617  return $out;
618  }
619 
625  public function writeTimestamp( $timestamp, $indent = " " ) {
626  $ts = wfTimestamp( TS_ISO_8601, $timestamp );
627  return $indent . Xml::element( 'timestamp', null, $ts ) . "\n";
628  }
629 
636  public function writeContributor( $id, $text, $indent = " " ) {
637  $out = $indent . "<contributor>\n";
638  if ( $id || !IPUtils::isValid( $text ) ) {
639  $out .= $indent . " " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
640  $out .= $indent . " " . Xml::element( 'id', null, strval( $id ) ) . "\n";
641  } else {
642  $out .= $indent . " " . Xml::elementClean( 'ip', null, strval( $text ) ) . "\n";
643  }
644  $out .= $indent . "</contributor>\n";
645  return $out;
646  }
647 
654  public function writeUploads( $row, $dumpContents = false ) {
655  if ( $row->page_namespace == NS_FILE ) {
656  $img = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
657  ->newFile( $row->page_title );
658  if ( $img && $img->exists() ) {
659  $out = '';
660  foreach ( array_reverse( $img->getHistory() ) as $ver ) {
661  $out .= $this->writeUpload( $ver, $dumpContents );
662  }
663  $out .= $this->writeUpload( $img, $dumpContents );
664  return $out;
665  }
666  }
667  return '';
668  }
669 
675  private function writeUpload( $file, $dumpContents = false ) {
676  if ( $file->isOld() ) {
678  '@phan-var OldLocalFile $file';
679  $archiveName = " " .
680  Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
681  } else {
682  $archiveName = '';
683  }
684  if ( $dumpContents ) {
685  $be = $file->getRepo()->getBackend();
686  # Dump file as base64
687  # Uses only XML-safe characters, so does not need escaping
688  # @todo Too bad this loads the contents into memory (script might swap)
689  $contents = ' <contents encoding="base64">' .
690  chunk_split( base64_encode(
691  $be->getFileContents( [ 'src' => $file->getPath() ] ) ) ) .
692  " </contents>\n";
693  } else {
694  $contents = '';
695  }
696  $uploader = $file->getUploader( File::FOR_PUBLIC );
697  if ( $uploader ) {
698  $uploader = $this->writeContributor( $uploader->getId(), $uploader->getName() );
699  } else {
700  $uploader = Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
701  }
702  $comment = $file->getDescription( File::FOR_PUBLIC );
703  if ( ( $comment ?? '' ) !== '' ) {
704  $comment = Xml::elementClean( 'comment', null, $comment );
705  } else {
706  $comment = Xml::element( 'comment', [ 'deleted' => 'deleted' ] );
707  }
708  return " <upload>\n" .
709  $this->writeTimestamp( $file->getTimestamp() ) .
710  $uploader .
711  " " . $comment . "\n" .
712  " " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
713  $archiveName .
714  " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" .
715  " " . Xml::element( 'size', null, (string)( $file->getSize() ?: 0 ) ) . "\n" .
716  " " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
717  " " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
718  $contents .
719  " </upload>\n";
720  }
721 
732  public static function canonicalTitle( Title $title ) {
733  if ( $title->isExternal() ) {
734  return $title->getPrefixedText();
735  }
736 
737  $prefix = MediaWikiServices::getInstance()->getContentLanguage()->
738  getFormattedNsText( $title->getNamespace() );
739 
740  // @todo Emit some kind of warning to the user if $title->getNamespace() !==
741  // NS_MAIN and $prefix === '' (viz. pages in an unregistered namespace)
742 
743  if ( $prefix !== '' ) {
744  $prefix .= ':';
745  }
746 
747  return $prefix . $title->getText();
748  }
749 }
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:317
const XML_DUMP_SCHEMA_VERSION_10
Definition: Defines.php:316
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getStore()
const FOR_PUBLIC
Definition: File.php:87
const DELETED_USER
Definition: LogPage.php:43
const DELETED_COMMENT
Definition: LogPage.php:42
const DELETED_ACTION
Definition: LogPage.php:41
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:569
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 representing revision data blobs.
Content object implementation for representing flat text.
Definition: TextContent.php:40
Represents a title within MediaWiki.
Definition: Title.php:52
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:703
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:3811
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:576
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
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.
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 representing page content.
Definition: Content.php:34
$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