Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 275 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
UploadForm | |
0.00% |
0 / 275 |
|
0.00% |
0 / 8 |
1560 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 48 |
|
0.00% |
0 / 1 |
90 | |||
getSourceSection | |
0.00% |
0 / 68 |
|
0.00% |
0 / 1 |
56 | |||
getExtensionsMessage | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
12 | |||
getDescriptionSection | |
0.00% |
0 / 79 |
|
0.00% |
0 / 1 |
132 | |||
getOptionsSection | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
30 | |||
show | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
addUploadJS | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
trySubmit | |
0.00% |
0 / 1 |
|
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 | |
21 | use MediaWiki\Context\IContextSource; |
22 | use MediaWiki\HookContainer\HookContainer; |
23 | use MediaWiki\HookContainer\HookRunner; |
24 | use MediaWiki\Html\Html; |
25 | use MediaWiki\HTMLForm\HTMLForm; |
26 | use MediaWiki\Language\Language; |
27 | use MediaWiki\Linker\LinkRenderer; |
28 | use MediaWiki\MainConfigNames; |
29 | use MediaWiki\MediaWikiServices; |
30 | use MediaWiki\Specials\SpecialUpload; |
31 | use MediaWiki\Status\Status; |
32 | use MediaWiki\Title\NamespaceInfo; |
33 | use Wikimedia\RequestTimeout\TimeoutException; |
34 | |
35 | /** |
36 | * Sub class of HTMLForm that provides the form section of SpecialUpload |
37 | */ |
38 | class 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 | } |