MediaWiki  master
SpecialImport.php
Go to the documentation of this file.
1 <?php
29 
35 class SpecialImport extends SpecialPage {
36  private $sourceName = false;
37  private $interwiki = false;
38  private $subproject;
40  private $mapping = 'default';
41  private $namespace;
42  private $rootpage = '';
43  private $frompage = '';
44  private $logcomment = false;
45  private $history = true;
46  private $includeTemplates = false;
47  private $pageLinkDepth;
48  private $importSources;
50  private $usernamePrefix;
51 
52  public function __construct() {
53  parent::__construct( 'Import', 'import' );
54  }
55 
56  public function doesWrites() {
57  return true;
58  }
59 
66  function execute( $par ) {
68 
69  $this->setHeaders();
70  $this->outputHeader();
71 
72  $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
73 
74  $this->getOutput()->addModules( 'mediawiki.special.import' );
75 
76  $this->importSources = $this->getConfig()->get( 'ImportSources' );
77  Hooks::run( 'ImportSources', [ &$this->importSources ] );
78 
79  $user = $this->getUser();
80  if ( !MediaWikiServices::getInstance()
82  ->userHasAnyRight( $user, 'import', 'importupload' )
83  ) {
84  throw new PermissionsError( 'import' );
85  }
86 
87  # @todo Allow Title::getUserPermissionsErrors() to take an array
88  # @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very weird expectation of what
89  # getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
90  $errors = wfMergeErrorArrays(
91  $this->getPageTitle()->getUserPermissionsErrors(
92  'import', $user, PermissionManager::RIGOR_FULL,
93  [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
94  ),
95  $this->getPageTitle()->getUserPermissionsErrors(
96  'importupload', $user, PermissionManager::RIGOR_FULL,
97  [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
98  )
99  );
100 
101  if ( $errors ) {
102  throw new PermissionsError( 'import', $errors );
103  }
104 
105  $this->checkReadOnly();
106 
107  $request = $this->getRequest();
108  if ( $request->wasPosted() && $request->getVal( 'action' ) == 'submit' ) {
109  $this->doImport();
110  }
111  $this->showForm();
112  }
113 
117  private function doImport() {
118  $isUpload = false;
119  $request = $this->getRequest();
120  $this->sourceName = $request->getVal( "source" );
121  $this->assignKnownUsers = $request->getCheck( 'assignKnownUsers' );
122 
123  $this->logcomment = $request->getText( 'log-comment' );
124  $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
125  ? 0
126  : $request->getIntOrNull( 'pagelink-depth' );
127 
128  $this->mapping = $request->getVal( 'mapping' );
129  if ( $this->mapping === 'namespace' ) {
130  $this->namespace = $request->getIntOrNull( 'namespace' );
131  } elseif ( $this->mapping === 'subpage' ) {
132  $this->rootpage = $request->getText( 'rootpage' );
133  } else {
134  $this->mapping = 'default';
135  }
136 
137  $user = $this->getUser();
138  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
139  if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
140  $source = Status::newFatal( 'import-token-mismatch' );
141  } elseif ( $this->sourceName === 'upload' ) {
142  $isUpload = true;
143  $this->usernamePrefix = $this->fullInterwikiPrefix = $request->getVal( 'usernamePrefix' );
144  if ( $permissionManager->userHasRight( $user, 'importupload' ) ) {
146  } else {
147  throw new PermissionsError( 'importupload' );
148  }
149  } elseif ( $this->sourceName === 'interwiki' ) {
150  if ( !$permissionManager->userHasRight( $user, 'import' ) ) {
151  throw new PermissionsError( 'import' );
152  }
153  $this->interwiki = $this->fullInterwikiPrefix = $request->getVal( 'interwiki' );
154  // does this interwiki have subprojects?
155  $hasSubprojects = array_key_exists( $this->interwiki, $this->importSources );
156  if ( !$hasSubprojects && !in_array( $this->interwiki, $this->importSources ) ) {
157  $source = Status::newFatal( "import-invalid-interwiki" );
158  } else {
159  if ( $hasSubprojects ) {
160  $this->subproject = $request->getVal( 'subproject' );
161  $this->fullInterwikiPrefix .= ':' . $request->getVal( 'subproject' );
162  }
163  if ( $hasSubprojects &&
164  !in_array( $this->subproject, $this->importSources[$this->interwiki] )
165  ) {
166  $source = Status::newFatal( "import-invalid-interwiki" );
167  } else {
168  $this->history = $request->getCheck( 'interwikiHistory' );
169  $this->frompage = $request->getText( "frompage" );
170  $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
172  $this->fullInterwikiPrefix,
173  $this->frompage,
174  $this->history,
175  $this->includeTemplates,
176  $this->pageLinkDepth );
177  }
178  }
179  } else {
180  $source = Status::newFatal( "importunknownsource" );
181  }
182 
183  if ( (string)$this->fullInterwikiPrefix === '' ) {
184  $source->fatal( 'importnoprefix' );
185  }
186 
187  $out = $this->getOutput();
188  if ( !$source->isGood() ) {
189  $out->wrapWikiTextAsInterface( 'error',
190  $this->msg( 'importfailed', $source->getWikiText( false, false, $this->getLanguage() ) )
191  ->plain()
192  );
193  } else {
194  $importer = new WikiImporter( $source->value, $this->getConfig() );
195  if ( !is_null( $this->namespace ) ) {
196  $importer->setTargetNamespace( $this->namespace );
197  } elseif ( !is_null( $this->rootpage ) ) {
198  $statusRootPage = $importer->setTargetRootPage( $this->rootpage );
199  if ( !$statusRootPage->isGood() ) {
200  $out->wrapWikiMsg(
201  "<div class=\"error\">\n$1\n</div>",
202  [
203  'import-options-wrong',
204  $statusRootPage->getWikiText( false, false, $this->getLanguage() ),
205  count( $statusRootPage->getErrorsArray() )
206  ]
207  );
208 
209  return;
210  }
211  }
212  $importer->setUsernamePrefix( $this->fullInterwikiPrefix, $this->assignKnownUsers );
213 
214  $out->addWikiMsg( "importstart" );
215 
216  $reporter = new ImportReporter(
217  $importer,
218  $isUpload,
219  $this->fullInterwikiPrefix,
220  $this->logcomment
221  );
222  $reporter->setContext( $this->getContext() );
223  $exception = false;
224 
225  $reporter->open();
226  try {
227  $importer->doImport();
228  } catch ( Exception $e ) {
229  $exception = $e;
230  }
231  $result = $reporter->close();
232 
233  if ( $exception ) {
234  # No source or XML parse error
235  $out->wrapWikiMsg(
236  "<div class=\"error\">\n$1\n</div>",
237  [ 'importfailed', $exception->getMessage() ]
238  );
239  } elseif ( !$result->isGood() ) {
240  # Zero revisions
241  $out->wrapWikiMsg(
242  "<div class=\"error\">\n$1\n</div>",
243  [ 'importfailed', $result->getWikiText( false, false, $this->getLanguage() ) ]
244  );
245  } else {
246  # Success!
247  $out->addWikiMsg( 'importsuccess' );
248  }
249  $out->addHTML( '<hr />' );
250  }
251  }
252 
253  private function getMappingFormPart( $sourceName ) {
254  $isSameSourceAsBefore = ( $this->sourceName === $sourceName );
255  $defaultNamespace = $this->getConfig()->get( 'ImportTargetNamespace' );
256  return "<tr>
257  <td>
258  </td>
259  <td class='mw-input'>" .
261  $this->msg( 'import-mapping-default' )->text(),
262  'mapping',
263  'default',
264  // mw-import-mapping-interwiki-default, mw-import-mapping-upload-default
265  "mw-import-mapping-$sourceName-default",
266  ( $isSameSourceAsBefore ?
267  ( $this->mapping === 'default' ) :
268  is_null( $defaultNamespace ) )
269  ) .
270  "</td>
271  </tr>
272  <tr>
273  <td>
274  </td>
275  <td class='mw-input'>" .
277  $this->msg( 'import-mapping-namespace' )->text(),
278  'mapping',
279  'namespace',
280  // mw-import-mapping-interwiki-namespace, mw-import-mapping-upload-namespace
281  "mw-import-mapping-$sourceName-namespace",
282  ( $isSameSourceAsBefore ?
283  ( $this->mapping === 'namespace' ) :
284  !is_null( $defaultNamespace ) )
285  ) . ' ' .
287  [
288  'selected' => ( $isSameSourceAsBefore ?
289  $this->namespace :
290  ( $defaultNamespace || '' ) ),
291  'in-user-lang' => true,
292  ], [
293  'name' => "namespace",
294  // mw-import-namespace-interwiki, mw-import-namespace-upload
295  'id' => "mw-import-namespace-$sourceName",
296  'class' => 'namespaceselector',
297  ]
298  ) .
299  "</td>
300  </tr>
301  <tr>
302  <td>
303  </td>
304  <td class='mw-input'>" .
306  $this->msg( 'import-mapping-subpage' )->text(),
307  'mapping',
308  'subpage',
309  // mw-import-mapping-interwiki-subpage, mw-import-mapping-upload-subpage
310  "mw-import-mapping-$sourceName-subpage",
311  ( $isSameSourceAsBefore ? ( $this->mapping === 'subpage' ) : '' )
312  ) . ' ' .
313  Xml::input( 'rootpage', 50,
314  ( $isSameSourceAsBefore ? $this->rootpage : '' ),
315  [
316  // Should be "mw-import-rootpage-...", but we keep this inaccurate
317  // ID for legacy reasons
318  // mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload
319  'id' => "mw-interwiki-rootpage-$sourceName",
320  'type' => 'text'
321  ]
322  ) . ' ' .
323  "</td>
324  </tr>";
325  }
326 
327  private function showForm() {
328  $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
329  $user = $this->getUser();
330  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
331  $out = $this->getOutput();
332  $this->addHelpLink( 'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
333 
334  if ( $permissionManager->userHasRight( $user, 'importupload' ) ) {
335  $mappingSelection = $this->getMappingFormPart( 'upload' );
336  $out->addHTML(
337  Xml::fieldset( $this->msg( 'import-upload' )->text() ) .
339  'form',
340  [
341  'enctype' => 'multipart/form-data',
342  'method' => 'post',
343  'action' => $action,
344  'id' => 'mw-import-upload-form'
345  ]
346  ) .
347  $this->msg( 'importtext' )->parseAsBlock() .
348  Html::hidden( 'action', 'submit' ) .
349  Html::hidden( 'source', 'upload' ) .
350  Xml::openElement( 'table', [ 'id' => 'mw-import-table-upload' ] ) .
351  "<tr>
352  <td class='mw-label'>" .
353  Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) .
354  "</td>
355  <td class='mw-input'>" .
356  Html::input( 'xmlimport', '', 'file', [ 'id' => 'xmlimport' ] ) . ' ' .
357  "</td>
358  </tr>
359  <tr>
360  <td class='mw-label'>" .
361  Xml::label( $this->msg( 'import-upload-username-prefix' )->text(),
362  'mw-import-usernamePrefix' ) .
363  "</td>
364  <td class='mw-input'>" .
365  Xml::input( 'usernamePrefix', 50,
366  $this->usernamePrefix,
367  [ 'id' => 'usernamePrefix', 'type' => 'text' ] ) . ' ' .
368  "</td>
369  </tr>
370  <tr>
371  <td></td>
372  <td class='mw-input'>" .
374  $this->msg( 'import-assign-known-users' )->text(),
375  'assignKnownUsers',
376  'assignKnownUsers',
377  $this->assignKnownUsers
378  ) .
379  "</td>
380  </tr>
381  <tr>
382  <td class='mw-label'>" .
383  Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
384  "</td>
385  <td class='mw-input'>" .
386  Xml::input( 'log-comment', 50,
387  ( $this->sourceName === 'upload' ? $this->logcomment : '' ),
388  [ 'id' => 'mw-import-comment', 'type' => 'text' ] ) . ' ' .
389  "</td>
390  </tr>
391  $mappingSelection
392  <tr>
393  <td></td>
394  <td class='mw-submit'>" .
395  Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) .
396  "</td>
397  </tr>" .
398  Xml::closeElement( 'table' ) .
399  Html::hidden( 'editToken', $user->getEditToken() ) .
400  Xml::closeElement( 'form' ) .
401  Xml::closeElement( 'fieldset' )
402  );
403  } elseif ( empty( $this->importSources ) ) {
404  $out->addWikiMsg( 'importnosources' );
405  }
406 
407  if ( $permissionManager->userHasRight( $user, 'import' ) && !empty( $this->importSources ) ) {
408  # Show input field for import depth only if $wgExportMaxLinkDepth > 0
409  $importDepth = '';
410  if ( $this->getConfig()->get( 'ExportMaxLinkDepth' ) > 0 ) {
411  $importDepth = "<tr>
412  <td class='mw-label'>" .
413  $this->msg( 'export-pagelinks' )->parse() .
414  "</td>
415  <td class='mw-input'>" .
416  Xml::input( 'pagelink-depth', 3, 0 ) .
417  "</td>
418  </tr>";
419  }
420  $mappingSelection = $this->getMappingFormPart( 'interwiki' );
421 
422  $out->addHTML(
423  Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) .
425  'form',
426  [
427  'method' => 'post',
428  'action' => $action,
429  'id' => 'mw-import-interwiki-form'
430  ]
431  ) .
432  $this->msg( 'import-interwiki-text' )->parseAsBlock() .
433  Html::hidden( 'action', 'submit' ) .
434  Html::hidden( 'source', 'interwiki' ) .
435  Html::hidden( 'editToken', $user->getEditToken() ) .
436  Xml::openElement( 'table', [ 'id' => 'mw-import-table-interwiki' ] ) .
437  "<tr>
438  <td class='mw-label'>" .
439  Xml::label( $this->msg( 'import-interwiki-sourcewiki' )->text(), 'interwiki' ) .
440  "</td>
441  <td class='mw-input'>" .
443  'select',
444  [ 'name' => 'interwiki', 'id' => 'interwiki' ]
445  )
446  );
447 
448  $needSubprojectField = false;
449  foreach ( $this->importSources as $key => $value ) {
450  if ( is_int( $key ) ) {
451  $key = $value;
452  } elseif ( $value !== $key ) {
453  $needSubprojectField = true;
454  }
455 
456  $attribs = [
457  'value' => $key,
458  ];
459  if ( is_array( $value ) ) {
460  $attribs['data-subprojects'] = implode( ' ', $value );
461  }
462  if ( $this->interwiki === $key ) {
463  $attribs['selected'] = 'selected';
464  }
465  $out->addHTML( Html::element( 'option', $attribs, $key ) );
466  }
467 
468  $out->addHTML(
469  Xml::closeElement( 'select' )
470  );
471 
472  if ( $needSubprojectField ) {
473  $out->addHTML(
475  'select',
476  [ 'name' => 'subproject', 'id' => 'subproject' ]
477  )
478  );
479 
480  $subprojectsToAdd = [];
481  foreach ( $this->importSources as $key => $value ) {
482  if ( is_array( $value ) ) {
483  $subprojectsToAdd = array_merge( $subprojectsToAdd, $value );
484  }
485  }
486  $subprojectsToAdd = array_unique( $subprojectsToAdd );
487  sort( $subprojectsToAdd );
488  foreach ( $subprojectsToAdd as $subproject ) {
489  $out->addHTML( Xml::option( $subproject, $subproject, $this->subproject === $subproject ) );
490  }
491 
492  $out->addHTML(
493  Xml::closeElement( 'select' )
494  );
495  }
496 
497  $out->addHTML(
498  "</td>
499  </tr>
500  <tr>
501  <td class='mw-label'>" .
502  Xml::label( $this->msg( 'import-interwiki-sourcepage' )->text(), 'frompage' ) .
503  "</td>
504  <td class='mw-input'>" .
505  Xml::input( 'frompage', 50, $this->frompage, [ 'id' => 'frompage' ] ) .
506  "</td>
507  </tr>
508  <tr>
509  <td>
510  </td>
511  <td class='mw-input'>" .
513  $this->msg( 'import-interwiki-history' )->text(),
514  'interwikiHistory',
515  'interwikiHistory',
516  $this->history
517  ) .
518  "</td>
519  </tr>
520  <tr>
521  <td>
522  </td>
523  <td class='mw-input'>" .
525  $this->msg( 'import-interwiki-templates' )->text(),
526  'interwikiTemplates',
527  'interwikiTemplates',
528  $this->includeTemplates
529  ) .
530  "</td>
531  </tr>
532  <tr>
533  <td></td>
534  <td class='mw-input'>" .
536  $this->msg( 'import-assign-known-users' )->text(),
537  'assignKnownUsers',
538  'interwikiAssignKnownUsers',
539  $this->assignKnownUsers
540  ) .
541  "</td>
542  </tr>
543  $importDepth
544  <tr>
545  <td class='mw-label'>" .
546  Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
547  "</td>
548  <td class='mw-input'>" .
549  Xml::input( 'log-comment', 50,
550  ( $this->sourceName === 'interwiki' ? $this->logcomment : '' ),
551  [ 'id' => 'mw-interwiki-comment', 'type' => 'text' ] ) . ' ' .
552  "</td>
553  </tr>
554  $mappingSelection
555  <tr>
556  <td>
557  </td>
558  <td class='mw-submit'>" .
560  $this->msg( 'import-interwiki-submit' )->text(),
562  ) .
563  "</td>
564  </tr>" .
565  Xml::closeElement( 'table' ) .
566  Xml::closeElement( 'form' ) .
567  Xml::closeElement( 'fieldset' )
568  );
569  }
570  }
571 
572  protected function getGroupName() {
573  return 'pagetools';
574  }
575 }
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
execute( $par)
Execute.
doImport()
Do the actual import.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
getContext()
Gets the context this SpecialPage is executed in.
MediaWiki page data importer.
XML file reader for the page data importer.
static option( $text, $value=null, $selected=false, $attribs=[])
Convenience function to build an HTML drop-down list item.
Definition: Xml.php:484
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:274
$source
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getOutput()
Get the OutputPage being used for this instance.
getPermissionManager()
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition: Xml.php:609
static newFromUpload( $fieldname="xmlimport")
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:459
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
static input( $name, $value='', $type='text', array $attribs=[])
Convenience function to produce an "<input>" element.
Definition: Html.php:667
getMappingFormPart( $sourceName)
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition: Xml.php:358
static radioLabel( $label, $name, $value, $id, $checked=false, $attribs=[])
Convenience function to build an HTML radio button with a label.
Definition: Xml.php:444
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
static newFromInterwiki( $interwiki, $page, $history=false, $templates=false, $pageLinkDepth=0)
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
setTargetNamespace( $namespace)
Set a target namespace to override the defaults.
getUser()
Shortcut to get the User executing this instance.
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:802
getConfig()
Shortcut to get main config object.
Show an error when a user tries to do something they do not have the necessary permissions for...
Reporting callback.
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:419
getRequest()
Get the WebRequest being used for this instance.
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2190
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getPageTitle( $subpage=false)
Get a self-referential title object.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static namespaceSelector(array $params=[], array $selectAttribs=[])
Build a drop-down box for selecting a namespace.
Definition: Html.php:892