]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/specials/SpecialImport.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / specials / SpecialImport.php
1 <?php
2 /**
3  * Implements Special:Import
4  *
5  * Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
6  * https://www.mediawiki.org/
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  * http://www.gnu.org/copyleft/gpl.html
22  *
23  * @file
24  * @ingroup SpecialPage
25  */
26
27 /**
28  * MediaWiki page data importer
29  *
30  * @ingroup SpecialPage
31  */
32 class SpecialImport extends SpecialPage {
33         private $sourceName = false;
34         private $interwiki = false;
35         private $subproject;
36         private $fullInterwikiPrefix;
37         private $mapping = 'default';
38         private $namespace;
39         private $rootpage = '';
40         private $frompage = '';
41         private $logcomment = false;
42         private $history = true;
43         private $includeTemplates = false;
44         private $pageLinkDepth;
45         private $importSources;
46
47         public function __construct() {
48                 parent::__construct( 'Import', 'import' );
49         }
50
51         public function doesWrites() {
52                 return true;
53         }
54
55         /**
56          * Execute
57          * @param string|null $par
58          * @throws PermissionsError
59          * @throws ReadOnlyError
60          */
61         function execute( $par ) {
62                 $this->useTransactionalTimeLimit();
63
64                 $this->setHeaders();
65                 $this->outputHeader();
66
67                 $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
68
69                 $this->getOutput()->addModules( 'mediawiki.special.import' );
70
71                 $this->importSources = $this->getConfig()->get( 'ImportSources' );
72                 Hooks::run( 'ImportSources', [ &$this->importSources ] );
73
74                 $user = $this->getUser();
75                 if ( !$user->isAllowedAny( 'import', 'importupload' ) ) {
76                         throw new PermissionsError( 'import' );
77                 }
78
79                 # @todo Allow Title::getUserPermissionsErrors() to take an array
80                 # @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what
81                 # getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
82                 $errors = wfMergeErrorArrays(
83                         $this->getPageTitle()->getUserPermissionsErrors(
84                                 'import', $user, true,
85                                 [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
86                         ),
87                         $this->getPageTitle()->getUserPermissionsErrors(
88                                 'importupload', $user, true,
89                                 [ 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' ]
90                         )
91                 );
92
93                 if ( $errors ) {
94                         throw new PermissionsError( 'import', $errors );
95                 }
96
97                 $this->checkReadOnly();
98
99                 $request = $this->getRequest();
100                 if ( $request->wasPosted() && $request->getVal( 'action' ) == 'submit' ) {
101                         $this->doImport();
102                 }
103                 $this->showForm();
104         }
105
106         /**
107          * Do the actual import
108          */
109         private function doImport() {
110                 $isUpload = false;
111                 $request = $this->getRequest();
112                 $this->sourceName = $request->getVal( "source" );
113
114                 $this->logcomment = $request->getText( 'log-comment' );
115                 $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
116                         ? 0
117                         : $request->getIntOrNull( 'pagelink-depth' );
118
119                 $this->mapping = $request->getVal( 'mapping' );
120                 if ( $this->mapping === 'namespace' ) {
121                         $this->namespace = $request->getIntOrNull( 'namespace' );
122                 } elseif ( $this->mapping === 'subpage' ) {
123                         $this->rootpage = $request->getText( 'rootpage' );
124                 } else {
125                         $this->mapping = 'default';
126                 }
127
128                 $user = $this->getUser();
129                 if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
130                         $source = Status::newFatal( 'import-token-mismatch' );
131                 } elseif ( $this->sourceName === 'upload' ) {
132                         $isUpload = true;
133                         if ( $user->isAllowed( 'importupload' ) ) {
134                                 $source = ImportStreamSource::newFromUpload( "xmlimport" );
135                         } else {
136                                 throw new PermissionsError( 'importupload' );
137                         }
138                 } elseif ( $this->sourceName === 'interwiki' ) {
139                         if ( !$user->isAllowed( 'import' ) ) {
140                                 throw new PermissionsError( 'import' );
141                         }
142                         $this->interwiki = $this->fullInterwikiPrefix = $request->getVal( 'interwiki' );
143                         // does this interwiki have subprojects?
144                         $hasSubprojects = array_key_exists( $this->interwiki, $this->importSources );
145                         if ( !$hasSubprojects && !in_array( $this->interwiki, $this->importSources ) ) {
146                                 $source = Status::newFatal( "import-invalid-interwiki" );
147                         } else {
148                                 if ( $hasSubprojects ) {
149                                         $this->subproject = $request->getVal( 'subproject' );
150                                         $this->fullInterwikiPrefix .= ':' . $request->getVal( 'subproject' );
151                                 }
152                                 if ( $hasSubprojects &&
153                                         !in_array( $this->subproject, $this->importSources[$this->interwiki] )
154                                 ) {
155                                         $source = Status::newFatal( "import-invalid-interwiki" );
156                                 } else {
157                                         $this->history = $request->getCheck( 'interwikiHistory' );
158                                         $this->frompage = $request->getText( "frompage" );
159                                         $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
160                                         $source = ImportStreamSource::newFromInterwiki(
161                                                 $this->fullInterwikiPrefix,
162                                                 $this->frompage,
163                                                 $this->history,
164                                                 $this->includeTemplates,
165                                                 $this->pageLinkDepth );
166                                 }
167                         }
168                 } else {
169                         $source = Status::newFatal( "importunknownsource" );
170                 }
171
172                 $out = $this->getOutput();
173                 if ( !$source->isGood() ) {
174                         $out->addWikiText( "<p class=\"error\">\n" .
175                                 $this->msg( 'importfailed', $source->getWikiText() )->parse() . "\n</p>" );
176                 } else {
177                         $importer = new WikiImporter( $source->value, $this->getConfig() );
178                         if ( !is_null( $this->namespace ) ) {
179                                 $importer->setTargetNamespace( $this->namespace );
180                         } elseif ( !is_null( $this->rootpage ) ) {
181                                 $statusRootPage = $importer->setTargetRootPage( $this->rootpage );
182                                 if ( !$statusRootPage->isGood() ) {
183                                         $out->wrapWikiMsg(
184                                                 "<p class=\"error\">\n$1\n</p>",
185                                                 [
186                                                         'import-options-wrong',
187                                                         $statusRootPage->getWikiText(),
188                                                         count( $statusRootPage->getErrorsArray() )
189                                                 ]
190                                         );
191
192                                         return;
193                                 }
194                         }
195
196                         $out->addWikiMsg( "importstart" );
197
198                         $reporter = new ImportReporter(
199                                 $importer,
200                                 $isUpload,
201                                 $this->fullInterwikiPrefix,
202                                 $this->logcomment
203                         );
204                         $reporter->setContext( $this->getContext() );
205                         $exception = false;
206
207                         $reporter->open();
208                         try {
209                                 $importer->doImport();
210                         } catch ( Exception $e ) {
211                                 $exception = $e;
212                         }
213                         $result = $reporter->close();
214
215                         if ( $exception ) {
216                                 # No source or XML parse error
217                                 $out->wrapWikiMsg(
218                                         "<p class=\"error\">\n$1\n</p>",
219                                         [ 'importfailed', $exception->getMessage() ]
220                                 );
221                         } elseif ( !$result->isGood() ) {
222                                 # Zero revisions
223                                 $out->wrapWikiMsg(
224                                         "<p class=\"error\">\n$1\n</p>",
225                                         [ 'importfailed', $result->getWikiText() ]
226                                 );
227                         } else {
228                                 # Success!
229                                 $out->addWikiMsg( 'importsuccess' );
230                         }
231                         $out->addHTML( '<hr />' );
232                 }
233         }
234
235         private function getMappingFormPart( $sourceName ) {
236                 $isSameSourceAsBefore = ( $this->sourceName === $sourceName );
237                 $defaultNamespace = $this->getConfig()->get( 'ImportTargetNamespace' );
238                 return "<tr>
239                                         <td>
240                                         </td>
241                                         <td class='mw-input'>" .
242                                         Xml::radioLabel(
243                                                 $this->msg( 'import-mapping-default' )->text(),
244                                                 'mapping',
245                                                 'default',
246                                                 // mw-import-mapping-interwiki-default, mw-import-mapping-upload-default
247                                                 "mw-import-mapping-$sourceName-default",
248                                                 ( $isSameSourceAsBefore ?
249                                                         ( $this->mapping === 'default' ) :
250                                                         is_null( $defaultNamespace ) )
251                                         ) .
252                                         "</td>
253                                 </tr>
254                                 <tr>
255                                         <td>
256                                         </td>
257                                         <td class='mw-input'>" .
258                                         Xml::radioLabel(
259                                                 $this->msg( 'import-mapping-namespace' )->text(),
260                                                 'mapping',
261                                                 'namespace',
262                                                 // mw-import-mapping-interwiki-namespace, mw-import-mapping-upload-namespace
263                                                 "mw-import-mapping-$sourceName-namespace",
264                                                 ( $isSameSourceAsBefore ?
265                                                         ( $this->mapping === 'namespace' ) :
266                                                         !is_null( $defaultNamespace ) )
267                                         ) . ' ' .
268                                         Html::namespaceSelector(
269                                                 [
270                                                         'selected' => ( $isSameSourceAsBefore ?
271                                                                 $this->namespace :
272                                                                 ( $defaultNamespace || '' ) ),
273                                                 ], [
274                                                         'name' => "namespace",
275                                                         // mw-import-namespace-interwiki, mw-import-namespace-upload
276                                                         'id' => "mw-import-namespace-$sourceName",
277                                                         'class' => 'namespaceselector',
278                                                 ]
279                                         ) .
280                                         "</td>
281                                 </tr>
282                                 <tr>
283                                         <td>
284                                         </td>
285                                         <td class='mw-input'>" .
286                                         Xml::radioLabel(
287                                                 $this->msg( 'import-mapping-subpage' )->text(),
288                                                 'mapping',
289                                                 'subpage',
290                                                 // mw-import-mapping-interwiki-subpage, mw-import-mapping-upload-subpage
291                                                 "mw-import-mapping-$sourceName-subpage",
292                                                 ( $isSameSourceAsBefore ? ( $this->mapping === 'subpage' ) : '' )
293                                         ) . ' ' .
294                                         Xml::input( 'rootpage', 50,
295                                                 ( $isSameSourceAsBefore ? $this->rootpage : '' ),
296                                                 [
297                                                         // Should be "mw-import-rootpage-...", but we keep this inaccurate
298                                                         // ID for legacy reasons
299                                                         // mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload
300                                                         'id' => "mw-interwiki-rootpage-$sourceName",
301                                                         'type' => 'text'
302                                                 ]
303                                         ) . ' ' .
304                                         "</td>
305                                 </tr>";
306         }
307
308         private function showForm() {
309                 $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
310                 $user = $this->getUser();
311                 $out = $this->getOutput();
312                 $this->addHelpLink( '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
313
314                 if ( $user->isAllowed( 'importupload' ) ) {
315                         $mappingSelection = $this->getMappingFormPart( 'upload' );
316                         $out->addHTML(
317                                 Xml::fieldset( $this->msg( 'import-upload' )->text() ) .
318                                         Xml::openElement(
319                                                 'form',
320                                                 [
321                                                         'enctype' => 'multipart/form-data',
322                                                         'method' => 'post',
323                                                         'action' => $action,
324                                                         'id' => 'mw-import-upload-form'
325                                                 ]
326                                         ) .
327                                         $this->msg( 'importtext' )->parseAsBlock() .
328                                         Html::hidden( 'action', 'submit' ) .
329                                         Html::hidden( 'source', 'upload' ) .
330                                         Xml::openElement( 'table', [ 'id' => 'mw-import-table-upload' ] ) .
331                                         "<tr>
332                                         <td class='mw-label'>" .
333                                         Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) .
334                                         "</td>
335                                         <td class='mw-input'>" .
336                                         Html::input( 'xmlimport', '', 'file', [ 'id' => 'xmlimport' ] ) . ' ' .
337                                         "</td>
338                                 </tr>
339                                 <tr>
340                                         <td class='mw-label'>" .
341                                         Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
342                                         "</td>
343                                         <td class='mw-input'>" .
344                                         Xml::input( 'log-comment', 50,
345                                                 ( $this->sourceName === 'upload' ? $this->logcomment : '' ),
346                                                 [ 'id' => 'mw-import-comment', 'type' => 'text' ] ) . ' ' .
347                                         "</td>
348                                 </tr>
349                                 $mappingSelection
350                                 <tr>
351                                         <td></td>
352                                         <td class='mw-submit'>" .
353                                         Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) .
354                                         "</td>
355                                 </tr>" .
356                                         Xml::closeElement( 'table' ) .
357                                         Html::hidden( 'editToken', $user->getEditToken() ) .
358                                         Xml::closeElement( 'form' ) .
359                                         Xml::closeElement( 'fieldset' )
360                         );
361                 } else {
362                         if ( empty( $this->importSources ) ) {
363                                 $out->addWikiMsg( 'importnosources' );
364                         }
365                 }
366
367                 if ( $user->isAllowed( 'import' ) && !empty( $this->importSources ) ) {
368                         # Show input field for import depth only if $wgExportMaxLinkDepth > 0
369                         $importDepth = '';
370                         if ( $this->getConfig()->get( 'ExportMaxLinkDepth' ) > 0 ) {
371                                 $importDepth = "<tr>
372                                                         <td class='mw-label'>" .
373                                         $this->msg( 'export-pagelinks' )->parse() .
374                                         "</td>
375                                                         <td class='mw-input'>" .
376                                         Xml::input( 'pagelink-depth', 3, 0 ) .
377                                         "</td>
378                                 </tr>";
379                         }
380                         $mappingSelection = $this->getMappingFormPart( 'interwiki' );
381
382                         $out->addHTML(
383                                 Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) .
384                                         Xml::openElement(
385                                                 'form',
386                                                 [
387                                                         'method' => 'post',
388                                                         'action' => $action,
389                                                         'id' => 'mw-import-interwiki-form'
390                                                 ]
391                                         ) .
392                                         $this->msg( 'import-interwiki-text' )->parseAsBlock() .
393                                         Html::hidden( 'action', 'submit' ) .
394                                         Html::hidden( 'source', 'interwiki' ) .
395                                         Html::hidden( 'editToken', $user->getEditToken() ) .
396                                         Xml::openElement( 'table', [ 'id' => 'mw-import-table-interwiki' ] ) .
397                                         "<tr>
398                                         <td class='mw-label'>" .
399                                         Xml::label( $this->msg( 'import-interwiki-sourcewiki' )->text(), 'interwiki' ) .
400                                         "</td>
401                                         <td class='mw-input'>" .
402                                         Xml::openElement(
403                                                 'select',
404                                                 [ 'name' => 'interwiki', 'id' => 'interwiki' ]
405                                         )
406                         );
407
408                         $needSubprojectField = false;
409                         foreach ( $this->importSources as $key => $value ) {
410                                 if ( is_int( $key ) ) {
411                                         $key = $value;
412                                 } elseif ( $value !== $key ) {
413                                         $needSubprojectField = true;
414                                 }
415
416                                 $attribs = [
417                                         'value' => $key,
418                                 ];
419                                 if ( is_array( $value ) ) {
420                                         $attribs['data-subprojects'] = implode( ' ', $value );
421                                 }
422                                 if ( $this->interwiki === $key ) {
423                                         $attribs['selected'] = 'selected';
424                                 }
425                                 $out->addHTML( Html::element( 'option', $attribs, $key ) );
426                         }
427
428                         $out->addHTML(
429                                 Xml::closeElement( 'select' )
430                         );
431
432                         if ( $needSubprojectField ) {
433                                 $out->addHTML(
434                                         Xml::openElement(
435                                                 'select',
436                                                 [ 'name' => 'subproject', 'id' => 'subproject' ]
437                                         )
438                                 );
439
440                                 $subprojectsToAdd = [];
441                                 foreach ( $this->importSources as $key => $value ) {
442                                         if ( is_array( $value ) ) {
443                                                 $subprojectsToAdd = array_merge( $subprojectsToAdd, $value );
444                                         }
445                                 }
446                                 $subprojectsToAdd = array_unique( $subprojectsToAdd );
447                                 sort( $subprojectsToAdd );
448                                 foreach ( $subprojectsToAdd as $subproject ) {
449                                         $out->addHTML( Xml::option( $subproject, $subproject, $this->subproject === $subproject ) );
450                                 }
451
452                                 $out->addHTML(
453                                         Xml::closeElement( 'select' )
454                                 );
455                         }
456
457                         $out->addHTML(
458                                         "</td>
459                                 </tr>
460                                 <tr>
461                                         <td class='mw-label'>" .
462                                         Xml::label( $this->msg( 'import-interwiki-sourcepage' )->text(), 'frompage' ) .
463                                         "</td>
464                                         <td class='mw-input'>" .
465                                         Xml::input( 'frompage', 50, $this->frompage, [ 'id' => 'frompage' ] ) .
466                                         "</td>
467                                 </tr>
468                                 <tr>
469                                         <td>
470                                         </td>
471                                         <td class='mw-input'>" .
472                                         Xml::checkLabel(
473                                                 $this->msg( 'import-interwiki-history' )->text(),
474                                                 'interwikiHistory',
475                                                 'interwikiHistory',
476                                                 $this->history
477                                         ) .
478                                         "</td>
479                                 </tr>
480                                 <tr>
481                                         <td>
482                                         </td>
483                                         <td class='mw-input'>" .
484                                         Xml::checkLabel(
485                                                 $this->msg( 'import-interwiki-templates' )->text(),
486                                                 'interwikiTemplates',
487                                                 'interwikiTemplates',
488                                                 $this->includeTemplates
489                                         ) .
490                                         "</td>
491                                 </tr>
492                                 $importDepth
493                                 <tr>
494                                         <td class='mw-label'>" .
495                                         Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
496                                         "</td>
497                                         <td class='mw-input'>" .
498                                         Xml::input( 'log-comment', 50,
499                                                 ( $this->sourceName === 'interwiki' ? $this->logcomment : '' ),
500                                                 [ 'id' => 'mw-interwiki-comment', 'type' => 'text' ] ) . ' ' .
501                                         "</td>
502                                 </tr>
503                                 $mappingSelection
504                                 <tr>
505                                         <td>
506                                         </td>
507                                         <td class='mw-submit'>" .
508                                         Xml::submitButton(
509                                                 $this->msg( 'import-interwiki-submit' )->text(),
510                                                 Linker::tooltipAndAccesskeyAttribs( 'import' )
511                                         ) .
512                                         "</td>
513                                 </tr>" .
514                                         Xml::closeElement( 'table' ) .
515                                         Xml::closeElement( 'form' ) .
516                                         Xml::closeElement( 'fieldset' )
517                         );
518                 }
519         }
520
521         protected function getGroupName() {
522                 return 'pagetools';
523         }
524 }