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