MediaWiki REL1_40
SpecialImport.php
Go to the documentation of this file.
1<?php
29
37 private $importSources;
38
40 private $permManager;
41
43 private $wikiImporterFactory;
44
49 public function __construct(
50 PermissionManager $permManager,
51 WikiImporterFactory $wikiImporterFactory
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->importSources = $this->getConfig()->get( MainConfigNames::ImportSources );
76 // Avoid phan error by checking the type
77 if ( !is_array( $this->importSources ) ) {
78 throw new UnexpectedValueException( '$wgImportSources must be an array' );
79 }
80 $this->getHookRunner()->onImportSources( $this->importSources );
81
82 $user = $this->getUser();
83 if ( !$this->permManager->userHasAnyRight( $user, 'import', 'importupload' ) ) {
84 throw new PermissionsError( 'import' );
85 }
86
87 # @todo Allow PermissionManager::getPermissionErrors() to take an array
88 $errors = wfMergeErrorArrays(
89 $this->permManager->getPermissionErrors(
90 'import', $user, $this->getPageTitle(),
91 PermissionManager::RIGOR_FULL,
92 [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
93 ),
94 $this->permManager->getPermissionErrors(
95 'importupload', $user, $this->getPageTitle(),
96 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->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
106 $this->getOutput()->addModuleStyles( 'mediawiki.special.import.styles.ooui' );
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 ( str_starts_with( $subproject, $interwiki . '::' ) ) {
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 // IDs: 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 // IDs: mw-import-namespace-interwiki, mw-import-namespace-upload
284 'id' => "mw-import-namespace-$sourceName",
285 'default' => $defaultNamespace ?: '',
286 'all' => null,
287 'disable-if' => [ '!==', 'mapping', 'namespace' ],
288 ],
289 'rootpage' => [
290 'type' => 'text',
291 'name' => 'rootpage',
292 // Should be "mw-import-...", but we keep the inaccurate ID for compat
293 // IDs: mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload
294 'id' => "mw-interwiki-rootpage-$sourceName",
295 'disable-if' => [ '!==', 'mapping', 'subpage' ],
296 ],
297 ];
298 }
299
300 private function showForm() {
301 $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
302 $user = $this->getUser();
303 $out = $this->getOutput();
304 $this->addHelpLink( 'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
305
306 $interwikiFormDescriptor = [];
307 $uploadFormDescriptor = [];
308
309 if ( $this->permManager->userHasRight( $user, 'importupload' ) ) {
310 $mappingSelection = $this->getMappingFormPart( 'upload' );
311 $uploadFormDescriptor += [
312 'intro' => [
313 'type' => 'info',
314 'raw' => true,
315 'default' => $this->msg( 'importtext' )->parseAsBlock()
316 ],
317 'xmlimport' => [
318 'type' => 'file',
319 'name' => 'xmlimport',
320 'accept' => [ 'application/xml', 'text/xml' ],
321 'label-message' => 'import-upload-filename',
322 'required' => true,
323 ],
324 'usernamePrefix' => [
325 'type' => 'text',
326 'name' => 'usernamePrefix',
327 'label-message' => 'import-upload-username-prefix',
328 // TODO: Is this field required?
329 ],
330 'assignKnownUsers' => [
331 'type' => 'check',
332 'name' => 'assignKnownUsers',
333 'label-message' => 'import-assign-known-users'
334 ],
335 'log-comment' => [
336 'type' => 'text',
337 'name' => 'log-comment',
338 'label-message' => 'import-comment'
339 ],
340 'source' => [
341 'type' => 'hidden',
342 'name' => 'source',
343 'default' => 'upload',
344 'id' => '',
345 ],
346 ];
347
348 $uploadFormDescriptor += $mappingSelection;
349
350 $htmlForm = HTMLForm::factory( 'ooui', $uploadFormDescriptor, $this->getContext() );
351 $htmlForm->setAction( $action );
352 $htmlForm->setId( 'mw-import-upload-form' );
353 $htmlForm->setWrapperLegendMsg( 'import-upload' );
354 $htmlForm->setSubmitTextMsg( 'uploadbtn' );
355 $htmlForm->prepareForm()->displayForm( false );
356
357 } elseif ( empty( $this->importSources ) ) {
358 $out->addWikiMsg( 'importnosources' );
359 }
360
361 if ( $this->permManager->userHasRight( $user, 'import' ) && !empty( $this->importSources ) ) {
362
363 $projects = [];
364 $needSubprojectField = false;
365 foreach ( $this->importSources as $key => $value ) {
366 if ( is_int( $key ) ) {
367 $key = $value;
368 } elseif ( $value !== $key ) {
369 $needSubprojectField = true;
370 }
371
372 $projects[ $key ] = $key;
373 }
374
375 $interwikiFormDescriptor += [
376 'intro' => [
377 'type' => 'info',
378 'raw' => true,
379 'default' => $this->msg( 'import-interwiki-text' )->parseAsBlock()
380 ],
381 'interwiki' => [
382 'type' => 'select',
383 'name' => 'interwiki',
384 'label-message' => 'import-interwiki-sourcewiki',
385 'options' => $projects
386 ],
387 ];
388
389 if ( $needSubprojectField ) {
390 $subprojects = [];
391 foreach ( $this->importSources as $key => $value ) {
392 if ( is_array( $value ) ) {
393 foreach ( $value as $subproject ) {
394 $subprojects[ $subproject ] = $key . '::' . $subproject;
395 }
396 }
397 }
398
399 $interwikiFormDescriptor += [
400 'subproject' => [
401 'type' => 'select',
402 'name' => 'subproject',
403 'options' => $subprojects
404 ]
405 ];
406 }
407
408 $interwikiFormDescriptor += [
409 'frompage' => [
410 'type' => 'text',
411 'name' => 'frompage',
412 'label-message' => 'import-interwiki-sourcepage'
413 ],
414 'interwikiHistory' => [
415 'type' => 'check',
416 'name' => 'interwikiHistory',
417 'label-message' => 'import-interwiki-history'
418 ],
419 'interwikiTemplates' => [
420 'type' => 'check',
421 'name' => 'interwikiTemplates',
422 'label-message' => 'import-interwiki-templates'
423 ],
424 'assignKnownUsers' => [
425 'type' => 'check',
426 'name' => 'assignKnownUsers',
427 'label-message' => 'import-assign-known-users'
428 ],
429 ];
430
431 if ( $this->getConfig()->get( MainConfigNames::ExportMaxLinkDepth ) > 0 ) {
432 $interwikiFormDescriptor += [
433 'pagelink-depth' => [
434 'type' => 'int',
435 'name' => 'pagelink-depth',
436 'label-message' => 'export-pagelinks',
437 'default' => 0
438 ]
439 ];
440 }
441
442 $interwikiFormDescriptor += [
443 'log-comment' => [
444 'type' => 'text',
445 'name' => 'log-comment',
446 'label-message' => 'import-comment'
447 ],
448 'source' => [
449 'type' => 'hidden',
450 'name' => 'source',
451 'default' => 'interwiki',
452 'id' => '',
453 ],
454 ];
455 $mappingSelection = $this->getMappingFormPart( 'interwiki' );
456
457 $interwikiFormDescriptor += $mappingSelection;
458
459 $htmlForm = HTMLForm::factory( 'ooui', $interwikiFormDescriptor, $this->getContext() );
460 $htmlForm->setAction( $action );
461 $htmlForm->setId( 'mw-import-interwiki-form' );
462 $htmlForm->setWrapperLegendMsg( 'importinterwiki' );
463 $htmlForm->setSubmitTextMsg( 'import-interwiki-submit' );
464 $htmlForm->prepareForm()->displayForm( false );
465 }
466 }
467
468 protected function getGroupName() {
469 return 'pagetools';
470 }
471}
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:352
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.
doesWrites()
Indicates whether this special page may perform database writes.
execute( $par)
Execute.
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.
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.
Factory service for WikiImporter instances.
$source