MediaWiki  master
WikiImporter.php
Go to the documentation of this file.
1 <?php
36 
43 class WikiImporter {
45  private $reader;
46 
48  private $foreignNamespaces = null;
49 
52 
55 
58 
60  private $mPageCallback;
61 
64 
67 
70 
72  private $mDebug;
73 
75  private $mImportUploads;
76 
78  private $mImageBasePath;
79 
81  private $mNoUpdates = false;
82 
84  private $pageOffset = 0;
85 
87  private $config;
88 
91 
93  private $hookRunner;
94 
96  private $countableCache = [];
97 
99  private $disableStatisticsUpdate = false;
100 
103 
106 
108  private $namespaceInfo;
109 
111  private $titleFactory;
112 
115 
118 
121 
124 
127 
143  public function __construct(
145  Config $config,
146  HookContainer $hookContainer,
155  ) {
156  $this->reader = new XMLReader();
157  $this->config = $config;
158  $this->hookRunner = new HookRunner( $hookContainer );
159  $this->contentLanguage = $contentLanguage;
160  $this->namespaceInfo = $namespaceInfo;
161  $this->titleFactory = $titleFactory;
162  $this->wikiPageFactory = $wikiPageFactory;
163  $this->uploadRevisionImporter = $uploadRevisionImporter;
164  $this->permissionManager = $permissionManager;
165  $this->contentHandlerFactory = $contentHandlerFactory;
166  $this->slotRoleRegistry = $slotRoleRegistry;
167 
168  if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) {
169  stream_wrapper_register( 'uploadsource', UploadSourceAdapter::class );
170  }
172 
173  // Enable the entity loader, as it is needed for loading external URLs via
174  // XMLReader::open (T86036)
175  $oldDisable = libxml_disable_entity_loader( false );
176  if ( defined( 'LIBXML_PARSEHUGE' ) ) {
177  $status = $this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
178  } else {
179  $status = $this->reader->open( "uploadsource://$id" );
180  }
181  if ( !$status ) {
182  $error = libxml_get_last_error();
183  libxml_disable_entity_loader( $oldDisable );
184  throw new MWException( 'Encountered an internal error while initializing WikiImporter object: ' .
185  $error->message );
186  }
187  libxml_disable_entity_loader( $oldDisable );
188 
189  // Default callbacks
190  $this->setPageCallback( [ $this, 'beforeImportPage' ] );
191  $this->setRevisionCallback( [ $this, "importRevision" ] );
192  $this->setUploadCallback( [ $this, 'importUpload' ] );
193  $this->setLogItemCallback( [ $this, 'importLogItem' ] );
194  $this->setPageOutCallback( [ $this, 'finishImportPage' ] );
195 
196  $this->importTitleFactory = new NaiveImportTitleFactory(
197  $this->contentLanguage,
198  $this->namespaceInfo,
199  $this->titleFactory
200  );
201  $this->externalUserNames = new ExternalUserNames( 'imported', false );
202  }
203 
207  public function getReader() {
208  return $this->reader;
209  }
210 
214  public function throwXmlError( $err ) {
215  $this->debug( "FAILURE: $err" );
216  wfDebug( "WikiImporter XML error: $err" );
217  }
218 
222  public function debug( $data ) {
223  if ( $this->mDebug ) {
224  wfDebug( "IMPORT: $data" );
225  }
226  }
227 
231  public function warn( $data ) {
232  wfDebug( "IMPORT: $data" );
233  }
234 
239  public function notice( $msg, ...$params ) {
240  if ( is_callable( $this->mNoticeCallback ) ) {
241  call_user_func( $this->mNoticeCallback, $msg, $params );
242  } else { # No ImportReporter -> CLI
243  // T177997: the command line importers should call setNoticeCallback()
244  // for their own custom callback to echo the notice
245  wfDebug( wfMessage( $msg, $params )->text() );
246  }
247  }
248 
253  public function setDebug( $debug ) {
254  $this->mDebug = $debug;
255  }
256 
261  public function setNoUpdates( $noupdates ) {
262  $this->mNoUpdates = $noupdates;
263  }
264 
271  public function setPageOffset( $nthPage ) {
272  $this->pageOffset = $nthPage;
273  }
274 
281  public function setNoticeCallback( $callback ) {
282  return wfSetVar( $this->mNoticeCallback, $callback );
283  }
284 
290  public function setPageCallback( $callback ) {
291  $previous = $this->mPageCallback;
292  $this->mPageCallback = $callback;
293  return $previous;
294  }
295 
305  public function setPageOutCallback( $callback ) {
306  $previous = $this->mPageOutCallback;
307  $this->mPageOutCallback = $callback;
308  return $previous;
309  }
310 
316  public function setRevisionCallback( $callback ) {
317  $previous = $this->mRevisionCallback;
318  $this->mRevisionCallback = $callback;
319  return $previous;
320  }
321 
327  public function setUploadCallback( $callback ) {
328  $previous = $this->mUploadCallback;
329  $this->mUploadCallback = $callback;
330  return $previous;
331  }
332 
338  public function setLogItemCallback( $callback ) {
339  $previous = $this->mLogItemCallback;
340  $this->mLogItemCallback = $callback;
341  return $previous;
342  }
343 
349  public function setSiteInfoCallback( $callback ) {
350  $previous = $this->mSiteInfoCallback;
351  $this->mSiteInfoCallback = $callback;
352  return $previous;
353  }
354 
360  public function setImportTitleFactory( $factory ) {
361  $this->importTitleFactory = $factory;
362  }
363 
369  public function setTargetNamespace( $namespace ) {
370  if ( $namespace === null ) {
371  // Don't override namespaces
372  $this->setImportTitleFactory(
374  $this->contentLanguage,
375  $this->namespaceInfo,
376  $this->titleFactory
377  )
378  );
379  return true;
380  } elseif (
381  $namespace >= 0 &&
382  $this->namespaceInfo->exists( intval( $namespace ) )
383  ) {
384  $namespace = intval( $namespace );
385  $this->setImportTitleFactory(
387  $this->namespaceInfo,
388  $this->titleFactory,
389  $namespace
390  )
391  );
392  return true;
393  } else {
394  return false;
395  }
396  }
397 
403  public function setTargetRootPage( $rootpage ) {
404  $status = Status::newGood();
405  $nsInfo = $this->namespaceInfo;
406  if ( $rootpage === null ) {
407  // No rootpage
408  $this->setImportTitleFactory(
410  $this->contentLanguage,
411  $nsInfo,
412  $this->titleFactory
413  )
414  );
415  } elseif ( $rootpage !== '' ) {
416  $rootpage = rtrim( $rootpage, '/' ); // avoid double slashes
417  $title = Title::newFromText( $rootpage );
418 
419  if ( !$title || $title->isExternal() ) {
420  $status->fatal( 'import-rootpage-invalid' );
421  } elseif ( !$nsInfo->hasSubpages( $title->getNamespace() ) ) {
422  $displayNSText = $title->getNamespace() === NS_MAIN
423  ? wfMessage( 'blanknamespace' )->text()
424  : $this->contentLanguage->getNsText( $title->getNamespace() );
425  $status->fatal( 'import-rootpage-nosubpage', $displayNSText );
426  } else {
427  // set namespace to 'all', so the namespace check in processTitle() can pass
428  $this->setTargetNamespace( null );
429  $this->setImportTitleFactory(
431  $nsInfo,
432  $this->titleFactory,
433  $title
434  )
435  );
436  }
437  }
438  return $status;
439  }
440 
444  public function setImageBasePath( $dir ) {
445  $this->mImageBasePath = $dir;
446  }
447 
451  public function setImportUploads( $import ) {
452  $this->mImportUploads = $import;
453  }
454 
460  public function setUsernamePrefix( $usernamePrefix, $assignKnownUsers ) {
461  $this->externalUserNames = new ExternalUserNames( $usernamePrefix, $assignKnownUsers );
462  }
463 
468  public function disableStatisticsUpdate() {
469  $this->disableStatisticsUpdate = true;
470  }
471 
478  public function beforeImportPage( $titleAndForeignTitle ) {
479  $title = $titleAndForeignTitle[0];
480  $page = $this->wikiPageFactory->newFromTitle( $title );
481  $this->countableCache['title_' . $title->getPrefixedText()] = $page->isCountable();
482  return true;
483  }
484 
490  public function importRevision( $revision ) {
491  if ( !$revision->getContentHandler()->canBeUsedOn( $revision->getTitle() ) ) {
492  $this->notice( 'import-error-bad-location',
493  $revision->getTitle()->getPrefixedText(),
494  $revision->getID(),
495  $revision->getModel(),
496  $revision->getFormat()
497  );
498 
499  return false;
500  }
501 
502  try {
503  return $revision->importOldRevision();
504  } catch ( MWContentSerializationException $ex ) {
505  $this->notice( 'import-error-unserialize',
506  $revision->getTitle()->getPrefixedText(),
507  $revision->getID(),
508  $revision->getModel(),
509  $revision->getFormat()
510  );
511  }
512 
513  return false;
514  }
515 
521  public function importLogItem( $revision ) {
522  return $revision->importLogItem();
523  }
524 
530  public function importUpload( $revision ) {
531  $status = $this->uploadRevisionImporter->import( $revision );
532  return $status->isGood();
533  }
534 
544  public function finishImportPage( PageIdentity $pageIdentity, $foreignTitle, $revCount,
545  $sRevCount, $pageInfo
546  ) {
547  // Update article count statistics (T42009)
548  // The normal counting logic in WikiPage->doEditUpdates() is designed for
549  // one-revision-at-a-time editing, not bulk imports. In this situation it
550  // suffers from issues of replica DB lag. We let WikiPage handle the total page
551  // and revision count, and we implement our own custom logic for the
552  // article (content page) count.
553  if ( !$this->disableStatisticsUpdate ) {
554  $page = $this->wikiPageFactory->newFromTitle( $pageIdentity );
555 
556  $page->loadPageData( 'fromdbmaster' );
557  $content = $page->getContent();
558  if ( $content === null ) {
559  wfDebug( __METHOD__ . ': Skipping article count adjustment for ' . $pageIdentity .
560  ' because WikiPage::getContent() returned null' );
561  } else {
562  // No user is available
563  $user = RequestContext::getMain()->getUser();
564  $editInfo = $page->prepareContentForEdit( $content, null, $user );
565  $countKey = 'title_' . CacheKeyHelper::getKeyForPage( $pageIdentity );
566  $countable = $page->isCountable( $editInfo );
567  if ( array_key_exists( $countKey, $this->countableCache ) &&
568  $countable != $this->countableCache[$countKey] ) {
570  'articles' => ( (int)$countable - (int)$this->countableCache[$countKey] )
571  ] ) );
572  }
573  }
574  }
575 
576  $title = Title::castFromPageIdentity( $pageIdentity );
577  return $this->hookRunner->onAfterImportPage( $title, $foreignTitle,
578  $revCount, $sRevCount, $pageInfo );
579  }
580 
586  private function siteInfoCallback( $siteInfo ) {
587  if ( isset( $this->mSiteInfoCallback ) ) {
588  return call_user_func_array(
589  $this->mSiteInfoCallback,
590  [ $siteInfo, $this ]
591  );
592  } else {
593  return false;
594  }
595  }
596 
601  public function pageCallback( $title ) {
602  if ( isset( $this->mPageCallback ) ) {
603  call_user_func( $this->mPageCallback, $title );
604  }
605  }
606 
615  private function pageOutCallback( PageIdentity $pageIdentity, $foreignTitle, $revCount,
616  $sucCount, $pageInfo ) {
617  if ( isset( $this->mPageOutCallback ) ) {
618  call_user_func_array( $this->mPageOutCallback, func_get_args() );
619  }
620  }
621 
627  private function revisionCallback( $revision ) {
628  if ( isset( $this->mRevisionCallback ) ) {
629  return call_user_func_array(
630  $this->mRevisionCallback,
631  [ $revision, $this ]
632  );
633  } else {
634  return false;
635  }
636  }
637 
643  private function logItemCallback( $revision ) {
644  if ( isset( $this->mLogItemCallback ) ) {
645  return call_user_func_array(
646  $this->mLogItemCallback,
647  [ $revision, $this ]
648  );
649  } else {
650  return false;
651  }
652  }
653 
660  public function nodeAttribute( $attr ) {
661  return $this->reader->getAttribute( $attr );
662  }
663 
671  public function nodeContents() {
672  if ( $this->reader->isEmptyElement ) {
673  return "";
674  }
675  $buffer = "";
676  while ( $this->reader->read() ) {
677  switch ( $this->reader->nodeType ) {
678  case XMLReader::TEXT:
679  case XMLReader::CDATA:
680  case XMLReader::SIGNIFICANT_WHITESPACE:
681  $buffer .= $this->reader->value;
682  break;
683  case XMLReader::END_ELEMENT:
684  return $buffer;
685  }
686  }
687 
688  $this->reader->close();
689  return '';
690  }
691 
698  public function doImport() {
699  // Calls to reader->read need to be wrapped in calls to
700  // libxml_disable_entity_loader() to avoid local file
701  // inclusion attacks (T48932).
702  $oldDisable = libxml_disable_entity_loader( true );
703  try {
704  $this->reader->read();
705 
706  if ( $this->reader->localName != 'mediawiki' ) {
707  libxml_disable_entity_loader( $oldDisable );
708  throw new MWException( "Expected <mediawiki> tag, got " .
709  $this->reader->localName );
710  }
711  $this->debug( "<mediawiki> tag is correct." );
712 
713  $this->debug( "Starting primary dump processing loop." );
714 
715  $keepReading = $this->reader->read();
716  $skip = false;
717  $pageCount = 0;
718  while ( $keepReading ) {
719  $tag = $this->reader->localName;
720  if ( $this->pageOffset ) {
721  if ( $tag === 'page' ) {
722  $pageCount++;
723  }
724  if ( $pageCount < $this->pageOffset ) {
725  $keepReading = $this->reader->next();
726  continue;
727  }
728  }
729  $type = $this->reader->nodeType;
730 
731  if ( !$this->hookRunner->onImportHandleToplevelXMLTag( $this ) ) {
732  // Do nothing
733  } elseif ( $tag == 'mediawiki' && $type == XMLReader::END_ELEMENT ) {
734  break;
735  } elseif ( $tag == 'siteinfo' ) {
736  $this->handleSiteInfo();
737  } elseif ( $tag == 'page' ) {
738  $this->handlePage();
739  } elseif ( $tag == 'logitem' ) {
740  $this->handleLogItem();
741  } elseif ( $tag != '#text' ) {
742  $this->warn( "Unhandled top-level XML tag $tag" );
743 
744  $skip = true;
745  }
746 
747  if ( $skip ) {
748  $keepReading = $this->reader->next();
749  $skip = false;
750  $this->debug( "Skip" );
751  } else {
752  $keepReading = $this->reader->read();
753  }
754  }
755  } finally {
756  libxml_disable_entity_loader( $oldDisable );
757  $this->reader->close();
758  }
759 
760  return true;
761  }
762 
763  private function handleSiteInfo() {
764  $this->debug( "Enter site info handler." );
765  $siteInfo = [];
766 
767  // Fields that can just be stuffed in the siteInfo object
768  $normalFields = [ 'sitename', 'base', 'generator', 'case' ];
769 
770  while ( $this->reader->read() ) {
771  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
772  $this->reader->localName == 'siteinfo' ) {
773  break;
774  }
775 
776  $tag = $this->reader->localName;
777 
778  if ( $tag == 'namespace' ) {
779  $this->foreignNamespaces[$this->nodeAttribute( 'key' )] =
780  $this->nodeContents();
781  } elseif ( in_array( $tag, $normalFields ) ) {
782  $siteInfo[$tag] = $this->nodeContents();
783  }
784  }
785 
786  $siteInfo['_namespaces'] = $this->foreignNamespaces;
787  $this->siteInfoCallback( $siteInfo );
788  }
789 
790  private function handleLogItem() {
791  $this->debug( "Enter log item handler." );
792  $logInfo = [];
793 
794  // Fields that can just be stuffed in the pageInfo object
795  $normalFields = [ 'id', 'comment', 'type', 'action', 'timestamp',
796  'logtitle', 'params' ];
797 
798  while ( $this->reader->read() ) {
799  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
800  $this->reader->localName == 'logitem' ) {
801  break;
802  }
803 
804  $tag = $this->reader->localName;
805 
806  if ( !$this->hookRunner->onImportHandleLogItemXMLTag( $this, $logInfo ) ) {
807  // Do nothing
808  } elseif ( in_array( $tag, $normalFields ) ) {
809  $logInfo[$tag] = $this->nodeContents();
810  } elseif ( $tag == 'contributor' ) {
811  $logInfo['contributor'] = $this->handleContributor();
812  } elseif ( $tag != '#text' ) {
813  $this->warn( "Unhandled log-item XML tag $tag" );
814  }
815  }
816 
817  $this->processLogItem( $logInfo );
818  }
819 
824  private function processLogItem( $logInfo ) {
825  $revision = new WikiRevision( $this->config );
826 
827  if ( isset( $logInfo['id'] ) ) {
828  $revision->setID( $logInfo['id'] );
829  }
830  $revision->setType( $logInfo['type'] );
831  $revision->setAction( $logInfo['action'] );
832  if ( isset( $logInfo['timestamp'] ) ) {
833  $revision->setTimestamp( $logInfo['timestamp'] );
834  }
835  if ( isset( $logInfo['params'] ) ) {
836  $revision->setParams( $logInfo['params'] );
837  }
838  if ( isset( $logInfo['logtitle'] ) ) {
839  // @todo Using Title for non-local titles is a recipe for disaster.
840  // We should use ForeignTitle here instead.
841  $revision->setTitle( Title::newFromText( $logInfo['logtitle'] ) );
842  }
843 
844  $revision->setNoUpdates( $this->mNoUpdates );
845 
846  if ( isset( $logInfo['comment'] ) ) {
847  $revision->setComment( $logInfo['comment'] );
848  }
849 
850  if ( isset( $logInfo['contributor']['ip'] ) ) {
851  $revision->setUserIP( $logInfo['contributor']['ip'] );
852  }
853 
854  if ( !isset( $logInfo['contributor']['username'] ) ) {
855  $revision->setUsername( $this->externalUserNames->addPrefix( 'Unknown user' ) );
856  } else {
857  $revision->setUsername(
858  $this->externalUserNames->applyPrefix( $logInfo['contributor']['username'] )
859  );
860  }
861 
862  return $this->logItemCallback( $revision );
863  }
864 
865  private function handlePage() {
866  // Handle page data.
867  $this->debug( "Enter page handler." );
868  $pageInfo = [ 'revisionCount' => 0, 'successfulRevisionCount' => 0 ];
869 
870  // Fields that can just be stuffed in the pageInfo object
871  $normalFields = [ 'title', 'ns', 'id', 'redirect', 'restrictions' ];
872 
873  $skip = false;
874  $badTitle = false;
875 
876  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
877  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
878  $this->reader->localName == 'page' ) {
879  break;
880  }
881 
882  $skip = false;
883 
884  $tag = $this->reader->localName;
885 
886  if ( $badTitle ) {
887  // The title is invalid, bail out of this page
888  $skip = true;
889  } elseif ( !$this->hookRunner->onImportHandlePageXMLTag( $this, $pageInfo ) ) {
890  // Do nothing
891  } elseif ( in_array( $tag, $normalFields ) ) {
892  // An XML snippet:
893  // <page>
894  // <id>123</id>
895  // <title>Page</title>
896  // <redirect title="NewTitle"/>
897  // ...
898  // Because the redirect tag is built differently, we need special handling for that case.
899  if ( $tag == 'redirect' ) {
900  $pageInfo[$tag] = $this->nodeAttribute( 'title' );
901  } else {
902  $pageInfo[$tag] = $this->nodeContents();
903  }
904  } elseif ( $tag == 'revision' || $tag == 'upload' ) {
905  if ( !isset( $title ) ) {
906  $title = $this->processTitle( $pageInfo['title'],
907  $pageInfo['ns'] ?? null );
908 
909  // $title is either an array of two titles or false.
910  if ( is_array( $title ) ) {
911  $this->pageCallback( $title );
912  list( $pageInfo['_title'], $foreignTitle ) = $title;
913  } else {
914  $badTitle = true;
915  $skip = true;
916  }
917  }
918 
919  if ( $title ) {
920  if ( $tag == 'revision' ) {
921  $this->handleRevision( $pageInfo );
922  } else {
923  $this->handleUpload( $pageInfo );
924  }
925  }
926  } elseif ( $tag != '#text' ) {
927  $this->warn( "Unhandled page XML tag $tag" );
928  $skip = true;
929  }
930  }
931 
932  // @note $pageInfo is only set if a valid $title is processed above with
933  // no error. If we have a valid $title, then pageCallback is called
934  // above, $pageInfo['title'] is set and we do pageOutCallback here.
935  // If $pageInfo['_title'] is not set, then $foreignTitle is also not
936  // set since they both come from $title above.
937  if ( array_key_exists( '_title', $pageInfo ) ) {
939  $title = $pageInfo['_title'];
940  $this->pageOutCallback(
941  $title,
942  $foreignTitle,
943  $pageInfo['revisionCount'],
944  $pageInfo['successfulRevisionCount'],
945  $pageInfo
946  );
947  }
948  }
949 
953  private function handleRevision( &$pageInfo ) {
954  $this->debug( "Enter revision handler" );
955  $revisionInfo = [];
956 
957  $normalFields = [ 'id', 'parentid', 'timestamp', 'comment', 'minor', 'origin',
958  'model', 'format', 'text', 'sha1' ];
959 
960  $skip = false;
961 
962  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
963  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
964  $this->reader->localName == 'revision' ) {
965  break;
966  }
967 
968  $tag = $this->reader->localName;
969 
970  if ( !$this->hookRunner->onImportHandleRevisionXMLTag(
971  $this, $pageInfo, $revisionInfo )
972  ) {
973  // Do nothing
974  } elseif ( in_array( $tag, $normalFields ) ) {
975  $revisionInfo[$tag] = $this->nodeContents();
976  } elseif ( $tag == 'content' ) {
977  // We can have multiple content tags, so make this an array.
978  $revisionInfo[$tag][] = $this->handleContent();
979  } elseif ( $tag == 'contributor' ) {
980  $revisionInfo['contributor'] = $this->handleContributor();
981  } elseif ( $tag != '#text' ) {
982  $this->warn( "Unhandled revision XML tag $tag" );
983  $skip = true;
984  }
985  }
986 
987  $pageInfo['revisionCount']++;
988  if ( $this->processRevision( $pageInfo, $revisionInfo ) ) {
989  $pageInfo['successfulRevisionCount']++;
990  }
991  }
992 
993  private function handleContent() {
994  $this->debug( "Enter content handler" );
995  $contentInfo = [];
996 
997  $normalFields = [ 'role', 'origin', 'model', 'format', 'text' ];
998 
999  $skip = false;
1000 
1001  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
1002  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1003  $this->reader->localName == 'content' ) {
1004  break;
1005  }
1006 
1007  $tag = $this->reader->localName;
1008 
1009  if ( !$this->hookRunner->onImportHandleContentXMLTag(
1010  $this, $contentInfo )
1011  ) {
1012  // Do nothing
1013  } elseif ( in_array( $tag, $normalFields ) ) {
1014  $contentInfo[$tag] = $this->nodeContents();
1015  } elseif ( $tag != '#text' ) {
1016  $this->warn( "Unhandled content XML tag $tag" );
1017  $skip = true;
1018  }
1019  }
1020 
1021  return $contentInfo;
1022  }
1023 
1032  private function makeContent( Title $title, $revisionId, $contentInfo ) {
1033  global $wgMaxArticleSize;
1034 
1035  if ( !isset( $contentInfo['text'] ) ) {
1036  throw new MWException( 'Missing text field in import.' );
1037  }
1038 
1039  // Make sure revisions won't violate $wgMaxArticleSize, which could lead to
1040  // database errors and instability. Testing for revisions with only listed
1041  // content models, as other content models might use serialization formats
1042  // which aren't checked against $wgMaxArticleSize.
1043  if ( ( !isset( $contentInfo['model'] ) ||
1044  in_array( $contentInfo['model'], [
1045  'wikitext',
1046  'css',
1047  'json',
1048  'javascript',
1049  'text',
1050  ''
1051  ] ) ) &&
1052  strlen( $contentInfo['text'] ) > $wgMaxArticleSize * 1024
1053  ) {
1054  throw new MWException( 'The text of ' .
1055  ( $revisionId ?
1056  "the revision with ID $revisionId" :
1057  'a revision'
1058  ) . " exceeds the maximum allowable size ($wgMaxArticleSize KiB)" );
1059  }
1060 
1061  $role = $contentInfo['role'] ?? SlotRecord::MAIN;
1062  $model = $contentInfo['model'] ?? $this->getDefaultContentModel( $title, $role );
1063  $handler = $this->getContentHandler( $model );
1064 
1065  $text = $handler->importTransform( $contentInfo['text'] );
1066 
1067  return $handler->unserializeContent( $text );
1068  }
1069 
1076  private function processRevision( $pageInfo, $revisionInfo ) {
1077  $revision = new WikiRevision( $this->config );
1078 
1079  $revId = $revisionInfo['id'] ?? 0;
1080  if ( $revId ) {
1081  $revision->setID( $revisionInfo['id'] );
1082  }
1083 
1084  $title = $pageInfo['_title'];
1085  $revision->setTitle( $title );
1086 
1087  $content = $this->makeContent( $title, $revId, $revisionInfo );
1088  $revision->setContent( SlotRecord::MAIN, $content );
1089 
1090  foreach ( $revisionInfo['content'] ?? [] as $slotInfo ) {
1091  if ( !isset( $slotInfo['role'] ) ) {
1092  throw new MWException( "Missing role for imported slot." );
1093  }
1094 
1095  $content = $this->makeContent( $title, $revId, $slotInfo );
1096  $revision->setContent( $slotInfo['role'], $content );
1097  }
1098  $revision->setTimestamp( $revisionInfo['timestamp'] ?? wfTimestampNow() );
1099 
1100  if ( isset( $revisionInfo['comment'] ) ) {
1101  $revision->setComment( $revisionInfo['comment'] );
1102  }
1103 
1104  if ( isset( $revisionInfo['minor'] ) ) {
1105  $revision->setMinor( true );
1106  }
1107  if ( isset( $revisionInfo['contributor']['ip'] ) ) {
1108  $revision->setUserIP( $revisionInfo['contributor']['ip'] );
1109  } elseif ( isset( $revisionInfo['contributor']['username'] ) ) {
1110  $revision->setUsername(
1111  $this->externalUserNames->applyPrefix( $revisionInfo['contributor']['username'] )
1112  );
1113  } else {
1114  $revision->setUsername( $this->externalUserNames->addPrefix( 'Unknown user' ) );
1115  }
1116  if ( isset( $revisionInfo['sha1'] ) ) {
1117  $revision->setSha1Base36( $revisionInfo['sha1'] );
1118  }
1119  $revision->setNoUpdates( $this->mNoUpdates );
1120 
1121  return $this->revisionCallback( $revision );
1122  }
1123 
1128  private function handleUpload( &$pageInfo ) {
1129  $this->debug( "Enter upload handler" );
1130  $uploadInfo = [];
1131 
1132  $normalFields = [ 'timestamp', 'comment', 'filename', 'text',
1133  'src', 'size', 'sha1base36', 'archivename', 'rel' ];
1134 
1135  $skip = false;
1136 
1137  while ( $skip ? $this->reader->next() : $this->reader->read() ) {
1138  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1139  $this->reader->localName == 'upload' ) {
1140  break;
1141  }
1142 
1143  $tag = $this->reader->localName;
1144 
1145  if ( !$this->hookRunner->onImportHandleUploadXMLTag( $this, $pageInfo ) ) {
1146  // Do nothing
1147  } elseif ( in_array( $tag, $normalFields ) ) {
1148  $uploadInfo[$tag] = $this->nodeContents();
1149  } elseif ( $tag == 'contributor' ) {
1150  $uploadInfo['contributor'] = $this->handleContributor();
1151  } elseif ( $tag == 'contents' ) {
1152  $contents = $this->nodeContents();
1153  $encoding = $this->reader->getAttribute( 'encoding' );
1154  if ( $encoding === 'base64' ) {
1155  $uploadInfo['fileSrc'] = $this->dumpTemp( base64_decode( $contents ) );
1156  $uploadInfo['isTempSrc'] = true;
1157  }
1158  } elseif ( $tag != '#text' ) {
1159  $this->warn( "Unhandled upload XML tag $tag" );
1160  $skip = true;
1161  }
1162  }
1163 
1164  if ( $this->mImageBasePath && isset( $uploadInfo['rel'] ) ) {
1165  $path = "{$this->mImageBasePath}/{$uploadInfo['rel']}";
1166  if ( file_exists( $path ) ) {
1167  $uploadInfo['fileSrc'] = $path;
1168  $uploadInfo['isTempSrc'] = false;
1169  }
1170  }
1171 
1172  if ( $this->mImportUploads ) {
1173  return $this->processUpload( $pageInfo, $uploadInfo );
1174  }
1175  }
1176 
1181  private function dumpTemp( $contents ) {
1182  $filename = tempnam( wfTempDir(), 'importupload' );
1183  file_put_contents( $filename, $contents );
1184  return $filename;
1185  }
1186 
1192  private function processUpload( $pageInfo, $uploadInfo ) {
1193  $revision = new WikiRevision( $this->config );
1194  $revId = $pageInfo['id'];
1195  $title = $pageInfo['_title'];
1196  $content = $this->makeContent( $title, $revId, $uploadInfo );
1197 
1198  $revision->setTitle( $title );
1199  $revision->setID( $revId );
1200  $revision->setTimestamp( $uploadInfo['timestamp'] );
1201  $revision->setContent( SlotRecord::MAIN, $content );
1202  $revision->setFilename( $uploadInfo['filename'] );
1203  if ( isset( $uploadInfo['archivename'] ) ) {
1204  $revision->setArchiveName( $uploadInfo['archivename'] );
1205  }
1206  $revision->setSrc( $uploadInfo['src'] );
1207  if ( isset( $uploadInfo['fileSrc'] ) ) {
1208  $revision->setFileSrc( $uploadInfo['fileSrc'],
1209  !empty( $uploadInfo['isTempSrc'] )
1210  );
1211  }
1212  if ( isset( $uploadInfo['sha1base36'] ) ) {
1213  $revision->setSha1Base36( $uploadInfo['sha1base36'] );
1214  }
1215  $revision->setSize( intval( $uploadInfo['size'] ) );
1216  $revision->setComment( $uploadInfo['comment'] );
1217 
1218  if ( isset( $uploadInfo['contributor']['ip'] ) ) {
1219  $revision->setUserIP( $uploadInfo['contributor']['ip'] );
1220  }
1221  if ( isset( $uploadInfo['contributor']['username'] ) ) {
1222  $revision->setUsername(
1223  $this->externalUserNames->applyPrefix( $uploadInfo['contributor']['username'] )
1224  );
1225  }
1226  $revision->setNoUpdates( $this->mNoUpdates );
1227 
1228  return call_user_func( $this->mUploadCallback, $revision );
1229  }
1230 
1234  private function handleContributor() {
1235  $this->debug( "Enter contributor handler." );
1236 
1237  if ( $this->reader->isEmptyElement ) {
1238  return [];
1239  }
1240 
1241  $fields = [ 'id', 'ip', 'username' ];
1242  $info = [];
1243 
1244  while ( $this->reader->read() ) {
1245  if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
1246  $this->reader->localName == 'contributor' ) {
1247  break;
1248  }
1249 
1250  $tag = $this->reader->localName;
1251 
1252  if ( in_array( $tag, $fields ) ) {
1253  $info[$tag] = $this->nodeContents();
1254  }
1255  }
1256 
1257  return $info;
1258  }
1259 
1265  private function processTitle( $text, $ns = null ) {
1266  if ( $this->foreignNamespaces === null ) {
1267  $foreignTitleFactory = new NaiveForeignTitleFactory(
1268  $this->contentLanguage
1269  );
1270  } else {
1271  $foreignTitleFactory = new NamespaceAwareForeignTitleFactory(
1272  $this->foreignNamespaces );
1273  }
1274 
1275  $foreignTitle = $foreignTitleFactory->createForeignTitle( $text,
1276  intval( $ns ) );
1277 
1278  $title = $this->importTitleFactory->createTitleFromForeignTitle(
1279  $foreignTitle );
1280 
1281  $commandLineMode = $this->config->get( 'CommandLineMode' );
1282  if ( $title === null ) {
1283  # Invalid page title? Ignore the page
1284  $this->notice( 'import-error-invalid', $foreignTitle->getFullText() );
1285  return false;
1286  } elseif ( $title->isExternal() ) {
1287  $this->notice( 'import-error-interwiki', $title->getPrefixedText() );
1288  return false;
1289  } elseif ( !$title->canExist() ) {
1290  $this->notice( 'import-error-special', $title->getPrefixedText() );
1291  return false;
1292  } elseif ( !$commandLineMode ) {
1293  $user = RequestContext::getMain()->getUser();
1294 
1295  if ( !$this->permissionManager->userCan( 'edit', $user, $title ) ) {
1296  # Do not import if the importing wiki user cannot edit this page
1297  $this->notice( 'import-error-edit', $title->getPrefixedText() );
1298 
1299  return false;
1300  }
1301  }
1302 
1303  return [ $title, $foreignTitle ];
1304  }
1305 
1310  private function getContentHandler( $model ) {
1311  return $this->contentHandlerFactory->getContentHandler( $model );
1312  }
1313 
1320  private function getDefaultContentModel( $title, $role ) {
1321  return $this->slotRoleRegistry
1322  ->getRoleHandler( $role )
1323  ->getDefaultModel( $title );
1324  }
1325 }
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:1076
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
WikiImporter\$titleFactory
TitleFactory $titleFactory
Definition: WikiImporter.php:111
WikiImporter
XML file reader for the page data importer.
Definition: WikiImporter.php:43
WikiImporter\makeContent
makeContent(Title $title, $revisionId, $contentInfo)
Definition: WikiImporter.php:1032
WikiImporter\$contentLanguage
Language $contentLanguage
Definition: WikiImporter.php:105
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:383
UploadRevisionImporter
Definition: UploadRevisionImporter.php:6
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kibibytes.
Definition: DefaultSettings.php:2648
WikiImporter\setImageBasePath
setImageBasePath( $dir)
Definition: WikiImporter.php:444
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:1515
WikiImporter\$mImportUploads
bool null $mImportUploads
Definition: WikiImporter.php:75
UploadSourceAdapter\registerSource
static registerSource(ImportSource $source)
Definition: UploadSourceAdapter.php:48
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:627
WikiImporter\setNoticeCallback
setNoticeCallback( $callback)
Set a callback that displays notice messages.
Definition: WikiImporter.php:281
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:119
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:261
WikiImporter\getReader
getReader()
Definition: WikiImporter.php:207
ExternalUserNames
Class to parse and build external user names.
Definition: ExternalUserNames.php:30
WikiImporter\processLogItem
processLogItem( $logInfo)
Definition: WikiImporter.php:824
WikiImporter\handleRevision
handleRevision(&$pageInfo)
Definition: WikiImporter.php:953
WikiImporter\setRevisionCallback
setRevisionCallback( $callback)
Sets the action to perform as each page revision is reached.
Definition: WikiImporter.php:316
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1186
WikiImporter\$mSiteInfoCallback
callable null $mSiteInfoCallback
Definition: WikiImporter.php:63
WikiImporter\$foreignNamespaces
array null $foreignNamespaces
Definition: WikiImporter.php:48
WikiImporter\handleContributor
handleContributor()
Definition: WikiImporter.php:1234
WikiImporter\setUsernamePrefix
setUsernamePrefix( $usernamePrefix, $assignKnownUsers)
Definition: WikiImporter.php:460
ImportReporter
Reporting callback.
Definition: ImportReporter.php:29
WikiImporter\$externalUserNames
ExternalUserNames $externalUserNames
Definition: WikiImporter.php:102
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:671
WikiImporter\siteInfoCallback
siteInfoCallback( $siteInfo)
Notify the callback function of site info.
Definition: WikiImporter.php:586
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:332
$debug
$debug
Definition: mcc.php:31
WikiImporter\getContentHandler
getContentHandler( $model)
Definition: WikiImporter.php:1310
Config
Interface for configuration instances.
Definition: Config.php:30
WikiImporter\$uploadRevisionImporter
UploadRevisionImporter $uploadRevisionImporter
Definition: WikiImporter.php:117
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:93
WikiImporter\finishImportPage
finishImportPage(PageIdentity $pageIdentity, $foreignTitle, $revCount, $sRevCount, $pageInfo)
Mostly for hook use.
Definition: WikiImporter.php:544
WikiImporter\dumpTemp
dumpTemp( $contents)
Definition: WikiImporter.php:1181
WikiImporter\$countableCache
array $countableCache
Definition: WikiImporter.php:96
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:8
WikiImporter\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: WikiImporter.php:123
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:143
WikiImporter\throwXmlError
throwXmlError( $err)
Definition: WikiImporter.php:214
Page\WikiPageFactory
Definition: WikiPageFactory.php:20
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:1697
WikiImporter\$importTitleFactory
ImportTitleFactory $importTitleFactory
Definition: WikiImporter.php:90
WikiImporter\processUpload
processUpload( $pageInfo, $uploadInfo)
Definition: WikiImporter.php:1192
WikiImporter\disableStatisticsUpdate
disableStatisticsUpdate()
Statistics update can cause a lot of time.
Definition: WikiImporter.php:468
WikiImporter\setImportUploads
setImportUploads( $import)
Definition: WikiImporter.php:451
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\$mPageCallback
callable $mPageCallback
Definition: WikiImporter.php:60
WikiImporter\$namespaceInfo
NamespaceInfo $namespaceInfo
Definition: WikiImporter.php:108
WikiImporter\$wikiPageFactory
WikiPageFactory $wikiPageFactory
Definition: WikiImporter.php:114
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:53
$content
$content
Definition: router.php:76
WikiImporter\$mUploadCallback
callable $mUploadCallback
Definition: WikiImporter.php:54
WikiImporter\beforeImportPage
beforeImportPage( $titleAndForeignTitle)
Default per-page callback.
Definition: WikiImporter.php:478
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:490
WikiImporter\doImport
doImport()
Primary entry point.
Definition: WikiImporter.php:698
WikiImporter\processTitle
processTitle( $text, $ns=null)
Definition: WikiImporter.php:1265
WikiImporter\setDebug
setDebug( $debug)
Set debug mode...
Definition: WikiImporter.php:253
WikiImporter\notice
notice( $msg,... $params)
Definition: WikiImporter.php:239
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
WikiImporter\handleUpload
handleUpload(&$pageInfo)
Definition: WikiImporter.php:1128
WikiImporter\setUploadCallback
setUploadCallback( $callback)
Sets the action to perform as each file upload version is reached.
Definition: WikiImporter.php:327
WikiImporter\warn
warn( $data)
Definition: WikiImporter.php:231
WikiImporter\handleSiteInfo
handleSiteInfo()
Definition: WikiImporter.php:763
WikiImporter\$slotRoleRegistry
SlotRoleRegistry $slotRoleRegistry
Definition: WikiImporter.php:126
WikiImporter\setTargetRootPage
setTargetRootPage( $rootpage)
Set a target root page under which all pages are imported.
Definition: WikiImporter.php:403
Title
Represents a title within MediaWiki.
Definition: Title.php:48
WikiImporter\$mNoUpdates
bool $mNoUpdates
Definition: WikiImporter.php:81
wfTempDir
wfTempDir()
Tries to get the system directory for temporary files.
Definition: GlobalFunctions.php:1731
WikiRevision
Represents a revision, log entry or upload during the import process.
Definition: WikiRevision.php:39
WikiImporter\debug
debug( $data)
Definition: WikiImporter.php:222
WikiImporter\importLogItem
importLogItem( $revision)
Default per-revision callback, performs the import.
Definition: WikiImporter.php:521
WikiImporter\handleContent
handleContent()
Definition: WikiImporter.php:993
WikiImporter\$mNoticeCallback
callable null $mNoticeCallback
Definition: WikiImporter.php:69
WikiImporter\importUpload
importUpload( $revision)
Dummy for now...
Definition: WikiImporter.php:530
WikiImporter\setPageOffset
setPageOffset( $nthPage)
Sets 'pageOffset' value.
Definition: WikiImporter.php:271
WikiImporter\handleLogItem
handleLogItem()
Definition: WikiImporter.php:790
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:87
WikiImporter\setSiteInfoCallback
setSiteInfoCallback( $callback)
Sets the action to perform when site info is encountered.
Definition: WikiImporter.php:349
WikiImporter\getDefaultContentModel
getDefaultContentModel( $title, $role)
Definition: WikiImporter.php:1320
WikiImporter\setPageOutCallback
setPageOutCallback( $callback)
Sets the action to perform as each page in the stream is completed.
Definition: WikiImporter.php:305
WikiImporter\$disableStatisticsUpdate
bool $disableStatisticsUpdate
Definition: WikiImporter.php:99
$source
$source
Definition: mwdoc-filter.php:34
WikiImporter\$reader
XMLReader $reader
Definition: WikiImporter.php:45
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:865
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
WikiImporter\$pageOffset
int $pageOffset
Definition: WikiImporter.php:84
WikiImporter\nodeAttribute
nodeAttribute( $attr)
Retrieves the contents of the named attribute of the current element.
Definition: WikiImporter.php:660
WikiImporter\$mRevisionCallback
callable $mRevisionCallback
Definition: WikiImporter.php:57
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
WikiImporter\$mPageOutCallback
callable $mPageOutCallback
Definition: WikiImporter.php:66
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:556
WikiImporter\setPageCallback
setPageCallback( $callback)
Sets the action to perform as each new page in the stream is reached.
Definition: WikiImporter.php:290
WikiImporter\setImportTitleFactory
setImportTitleFactory( $factory)
Sets the factory object to use to convert ForeignTitle objects into local Title objects.
Definition: WikiImporter.php:360
WikiImporter\setTargetNamespace
setTargetNamespace( $namespace)
Set a target namespace to override the defaults.
Definition: WikiImporter.php:369
WikiImporter\$mDebug
bool null $mDebug
Definition: WikiImporter.php:72
WikiImporter\logItemCallback
logItemCallback( $revision)
Notify the callback function of a new log item.
Definition: WikiImporter.php:643
WikiImporter\pageOutCallback
pageOutCallback(PageIdentity $pageIdentity, $foreignTitle, $revCount, $sucCount, $pageInfo)
Notify the callback function when a "</page>" is closed.
Definition: WikiImporter.php:615
WikiImporter\pageCallback
pageCallback( $title)
Notify the callback function when a new "<page>" is reached.
Definition: WikiImporter.php:601
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:120
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:78
WikiImporter\setLogItemCallback
setLogItemCallback( $callback)
Sets the action to perform as each log item reached.
Definition: WikiImporter.php:338
WikiImporter\$mLogItemCallback
callable $mLogItemCallback
Definition: WikiImporter.php:51
$type
$type
Definition: testCompression.php:52