MediaWiki  master
WikiImporter.php
Go to the documentation of this file.
1 <?php
37 use Wikimedia\NormalizedException\NormalizedException;
38 
45 class WikiImporter {
47  private $reader;
48 
50  private $foreignNamespaces = null;
51 
54 
57 
60 
62  private $mPageCallback;
63 
66 
69 
72 
74  private $mDebug;
75 
77  private $mImportUploads;
78 
80  private $mImageBasePath;
81 
83  private $mNoUpdates = false;
84 
86  private $pageOffset = 0;
87 
89  private $config;
90 
93 
95  private $hookRunner;
96 
98  private $countableCache = [];
99 
101  private $disableStatisticsUpdate = false;
102 
105 
108 
110  private $namespaceInfo;
111 
113  private $titleFactory;
114 
117 
120 
123 
126 
129 
145  public function __construct(
147  Config $config,
148  HookContainer $hookContainer,
157  ) {
158  $this->reader = new XMLReader();
159  $this->config = $config;
160  $this->hookRunner = new HookRunner( $hookContainer );
161  $this->contentLanguage = $contentLanguage;
162  $this->namespaceInfo = $namespaceInfo;
163  $this->titleFactory = $titleFactory;
164  $this->wikiPageFactory = $wikiPageFactory;
165  $this->uploadRevisionImporter = $uploadRevisionImporter;
166  $this->permissionManager = $permissionManager;
167  $this->contentHandlerFactory = $contentHandlerFactory;
168  $this->slotRoleRegistry = $slotRoleRegistry;
169 
170  if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) {
171  stream_wrapper_register( 'uploadsource', UploadSourceAdapter::class );
172  }
174 
175  // Enable the entity loader, as it is needed for loading external URLs via
176  // XMLReader::open (T86036)
177  $oldDisable = libxml_disable_entity_loader( false );
178  if ( defined( 'LIBXML_PARSEHUGE' ) ) {
179  $status = $this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
180  } else {
181  $status = $this->reader->open( "uploadsource://$id" );
182  }
183  if ( !$status ) {
184  $error = libxml_get_last_error();
185  libxml_disable_entity_loader( $oldDisable );
186  throw new MWException( 'Encountered an internal error while initializing WikiImporter object: ' .
187  $error->message );
188  }
189  libxml_disable_entity_loader( $oldDisable );
190 
191  // Default callbacks
192  $this->setPageCallback( [ $this, 'beforeImportPage' ] );
193  $this->setRevisionCallback( [ $this, "importRevision" ] );
194  $this->setUploadCallback( [ $this, 'importUpload' ] );
195  $this->setLogItemCallback( [ $this, 'importLogItem' ] );
196  $this->setPageOutCallback( [ $this, 'finishImportPage' ] );
197 
198  $this->importTitleFactory = new NaiveImportTitleFactory(
199  $this->contentLanguage,
200  $this->namespaceInfo,
201  $this->titleFactory
202  );
203  $this->externalUserNames = new ExternalUserNames( 'imported', false );
204  }
205 
209  public function getReader() {
210  return $this->reader;
211  }
212 
216  public function throwXmlError( $err ) {
217  $this->debug( "FAILURE: $err" );
218  wfDebug( "WikiImporter XML error: $err" );
219  }
220 
224  public function debug( $data ) {
225  if ( $this->mDebug ) {
226  wfDebug( "IMPORT: $data" );
227  }
228  }
229 
233  public function warn( $data ) {
234  wfDebug( "IMPORT: $data" );
235  }
236 
241  public function notice( $msg, ...$params ) {
242  if ( is_callable( $this->mNoticeCallback ) ) {
243  call_user_func( $this->mNoticeCallback, $msg, $params );
244  } else { # No ImportReporter -> CLI
245  // T177997: the command line importers should call setNoticeCallback()
246  // for their own custom callback to echo the notice
247  wfDebug( wfMessage( $msg, $params )->text() );
248  }
249  }
250 
255  public function setDebug( $debug ) {
256  $this->mDebug = $debug;
257  }
258 
263  public function setNoUpdates( $noupdates ) {
264  $this->mNoUpdates = $noupdates;
265  }
266 
273  public function setPageOffset( $nthPage ) {
274  $this->pageOffset = $nthPage;
275  }
276 
283  public function setNoticeCallback( $callback ) {
284  return wfSetVar( $this->mNoticeCallback, $callback );
285  }
286 
292  public function setPageCallback( $callback ) {
293  $previous = $this->mPageCallback;
294  $this->mPageCallback = $callback;
295  return $previous;
296  }
297 
307  public function setPageOutCallback( $callback ) {
308  $previous = $this->mPageOutCallback;
309  $this->mPageOutCallback = $callback;
310  return $previous;
311  }
312 
318  public function setRevisionCallback( $callback ) {
319  $previous = $this->mRevisionCallback;
320  $this->mRevisionCallback = $callback;
321  return $previous;
322  }
323 
329  public function setUploadCallback( $callback ) {
330  $previous = $this->mUploadCallback;
331  $this->mUploadCallback = $callback;
332  return $previous;
333  }
334 
340  public function setLogItemCallback( $callback ) {
341  $previous = $this->mLogItemCallback;
342  $this->mLogItemCallback = $callback;
343  return $previous;
344  }
345 
351  public function setSiteInfoCallback( $callback ) {
352  $previous = $this->mSiteInfoCallback;
353  $this->mSiteInfoCallback = $callback;
354  return $previous;
355  }
356 
362  public function setImportTitleFactory( $factory ) {
363  $this->importTitleFactory = $factory;
364  }
365 
371  public function setTargetNamespace( $namespace ) {
372  if ( $namespace === null ) {
373  // Don't override namespaces
374  $this->setImportTitleFactory(
376  $this->contentLanguage,
377  $this->namespaceInfo,
378  $this->titleFactory
379  )
380  );
381  return true;
382  } elseif (
383  $namespace >= 0 &&
384  $this->namespaceInfo->exists( intval( $namespace ) )
385  ) {
386  $namespace = intval( $namespace );
387  $this->setImportTitleFactory(
389  $this->namespaceInfo,
390  $this->titleFactory,
391  $namespace
392  )
393  );
394  return true;
395  } else {
396  return false;
397  }
398  }
399 
405  public function setTargetRootPage( $rootpage ) {
406  $status = Status::newGood();
407  $nsInfo = $this->namespaceInfo;
408  if ( $rootpage === null ) {
409  // No rootpage
410  $this->setImportTitleFactory(
412  $this->contentLanguage,
413  $nsInfo,
414  $this->titleFactory
415  )
416  );
417  } elseif ( $rootpage !== '' ) {
418  $rootpage = rtrim( $rootpage, '/' ); // avoid double slashes
419  $title = Title::newFromText( $rootpage );
420 
421  if ( !$title || $title->isExternal() ) {
422  $status->fatal( 'import-rootpage-invalid' );
423  } elseif ( !$nsInfo->hasSubpages( $title->getNamespace() ) ) {
424  $displayNSText = $title->getNamespace() === NS_MAIN
425  ? wfMessage( 'blanknamespace' )->text()
426  : $this->contentLanguage->getNsText( $title->getNamespace() );
427  $status->fatal( 'import-rootpage-nosubpage', $displayNSText );
428  } else {
429  // set namespace to 'all', so the namespace check in processTitle() can pass
430  $this->setTargetNamespace( null );
431  $this->setImportTitleFactory(
433  $nsInfo,
434  $this->titleFactory,
435  $title
436  )
437  );
438  }
439  }
440  return $status;
441  }
442 
446  public function setImageBasePath( $dir ) {
447  $this->mImageBasePath = $dir;
448  }
449 
453  public function setImportUploads( $import ) {
454  $this->mImportUploads = $import;
455  }
456 
462  public function setUsernamePrefix( $usernamePrefix, $assignKnownUsers ) {
463  $this->externalUserNames = new ExternalUserNames( $usernamePrefix, $assignKnownUsers );
464  }
465 
470  public function disableStatisticsUpdate() {
471  $this->disableStatisticsUpdate = true;
472  }
473 
480  public function beforeImportPage( $titleAndForeignTitle ) {
481  $title = $titleAndForeignTitle[0];
482  $page = $this->wikiPageFactory->newFromTitle( $title );
483  $this->countableCache['title_' . $title->getPrefixedText()] = $page->isCountable();
484  return true;
485  }
486 
492  public function importRevision( $revision ) {
493  if ( !$revision->getContentHandler()->canBeUsedOn( $revision->getTitle() ) ) {
494  $this->notice( 'import-error-bad-location',
495  $revision->getTitle()->getPrefixedText(),
496  $revision->getID(),
497  $revision->getModel(),
498  $revision->getFormat()
499  );
500 
501  return false;
502  }
503 
504  try {
505  return $revision->importOldRevision();
506  } catch ( MWContentSerializationException $ex ) {
507  $this->notice( 'import-error-unserialize',
508  $revision->getTitle()->getPrefixedText(),
509  $revision->getID(),
510  $revision->getModel(),
511  $revision->getFormat()
512  );
513  }
514 
515  return false;
516  }
517 
523  public function importLogItem( $revision ) {
524  return $revision->importLogItem();
525  }
526 
532  public function importUpload( $revision ) {
533  $status = $this->uploadRevisionImporter->import( $revision );
534  return $status->isGood();
535  }
536 
546  public function finishImportPage( PageIdentity $pageIdentity, $foreignTitle, $revCount,
547  $sRevCount, $pageInfo
548  ) {
549  // Update article count statistics (T42009)
550  // The normal counting logic in WikiPage->doEditUpdates() is designed for
551  // one-revision-at-a-time editing, not bulk imports. In this situation it
552  // suffers from issues of replica DB lag. We let WikiPage handle the total page
553  // and revision count, and we implement our own custom logic for the
554  // article (content page) count.
555  if ( !$this->disableStatisticsUpdate ) {
556  $page = $this->wikiPageFactory->newFromTitle( $pageIdentity );
557 
558  $page->loadPageData( WikiPage::READ_LATEST );
559  $rev = $page->getRevisionRecord();
560  if ( $rev === null ) {
561 
562  wfDebug( __METHOD__ . ': Skipping article count adjustment for ' . $pageIdentity .
563  ' because WikiPage::getRevisionRecord() returned null' );
564  } else {
565  $user = RequestContext::getMain()->getUser();
566  $update = $page->newPageUpdater( $user )->prepareUpdate();
567  $countKey = 'title_' . CacheKeyHelper::getKeyForPage( $pageIdentity );
568  $countable = $update->isCountable();
569  if ( array_key_exists( $countKey, $this->countableCache ) &&
570  $countable != $this->countableCache[$countKey] ) {
572  'articles' => ( (int)$countable - (int)$this->countableCache[$countKey] )
573  ] ) );
574  }
575  }
576  }
577 
578  $title = Title::castFromPageIdentity( $pageIdentity );
579  return $this->hookRunner->onAfterImportPage( $title, $foreignTitle,
580  $revCount, $sRevCount, $pageInfo );
581  }
582 
588  private function siteInfoCallback( $siteInfo ) {
589  if ( isset( $this->mSiteInfoCallback ) ) {
590  return call_user_func_array(
591  $this->mSiteInfoCallback,
592  [ $siteInfo, $this ]
593  );
594  } else {
595  return false;
596  }
597  }
598 
603  public function pageCallback( $title ) {
604  if ( isset( $this->mPageCallback ) ) {
605  call_user_func( $this->mPageCallback, $title );
606  }
607  }
608 
617  private function pageOutCallback( PageIdentity $pageIdentity, $foreignTitle, $revCount,
618  $sucCount, $pageInfo ) {
619  if ( isset( $this->mPageOutCallback ) ) {
620  call_user_func_array( $this->mPageOutCallback, func_get_args() );
621  }
622  }
623 
629  private function revisionCallback( $revision ) {
630  if ( isset( $this->mRevisionCallback ) ) {
631  return call_user_func_array(
632  $this->mRevisionCallback,
633  [ $revision, $this ]
634  );
635  } else {
636  return false;
637  }
638  }
639 
645  private function logItemCallback( $revision ) {
646  if ( isset( $this->mLogItemCallback ) ) {
647  return call_user_func_array(
648  $this->mLogItemCallback,
649  [ $revision, $this ]
650  );
651  } else {
652  return false;
653  }
654  }
655 
662  public function nodeAttribute( $attr ) {
663  return $this->reader->getAttribute( $attr );
664  }
665 
673  public function nodeContents() {
674  if ( $this->reader->isEmptyElement ) {
675  return "";
676  }
677  $buffer = "";
678  while ( $this->reader->read() ) {
679  switch ( $this->reader->nodeType ) {
680  case XMLReader::TEXT:
681  case XMLReader::CDATA:
682  case XMLReader::SIGNIFICANT_WHITESPACE:
683  $buffer .= $this->reader->value;
684  break;
685  case XMLReader::END_ELEMENT:
686  return $buffer;
687  }
688  }
689 
690  $this->reader->close();
691  return '';
692  }
693 
700  public function doImport() {
701  // Calls to reader->read need to be wrapped in calls to
702  // libxml_disable_entity_loader() to avoid local file
703  // inclusion attacks (T48932).
704  $oldDisable = libxml_disable_entity_loader( true );
705  try {
706  $this->reader->read();
707 
708  if ( $this->reader->localName != 'mediawiki' ) {
709  libxml_disable_entity_loader( $oldDisable );
710  $error = libxml_get_last_error();
711  if ( $error ) {
712  throw new NormalizedException( "XML error at line {line}: {message}", [
713  'line' => $error->line,
714  'message' => $error->message,
715  ] );
716  } else {
717  throw new MWException( "Expected <mediawiki> tag, got " .
718  $this->reader->localName );
719  }
720  }
721  $this->debug( "<mediawiki> tag is correct." );
722 
723  $this->debug( "Starting primary dump processing loop." );
724 
725  $keepReading = $this->reader->read();
726  $skip = false;
727  $pageCount = 0;
728  while ( $keepReading ) {
729  $tag = $this->reader->localName;
730  if ( $this->pageOffset ) {
731  if ( $tag === 'page' ) {
732  $pageCount++;
733  }
734  if ( $pageCount < $this->pageOffset ) {
735  $keepReading = $this->reader->next();
736  continue;
737  }
738  }
739  $type = $this->reader->nodeType;
740 
741  if ( !$this->hookRunner->onImportHandleToplevelXMLTag( $this ) ) {
742  // Do nothing
743  } elseif ( $tag == 'mediawiki' && $type == XMLReader::END_ELEMENT ) {
744  break;
745  } elseif ( $tag == 'siteinfo' ) {
746  $this->handleSiteInfo();
747  } elseif ( $tag == 'page' ) {
748  $this->handlePage();
749  } elseif ( $tag == 'logitem' ) {
750  $this->handleLogItem();
751  } elseif ( $tag != '#text' ) {
752  $this->warn( "Unhandled top-level XML tag $tag" );
753 
754  $skip = true;
755  }
756 
757  if ( $skip ) {
758  $keepReading = $this->reader->next();
759  $skip = false;
760  $this->debug( "Skip" );
761  } else {
762  $keepReading = $this->reader->read();
763  }
764  }
765  } finally {
766  libxml_disable_entity_loader( $oldDisable );
767  $this->reader->close();
768  }
769 
770  return true;
771  }
772 
773  private function handleSiteInfo() {
774  $this->debug( "Enter site info handler." );
775  $siteInfo = [];
776 
777  // Fields that can just be stuffed in the siteInfo object
778  $normalFields = [ 'sitename', 'base', 'generator', 'case' ];
779 
780  while ( $this->reader->read() ) {
781  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
782  $this->reader->localName == 'siteinfo' ) {
783  break;
784  }
785 
786  $tag = $this->reader->localName;
787 
788  if ( $tag == 'namespace' ) {
789  $this->foreignNamespaces[$this->nodeAttribute( 'key' )] =
790  $this->nodeContents();
791  } elseif ( in_array( $tag, $normalFields ) ) {
792  $siteInfo[$tag] = $this->nodeContents();
793  }
794  }
795 
796  $siteInfo['_namespaces'] = $this->foreignNamespaces;
797  $this->siteInfoCallback( $siteInfo );
798  }
799 
800  private function handleLogItem() {
801  $this->debug( "Enter log item handler." );
802  $logInfo = [];
803 
804  // Fields that can just be stuffed in the pageInfo object
805  $normalFields = [ 'id', 'comment', 'type', 'action', 'timestamp',
806  'logtitle', 'params' ];
807 
808  while ( $this->reader->read() ) {
809  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
810  $this->reader->localName == 'logitem' ) {
811  break;
812  }
813 
814  $tag = $this->reader->localName;
815 
816  if ( !$this->hookRunner->onImportHandleLogItemXMLTag( $this, $logInfo ) ) {
817  // Do nothing
818  } elseif ( in_array( $tag, $normalFields ) ) {
819  $logInfo[$tag] = $this->nodeContents();
820  } elseif ( $tag == 'contributor' ) {
821  $logInfo['contributor'] = $this->handleContributor();
822  } elseif ( $tag != '#text' ) {
823  $this->warn( "Unhandled log-item XML tag $tag" );
824  }
825  }
826 
827  $this->processLogItem( $logInfo );
828  }
829 
834  private function processLogItem( $logInfo ) {
835  $revision = new WikiRevision( $this->config );
836 
837  if ( isset( $logInfo['id'] ) ) {
838  $revision->setID( $logInfo['id'] );
839  }
840  $revision->setType( $logInfo['type'] );
841  $revision->setAction( $logInfo['action'] );
842  if ( isset( $logInfo['timestamp'] ) ) {
843  $revision->setTimestamp( $logInfo['timestamp'] );
844  }
845  if ( isset( $logInfo['params'] ) ) {
846  $revision->setParams( $logInfo['params'] );
847  }
848  if ( isset( $logInfo['logtitle'] ) ) {
849  // @todo Using Title for non-local titles is a recipe for disaster.
850  // We should use ForeignTitle here instead.
851  $revision->setTitle( Title::newFromText( $logInfo['logtitle'] ) );
852  }
853 
854  $revision->setNoUpdates( $this->mNoUpdates );
855 
856  if ( isset( $logInfo['comment'] ) ) {
857  $revision->setComment( $logInfo['comment'] );
858  }
859 
860  if ( isset( $logInfo['contributor']['ip'] ) ) {
861  $revision->setUserIP( $logInfo['contributor']['ip'] );
862  }
863 
864  if ( !isset( $logInfo['contributor']['username'] ) ) {
865  $revision->setUsername( $this->externalUserNames->addPrefix( 'Unknown user' ) );
866  } else {
867  $revision->setUsername(
868  $this->externalUserNames->applyPrefix( $logInfo['contributor']['username'] )
869  );
870  }
871 
872  return $this->logItemCallback( $revision );
873  }
874 
875  private function handlePage() {
876  // Handle page data.
877  $this->debug( "Enter page handler." );
878  $pageInfo = [ 'revisionCount' => 0, 'successfulRevisionCount' => 0 ];
879 
880  // Fields that can just be stuffed in the pageInfo object
881  $normalFields = [ 'title', 'ns', 'id', 'redirect', 'restrictions' ];
882 
883  $skip = false;
884  $badTitle = false;
885 
886  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
887  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
888  $this->reader->localName == 'page' ) {
889  break;
890  }
891 
892  $skip = false;
893 
894  $tag = $this->reader->localName;
895 
896  if ( $badTitle ) {
897  // The title is invalid, bail out of this page
898  $skip = true;
899  } elseif ( !$this->hookRunner->onImportHandlePageXMLTag( $this, $pageInfo ) ) {
900  // Do nothing
901  } elseif ( in_array( $tag, $normalFields ) ) {
902  // An XML snippet:
903  // <page>
904  // <id>123</id>
905  // <title>Page</title>
906  // <redirect title="NewTitle"/>
907  // ...
908  // Because the redirect tag is built differently, we need special handling for that case.
909  if ( $tag == 'redirect' ) {
910  $pageInfo[$tag] = $this->nodeAttribute( 'title' );
911  } else {
912  $pageInfo[$tag] = $this->nodeContents();
913  }
914  } elseif ( $tag == 'revision' || $tag == 'upload' ) {
915  if ( !isset( $title ) ) {
916  $title = $this->processTitle( $pageInfo['title'],
917  $pageInfo['ns'] ?? null );
918 
919  // $title is either an array of two titles or false.
920  if ( is_array( $title ) ) {
921  $this->pageCallback( $title );
922  list( $pageInfo['_title'], $foreignTitle ) = $title;
923  } else {
924  $badTitle = true;
925  $skip = true;
926  }
927  }
928 
929  if ( $title ) {
930  if ( $tag == 'revision' ) {
931  $this->handleRevision( $pageInfo );
932  } else {
933  $this->handleUpload( $pageInfo );
934  }
935  }
936  } elseif ( $tag != '#text' ) {
937  $this->warn( "Unhandled page XML tag $tag" );
938  $skip = true;
939  }
940  }
941 
942  // @note $pageInfo is only set if a valid $title is processed above with
943  // no error. If we have a valid $title, then pageCallback is called
944  // above, $pageInfo['title'] is set and we do pageOutCallback here.
945  // If $pageInfo['_title'] is not set, then $foreignTitle is also not
946  // set since they both come from $title above.
947  if ( array_key_exists( '_title', $pageInfo ) ) {
949  $title = $pageInfo['_title'];
950  $this->pageOutCallback(
951  $title,
952  $foreignTitle,
953  $pageInfo['revisionCount'],
954  $pageInfo['successfulRevisionCount'],
955  $pageInfo
956  );
957  }
958  }
959 
963  private function handleRevision( &$pageInfo ) {
964  $this->debug( "Enter revision handler" );
965  $revisionInfo = [];
966 
967  $normalFields = [ 'id', 'parentid', 'timestamp', 'comment', 'minor', 'origin',
968  'model', 'format', 'text', 'sha1' ];
969 
970  $skip = false;
971 
972  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
973  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
974  $this->reader->localName == 'revision' ) {
975  break;
976  }
977 
978  $tag = $this->reader->localName;
979 
980  if ( !$this->hookRunner->onImportHandleRevisionXMLTag(
981  $this, $pageInfo, $revisionInfo )
982  ) {
983  // Do nothing
984  } elseif ( in_array( $tag, $normalFields ) ) {
985  $revisionInfo[$tag] = $this->nodeContents();
986  } elseif ( $tag == 'content' ) {
987  // We can have multiple content tags, so make this an array.
988  $revisionInfo[$tag][] = $this->handleContent();
989  } elseif ( $tag == 'contributor' ) {
990  $revisionInfo['contributor'] = $this->handleContributor();
991  } elseif ( $tag != '#text' ) {
992  $this->warn( "Unhandled revision XML tag $tag" );
993  $skip = true;
994  }
995  }
996 
997  $pageInfo['revisionCount']++;
998  if ( $this->processRevision( $pageInfo, $revisionInfo ) ) {
999  $pageInfo['successfulRevisionCount']++;
1000  }
1001  }
1002 
1003  private function handleContent() {
1004  $this->debug( "Enter content handler" );
1005  $contentInfo = [];
1006 
1007  $normalFields = [ 'role', 'origin', 'model', 'format', 'text' ];
1008 
1009  $skip = false;
1010 
1011  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
1012  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1013  $this->reader->localName == 'content' ) {
1014  break;
1015  }
1016 
1017  $tag = $this->reader->localName;
1018 
1019  if ( !$this->hookRunner->onImportHandleContentXMLTag(
1020  $this, $contentInfo )
1021  ) {
1022  // Do nothing
1023  } elseif ( in_array( $tag, $normalFields ) ) {
1024  $contentInfo[$tag] = $this->nodeContents();
1025  } elseif ( $tag != '#text' ) {
1026  $this->warn( "Unhandled content XML tag $tag" );
1027  $skip = true;
1028  }
1029  }
1030 
1031  return $contentInfo;
1032  }
1033 
1042  private function makeContent( Title $title, $revisionId, $contentInfo ) {
1043  $maxArticleSize = MediaWikiServices::getInstance()->getMainConfig()->get( 'MaxArticleSize' );
1044 
1045  if ( !isset( $contentInfo['text'] ) ) {
1046  throw new MWException( 'Missing text field in import.' );
1047  }
1048 
1049  // Make sure revisions won't violate $wgMaxArticleSize, which could lead to
1050  // database errors and instability. Testing for revisions with only listed
1051  // content models, as other content models might use serialization formats
1052  // which aren't checked against $wgMaxArticleSize.
1053  if ( ( !isset( $contentInfo['model'] ) ||
1054  in_array( $contentInfo['model'], [
1055  'wikitext',
1056  'css',
1057  'json',
1058  'javascript',
1059  'text',
1060  ''
1061  ] ) ) &&
1062  strlen( $contentInfo['text'] ) > $maxArticleSize * 1024
1063  ) {
1064  throw new MWException( 'The text of ' .
1065  ( $revisionId ?
1066  "the revision with ID $revisionId" :
1067  'a revision'
1068  ) . " exceeds the maximum allowable size ({$maxArticleSize} KiB)" );
1069  }
1070 
1071  $role = $contentInfo['role'] ?? SlotRecord::MAIN;
1072  $model = $contentInfo['model'] ?? $this->getDefaultContentModel( $title, $role );
1073  $handler = $this->getContentHandler( $model );
1074 
1075  $text = $handler->importTransform( $contentInfo['text'] );
1076 
1077  return $handler->unserializeContent( $text );
1078  }
1079 
1086  private function processRevision( $pageInfo, $revisionInfo ) {
1087  $revision = new WikiRevision( $this->config );
1088 
1089  $revId = $revisionInfo['id'] ?? 0;
1090  if ( $revId ) {
1091  $revision->setID( $revisionInfo['id'] );
1092  }
1093 
1094  $title = $pageInfo['_title'];
1095  $revision->setTitle( $title );
1096 
1097  $content = $this->makeContent( $title, $revId, $revisionInfo );
1098  $revision->setContent( SlotRecord::MAIN, $content );
1099 
1100  foreach ( $revisionInfo['content'] ?? [] as $slotInfo ) {
1101  if ( !isset( $slotInfo['role'] ) ) {
1102  throw new MWException( "Missing role for imported slot." );
1103  }
1104 
1105  $content = $this->makeContent( $title, $revId, $slotInfo );
1106  $revision->setContent( $slotInfo['role'], $content );
1107  }
1108  $revision->setTimestamp( $revisionInfo['timestamp'] ?? wfTimestampNow() );
1109 
1110  if ( isset( $revisionInfo['comment'] ) ) {
1111  $revision->setComment( $revisionInfo['comment'] );
1112  }
1113 
1114  if ( isset( $revisionInfo['minor'] ) ) {
1115  $revision->setMinor( true );
1116  }
1117  if ( isset( $revisionInfo['contributor']['ip'] ) ) {
1118  $revision->setUserIP( $revisionInfo['contributor']['ip'] );
1119  } elseif ( isset( $revisionInfo['contributor']['username'] ) ) {
1120  $revision->setUsername(
1121  $this->externalUserNames->applyPrefix( $revisionInfo['contributor']['username'] )
1122  );
1123  } else {
1124  $revision->setUsername( $this->externalUserNames->addPrefix( 'Unknown user' ) );
1125  }
1126  if ( isset( $revisionInfo['sha1'] ) ) {
1127  $revision->setSha1Base36( $revisionInfo['sha1'] );
1128  }
1129  $revision->setNoUpdates( $this->mNoUpdates );
1130 
1131  return $this->revisionCallback( $revision );
1132  }
1133 
1138  private function handleUpload( &$pageInfo ) {
1139  $this->debug( "Enter upload handler" );
1140  $uploadInfo = [];
1141 
1142  $normalFields = [ 'timestamp', 'comment', 'filename', 'text',
1143  'src', 'size', 'sha1base36', 'archivename', 'rel' ];
1144 
1145  $skip = false;
1146 
1147  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
1148  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1149  $this->reader->localName == 'upload' ) {
1150  break;
1151  }
1152 
1153  $tag = $this->reader->localName;
1154 
1155  if ( !$this->hookRunner->onImportHandleUploadXMLTag( $this, $pageInfo ) ) {
1156  // Do nothing
1157  } elseif ( in_array( $tag, $normalFields ) ) {
1158  $uploadInfo[$tag] = $this->nodeContents();
1159  } elseif ( $tag == 'contributor' ) {
1160  $uploadInfo['contributor'] = $this->handleContributor();
1161  } elseif ( $tag == 'contents' ) {
1162  $contents = $this->nodeContents();
1163  $encoding = $this->reader->getAttribute( 'encoding' );
1164  if ( $encoding === 'base64' ) {
1165  $uploadInfo['fileSrc'] = $this->dumpTemp( base64_decode( $contents ) );
1166  $uploadInfo['isTempSrc'] = true;
1167  }
1168  } elseif ( $tag != '#text' ) {
1169  $this->warn( "Unhandled upload XML tag $tag" );
1170  $skip = true;
1171  }
1172  }
1173 
1174  if ( $this->mImageBasePath && isset( $uploadInfo['rel'] ) ) {
1175  $path = "{$this->mImageBasePath}/{$uploadInfo['rel']}";
1176  if ( file_exists( $path ) ) {
1177  $uploadInfo['fileSrc'] = $path;
1178  $uploadInfo['isTempSrc'] = false;
1179  }
1180  }
1181 
1182  if ( $this->mImportUploads ) {
1183  return $this->processUpload( $pageInfo, $uploadInfo );
1184  }
1185  }
1186 
1191  private function dumpTemp( $contents ) {
1192  $filename = tempnam( wfTempDir(), 'importupload' );
1193  file_put_contents( $filename, $contents );
1194  return $filename;
1195  }
1196 
1202  private function processUpload( $pageInfo, $uploadInfo ) {
1203  $revision = new WikiRevision( $this->config );
1204  $revId = $pageInfo['id'];
1205  $title = $pageInfo['_title'];
1206  $content = $this->makeContent( $title, $revId, $uploadInfo );
1207 
1208  $revision->setTitle( $title );
1209  $revision->setID( $revId );
1210  $revision->setTimestamp( $uploadInfo['timestamp'] );
1211  $revision->setContent( SlotRecord::MAIN, $content );
1212  $revision->setFilename( $uploadInfo['filename'] );
1213  if ( isset( $uploadInfo['archivename'] ) ) {
1214  $revision->setArchiveName( $uploadInfo['archivename'] );
1215  }
1216  $revision->setSrc( $uploadInfo['src'] );
1217  if ( isset( $uploadInfo['fileSrc'] ) ) {
1218  $revision->setFileSrc( $uploadInfo['fileSrc'],
1219  !empty( $uploadInfo['isTempSrc'] )
1220  );
1221  }
1222  if ( isset( $uploadInfo['sha1base36'] ) ) {
1223  $revision->setSha1Base36( $uploadInfo['sha1base36'] );
1224  }
1225  $revision->setSize( intval( $uploadInfo['size'] ) );
1226  $revision->setComment( $uploadInfo['comment'] );
1227 
1228  if ( isset( $uploadInfo['contributor']['ip'] ) ) {
1229  $revision->setUserIP( $uploadInfo['contributor']['ip'] );
1230  }
1231  if ( isset( $uploadInfo['contributor']['username'] ) ) {
1232  $revision->setUsername(
1233  $this->externalUserNames->applyPrefix( $uploadInfo['contributor']['username'] )
1234  );
1235  }
1236  $revision->setNoUpdates( $this->mNoUpdates );
1237 
1238  return call_user_func( $this->mUploadCallback, $revision );
1239  }
1240 
1244  private function handleContributor() {
1245  $this->debug( "Enter contributor handler." );
1246 
1247  if ( $this->reader->isEmptyElement ) {
1248  return [];
1249  }
1250 
1251  $fields = [ 'id', 'ip', 'username' ];
1252  $info = [];
1253 
1254  while ( $this->reader->read() ) {
1255  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1256  $this->reader->localName == 'contributor' ) {
1257  break;
1258  }
1259 
1260  $tag = $this->reader->localName;
1261 
1262  if ( in_array( $tag, $fields ) ) {
1263  $info[$tag] = $this->nodeContents();
1264  }
1265  }
1266 
1267  return $info;
1268  }
1269 
1275  private function processTitle( $text, $ns = null ) {
1276  if ( $this->foreignNamespaces === null ) {
1277  $foreignTitleFactory = new NaiveForeignTitleFactory(
1278  $this->contentLanguage
1279  );
1280  } else {
1281  $foreignTitleFactory = new NamespaceAwareForeignTitleFactory(
1282  $this->foreignNamespaces );
1283  }
1284 
1285  $foreignTitle = $foreignTitleFactory->createForeignTitle( $text,
1286  intval( $ns ) );
1287 
1288  $title = $this->importTitleFactory->createTitleFromForeignTitle(
1289  $foreignTitle );
1290 
1291  $commandLineMode = $this->config->get( 'CommandLineMode' );
1292  if ( $title === null ) {
1293  # Invalid page title? Ignore the page
1294  $this->notice( 'import-error-invalid', $foreignTitle->getFullText() );
1295  return false;
1296  } elseif ( $title->isExternal() ) {
1297  $this->notice( 'import-error-interwiki', $title->getPrefixedText() );
1298  return false;
1299  } elseif ( !$title->canExist() ) {
1300  $this->notice( 'import-error-special', $title->getPrefixedText() );
1301  return false;
1302  } elseif ( !$commandLineMode ) {
1303  $user = RequestContext::getMain()->getUser();
1304 
1305  if ( !$this->permissionManager->userCan( 'edit', $user, $title ) ) {
1306  # Do not import if the importing wiki user cannot edit this page
1307  $this->notice( 'import-error-edit', $title->getPrefixedText() );
1308 
1309  return false;
1310  }
1311  }
1312 
1313  return [ $title, $foreignTitle ];
1314  }
1315 
1320  private function getContentHandler( $model ) {
1321  return $this->contentHandlerFactory->getContentHandler( $model );
1322  }
1323 
1330  private function getDefaultContentModel( $title, $role ) {
1331  return $this->slotRoleRegistry
1332  ->getRoleHandler( $role )
1333  ->getDefaultModel( $title );
1334  }
1335 }
NaiveImportTitleFactory
A class to convert page titles on a foreign wiki (ForeignTitle objects) into page titles on the local...
Definition: NaiveImportTitleFactory.php:32
WikiImporter\processRevision
processRevision( $pageInfo, $revisionInfo)
Definition: WikiImporter.php:1086
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
WikiImporter\$titleFactory
TitleFactory $titleFactory
Definition: WikiImporter.php:113
WikiImporter
XML file reader for the page data importer.
Definition: WikiImporter.php:45
WikiImporter\makeContent
makeContent(Title $title, $revisionId, $contentInfo)
Definition: WikiImporter.php:1042
WikiImporter\$contentLanguage
Language $contentLanguage
Definition: WikiImporter.php:107
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:377
UploadRevisionImporter
Definition: UploadRevisionImporter.php:6
WikiImporter\setImageBasePath
setImageBasePath( $dir)
Definition: WikiImporter.php:446
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
wfSetVar
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
Definition: GlobalFunctions.php:1496
WikiImporter\$mImportUploads
bool null $mImportUploads
Definition: WikiImporter.php:77
UploadSourceAdapter\registerSource
static registerSource(ImportSource $source)
Definition: UploadSourceAdapter.php:48
WikiImporter\$mPageCallback
callable null $mPageCallback
Definition: WikiImporter.php:62
NamespaceAwareForeignTitleFactory
A parser that translates page titles on a foreign wiki into ForeignTitle objects, using information a...
Definition: NamespaceAwareForeignTitleFactory.php:25
WikiImporter\revisionCallback
revisionCallback( $revision)
Notify the callback function of a revision.
Definition: WikiImporter.php:629
WikiImporter\setNoticeCallback
setNoticeCallback( $callback)
Set a callback that displays notice messages.
Definition: WikiImporter.php:283
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:125
NaiveForeignTitleFactory
A parser that translates page titles on a foreign wiki into ForeignTitle objects, with no knowledge o...
Definition: NaiveForeignTitleFactory.php:25
WikiImporter\setNoUpdates
setNoUpdates( $noupdates)
Set 'no updates' mode.
Definition: WikiImporter.php:263
WikiImporter\getReader
getReader()
Definition: WikiImporter.php:209
ExternalUserNames
Class to parse and build external user names.
Definition: ExternalUserNames.php:30
WikiImporter\processLogItem
processLogItem( $logInfo)
Definition: WikiImporter.php:834
WikiImporter\handleRevision
handleRevision(&$pageInfo)
Definition: WikiImporter.php:963
WikiImporter\setRevisionCallback
setRevisionCallback( $callback)
Sets the action to perform as each page revision is reached.
Definition: WikiImporter.php:318
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
WikiImporter\$mSiteInfoCallback
callable null $mSiteInfoCallback
Definition: WikiImporter.php:65
WikiImporter\$foreignNamespaces
array null $foreignNamespaces
Definition: WikiImporter.php:50
WikiImporter\handleContributor
handleContributor()
Definition: WikiImporter.php:1244
WikiImporter\setUsernamePrefix
setUsernamePrefix( $usernamePrefix, $assignKnownUsers)
Definition: WikiImporter.php:462
ImportReporter
Reporting callback.
Definition: ImportReporter.php:29
WikiImporter\$externalUserNames
ExternalUserNames $externalUserNames
Definition: WikiImporter.php:104
NamespaceImportTitleFactory
A class to convert page titles on a foreign wiki (ForeignTitle objects) into page titles on the local...
Definition: NamespaceImportTitleFactory.php:26
WikiImporter\nodeContents
nodeContents()
Shouldn't something like this be built-in to XMLReader? Fetches text contents of the current element,...
Definition: WikiImporter.php:673
WikiImporter\siteInfoCallback
siteInfoCallback( $siteInfo)
Notify the callback function of site info.
Definition: WikiImporter.php:588
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
Title\castFromPageIdentity
static castFromPageIdentity(?PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Definition: Title.php:326
$debug
$debug
Definition: mcc.php:31
WikiImporter\getContentHandler
getContentHandler( $model)
Definition: WikiImporter.php:1320
Config
Interface for configuration instances.
Definition: Config.php:30
WikiImporter\$uploadRevisionImporter
UploadRevisionImporter $uploadRevisionImporter
Definition: WikiImporter.php:119
MWException
MediaWiki exception.
Definition: MWException.php:29
ImportTitleFactory
Represents an object that can convert page titles on a foreign wiki (ForeignTitle objects) into page ...
Definition: ImportTitleFactory.php:25
WikiImporter\$hookRunner
HookRunner $hookRunner
Definition: WikiImporter.php:95
WikiImporter\finishImportPage
finishImportPage(PageIdentity $pageIdentity, $foreignTitle, $revCount, $sRevCount, $pageInfo)
Mostly for hook use.
Definition: WikiImporter.php:546
WikiImporter\dumpTemp
dumpTemp( $contents)
Definition: WikiImporter.php:1191
WikiImporter\$countableCache
array $countableCache
Definition: WikiImporter.php:98
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:8
WikiImporter\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: WikiImporter.php:125
WikiImporter\__construct
__construct(ImportSource $source, Config $config, HookContainer $hookContainer, Language $contentLanguage, NamespaceInfo $namespaceInfo, TitleFactory $titleFactory, WikiPageFactory $wikiPageFactory, UploadRevisionImporter $uploadRevisionImporter, PermissionManager $permissionManager, IContentHandlerFactory $contentHandlerFactory, SlotRoleRegistry $slotRoleRegistry)
Creates an ImportXMLReader drawing from the source provided.
Definition: WikiImporter.php:145
WikiImporter\throwXmlError
throwXmlError( $err)
Definition: WikiImporter.php:216
Page\WikiPageFactory
Definition: WikiPageFactory.php:19
SubpageImportTitleFactory
A class to convert page titles on a foreign wiki (ForeignTitle objects) into page titles on the local...
Definition: SubpageImportTitleFactory.php:26
$title
$title
Definition: testCompression.php:38
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:71
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1678
WikiImporter\$importTitleFactory
ImportTitleFactory $importTitleFactory
Definition: WikiImporter.php:92
WikiImporter\processUpload
processUpload( $pageInfo, $uploadInfo)
Definition: WikiImporter.php:1202
WikiImporter\disableStatisticsUpdate
disableStatisticsUpdate()
Statistics update can cause a lot of time.
Definition: WikiImporter.php:470
WikiImporter\setImportUploads
setImportUploads( $import)
Definition: WikiImporter.php:453
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
WikiImporter\$mPageOutCallback
callable null $mPageOutCallback
Definition: WikiImporter.php:68
WikiImporter\$namespaceInfo
NamespaceInfo $namespaceInfo
Definition: WikiImporter.php:110
WikiImporter\$wikiPageFactory
WikiPageFactory $wikiPageFactory
Definition: WikiImporter.php:116
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:52
$content
$content
Definition: router.php:76
WikiImporter\$mUploadCallback
callable $mUploadCallback
Definition: WikiImporter.php:56
WikiImporter\beforeImportPage
beforeImportPage( $titleAndForeignTitle)
Default per-page callback.
Definition: WikiImporter.php:480
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
WikiImporter\importRevision
importRevision( $revision)
Default per-revision callback, performs the import.
Definition: WikiImporter.php:492
WikiImporter\doImport
doImport()
Primary entry point.
Definition: WikiImporter.php:700
WikiImporter\processTitle
processTitle( $text, $ns=null)
Definition: WikiImporter.php:1275
WikiImporter\$mRevisionCallback
callable null $mRevisionCallback
Definition: WikiImporter.php:59
WikiImporter\setDebug
setDebug( $debug)
Set debug mode...
Definition: WikiImporter.php:255
WikiImporter\notice
notice( $msg,... $params)
Definition: WikiImporter.php:241
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
WikiImporter\handleUpload
handleUpload(&$pageInfo)
Definition: WikiImporter.php:1138
WikiImporter\setUploadCallback
setUploadCallback( $callback)
Sets the action to perform as each file upload version is reached.
Definition: WikiImporter.php:329
WikiImporter\warn
warn( $data)
Definition: WikiImporter.php:233
WikiImporter\handleSiteInfo
handleSiteInfo()
Definition: WikiImporter.php:773
WikiImporter\$slotRoleRegistry
SlotRoleRegistry $slotRoleRegistry
Definition: WikiImporter.php:128
WikiImporter\setTargetRootPage
setTargetRootPage( $rootpage)
Set a target root page under which all pages are imported.
Definition: WikiImporter.php:405
Title
Represents a title within MediaWiki.
Definition: Title.php:47
WikiImporter\$mNoUpdates
bool $mNoUpdates
Definition: WikiImporter.php:83
wfTempDir
wfTempDir()
Tries to get the system directory for temporary files.
Definition: GlobalFunctions.php:1712
WikiRevision
Represents a revision, log entry or upload during the import process.
Definition: WikiRevision.php:39
WikiImporter\debug
debug( $data)
Definition: WikiImporter.php:224
WikiImporter\importLogItem
importLogItem( $revision)
Default per-revision callback, performs the import.
Definition: WikiImporter.php:523
WikiImporter\handleContent
handleContent()
Definition: WikiImporter.php:1003
WikiImporter\$mNoticeCallback
callable null $mNoticeCallback
Definition: WikiImporter.php:71
WikiImporter\importUpload
importUpload( $revision)
Dummy for now...
Definition: WikiImporter.php:532
WikiImporter\setPageOffset
setPageOffset( $nthPage)
Sets 'pageOffset' value.
Definition: WikiImporter.php:273
WikiImporter\handleLogItem
handleLogItem()
Definition: WikiImporter.php:800
ImportSource
Source interface for XML import.
Definition: ImportSource.php:32
$path
$path
Definition: NoLocalSettings.php:25
TitleFactory
Creates Title objects.
Definition: TitleFactory.php:35
MediaWiki\Cache\CacheKeyHelper
Helper class for mapping value objects representing basic entities to cache keys.
Definition: CacheKeyHelper.php:43
WikiImporter\$config
Config $config
Definition: WikiImporter.php:89
WikiImporter\setSiteInfoCallback
setSiteInfoCallback( $callback)
Sets the action to perform when site info is encountered.
Definition: WikiImporter.php:351
WikiImporter\getDefaultContentModel
getDefaultContentModel( $title, $role)
Definition: WikiImporter.php:1330
WikiImporter\setPageOutCallback
setPageOutCallback( $callback)
Sets the action to perform as each page in the stream is completed.
Definition: WikiImporter.php:307
WikiImporter\$disableStatisticsUpdate
bool $disableStatisticsUpdate
Definition: WikiImporter.php:101
$source
$source
Definition: mwdoc-filter.php:34
WikiImporter\$reader
XMLReader $reader
Definition: WikiImporter.php:47
MediaWiki\Revision\SlotRoleRegistry
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Definition: SlotRoleRegistry.php:48
WikiImporter\handlePage
handlePage()
Definition: WikiImporter.php:875
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
WikiImporter\$pageOffset
int $pageOffset
Definition: WikiImporter.php:86
WikiImporter\nodeAttribute
nodeAttribute( $attr)
Retrieves the contents of the named attribute of the current element.
Definition: WikiImporter.php:662
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:557
WikiImporter\setPageCallback
setPageCallback( $callback)
Sets the action to perform as each new page in the stream is reached.
Definition: WikiImporter.php:292
WikiImporter\setImportTitleFactory
setImportTitleFactory( $factory)
Sets the factory object to use to convert ForeignTitle objects into local Title objects.
Definition: WikiImporter.php:362
WikiImporter\setTargetNamespace
setTargetNamespace( $namespace)
Set a target namespace to override the defaults.
Definition: WikiImporter.php:371
WikiImporter\$mDebug
bool null $mDebug
Definition: WikiImporter.php:74
WikiImporter\logItemCallback
logItemCallback( $revision)
Notify the callback function of a new log item.
Definition: WikiImporter.php:645
WikiImporter\pageOutCallback
pageOutCallback(PageIdentity $pageIdentity, $foreignTitle, $revCount, $sucCount, $pageInfo)
Notify the callback function when a "</page>" is closed.
Definition: WikiImporter.php:617
WikiImporter\pageCallback
pageCallback( $title)
Notify the callback function when a new "<page>" is reached.
Definition: WikiImporter.php:603
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
WikiImporter\$permissionManager
PermissionManager $permissionManager
Definition: WikiImporter.php:122
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
WikiImporter\$mImageBasePath
string null $mImageBasePath
Definition: WikiImporter.php:80
WikiImporter\setLogItemCallback
setLogItemCallback( $callback)
Sets the action to perform as each log item reached.
Definition: WikiImporter.php:340
WikiImporter\$mLogItemCallback
callable $mLogItemCallback
Definition: WikiImporter.php:53
$type
$type
Definition: testCompression.php:52