Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 275
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
UploadForm
0.00% covered (danger)
0.00%
0 / 275
0.00% covered (danger)
0.00%
0 / 8
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
90
 getSourceSection
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
56
 getExtensionsMessage
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 getDescriptionSection
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 1
132
 getOptionsSection
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
 show
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addUploadJS
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 trySubmit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21use MediaWiki\Context\IContextSource;
22use MediaWiki\HookContainer\HookContainer;
23use MediaWiki\HookContainer\HookRunner;
24use MediaWiki\Html\Html;
25use MediaWiki\HTMLForm\HTMLForm;
26use MediaWiki\Language\Language;
27use MediaWiki\Linker\LinkRenderer;
28use MediaWiki\MainConfigNames;
29use MediaWiki\MediaWikiServices;
30use MediaWiki\Specials\SpecialUpload;
31use MediaWiki\Status\Status;
32use MediaWiki\Title\NamespaceInfo;
33use Wikimedia\RequestTimeout\TimeoutException;
34
35/**
36 * Sub class of HTMLForm that provides the form section of SpecialUpload
37 */
38class UploadForm extends HTMLForm {
39    /** @var bool */
40    protected $mWatch;
41    /** @var bool */
42    protected $mForReUpload;
43    /** @var string */
44    protected $mSessionKey;
45    /** @var bool */
46    protected $mHideIgnoreWarning;
47    /** @var bool */
48    protected $mDestWarningAck;
49    /** @var string */
50    protected $mDestFile;
51
52    /** @var string */
53    protected $mComment;
54    /** @var string raw html */
55    protected $mTextTop;
56    /** @var string raw html */
57    protected $mTextAfterSummary;
58
59    /** @var string[] */
60    protected $mSourceIds;
61
62    /** @var array */
63    protected $mMaxUploadSize = [];
64
65    private LocalRepo $localRepo;
66    private Language $contentLanguage;
67    private NamespaceInfo $nsInfo;
68    private HookRunner $hookRunner;
69
70    /**
71     * @param array $options
72     * @param IContextSource|null $context
73     * @param LinkRenderer|null $linkRenderer
74     * @param LocalRepo|null $localRepo
75     * @param Language|null $contentLanguage
76     * @param NamespaceInfo|null $nsInfo
77     * @param HookContainer|null $hookContainer
78     */
79    public function __construct(
80        array $options = [],
81        ?IContextSource $context = null,
82        ?LinkRenderer $linkRenderer = null,
83        ?LocalRepo $localRepo = null,
84        ?Language $contentLanguage = null,
85        ?NamespaceInfo $nsInfo = null,
86        ?HookContainer $hookContainer = null
87    ) {
88        if ( $context instanceof IContextSource ) {
89            $this->setContext( $context );
90        }
91
92        $services = MediaWikiServices::getInstance();
93        if ( !$linkRenderer ) {
94            $linkRenderer = $services->getLinkRenderer();
95        }
96        if ( !$localRepo ) {
97            $localRepo = $services->getRepoGroup()->getLocalRepo();
98        }
99        if ( !$contentLanguage ) {
100            $contentLanguage = $services->getContentLanguage();
101        }
102        if ( !$nsInfo ) {
103            $nsInfo = $services->getNamespaceInfo();
104        }
105        $this->localRepo = $localRepo;
106        $this->contentLanguage = $contentLanguage;
107        $this->nsInfo = $nsInfo;
108        $this->hookRunner = new HookRunner( $hookContainer ?? $services->getHookContainer() );
109
110        $this->mWatch = !empty( $options['watch'] );
111        $this->mForReUpload = !empty( $options['forreupload'] );
112        $this->mSessionKey = $options['sessionkey'] ?? '';
113        $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
114        $this->mDestWarningAck = !empty( $options['destwarningack'] );
115        $this->mDestFile = $options['destfile'] ?? '';
116
117        $this->mComment = $options['description'] ?? '';
118
119        $this->mTextTop = $options['texttop'] ?? '';
120
121        $this->mTextAfterSummary = $options['textaftersummary'] ?? '';
122
123        $sourceDescriptor = $this->getSourceSection();
124        $descriptor = $sourceDescriptor
125            + $this->getDescriptionSection()
126            + $this->getOptionsSection();
127
128        $this->hookRunner->onUploadFormInitDescriptor( $descriptor );
129        parent::__construct( $descriptor, $this->getContext(), 'upload' );
130
131        # Add a link to edit MediaWiki:Licenses
132        if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
133            $this->getOutput()->addModuleStyles( 'mediawiki.special' );
134            $licensesLink = $linkRenderer->makeKnownLink(
135                $this->msg( 'licenses' )->inContentLanguage()->getTitle(),
136                $this->msg( 'licenses-edit' )->text(),
137                [],
138                [ 'action' => 'edit' ]
139            );
140            $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
141            $this->addFooterHtml( $editLicenses, 'description' );
142        }
143
144        # Set some form properties
145        $this->setSubmitTextMsg( 'uploadbtn' );
146        $this->setSubmitName( 'wpUpload' );
147        # Used message keys: 'accesskey-upload', 'tooltip-upload'
148        $this->setSubmitTooltip( 'upload' );
149        $this->setId( 'mw-upload-form' );
150
151        # Build a list of IDs for javascript insertion
152        $this->mSourceIds = [];
153        foreach ( $sourceDescriptor as $field ) {
154            if ( !empty( $field['id'] ) ) {
155                $this->mSourceIds[] = $field['id'];
156            }
157        }
158    }
159
160    /**
161     * Get the descriptor of the fieldset that contains the file source
162     * selection. The section is 'source'
163     *
164     * @return array Descriptor array
165     */
166    protected function getSourceSection() {
167        if ( $this->mSessionKey ) {
168            return [
169                'SessionKey' => [
170                    'type' => 'hidden',
171                    'default' => $this->mSessionKey,
172                ],
173                'SourceType' => [
174                    'type' => 'hidden',
175                    'default' => 'Stash',
176                ],
177            ];
178        }
179
180        $canUploadByUrl = UploadFromUrl::isEnabled()
181            && ( UploadFromUrl::isAllowed( $this->getAuthority() ) === true )
182            && $this->getConfig()->get( MainConfigNames::CopyUploadsFromSpecialUpload );
183        $radio = $canUploadByUrl;
184        $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
185
186        $descriptor = [];
187        if ( $this->mTextTop ) {
188            $descriptor['UploadFormTextTop'] = [
189                'type' => 'info',
190                'section' => 'source',
191                'default' => $this->mTextTop,
192                'raw' => true,
193            ];
194        }
195
196        $this->mMaxUploadSize['file'] = min(
197            UploadBase::getMaxUploadSize( 'file' ),
198            UploadBase::getMaxPhpUploadSize()
199        );
200
201        $help = $this->msg( 'upload-maxfilesize' )->sizeParams( $this->mMaxUploadSize['file'] )->parse();
202
203        // If the user can also upload by URL, there are 2 different file size limits.
204        // This extra message helps stress which limit corresponds to what.
205        if ( $canUploadByUrl ) {
206            $help .= $this->msg( 'word-separator' )->escaped();
207            $help .= $this->msg( 'upload_source_file' )->parse();
208        }
209
210        $descriptor['UploadFile'] = [
211            'class' => UploadSourceField::class,
212            'section' => 'source',
213            'type' => 'file',
214            'id' => 'wpUploadFile',
215            'radio-id' => 'wpSourceTypeFile',
216            'label-message' => 'sourcefilename',
217            'upload-type' => 'File',
218            'radio' => &$radio,
219            'help' => $help,
220            'checked' => $selectedSourceType == 'file',
221        ];
222
223        if ( $canUploadByUrl ) {
224            $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
225            $descriptor['UploadFileURL'] = [
226                'class' => UploadSourceField::class,
227                'section' => 'source',
228                'id' => 'wpUploadFileURL',
229                'radio-id' => 'wpSourceTypeurl',
230                'label-message' => 'sourceurl',
231                'upload-type' => 'url',
232                'radio' => &$radio,
233                'help' => $this->msg( 'upload-maxfilesize' )->sizeParams( $this->mMaxUploadSize['url'] )->parse() .
234                    $this->msg( 'word-separator' )->escaped() .
235                    $this->msg( 'upload_source_url' )->parse(),
236                'checked' => $selectedSourceType == 'url',
237            ];
238        }
239        $this->hookRunner->onUploadFormSourceDescriptors(
240            $descriptor, $radio, $selectedSourceType );
241
242        $descriptor['Extensions'] = [
243            'type' => 'info',
244            'section' => 'source',
245            'default' => $this->getExtensionsMessage(),
246            'raw' => true,
247        ];
248
249        return $descriptor;
250    }
251
252    /**
253     * Get the messages indicating which extensions are preferred and prohibited.
254     *
255     * @return string HTML string containing the message
256     */
257    protected function getExtensionsMessage() {
258        # Print a list of allowed file extensions, if so configured.  We ignore
259        # MIME type here, it's incomprehensible to most people and too long.
260        $config = $this->getConfig();
261
262        if ( $config->get( MainConfigNames::CheckFileExtensions ) ) {
263            $fileExtensions = array_unique( $config->get( MainConfigNames::FileExtensions ) );
264            if ( $config->get( MainConfigNames::StrictFileExtensions ) ) {
265                # Everything not permitted is banned
266                $extensionsList =
267                    '<div id="mw-upload-permitted">' .
268                    $this->msg( 'upload-permitted' )
269                        ->params( $this->getLanguage()->commaList( $fileExtensions ) )
270                        ->numParams( count( $fileExtensions ) )
271                        ->parseAsBlock() .
272                    "</div>\n";
273            } else {
274                # We have to list both preferred and prohibited
275                $prohibitedExtensions =
276                    array_unique( $config->get( MainConfigNames::ProhibitedFileExtensions ) );
277                $extensionsList =
278                    '<div id="mw-upload-preferred">' .
279                        $this->msg( 'upload-preferred' )
280                            ->params( $this->getLanguage()->commaList( $fileExtensions ) )
281                            ->numParams( count( $fileExtensions ) )
282                            ->parseAsBlock() .
283                    "</div>\n" .
284                    '<div id="mw-upload-prohibited">' .
285                        $this->msg( 'upload-prohibited' )
286                            ->params( $this->getLanguage()->commaList( $prohibitedExtensions ) )
287                            ->numParams( count( $prohibitedExtensions ) )
288                            ->parseAsBlock() .
289                    "</div>\n";
290            }
291        } else {
292            # Everything is permitted.
293            $extensionsList = '';
294        }
295
296        return $extensionsList;
297    }
298
299    /**
300     * Get the descriptor of the fieldset that contains the file description
301     * input. The section is 'description'
302     *
303     * @return array Descriptor array
304     */
305    protected function getDescriptionSection() {
306        $config = $this->getConfig();
307        if ( $this->mSessionKey ) {
308            $stash = $this->localRepo->getUploadStash( $this->getUser() );
309            try {
310                $file = $stash->getFile( $this->mSessionKey );
311            } catch ( TimeoutException $e ) {
312                throw $e;
313            } catch ( Exception $e ) {
314                $file = null;
315            }
316            if ( $file ) {
317                $mto = $file->transform( [ 'width' => 120 ] );
318                if ( $mto ) {
319                    $this->addHeaderHtml(
320                        '<div class="thumb t' .
321                        $this->contentLanguage->alignEnd() . '">' .
322                        Html::element( 'img', [
323                            'src' => $mto->getUrl(),
324                            'class' => 'thumbimage',
325                        ] ) . '</div>', 'description' );
326                }
327            }
328        }
329
330        $descriptor = [
331            'DestFile' => [
332                'type' => 'text',
333                'section' => 'description',
334                'id' => 'wpDestFile',
335                'label-message' => 'destfilename',
336                'size' => 60,
337                'default' => $this->mDestFile,
338                # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
339                'nodata' => strval( $this->mDestFile ) !== '',
340            ],
341            'UploadDescription' => [
342                'type' => $this->mForReUpload
343                    ? 'text'
344                    : 'textarea',
345                'section' => 'description',
346                'id' => 'wpUploadDescription',
347                'label-message' => $this->mForReUpload
348                    ? 'filereuploadsummary'
349                    : 'fileuploadsummary',
350                'default' => $this->mComment,
351            ]
352        ];
353        if ( $this->mTextAfterSummary ) {
354            $descriptor['UploadFormTextAfterSummary'] = [
355                'type' => 'info',
356                'section' => 'description',
357                'default' => $this->mTextAfterSummary,
358                'raw' => true,
359            ];
360        }
361
362        $descriptor += [
363            'EditTools' => [
364                'type' => 'edittools',
365                'section' => 'description',
366                'message' => 'edittools-upload',
367            ]
368        ];
369
370        if ( $this->mForReUpload ) {
371            $descriptor['DestFile']['readonly'] = true;
372            $descriptor['UploadDescription']['size'] = 60;
373        } else {
374            $descriptor['License'] = [
375                'type' => 'select',
376                'class' => Licenses::class,
377                'section' => 'description',
378                'id' => 'wpLicense',
379                'label-message' => 'license',
380            ];
381            $descriptor['UploadDescription']['rows'] = 8;
382        }
383
384        if ( $config->get( MainConfigNames::UseCopyrightUpload ) ) {
385            $descriptor['UploadCopyStatus'] = [
386                'type' => 'text',
387                'section' => 'description',
388                'id' => 'wpUploadCopyStatus',
389                'label-message' => 'filestatus',
390            ];
391            $descriptor['UploadSource'] = [
392                'type' => 'text',
393                'section' => 'description',
394                'id' => 'wpUploadSource',
395                'label-message' => 'filesource',
396            ];
397        }
398
399        return $descriptor;
400    }
401
402    /**
403     * Get the descriptor of the fieldset that contains the upload options,
404     * such as "watch this file". The section is 'options'
405     *
406     * @return array Descriptor array
407     */
408    protected function getOptionsSection() {
409        $user = $this->getUser();
410        if ( $user->isRegistered() ) {
411            $descriptor = [
412                'Watchthis' => [
413                    'type' => 'check',
414                    'id' => 'wpWatchthis',
415                    'label-message' => 'watchthisupload',
416                    'section' => 'options',
417                    'default' => $this->mWatch,
418                ]
419            ];
420        }
421        if ( !$this->mHideIgnoreWarning ) {
422            $descriptor['IgnoreWarning'] = [
423                'type' => 'check',
424                'id' => 'wpIgnoreWarning',
425                'label-message' => 'ignorewarnings',
426                'section' => 'options',
427            ];
428        }
429
430        $descriptor['DestFileWarningAck'] = [
431            'type' => 'hidden',
432            'id' => 'wpDestFileWarningAck',
433            'default' => $this->mDestWarningAck ? '1' : '',
434        ];
435
436        if ( $this->mForReUpload ) {
437            $descriptor['ForReUpload'] = [
438                'type' => 'hidden',
439                'id' => 'wpForReUpload',
440                'default' => '1',
441            ];
442        }
443
444        return $descriptor;
445    }
446
447    /**
448     * Add the upload JS and show the form.
449     * @return bool|Status
450     */
451    public function show() {
452        $this->addUploadJS();
453        return parent::show();
454    }
455
456    /**
457     * Add upload JS to the OutputPage
458     */
459    protected function addUploadJS() {
460        $config = $this->getConfig();
461
462        $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
463
464        $scriptVars = [
465            'wgAjaxLicensePreview' => $config->get( MainConfigNames::AjaxLicensePreview ),
466            'wgUploadAutoFill' => !$this->mForReUpload &&
467                // If we received mDestFile from the request, don't autofill
468                // the wpDestFile textbox
469                $this->mDestFile === '',
470            'wgUploadSourceIds' => $this->mSourceIds,
471            'wgCheckFileExtensions' => $config->get( MainConfigNames::CheckFileExtensions ),
472            'wgStrictFileExtensions' => $config->get( MainConfigNames::StrictFileExtensions ),
473            'wgFileExtensions' =>
474                array_values( array_unique( $config->get( MainConfigNames::FileExtensions ) ) ),
475            'wgMaxUploadSize' => $this->mMaxUploadSize,
476            'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
477        ];
478
479        $out = $this->getOutput();
480        $out->addJsConfigVars( $scriptVars );
481
482        $out->addModules( [
483            'mediawiki.special.upload', // Extras for thumbnail and license preview.
484        ] );
485    }
486
487    /**
488     * Empty function; submission is handled elsewhere.
489     *
490     * @return bool False
491     */
492    public function trySubmit() {
493        return false;
494    }
495}