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