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\Linker\LinkRenderer; |
27 | use MediaWiki\MainConfigNames; |
28 | use MediaWiki\MediaWikiServices; |
29 | use MediaWiki\Specials\SpecialUpload; |
30 | use MediaWiki\Status\Status; |
31 | use MediaWiki\Title\NamespaceInfo; |
32 | use Wikimedia\RequestTimeout\TimeoutException; |
33 | |
34 | /** |
35 | * Sub class of HTMLForm that provides the form section of SpecialUpload |
36 | */ |
37 | class 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 | } |