MediaWiki  master
SpecialImport.php
Go to the documentation of this file.
1 <?php
29 
35 class SpecialImport extends SpecialPage {
37  private $importSources;
38 
40  private $permManager;
41 
44 
49  public function __construct(
52  ) {
53  parent::__construct( 'Import', 'import' );
54 
55  $this->permManager = $permManager;
56  $this->wikiImporterFactory = $wikiImporterFactory;
57  }
58 
59  public function doesWrites() {
60  return true;
61  }
62 
69  public function execute( $par ) {
71 
72  $this->setHeaders();
73  $this->outputHeader();
74 
75  $this->getOutput()->addModules( 'mediawiki.special.import' );
76  $this->getOutput()->addModuleStyles( 'mediawiki.special.import.styles.ooui' );
77 
78  $this->importSources = $this->getConfig()->get( MainConfigNames::ImportSources );
79  // Avoid phan error by checking the type
80  if ( !is_array( $this->importSources ) ) {
81  throw new UnexpectedValueException( '$wgImportSources must be an array' );
82  }
83  $this->getHookRunner()->onImportSources( $this->importSources );
84 
85  $user = $this->getUser();
86  if ( !$this->permManager->userHasAnyRight( $user, 'import', 'importupload' ) ) {
87  throw new PermissionsError( 'import' );
88  }
89 
90  # @todo Allow PermissionManager::getPermissionErrors() to take an array
91  $errors = wfMergeErrorArrays(
92  $this->permManager->getPermissionErrors(
93  'import', $user, $this->getPageTitle(),
94  PermissionManager::RIGOR_FULL,
95  [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
96  ),
97  $this->permManager->getPermissionErrors(
98  'importupload', $user, $this->getPageTitle(),
99  PermissionManager::RIGOR_FULL,
100  [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
101  )
102  );
103 
104  if ( $errors ) {
105  throw new PermissionsError( 'import', $errors );
106  }
107 
108  $this->checkReadOnly();
109 
110  $request = $this->getRequest();
111  if ( $request->wasPosted() && $request->getRawVal( 'action' ) == 'submit' ) {
112  $this->doImport();
113  }
114  $this->showForm();
115  }
116 
120  private function doImport() {
121  $isUpload = false;
122  $request = $this->getRequest();
123  $sourceName = $request->getVal( 'source' );
124  $assignKnownUsers = $request->getCheck( 'assignKnownUsers' );
125 
126  $logcomment = $request->getText( 'log-comment' );
127  $pageLinkDepth = $this->getConfig()->get( MainConfigNames::ExportMaxLinkDepth ) == 0
128  ? 0
129  : $request->getIntOrNull( 'pagelink-depth' );
130 
131  $rootpage = '';
132  $mapping = $request->getVal( 'mapping' );
133  $namespace = $this->getConfig()->get( MainConfigNames::ImportTargetNamespace );
134  if ( $mapping === 'namespace' ) {
135  $namespace = $request->getIntOrNull( 'namespace' );
136  } elseif ( $mapping === 'subpage' ) {
137  $rootpage = $request->getText( 'rootpage' );
138  }
139 
140  $user = $this->getUser();
141 
142  $fullInterwikiPrefix = null;
143  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
144  $source = Status::newFatal( 'import-token-mismatch' );
145  } elseif ( $sourceName === 'upload' ) {
146  $isUpload = true;
147  $fullInterwikiPrefix = $request->getVal( 'usernamePrefix' );
148  if ( $this->permManager->userHasRight( $user, 'importupload' ) ) {
150  } else {
151  throw new PermissionsError( 'importupload' );
152  }
153  } elseif ( $sourceName === 'interwiki' ) {
154  if ( !$this->permManager->userHasRight( $user, 'import' ) ) {
155  throw new PermissionsError( 'import' );
156  }
157  $interwiki = $fullInterwikiPrefix = $request->getVal( 'interwiki' );
158  // does this interwiki have subprojects?
159  $hasSubprojects = array_key_exists( $interwiki, $this->importSources );
160  if ( !$hasSubprojects && !in_array( $interwiki, $this->importSources ) ) {
161  $source = Status::newFatal( "import-invalid-interwiki" );
162  } else {
163  $subproject = null;
164  if ( $hasSubprojects ) {
165  $subproject = $request->getVal( 'subproject' );
166  // Trim "project::" prefix added for JS
167  if ( strpos( $subproject, $interwiki . '::' ) === 0 ) {
168  $subproject = substr( $subproject, strlen( $interwiki . '::' ) );
169  }
170  $fullInterwikiPrefix .= ':' . $subproject;
171  }
172  if ( $hasSubprojects &&
173  !in_array( $subproject, $this->importSources[$interwiki] )
174  ) {
175  $source = Status::newFatal( 'import-invalid-interwiki' );
176  } else {
177  $history = $request->getCheck( 'interwikiHistory' );
178  $frompage = $request->getText( 'frompage' );
179  $includeTemplates = $request->getCheck( 'interwikiTemplates' );
181  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
182  $fullInterwikiPrefix,
183  $frompage,
184  $history,
185  $includeTemplates,
186  $pageLinkDepth );
187  }
188  }
189  } else {
190  $source = Status::newFatal( "importunknownsource" );
191  }
192 
193  if ( (string)$fullInterwikiPrefix === '' ) {
194  $source->fatal( 'importnoprefix' );
195  }
196 
197  $out = $this->getOutput();
198  if ( !$source->isGood() ) {
199  $out->wrapWikiTextAsInterface( 'error',
200  $this->msg( 'importfailed', $source->getWikiText( false, false, $this->getLanguage() ) )
201  ->plain()
202  );
203  } else {
204  $importer = $this->wikiImporterFactory->getWikiImporter( $source->value );
205  if ( $namespace !== null ) {
206  $importer->setTargetNamespace( $namespace );
207  } elseif ( $rootpage !== null ) {
208  $statusRootPage = $importer->setTargetRootPage( $rootpage );
209  if ( !$statusRootPage->isGood() ) {
210  $out->wrapWikiMsg(
211  "<div class=\"error\">\n$1\n</div>",
212  [
213  'import-options-wrong',
214  $statusRootPage->getWikiText( false, false, $this->getLanguage() ),
215  count( $statusRootPage->getErrorsArray() )
216  ]
217  );
218 
219  return;
220  }
221  }
222  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
223  $importer->setUsernamePrefix( $fullInterwikiPrefix, $assignKnownUsers );
224 
225  $out->addWikiMsg( "importstart" );
226 
227  $reporter = new ImportReporter(
228  $importer,
229  $isUpload,
230  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
231  $fullInterwikiPrefix,
232  $logcomment
233  );
234  $reporter->setContext( $this->getContext() );
235  $exception = false;
236 
237  $reporter->open();
238  try {
239  $importer->doImport();
240  } catch ( Exception $e ) {
241  $exception = $e;
242  }
243  $result = $reporter->close();
244 
245  if ( $exception ) {
246  # No source or XML parse error
247  $out->wrapWikiMsg(
248  "<div class=\"error\">\n$1\n</div>",
249  [ 'importfailed', $exception->getMessage() ]
250  );
251  } elseif ( !$result->isGood() ) {
252  # Zero revisions
253  $out->wrapWikiMsg(
254  "<div class=\"error\">\n$1\n</div>",
255  [ 'importfailed', $result->getWikiText( false, false, $this->getLanguage() ) ]
256  );
257  } else {
258  # Success!
259  $out->addWikiMsg( 'importsuccess' );
260  }
261  $out->addHTML( '<hr />' );
262  }
263  }
264 
265  private function getMappingFormPart( $sourceName ) {
266  $defaultNamespace = $this->getConfig()->get( MainConfigNames::ImportTargetNamespace );
267  return [
268  'mapping' => [
269  'type' => 'radio',
270  'name' => 'mapping',
271  // mw-import-mapping-interwiki, mw-import-mapping-upload
272  'id' => "mw-import-mapping-$sourceName",
273  'options-messages' => [
274  'import-mapping-default' => 'default',
275  'import-mapping-namespace' => 'namespace',
276  'import-mapping-subpage' => 'subpage'
277  ],
278  'default' => $defaultNamespace !== null ? 'namespace' : 'default'
279  ],
280  'namespace' => [
281  'type' => 'namespaceselect',
282  'name' => 'namespace',
283  // mw-import-namespace-interwiki, mw-import-namespace-upload
284  'id' => "mw-import-namespace-$sourceName",
285  'default' => $defaultNamespace ?: '',
286  'all' => null
287  ],
288  'rootpage' => [
289  'type' => 'text',
290  'name' => 'rootpage',
291  // Should be "mw-import-rootpage-...", but we keep this inaccurate
292  // ID for legacy reasons
293  // mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload
294  'id' => "mw-interwiki-rootpage-$sourceName",
295  ],
296  ];
297  }
298 
299  private function showForm() {
300  $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
301  $user = $this->getUser();
302  $out = $this->getOutput();
303  $this->addHelpLink( 'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
304 
305  $interwikiFormDescriptor = [];
306  $uploadFormDescriptor = [];
307 
308  if ( $this->permManager->userHasRight( $user, 'importupload' ) ) {
309  $mappingSelection = $this->getMappingFormPart( 'upload' );
310  $uploadFormDescriptor += [
311  'intro' => [
312  'type' => 'info',
313  'raw' => true,
314  'default' => $this->msg( 'importtext' )->parseAsBlock()
315  ],
316  'xmlimport' => [
317  'type' => 'file',
318  'name' => 'xmlimport',
319  'accept' => [ 'application/xml', 'text/xml' ],
320  'label-message' => 'import-upload-filename',
321  'required' => true,
322  ],
323  'usernamePrefix' => [
324  'type' => 'text',
325  'name' => 'usernamePrefix',
326  'label-message' => 'import-upload-username-prefix',
327  // TODO: Is this field required?
328  ],
329  'assignKnownUsers' => [
330  'type' => 'check',
331  'name' => 'assignKnownUsers',
332  'label-message' => 'import-assign-known-users'
333  ],
334  'log-comment' => [
335  'type' => 'text',
336  'name' => 'log-comment',
337  'label-message' => 'import-comment'
338  ],
339  'source' => [
340  'type' => 'hidden',
341  'name' => 'source',
342  'default' => 'upload',
343  'id' => '',
344  ],
345  ];
346 
347  $uploadFormDescriptor += $mappingSelection;
348 
349  $htmlForm = HTMLForm::factory( 'ooui', $uploadFormDescriptor, $this->getContext() );
350  $htmlForm->setAction( $action );
351  $htmlForm->setId( 'mw-import-upload-form' );
352  $htmlForm->setWrapperLegendMsg( 'import-upload' );
353  $htmlForm->setSubmitTextMsg( 'uploadbtn' );
354  $htmlForm->prepareForm()->displayForm( false );
355 
356  } elseif ( empty( $this->importSources ) ) {
357  $out->addWikiMsg( 'importnosources' );
358  }
359 
360  if ( $this->permManager->userHasRight( $user, 'import' ) && !empty( $this->importSources ) ) {
361 
362  $projects = [];
363  $needSubprojectField = false;
364  foreach ( $this->importSources as $key => $value ) {
365  if ( is_int( $key ) ) {
366  $key = $value;
367  } elseif ( $value !== $key ) {
368  $needSubprojectField = true;
369  }
370 
371  $projects[ $key ] = $key;
372  }
373 
374  $interwikiFormDescriptor += [
375  'intro' => [
376  'type' => 'info',
377  'raw' => true,
378  'default' => $this->msg( 'import-interwiki-text' )->parseAsBlock()
379  ],
380  'interwiki' => [
381  'type' => 'select',
382  'name' => 'interwiki',
383  'label-message' => 'import-interwiki-sourcewiki',
384  'options' => $projects
385  ],
386  ];
387 
388  if ( $needSubprojectField ) {
389  $subprojects = [];
390  foreach ( $this->importSources as $key => $value ) {
391  if ( is_array( $value ) ) {
392  foreach ( $value as $subproject ) {
393  $subprojects[ $subproject ] = $key . '::' . $subproject;
394  }
395  }
396  }
397 
398  $interwikiFormDescriptor += [
399  'subproject' => [
400  'type' => 'select',
401  'name' => 'subproject',
402  'options' => $subprojects
403  ]
404  ];
405  }
406 
407  $interwikiFormDescriptor += [
408  'frompage' => [
409  'type' => 'text',
410  'name' => 'frompage',
411  'label-message' => 'import-interwiki-sourcepage'
412  ],
413  'interwikiHistory' => [
414  'type' => 'check',
415  'name' => 'interwikiHistory',
416  'label-message' => 'import-interwiki-history'
417  ],
418  'interwikiTemplates' => [
419  'type' => 'check',
420  'name' => 'interwikiTemplates',
421  'label-message' => 'import-interwiki-templates'
422  ],
423  'assignKnownUsers' => [
424  'type' => 'check',
425  'name' => 'assignKnownUsers',
426  'label-message' => 'import-assign-known-users'
427  ],
428  ];
429 
430  if ( $this->getConfig()->get( MainConfigNames::ExportMaxLinkDepth ) > 0 ) {
431  $interwikiFormDescriptor += [
432  'pagelink-depth' => [
433  'type' => 'int',
434  'name' => 'pagelink-depth',
435  'label-message' => 'export-pagelinks',
436  'default' => 0
437  ]
438  ];
439  }
440 
441  $interwikiFormDescriptor += [
442  'log-comment' => [
443  'type' => 'text',
444  'name' => 'log-comment',
445  'label-message' => 'import-comment'
446  ],
447  'source' => [
448  'type' => 'hidden',
449  'name' => 'source',
450  'default' => 'interwiki',
451  'id' => '',
452  ],
453  ];
454  $mappingSelection = $this->getMappingFormPart( 'interwiki' );
455 
456  $interwikiFormDescriptor += $mappingSelection;
457 
458  $htmlForm = HTMLForm::factory( 'ooui', $interwikiFormDescriptor, $this->getContext() );
459  $htmlForm->setAction( $action );
460  $htmlForm->setId( 'mw-import-interwiki-form' );
461  $htmlForm->setWrapperLegendMsg( 'importinterwiki' );
462  $htmlForm->setSubmitTextMsg( 'import-interwiki-submit' );
463  $htmlForm->prepareForm()->displayForm( false );
464  }
465  }
466 
467  protected function getGroupName() {
468  return 'pagetools';
469  }
470 }
wfMergeErrorArrays(... $args)
Merge arrays in the style of PermissionManager::getPermissionErrors, with duplicate removal e....
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:338
Reporting callback.
static newFromInterwiki( $interwiki, $page, $history=false, $templates=false, $pageLinkDepth=0)
static newFromUpload( $fieldname="xmlimport")
A class containing constants representing the names of configuration variables.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Show an error when a user tries to do something they do not have the necessary permissions for.
MediaWiki page data importer.
WikiImporterFactory $wikiImporterFactory
doesWrites()
Indicates whether this special page may perform database writes.
array $importSources
getMappingFormPart( $sourceName)
PermissionManager $permManager
execute( $par)
Execute.
doImport()
Do the actual import.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
__construct(PermissionManager $permManager, WikiImporterFactory $wikiImporterFactory)
Parent class for all special pages.
Definition: SpecialPage.php:44
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
Factory service for WikiImporter instances.
$source