These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Implements Special:Upload |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup SpecialPage |
||
22 | * @ingroup Upload |
||
23 | */ |
||
24 | |||
25 | use MediaWiki\Linker\LinkRenderer; |
||
26 | use MediaWiki\MediaWikiServices; |
||
27 | |||
28 | /** |
||
29 | * Form for handling uploads and special page. |
||
30 | * |
||
31 | * @ingroup SpecialPage |
||
32 | * @ingroup Upload |
||
33 | */ |
||
34 | class SpecialUpload extends SpecialPage { |
||
35 | /** |
||
36 | * Constructor : initialise object |
||
37 | * Get data POSTed through the form and assign them to the object |
||
38 | * @param WebRequest $request Data posted. |
||
39 | */ |
||
40 | public function __construct( $request = null ) { |
||
41 | parent::__construct( 'Upload', 'upload' ); |
||
42 | } |
||
43 | |||
44 | public function doesWrites() { |
||
45 | return true; |
||
46 | } |
||
47 | |||
48 | /** Misc variables **/ |
||
49 | |||
50 | /** @var WebRequest|FauxRequest The request this form is supposed to handle */ |
||
51 | public $mRequest; |
||
52 | public $mSourceType; |
||
53 | |||
54 | /** @var UploadBase */ |
||
55 | public $mUpload; |
||
56 | |||
57 | /** @var LocalFile */ |
||
58 | public $mLocalFile; |
||
59 | public $mUploadClicked; |
||
60 | |||
61 | /** User input variables from the "description" section **/ |
||
62 | |||
63 | /** @var string The requested target file name */ |
||
64 | public $mDesiredDestName; |
||
65 | public $mComment; |
||
66 | public $mLicense; |
||
67 | |||
68 | /** User input variables from the root section **/ |
||
69 | |||
70 | public $mIgnoreWarning; |
||
71 | public $mWatchthis; |
||
72 | public $mCopyrightStatus; |
||
73 | public $mCopyrightSource; |
||
74 | |||
75 | /** Hidden variables **/ |
||
76 | |||
77 | public $mDestWarningAck; |
||
78 | |||
79 | /** @var bool The user followed an "overwrite this file" link */ |
||
80 | public $mForReUpload; |
||
81 | |||
82 | /** @var bool The user clicked "Cancel and return to upload form" button */ |
||
83 | public $mCancelUpload; |
||
84 | public $mTokenOk; |
||
85 | |||
86 | /** @var bool Subclasses can use this to determine whether a file was uploaded */ |
||
87 | public $mUploadSuccessful = false; |
||
88 | |||
89 | /** Text injection points for hooks not using HTMLForm **/ |
||
90 | public $uploadFormTextTop; |
||
91 | public $uploadFormTextAfterSummary; |
||
92 | |||
93 | /** |
||
94 | * Initialize instance variables from request and create an Upload handler |
||
95 | */ |
||
96 | protected function loadRequest() { |
||
97 | $this->mRequest = $request = $this->getRequest(); |
||
98 | $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); |
||
99 | $this->mUpload = UploadBase::createFromRequest( $request ); |
||
100 | $this->mUploadClicked = $request->wasPosted() |
||
101 | && ( $request->getCheck( 'wpUpload' ) |
||
102 | || $request->getCheck( 'wpUploadIgnoreWarning' ) ); |
||
103 | |||
104 | // Guess the desired name from the filename if not provided |
||
105 | $this->mDesiredDestName = $request->getText( 'wpDestFile' ); |
||
106 | if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { |
||
107 | $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' ); |
||
108 | } |
||
109 | $this->mLicense = $request->getText( 'wpLicense' ); |
||
110 | |||
111 | $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); |
||
112 | $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) |
||
113 | || $request->getCheck( 'wpUploadIgnoreWarning' ); |
||
114 | $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn(); |
||
115 | $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); |
||
116 | $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); |
||
117 | |||
118 | $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file |
||
119 | |||
120 | $commentDefault = ''; |
||
121 | $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage(); |
||
122 | if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) { |
||
123 | $commentDefault = $commentMsg->plain(); |
||
124 | } |
||
125 | $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault ); |
||
126 | |||
127 | $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) |
||
128 | || $request->getCheck( 'wpReUpload' ); // b/w compat |
||
129 | |||
130 | // If it was posted check for the token (no remote POST'ing with user credentials) |
||
131 | $token = $request->getVal( 'wpEditToken' ); |
||
132 | $this->mTokenOk = $this->getUser()->matchEditToken( $token ); |
||
133 | |||
134 | $this->uploadFormTextTop = ''; |
||
135 | $this->uploadFormTextAfterSummary = ''; |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * This page can be shown if uploading is enabled. |
||
140 | * Handle permission checking elsewhere in order to be able to show |
||
141 | * custom error messages. |
||
142 | * |
||
143 | * @param User $user |
||
144 | * @return bool |
||
145 | */ |
||
146 | public function userCanExecute( User $user ) { |
||
147 | return UploadBase::isEnabled() && parent::userCanExecute( $user ); |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Special page entry point |
||
152 | * @param string $par |
||
153 | * @throws ErrorPageError |
||
154 | * @throws Exception |
||
155 | * @throws FatalError |
||
156 | * @throws MWException |
||
157 | * @throws PermissionsError |
||
158 | * @throws ReadOnlyError |
||
159 | * @throws UserBlockedError |
||
160 | */ |
||
161 | public function execute( $par ) { |
||
162 | $this->useTransactionalTimeLimit(); |
||
163 | |||
164 | $this->setHeaders(); |
||
165 | $this->outputHeader(); |
||
166 | |||
167 | # Check uploading enabled |
||
168 | if ( !UploadBase::isEnabled() ) { |
||
169 | throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' ); |
||
170 | } |
||
171 | |||
172 | $this->addHelpLink( 'Help:Managing files' ); |
||
173 | |||
174 | # Check permissions |
||
175 | $user = $this->getUser(); |
||
176 | $permissionRequired = UploadBase::isAllowed( $user ); |
||
177 | if ( $permissionRequired !== true ) { |
||
178 | throw new PermissionsError( $permissionRequired ); |
||
179 | } |
||
180 | |||
181 | # Check blocks |
||
182 | if ( $user->isBlocked() ) { |
||
183 | throw new UserBlockedError( $user->getBlock() ); |
||
184 | } |
||
185 | |||
186 | // Global blocks |
||
187 | if ( $user->isBlockedGlobally() ) { |
||
188 | throw new UserBlockedError( $user->getGlobalBlock() ); |
||
189 | } |
||
190 | |||
191 | # Check whether we actually want to allow changing stuff |
||
192 | $this->checkReadOnly(); |
||
193 | |||
194 | $this->loadRequest(); |
||
195 | |||
196 | # Unsave the temporary file in case this was a cancelled upload |
||
197 | if ( $this->mCancelUpload ) { |
||
198 | if ( !$this->unsaveUploadedFile() ) { |
||
199 | # Something went wrong, so unsaveUploadedFile showed a warning |
||
200 | return; |
||
201 | } |
||
202 | } |
||
203 | |||
204 | # Process upload or show a form |
||
205 | if ( |
||
206 | $this->mTokenOk && !$this->mCancelUpload && |
||
207 | ( $this->mUpload && $this->mUploadClicked ) |
||
208 | ) { |
||
209 | $this->processUpload(); |
||
210 | } else { |
||
211 | # Backwards compatibility hook |
||
212 | if ( !Hooks::run( 'UploadForm:initial', [ &$this ] ) ) { |
||
213 | wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" ); |
||
214 | |||
215 | return; |
||
216 | } |
||
217 | $this->showUploadForm( $this->getUploadForm() ); |
||
218 | } |
||
219 | |||
220 | # Cleanup |
||
221 | if ( $this->mUpload ) { |
||
222 | $this->mUpload->cleanupTempFile(); |
||
223 | } |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Show the main upload form |
||
228 | * |
||
229 | * @param HTMLForm|string $form An HTMLForm instance or HTML string to show |
||
230 | */ |
||
231 | protected function showUploadForm( $form ) { |
||
232 | # Add links if file was previously deleted |
||
233 | if ( $this->mDesiredDestName ) { |
||
234 | $this->showViewDeletedLinks(); |
||
235 | } |
||
236 | |||
237 | if ( $form instanceof HTMLForm ) { |
||
238 | $form->show(); |
||
239 | } else { |
||
240 | $this->getOutput()->addHTML( $form ); |
||
241 | } |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Get an UploadForm instance with title and text properly set. |
||
246 | * |
||
247 | * @param string $message HTML string to add to the form |
||
248 | * @param string $sessionKey Session key in case this is a stashed upload |
||
249 | * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box |
||
250 | * @return UploadForm |
||
251 | */ |
||
252 | protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { |
||
253 | # Initialize form |
||
254 | $context = new DerivativeContext( $this->getContext() ); |
||
255 | $context->setTitle( $this->getPageTitle() ); // Remove subpage |
||
256 | $form = new UploadForm( [ |
||
257 | 'watch' => $this->getWatchCheck(), |
||
258 | 'forreupload' => $this->mForReUpload, |
||
259 | 'sessionkey' => $sessionKey, |
||
260 | 'hideignorewarning' => $hideIgnoreWarning, |
||
261 | 'destwarningack' => (bool)$this->mDestWarningAck, |
||
262 | |||
263 | 'description' => $this->mComment, |
||
264 | 'texttop' => $this->uploadFormTextTop, |
||
265 | 'textaftersummary' => $this->uploadFormTextAfterSummary, |
||
266 | 'destfile' => $this->mDesiredDestName, |
||
267 | ], $context, $this->getLinkRenderer() ); |
||
268 | |||
269 | # Check the token, but only if necessary |
||
270 | if ( |
||
271 | !$this->mTokenOk && !$this->mCancelUpload && |
||
272 | ( $this->mUpload && $this->mUploadClicked ) |
||
273 | ) { |
||
274 | $form->addPreText( $this->msg( 'session_fail_preview' )->parse() ); |
||
275 | } |
||
276 | |||
277 | # Give a notice if the user is uploading a file that has been deleted or moved |
||
278 | # Note that this is independent from the message 'filewasdeleted' |
||
279 | $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); |
||
280 | $delNotice = ''; // empty by default |
||
281 | if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { |
||
282 | LogEventsList::showLogExtract( $delNotice, [ 'delete', 'move' ], |
||
283 | $desiredTitleObj, |
||
284 | '', [ 'lim' => 10, |
||
285 | 'conds' => [ "log_action != 'revision'" ], |
||
286 | 'showIfEmpty' => false, |
||
287 | 'msgKey' => [ 'upload-recreate-warning' ] ] |
||
288 | ); |
||
289 | } |
||
290 | $form->addPreText( $delNotice ); |
||
0 ignored issues
–
show
|
|||
291 | |||
292 | # Add text to form |
||
293 | $form->addPreText( '<div id="uploadtext">' . |
||
294 | $this->msg( 'uploadtext', [ $this->mDesiredDestName ] )->parseAsBlock() . |
||
295 | '</div>' ); |
||
296 | # Add upload error message |
||
297 | $form->addPreText( $message ); |
||
298 | |||
299 | # Add footer to form |
||
300 | $uploadFooter = $this->msg( 'uploadfooter' ); |
||
301 | if ( !$uploadFooter->isDisabled() ) { |
||
302 | $form->addPostText( '<div id="mw-upload-footer-message">' |
||
303 | . $uploadFooter->parseAsBlock() . "</div>\n" ); |
||
304 | } |
||
305 | |||
306 | return $form; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Shows the "view X deleted revivions link"" |
||
311 | */ |
||
312 | protected function showViewDeletedLinks() { |
||
313 | $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); |
||
314 | $user = $this->getUser(); |
||
315 | // Show a subtitle link to deleted revisions (to sysops et al only) |
||
316 | if ( $title instanceof Title ) { |
||
317 | $count = $title->isDeleted(); |
||
318 | if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) { |
||
319 | $restorelink = $this->getLinkRenderer()->makeKnownLink( |
||
320 | SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), |
||
321 | $this->msg( 'restorelink' )->numParams( $count )->text() |
||
322 | ); |
||
323 | $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ) |
||
324 | ->rawParams( $restorelink )->parseAsBlock(); |
||
325 | $this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" ); |
||
326 | } |
||
327 | } |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Stashes the upload and shows the main upload form. |
||
332 | * |
||
333 | * Note: only errors that can be handled by changing the name or |
||
334 | * description should be redirected here. It should be assumed that the |
||
335 | * file itself is sane and has passed UploadBase::verifyFile. This |
||
336 | * essentially means that UploadBase::VERIFICATION_ERROR and |
||
337 | * UploadBase::EMPTY_FILE should not be passed here. |
||
338 | * |
||
339 | * @param string $message HTML message to be passed to mainUploadForm |
||
340 | */ |
||
341 | protected function showRecoverableUploadError( $message ) { |
||
342 | $stashStatus = $this->mUpload->tryStashFile( $this->getUser() ); |
||
343 | if ( $stashStatus->isGood() ) { |
||
344 | $sessionKey = $stashStatus->getValue()->getFileKey(); |
||
345 | } else { |
||
346 | $sessionKey = null; |
||
347 | // TODO Add a warning message about the failure to stash here? |
||
348 | } |
||
349 | $message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" . |
||
350 | '<div class="error">' . $message . "</div>\n"; |
||
351 | |||
352 | $form = $this->getUploadForm( $message, $sessionKey ); |
||
353 | $form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() ); |
||
354 | $this->showUploadForm( $form ); |
||
355 | } |
||
356 | |||
357 | /** |
||
358 | * Stashes the upload, shows the main form, but adds a "continue anyway button". |
||
359 | * Also checks whether there are actually warnings to display. |
||
360 | * |
||
361 | * @param array $warnings |
||
362 | * @return bool True if warnings were displayed, false if there are no |
||
363 | * warnings and it should continue processing |
||
364 | */ |
||
365 | protected function showUploadWarning( $warnings ) { |
||
366 | # If there are no warnings, or warnings we can ignore, return early. |
||
367 | # mDestWarningAck is set when some javascript has shown the warning |
||
368 | # to the user. mForReUpload is set when the user clicks the "upload a |
||
369 | # new version" link. |
||
370 | if ( !$warnings || ( count( $warnings ) == 1 |
||
371 | && isset( $warnings['exists'] ) |
||
372 | && ( $this->mDestWarningAck || $this->mForReUpload ) ) |
||
373 | ) { |
||
374 | return false; |
||
375 | } |
||
376 | |||
377 | $stashStatus = $this->mUpload->tryStashFile( $this->getUser() ); |
||
378 | if ( $stashStatus->isGood() ) { |
||
379 | $sessionKey = $stashStatus->getValue()->getFileKey(); |
||
380 | } else { |
||
381 | $sessionKey = null; |
||
382 | // TODO Add a warning message about the failure to stash here? |
||
383 | } |
||
384 | |||
385 | // Add styles for the warning, reused from the live preview |
||
386 | $this->getOutput()->addModuleStyles( 'mediawiki.special.upload.styles' ); |
||
387 | |||
388 | $linkRenderer = $this->getLinkRenderer(); |
||
389 | $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" |
||
390 | . '<div class="mw-destfile-warning"><ul>'; |
||
391 | foreach ( $warnings as $warning => $args ) { |
||
392 | if ( $warning == 'badfilename' ) { |
||
393 | $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText(); |
||
394 | } |
||
395 | if ( $warning == 'exists' ) { |
||
396 | $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n"; |
||
397 | } elseif ( $warning == 'no-change' ) { |
||
398 | $file = $args; |
||
399 | $filename = $file->getTitle()->getPrefixedText(); |
||
400 | $msg = "\t<li>" . wfMessage( 'fileexists-no-change', $filename )->parse() . "</li>\n"; |
||
401 | } elseif ( $warning == 'duplicate-version' ) { |
||
402 | $file = $args[0]; |
||
403 | $count = count( $args ); |
||
404 | $filename = $file->getTitle()->getPrefixedText(); |
||
405 | $message = wfMessage( 'fileexists-duplicate-version' ) |
||
406 | ->params( $filename ) |
||
407 | ->numParams( $count ); |
||
408 | $msg = "\t<li>" . $message->parse() . "</li>\n"; |
||
409 | } elseif ( $warning == 'was-deleted' ) { |
||
410 | # If the file existed before and was deleted, warn the user of this |
||
411 | $ltitle = SpecialPage::getTitleFor( 'Log' ); |
||
412 | $llink = $linkRenderer->makeKnownLink( |
||
413 | $ltitle, |
||
414 | wfMessage( 'deletionlog' )->text(), |
||
415 | [], |
||
416 | [ |
||
417 | 'type' => 'delete', |
||
418 | 'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(), |
||
419 | ] |
||
420 | ); |
||
421 | $msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n"; |
||
422 | } elseif ( $warning == 'duplicate' ) { |
||
423 | $msg = $this->getDupeWarning( $args ); |
||
424 | } elseif ( $warning == 'duplicate-archive' ) { |
||
425 | if ( $args === '' ) { |
||
426 | $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse() |
||
427 | . "</li>\n"; |
||
428 | } else { |
||
429 | $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate', |
||
430 | Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse() |
||
431 | . "</li>\n"; |
||
432 | } |
||
433 | } else { |
||
434 | View Code Duplication | if ( $args === true ) { |
|
435 | $args = []; |
||
436 | } elseif ( !is_array( $args ) ) { |
||
437 | $args = [ $args ]; |
||
438 | } |
||
439 | $msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n"; |
||
440 | } |
||
441 | $warningHtml .= $msg; |
||
442 | } |
||
443 | $warningHtml .= "</ul></div>\n"; |
||
444 | $warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock(); |
||
445 | |||
446 | $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true ); |
||
447 | $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() ); |
||
448 | $form->addButton( [ |
||
449 | 'name' => 'wpUploadIgnoreWarning', |
||
450 | 'value' => $this->msg( 'ignorewarning' )->text() |
||
451 | ] ); |
||
452 | $form->addButton( [ |
||
453 | 'name' => 'wpCancelUpload', |
||
454 | 'value' => $this->msg( 'reuploaddesc' )->text() |
||
455 | ] ); |
||
456 | |||
457 | $this->showUploadForm( $form ); |
||
458 | |||
459 | # Indicate that we showed a form |
||
460 | return true; |
||
461 | } |
||
462 | |||
463 | /** |
||
464 | * Show the upload form with error message, but do not stash the file. |
||
465 | * |
||
466 | * @param string $message HTML string |
||
467 | */ |
||
468 | protected function showUploadError( $message ) { |
||
469 | $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" . |
||
470 | '<div class="error">' . $message . "</div>\n"; |
||
471 | $this->showUploadForm( $this->getUploadForm( $message ) ); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Do the upload. |
||
476 | * Checks are made in SpecialUpload::execute() |
||
477 | */ |
||
478 | protected function processUpload() { |
||
479 | // Fetch the file if required |
||
480 | $status = $this->mUpload->fetchFile(); |
||
481 | if ( !$status->isOK() ) { |
||
482 | $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); |
||
483 | |||
484 | return; |
||
485 | } |
||
486 | |||
487 | if ( !Hooks::run( 'UploadForm:BeforeProcessing', [ &$this ] ) ) { |
||
488 | wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); |
||
489 | // This code path is deprecated. If you want to break upload processing |
||
490 | // do so by hooking into the appropriate hooks in UploadBase::verifyUpload |
||
491 | // and UploadBase::verifyFile. |
||
492 | // If you use this hook to break uploading, the user will be returned |
||
493 | // an empty form with no error message whatsoever. |
||
494 | return; |
||
495 | } |
||
496 | |||
497 | // Upload verification |
||
498 | $details = $this->mUpload->verifyUpload(); |
||
499 | if ( $details['status'] != UploadBase::OK ) { |
||
500 | $this->processVerificationError( $details ); |
||
501 | |||
502 | return; |
||
503 | } |
||
504 | |||
505 | // Verify permissions for this title |
||
506 | $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() ); |
||
507 | if ( $permErrors !== true ) { |
||
508 | $code = array_shift( $permErrors[0] ); |
||
509 | $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() ); |
||
510 | |||
511 | return; |
||
512 | } |
||
513 | |||
514 | $this->mLocalFile = $this->mUpload->getLocalFile(); |
||
515 | |||
516 | // Check warnings if necessary |
||
517 | if ( !$this->mIgnoreWarning ) { |
||
518 | $warnings = $this->mUpload->checkWarnings(); |
||
519 | if ( $this->showUploadWarning( $warnings ) ) { |
||
520 | return; |
||
521 | } |
||
522 | } |
||
523 | |||
524 | // This is as late as we can throttle, after expected issues have been handled |
||
525 | if ( UploadBase::isThrottled( $this->getUser() ) ) { |
||
526 | $this->showRecoverableUploadError( |
||
527 | $this->msg( 'actionthrottledtext' )->escaped() |
||
528 | ); |
||
529 | return; |
||
530 | } |
||
531 | |||
532 | // Get the page text if this is not a reupload |
||
533 | if ( !$this->mForReUpload ) { |
||
534 | $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, |
||
535 | $this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() ); |
||
536 | } else { |
||
537 | $pageText = false; |
||
538 | } |
||
539 | |||
540 | $changeTags = $this->getRequest()->getVal( 'wpChangeTags' ); |
||
541 | if ( is_null( $changeTags ) || $changeTags === '' ) { |
||
542 | $changeTags = []; |
||
543 | } else { |
||
544 | $changeTags = array_filter( array_map( 'trim', explode( ',', $changeTags ) ) ); |
||
545 | } |
||
546 | |||
547 | if ( $changeTags ) { |
||
548 | $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange( |
||
549 | $changeTags, $this->getUser() ); |
||
550 | if ( !$changeTagsStatus->isOK() ) { |
||
551 | $this->showUploadError( $this->getOutput()->parse( $changeTagsStatus->getWikiText() ) ); |
||
552 | |||
553 | return; |
||
554 | } |
||
555 | } |
||
556 | |||
557 | $status = $this->mUpload->performUpload( |
||
558 | $this->mComment, |
||
559 | $pageText, |
||
560 | $this->mWatchthis, |
||
561 | $this->getUser(), |
||
562 | $changeTags |
||
563 | ); |
||
564 | |||
565 | if ( !$status->isGood() ) { |
||
566 | $this->showRecoverableUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); |
||
567 | |||
568 | return; |
||
569 | } |
||
570 | |||
571 | // Success, redirect to description page |
||
572 | $this->mUploadSuccessful = true; |
||
573 | Hooks::run( 'SpecialUploadComplete', [ &$this ] ); |
||
574 | $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() ); |
||
575 | } |
||
576 | |||
577 | /** |
||
578 | * Get the initial image page text based on a comment and optional file status information |
||
579 | * @param string $comment |
||
580 | * @param string $license |
||
581 | * @param string $copyStatus |
||
582 | * @param string $source |
||
583 | * @param Config $config Configuration object to load data from |
||
584 | * @return string |
||
585 | */ |
||
586 | public static function getInitialPageText( $comment = '', $license = '', |
||
587 | $copyStatus = '', $source = '', Config $config = null |
||
588 | ) { |
||
589 | View Code Duplication | if ( $config === null ) { |
|
590 | wfDebug( __METHOD__ . ' called without a Config instance passed to it' ); |
||
591 | $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); |
||
592 | } |
||
593 | |||
594 | $msg = []; |
||
595 | $forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' ); |
||
596 | /* These messages are transcluded into the actual text of the description page. |
||
597 | * Thus, forcing them as content messages makes the upload to produce an int: template |
||
598 | * instead of hardcoding it there in the uploader language. |
||
599 | */ |
||
600 | foreach ( [ 'license-header', 'filedesc', 'filestatus', 'filesource' ] as $msgName ) { |
||
601 | if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) { |
||
602 | $msg[$msgName] = "{{int:$msgName}}"; |
||
603 | } else { |
||
604 | $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text(); |
||
605 | } |
||
606 | } |
||
607 | |||
608 | if ( $config->get( 'UseCopyrightUpload' ) ) { |
||
609 | $licensetxt = ''; |
||
610 | if ( $license != '' ) { |
||
611 | $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; |
||
612 | } |
||
613 | $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" . |
||
614 | '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" . |
||
615 | "$licensetxt" . |
||
616 | '== ' . $msg['filesource'] . " ==\n" . $source; |
||
617 | } else { |
||
618 | if ( $license != '' ) { |
||
619 | $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n"; |
||
620 | $pageText = $filedesc . |
||
621 | '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; |
||
622 | } else { |
||
623 | $pageText = $comment; |
||
624 | } |
||
625 | } |
||
626 | |||
627 | return $pageText; |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * See if we should check the 'watch this page' checkbox on the form |
||
632 | * based on the user's preferences and whether we're being asked |
||
633 | * to create a new file or update an existing one. |
||
634 | * |
||
635 | * In the case where 'watch edits' is off but 'watch creations' is on, |
||
636 | * we'll leave the box unchecked. |
||
637 | * |
||
638 | * Note that the page target can be changed *on the form*, so our check |
||
639 | * state can get out of sync. |
||
640 | * @return bool|string |
||
641 | */ |
||
642 | protected function getWatchCheck() { |
||
643 | if ( $this->getUser()->getOption( 'watchdefault' ) ) { |
||
644 | // Watch all edits! |
||
645 | return true; |
||
646 | } |
||
647 | |||
648 | $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); |
||
649 | if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) { |
||
650 | // Already watched, don't change that |
||
651 | return true; |
||
652 | } |
||
653 | |||
654 | $local = wfLocalFile( $this->mDesiredDestName ); |
||
655 | if ( $local && $local->exists() ) { |
||
656 | // We're uploading a new version of an existing file. |
||
657 | // No creation, so don't watch it if we're not already. |
||
658 | return false; |
||
659 | } else { |
||
660 | // New page should get watched if that's our option. |
||
661 | return $this->getUser()->getOption( 'watchcreations' ) || |
||
662 | $this->getUser()->getOption( 'watchuploads' ); |
||
663 | } |
||
664 | } |
||
665 | |||
666 | /** |
||
667 | * Provides output to the user for a result of UploadBase::verifyUpload |
||
668 | * |
||
669 | * @param array $details Result of UploadBase::verifyUpload |
||
670 | * @throws MWException |
||
671 | */ |
||
672 | protected function processVerificationError( $details ) { |
||
673 | switch ( $details['status'] ) { |
||
674 | |||
675 | /** Statuses that only require name changing **/ |
||
676 | case UploadBase::MIN_LENGTH_PARTNAME: |
||
677 | $this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() ); |
||
678 | break; |
||
679 | case UploadBase::ILLEGAL_FILENAME: |
||
680 | $this->showRecoverableUploadError( $this->msg( 'illegalfilename', |
||
681 | $details['filtered'] )->parse() ); |
||
682 | break; |
||
683 | case UploadBase::FILENAME_TOO_LONG: |
||
684 | $this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() ); |
||
685 | break; |
||
686 | case UploadBase::FILETYPE_MISSING: |
||
687 | $this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() ); |
||
688 | break; |
||
689 | case UploadBase::WINDOWS_NONASCII_FILENAME: |
||
690 | $this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() ); |
||
691 | break; |
||
692 | |||
693 | /** Statuses that require reuploading **/ |
||
694 | case UploadBase::EMPTY_FILE: |
||
695 | $this->showUploadError( $this->msg( 'emptyfile' )->escaped() ); |
||
696 | break; |
||
697 | case UploadBase::FILE_TOO_LARGE: |
||
698 | $this->showUploadError( $this->msg( 'largefileserver' )->escaped() ); |
||
699 | break; |
||
700 | case UploadBase::FILETYPE_BADTYPE: |
||
701 | $msg = $this->msg( 'filetype-banned-type' ); |
||
702 | if ( isset( $details['blacklistedExt'] ) ) { |
||
703 | $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) ); |
||
704 | } else { |
||
705 | $msg->params( $details['finalExt'] ); |
||
706 | } |
||
707 | $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) ); |
||
708 | $msg->params( $this->getLanguage()->commaList( $extensions ), |
||
709 | count( $extensions ) ); |
||
710 | |||
711 | // Add PLURAL support for the first parameter. This results |
||
712 | // in a bit unlogical parameter sequence, but does not break |
||
713 | // old translations |
||
714 | if ( isset( $details['blacklistedExt'] ) ) { |
||
715 | $msg->params( count( $details['blacklistedExt'] ) ); |
||
716 | } else { |
||
717 | $msg->params( 1 ); |
||
718 | } |
||
719 | |||
720 | $this->showUploadError( $msg->parse() ); |
||
721 | break; |
||
722 | case UploadBase::VERIFICATION_ERROR: |
||
723 | unset( $details['status'] ); |
||
724 | $code = array_shift( $details['details'] ); |
||
725 | $this->showUploadError( $this->msg( $code, $details['details'] )->parse() ); |
||
726 | break; |
||
727 | case UploadBase::HOOK_ABORTED: |
||
728 | if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array |
||
729 | $args = $details['error']; |
||
730 | $error = array_shift( $args ); |
||
731 | } else { |
||
732 | $error = $details['error']; |
||
733 | $args = null; |
||
734 | } |
||
735 | |||
736 | $this->showUploadError( $this->msg( $error, $args )->parse() ); |
||
737 | break; |
||
738 | default: |
||
739 | throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" ); |
||
740 | } |
||
741 | } |
||
742 | |||
743 | /** |
||
744 | * Remove a temporarily kept file stashed by saveTempUploadedFile(). |
||
745 | * |
||
746 | * @return bool Success |
||
747 | */ |
||
748 | protected function unsaveUploadedFile() { |
||
749 | if ( !( $this->mUpload instanceof UploadFromStash ) ) { |
||
750 | return true; |
||
751 | } |
||
752 | $success = $this->mUpload->unsaveUploadedFile(); |
||
753 | if ( !$success ) { |
||
754 | $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() ); |
||
755 | |||
756 | return false; |
||
757 | } else { |
||
758 | return true; |
||
759 | } |
||
760 | } |
||
761 | |||
762 | /*** Functions for formatting warnings ***/ |
||
763 | |||
764 | /** |
||
765 | * Formats a result of UploadBase::getExistsWarning as HTML |
||
766 | * This check is static and can be done pre-upload via AJAX |
||
767 | * |
||
768 | * @param array $exists The result of UploadBase::getExistsWarning |
||
769 | * @return string Empty string if there is no warning or an HTML fragment |
||
770 | */ |
||
771 | public static function getExistsWarning( $exists ) { |
||
772 | if ( !$exists ) { |
||
773 | return ''; |
||
774 | } |
||
775 | |||
776 | $file = $exists['file']; |
||
777 | $filename = $file->getTitle()->getPrefixedText(); |
||
778 | $warnMsg = null; |
||
779 | |||
780 | if ( $exists['warning'] == 'exists' ) { |
||
781 | // Exact match |
||
782 | $warnMsg = wfMessage( 'fileexists', $filename ); |
||
783 | } elseif ( $exists['warning'] == 'page-exists' ) { |
||
784 | // Page exists but file does not |
||
785 | $warnMsg = wfMessage( 'filepageexists', $filename ); |
||
786 | } elseif ( $exists['warning'] == 'exists-normalized' ) { |
||
787 | $warnMsg = wfMessage( 'fileexists-extension', $filename, |
||
788 | $exists['normalizedFile']->getTitle()->getPrefixedText() ); |
||
789 | } elseif ( $exists['warning'] == 'thumb' ) { |
||
790 | // Swapped argument order compared with other messages for backwards compatibility |
||
791 | $warnMsg = wfMessage( 'fileexists-thumbnail-yes', |
||
792 | $exists['thumbFile']->getTitle()->getPrefixedText(), $filename ); |
||
793 | } elseif ( $exists['warning'] == 'thumb-name' ) { |
||
794 | // Image w/o '180px-' does not exists, but we do not like these filenames |
||
795 | $name = $file->getName(); |
||
796 | $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 ); |
||
797 | $warnMsg = wfMessage( 'file-thumbnail-no', $badPart ); |
||
798 | } elseif ( $exists['warning'] == 'bad-prefix' ) { |
||
799 | $warnMsg = wfMessage( 'filename-bad-prefix', $exists['prefix'] ); |
||
800 | } |
||
801 | |||
802 | return $warnMsg ? $warnMsg->title( $file->getTitle() )->parse() : ''; |
||
803 | } |
||
804 | |||
805 | /** |
||
806 | * Construct a warning and a gallery from an array of duplicate files. |
||
807 | * @param array $dupes |
||
808 | * @return string |
||
809 | */ |
||
810 | public function getDupeWarning( $dupes ) { |
||
811 | if ( !$dupes ) { |
||
812 | return ''; |
||
813 | } |
||
814 | |||
815 | $gallery = ImageGalleryBase::factory( false, $this->getContext() ); |
||
816 | $gallery->setShowBytes( false ); |
||
817 | foreach ( $dupes as $file ) { |
||
818 | $gallery->add( $file->getTitle() ); |
||
819 | } |
||
820 | |||
821 | return '<li>' . |
||
822 | $this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() . |
||
823 | $gallery->toHTML() . "</li>\n"; |
||
824 | } |
||
825 | |||
826 | protected function getGroupName() { |
||
827 | return 'media'; |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Should we rotate images in the preview on Special:Upload. |
||
832 | * |
||
833 | * This controls js: mw.config.get( 'wgFileCanRotate' ) |
||
834 | * |
||
835 | * @todo What about non-BitmapHandler handled files? |
||
836 | */ |
||
837 | public static function rotationEnabled() { |
||
838 | $bitmapHandler = new BitmapHandler(); |
||
839 | return $bitmapHandler->autoRotateEnabled(); |
||
840 | } |
||
841 | } |
||
842 | |||
843 | /** |
||
844 | * Sub class of HTMLForm that provides the form section of SpecialUpload |
||
845 | */ |
||
846 | class UploadForm extends HTMLForm { |
||
847 | protected $mWatch; |
||
848 | protected $mForReUpload; |
||
849 | protected $mSessionKey; |
||
850 | protected $mHideIgnoreWarning; |
||
851 | protected $mDestWarningAck; |
||
852 | protected $mDestFile; |
||
853 | |||
854 | protected $mComment; |
||
855 | protected $mTextTop; |
||
856 | protected $mTextAfterSummary; |
||
857 | |||
858 | protected $mSourceIds; |
||
859 | |||
860 | protected $mMaxFileSize = []; |
||
861 | |||
862 | protected $mMaxUploadSize = []; |
||
863 | |||
864 | public function __construct( array $options = [], IContextSource $context = null, |
||
865 | LinkRenderer $linkRenderer = null |
||
866 | ) { |
||
867 | if ( $context instanceof IContextSource ) { |
||
868 | $this->setContext( $context ); |
||
869 | } |
||
870 | |||
871 | if ( !$linkRenderer ) { |
||
872 | $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); |
||
873 | } |
||
874 | |||
875 | $this->mWatch = !empty( $options['watch'] ); |
||
876 | $this->mForReUpload = !empty( $options['forreupload'] ); |
||
877 | $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : ''; |
||
878 | $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] ); |
||
879 | $this->mDestWarningAck = !empty( $options['destwarningack'] ); |
||
880 | $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : ''; |
||
881 | |||
882 | $this->mComment = isset( $options['description'] ) ? |
||
883 | $options['description'] : ''; |
||
884 | |||
885 | $this->mTextTop = isset( $options['texttop'] ) |
||
886 | ? $options['texttop'] : ''; |
||
887 | |||
888 | $this->mTextAfterSummary = isset( $options['textaftersummary'] ) |
||
889 | ? $options['textaftersummary'] : ''; |
||
890 | |||
891 | $sourceDescriptor = $this->getSourceSection(); |
||
892 | $descriptor = $sourceDescriptor |
||
893 | + $this->getDescriptionSection() |
||
894 | + $this->getOptionsSection(); |
||
895 | |||
896 | Hooks::run( 'UploadFormInitDescriptor', [ &$descriptor ] ); |
||
897 | parent::__construct( $descriptor, $context, 'upload' ); |
||
898 | |||
899 | # Add a link to edit MediaWiki:Licenses |
||
900 | if ( $this->getUser()->isAllowed( 'editinterface' ) ) { |
||
901 | $this->getOutput()->addModuleStyles( 'mediawiki.special.upload.styles' ); |
||
902 | $licensesLink = $linkRenderer->makeKnownLink( |
||
903 | $this->msg( 'licenses' )->inContentLanguage()->getTitle(), |
||
904 | $this->msg( 'licenses-edit' )->text(), |
||
905 | [], |
||
906 | [ 'action' => 'edit' ] |
||
907 | ); |
||
908 | $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>'; |
||
909 | $this->addFooterText( $editLicenses, 'description' ); |
||
910 | } |
||
911 | |||
912 | # Set some form properties |
||
913 | $this->setSubmitText( $this->msg( 'uploadbtn' )->text() ); |
||
914 | $this->setSubmitName( 'wpUpload' ); |
||
915 | # Used message keys: 'accesskey-upload', 'tooltip-upload' |
||
916 | $this->setSubmitTooltip( 'upload' ); |
||
917 | $this->setId( 'mw-upload-form' ); |
||
918 | |||
919 | # Build a list of IDs for javascript insertion |
||
920 | $this->mSourceIds = []; |
||
921 | foreach ( $sourceDescriptor as $field ) { |
||
922 | if ( !empty( $field['id'] ) ) { |
||
923 | $this->mSourceIds[] = $field['id']; |
||
924 | } |
||
925 | } |
||
926 | } |
||
927 | |||
928 | /** |
||
929 | * Get the descriptor of the fieldset that contains the file source |
||
930 | * selection. The section is 'source' |
||
931 | * |
||
932 | * @return array Descriptor array |
||
933 | */ |
||
934 | protected function getSourceSection() { |
||
935 | if ( $this->mSessionKey ) { |
||
936 | return [ |
||
937 | 'SessionKey' => [ |
||
938 | 'type' => 'hidden', |
||
939 | 'default' => $this->mSessionKey, |
||
940 | ], |
||
941 | 'SourceType' => [ |
||
942 | 'type' => 'hidden', |
||
943 | 'default' => 'Stash', |
||
944 | ], |
||
945 | ]; |
||
946 | } |
||
947 | |||
948 | $canUploadByUrl = UploadFromUrl::isEnabled() |
||
949 | && ( UploadFromUrl::isAllowed( $this->getUser() ) === true ) |
||
950 | && $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' ); |
||
951 | $radio = $canUploadByUrl; |
||
952 | $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) ); |
||
953 | |||
954 | $descriptor = []; |
||
955 | if ( $this->mTextTop ) { |
||
956 | $descriptor['UploadFormTextTop'] = [ |
||
957 | 'type' => 'info', |
||
958 | 'section' => 'source', |
||
959 | 'default' => $this->mTextTop, |
||
960 | 'raw' => true, |
||
961 | ]; |
||
962 | } |
||
963 | |||
964 | $this->mMaxUploadSize['file'] = min( |
||
965 | UploadBase::getMaxUploadSize( 'file' ), |
||
966 | UploadBase::getMaxPhpUploadSize() |
||
967 | ); |
||
968 | |||
969 | $help = $this->msg( 'upload-maxfilesize', |
||
970 | $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) |
||
971 | )->parse(); |
||
972 | |||
973 | // If the user can also upload by URL, there are 2 different file size limits. |
||
974 | // This extra message helps stress which limit corresponds to what. |
||
975 | if ( $canUploadByUrl ) { |
||
976 | $help .= $this->msg( 'word-separator' )->escaped(); |
||
977 | $help .= $this->msg( 'upload_source_file' )->parse(); |
||
978 | } |
||
979 | |||
980 | $descriptor['UploadFile'] = [ |
||
981 | 'class' => 'UploadSourceField', |
||
982 | 'section' => 'source', |
||
983 | 'type' => 'file', |
||
984 | 'id' => 'wpUploadFile', |
||
985 | 'radio-id' => 'wpSourceTypeFile', |
||
986 | 'label-message' => 'sourcefilename', |
||
987 | 'upload-type' => 'File', |
||
988 | 'radio' => &$radio, |
||
989 | 'help' => $help, |
||
990 | 'checked' => $selectedSourceType == 'file', |
||
991 | ]; |
||
992 | |||
993 | if ( $canUploadByUrl ) { |
||
994 | $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' ); |
||
995 | $descriptor['UploadFileURL'] = [ |
||
996 | 'class' => 'UploadSourceField', |
||
997 | 'section' => 'source', |
||
998 | 'id' => 'wpUploadFileURL', |
||
999 | 'radio-id' => 'wpSourceTypeurl', |
||
1000 | 'label-message' => 'sourceurl', |
||
1001 | 'upload-type' => 'url', |
||
1002 | 'radio' => &$radio, |
||
1003 | 'help' => $this->msg( 'upload-maxfilesize', |
||
1004 | $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) |
||
1005 | )->parse() . |
||
1006 | $this->msg( 'word-separator' )->escaped() . |
||
1007 | $this->msg( 'upload_source_url' )->parse(), |
||
1008 | 'checked' => $selectedSourceType == 'url', |
||
1009 | ]; |
||
1010 | } |
||
1011 | Hooks::run( 'UploadFormSourceDescriptors', [ &$descriptor, &$radio, $selectedSourceType ] ); |
||
1012 | |||
1013 | $descriptor['Extensions'] = [ |
||
1014 | 'type' => 'info', |
||
1015 | 'section' => 'source', |
||
1016 | 'default' => $this->getExtensionsMessage(), |
||
1017 | 'raw' => true, |
||
1018 | ]; |
||
1019 | |||
1020 | return $descriptor; |
||
1021 | } |
||
1022 | |||
1023 | /** |
||
1024 | * Get the messages indicating which extensions are preferred and prohibitted. |
||
1025 | * |
||
1026 | * @return string HTML string containing the message |
||
1027 | */ |
||
1028 | protected function getExtensionsMessage() { |
||
1029 | # Print a list of allowed file extensions, if so configured. We ignore |
||
1030 | # MIME type here, it's incomprehensible to most people and too long. |
||
1031 | $config = $this->getConfig(); |
||
1032 | |||
1033 | if ( $config->get( 'CheckFileExtensions' ) ) { |
||
1034 | $fileExtensions = array_unique( $config->get( 'FileExtensions' ) ); |
||
1035 | if ( $config->get( 'StrictFileExtensions' ) ) { |
||
1036 | # Everything not permitted is banned |
||
1037 | $extensionsList = |
||
1038 | '<div id="mw-upload-permitted">' . |
||
1039 | $this->msg( 'upload-permitted' ) |
||
1040 | ->params( $this->getLanguage()->commaList( $fileExtensions ) ) |
||
1041 | ->numParams( count( $fileExtensions ) ) |
||
1042 | ->parseAsBlock() . |
||
1043 | "</div>\n"; |
||
1044 | } else { |
||
1045 | # We have to list both preferred and prohibited |
||
1046 | $fileBlacklist = array_unique( $config->get( 'FileBlacklist' ) ); |
||
1047 | $extensionsList = |
||
1048 | '<div id="mw-upload-preferred">' . |
||
1049 | $this->msg( 'upload-preferred' ) |
||
1050 | ->params( $this->getLanguage()->commaList( $fileExtensions ) ) |
||
1051 | ->numParams( count( $fileExtensions ) ) |
||
1052 | ->parseAsBlock() . |
||
1053 | "</div>\n" . |
||
1054 | '<div id="mw-upload-prohibited">' . |
||
1055 | $this->msg( 'upload-prohibited' ) |
||
1056 | ->params( $this->getLanguage()->commaList( $fileBlacklist ) ) |
||
1057 | ->numParams( count( $fileBlacklist ) ) |
||
1058 | ->parseAsBlock() . |
||
1059 | "</div>\n"; |
||
1060 | } |
||
1061 | } else { |
||
1062 | # Everything is permitted. |
||
1063 | $extensionsList = ''; |
||
1064 | } |
||
1065 | |||
1066 | return $extensionsList; |
||
1067 | } |
||
1068 | |||
1069 | /** |
||
1070 | * Get the descriptor of the fieldset that contains the file description |
||
1071 | * input. The section is 'description' |
||
1072 | * |
||
1073 | * @return array Descriptor array |
||
1074 | */ |
||
1075 | protected function getDescriptionSection() { |
||
1076 | $config = $this->getConfig(); |
||
1077 | if ( $this->mSessionKey ) { |
||
1078 | $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() ); |
||
1079 | try { |
||
1080 | $file = $stash->getFile( $this->mSessionKey ); |
||
1081 | } catch ( Exception $e ) { |
||
1082 | $file = null; |
||
1083 | } |
||
1084 | if ( $file ) { |
||
1085 | global $wgContLang; |
||
1086 | |||
1087 | $mto = $file->transform( [ 'width' => 120 ] ); |
||
1088 | $this->addHeaderText( |
||
1089 | '<div class="thumb t' . $wgContLang->alignEnd() . '">' . |
||
1090 | Html::element( 'img', [ |
||
1091 | 'src' => $mto->getUrl(), |
||
1092 | 'class' => 'thumbimage', |
||
1093 | ] ) . '</div>', 'description' ); |
||
1094 | } |
||
1095 | } |
||
1096 | |||
1097 | $descriptor = [ |
||
1098 | 'DestFile' => [ |
||
1099 | 'type' => 'text', |
||
1100 | 'section' => 'description', |
||
1101 | 'id' => 'wpDestFile', |
||
1102 | 'label-message' => 'destfilename', |
||
1103 | 'size' => 60, |
||
1104 | 'default' => $this->mDestFile, |
||
1105 | # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm |
||
1106 | 'nodata' => strval( $this->mDestFile ) !== '', |
||
1107 | ], |
||
1108 | 'UploadDescription' => [ |
||
1109 | 'type' => 'textarea', |
||
1110 | 'section' => 'description', |
||
1111 | 'id' => 'wpUploadDescription', |
||
1112 | 'label-message' => $this->mForReUpload |
||
1113 | ? 'filereuploadsummary' |
||
1114 | : 'fileuploadsummary', |
||
1115 | 'default' => $this->mComment, |
||
1116 | 'cols' => $this->getUser()->getIntOption( 'cols' ), |
||
1117 | 'rows' => 8, |
||
1118 | ] |
||
1119 | ]; |
||
1120 | if ( $this->mTextAfterSummary ) { |
||
1121 | $descriptor['UploadFormTextAfterSummary'] = [ |
||
1122 | 'type' => 'info', |
||
1123 | 'section' => 'description', |
||
1124 | 'default' => $this->mTextAfterSummary, |
||
1125 | 'raw' => true, |
||
1126 | ]; |
||
1127 | } |
||
1128 | |||
1129 | $descriptor += [ |
||
1130 | 'EditTools' => [ |
||
1131 | 'type' => 'edittools', |
||
1132 | 'section' => 'description', |
||
1133 | 'message' => 'edittools-upload', |
||
1134 | ] |
||
1135 | ]; |
||
1136 | |||
1137 | if ( $this->mForReUpload ) { |
||
1138 | $descriptor['DestFile']['readonly'] = true; |
||
1139 | } else { |
||
1140 | $descriptor['License'] = [ |
||
1141 | 'type' => 'select', |
||
1142 | 'class' => 'Licenses', |
||
1143 | 'section' => 'description', |
||
1144 | 'id' => 'wpLicense', |
||
1145 | 'label-message' => 'license', |
||
1146 | ]; |
||
1147 | } |
||
1148 | |||
1149 | if ( $config->get( 'UseCopyrightUpload' ) ) { |
||
1150 | $descriptor['UploadCopyStatus'] = [ |
||
1151 | 'type' => 'text', |
||
1152 | 'section' => 'description', |
||
1153 | 'id' => 'wpUploadCopyStatus', |
||
1154 | 'label-message' => 'filestatus', |
||
1155 | ]; |
||
1156 | $descriptor['UploadSource'] = [ |
||
1157 | 'type' => 'text', |
||
1158 | 'section' => 'description', |
||
1159 | 'id' => 'wpUploadSource', |
||
1160 | 'label-message' => 'filesource', |
||
1161 | ]; |
||
1162 | } |
||
1163 | |||
1164 | return $descriptor; |
||
1165 | } |
||
1166 | |||
1167 | /** |
||
1168 | * Get the descriptor of the fieldset that contains the upload options, |
||
1169 | * such as "watch this file". The section is 'options' |
||
1170 | * |
||
1171 | * @return array Descriptor array |
||
1172 | */ |
||
1173 | protected function getOptionsSection() { |
||
1174 | $user = $this->getUser(); |
||
1175 | if ( $user->isLoggedIn() ) { |
||
1176 | $descriptor = [ |
||
1177 | 'Watchthis' => [ |
||
1178 | 'type' => 'check', |
||
1179 | 'id' => 'wpWatchthis', |
||
1180 | 'label-message' => 'watchthisupload', |
||
1181 | 'section' => 'options', |
||
1182 | 'default' => $this->mWatch, |
||
1183 | ] |
||
1184 | ]; |
||
1185 | } |
||
1186 | if ( !$this->mHideIgnoreWarning ) { |
||
1187 | $descriptor['IgnoreWarning'] = [ |
||
1188 | 'type' => 'check', |
||
1189 | 'id' => 'wpIgnoreWarning', |
||
1190 | 'label-message' => 'ignorewarnings', |
||
1191 | 'section' => 'options', |
||
1192 | ]; |
||
1193 | } |
||
1194 | |||
1195 | $descriptor['DestFileWarningAck'] = [ |
||
1196 | 'type' => 'hidden', |
||
1197 | 'id' => 'wpDestFileWarningAck', |
||
1198 | 'default' => $this->mDestWarningAck ? '1' : '', |
||
1199 | ]; |
||
1200 | |||
1201 | if ( $this->mForReUpload ) { |
||
1202 | $descriptor['ForReUpload'] = [ |
||
1203 | 'type' => 'hidden', |
||
1204 | 'id' => 'wpForReUpload', |
||
1205 | 'default' => '1', |
||
1206 | ]; |
||
1207 | } |
||
1208 | |||
1209 | return $descriptor; |
||
1210 | } |
||
1211 | |||
1212 | /** |
||
1213 | * Add the upload JS and show the form. |
||
1214 | */ |
||
1215 | public function show() { |
||
1216 | $this->addUploadJS(); |
||
1217 | parent::show(); |
||
1218 | } |
||
1219 | |||
1220 | /** |
||
1221 | * Add upload JS to the OutputPage |
||
1222 | */ |
||
1223 | protected function addUploadJS() { |
||
1224 | $config = $this->getConfig(); |
||
1225 | |||
1226 | $useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' ); |
||
1227 | $useAjaxLicensePreview = $config->get( 'UseAjax' ) && |
||
1228 | $config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' ); |
||
1229 | $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize(); |
||
1230 | |||
1231 | $scriptVars = [ |
||
1232 | 'wgAjaxUploadDestCheck' => $useAjaxDestCheck, |
||
1233 | 'wgAjaxLicensePreview' => $useAjaxLicensePreview, |
||
1234 | 'wgUploadAutoFill' => !$this->mForReUpload && |
||
1235 | // If we received mDestFile from the request, don't autofill |
||
1236 | // the wpDestFile textbox |
||
1237 | $this->mDestFile === '', |
||
1238 | 'wgUploadSourceIds' => $this->mSourceIds, |
||
1239 | 'wgCheckFileExtensions' => $config->get( 'CheckFileExtensions' ), |
||
1240 | 'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ), |
||
1241 | 'wgFileExtensions' => array_values( array_unique( $config->get( 'FileExtensions' ) ) ), |
||
1242 | 'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ), |
||
1243 | 'wgMaxUploadSize' => $this->mMaxUploadSize, |
||
1244 | 'wgFileCanRotate' => SpecialUpload::rotationEnabled(), |
||
1245 | ]; |
||
1246 | |||
1247 | $out = $this->getOutput(); |
||
1248 | $out->addJsConfigVars( $scriptVars ); |
||
1249 | |||
1250 | $out->addModules( [ |
||
1251 | 'mediawiki.action.edit', // For <charinsert> support |
||
1252 | 'mediawiki.special.upload', // Extras for thumbnail and license preview. |
||
1253 | ] ); |
||
1254 | } |
||
1255 | |||
1256 | /** |
||
1257 | * Empty function; submission is handled elsewhere. |
||
1258 | * |
||
1259 | * @return bool False |
||
1260 | */ |
||
1261 | function trySubmit() { |
||
1262 | return false; |
||
1263 | } |
||
1264 | } |
||
1265 | |||
1266 | /** |
||
1267 | * A form field that contains a radio box in the label |
||
1268 | */ |
||
1269 | class UploadSourceField extends HTMLTextField { |
||
1270 | |||
1271 | /** |
||
1272 | * @param array $cellAttributes |
||
1273 | * @return string |
||
1274 | */ |
||
1275 | function getLabelHtml( $cellAttributes = [] ) { |
||
1276 | $id = $this->mParams['id']; |
||
1277 | $label = Html::rawElement( 'label', [ 'for' => $id ], $this->mLabel ); |
||
1278 | |||
1279 | if ( !empty( $this->mParams['radio'] ) ) { |
||
1280 | if ( isset( $this->mParams['radio-id'] ) ) { |
||
1281 | $radioId = $this->mParams['radio-id']; |
||
1282 | } else { |
||
1283 | // Old way. For the benefit of extensions that do not define |
||
1284 | // the 'radio-id' key. |
||
1285 | $radioId = 'wpSourceType' . $this->mParams['upload-type']; |
||
1286 | } |
||
1287 | |||
1288 | $attribs = [ |
||
1289 | 'name' => 'wpSourceType', |
||
1290 | 'type' => 'radio', |
||
1291 | 'id' => $radioId, |
||
1292 | 'value' => $this->mParams['upload-type'], |
||
1293 | ]; |
||
1294 | |||
1295 | if ( !empty( $this->mParams['checked'] ) ) { |
||
1296 | $attribs['checked'] = 'checked'; |
||
1297 | } |
||
1298 | |||
1299 | $label .= Html::element( 'input', $attribs ); |
||
1300 | } |
||
1301 | |||
1302 | return Html::rawElement( 'td', [ 'class' => 'mw-label' ] + $cellAttributes, $label ); |
||
1303 | } |
||
1304 | |||
1305 | /** |
||
1306 | * @return int |
||
1307 | */ |
||
1308 | function getSize() { |
||
1309 | return isset( $this->mParams['size'] ) |
||
1310 | ? $this->mParams['size'] |
||
1311 | : 60; |
||
1312 | } |
||
1313 | } |
||
1314 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.