Completed
Branch master (86dc85)
by
unknown
23:45
created

SpecialUpload::getWatchCheck()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 23
rs 6.7272
c 1
b 0
f 1
cc 7
eloc 12
nc 5
nop 0
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
/**
26
 * Form for handling uploads and special page.
27
 *
28
 * @ingroup SpecialPage
29
 * @ingroup Upload
30
 */
31
class SpecialUpload extends SpecialPage {
32
	/**
33
	 * Constructor : initialise object
34
	 * Get data POSTed through the form and assign them to the object
35
	 * @param WebRequest $request Data posted.
36
	 */
37
	public function __construct( $request = null ) {
38
		parent::__construct( 'Upload', 'upload' );
39
	}
40
41
	public function doesWrites() {
42
		return true;
43
	}
44
45
	/** Misc variables **/
46
47
	/** @var WebRequest|FauxRequest The request this form is supposed to handle */
48
	public $mRequest;
49
	public $mSourceType;
50
51
	/** @var UploadBase */
52
	public $mUpload;
53
54
	/** @var LocalFile */
55
	public $mLocalFile;
56
	public $mUploadClicked;
57
58
	/** User input variables from the "description" section **/
59
60
	/** @var string The requested target file name */
61
	public $mDesiredDestName;
62
	public $mComment;
63
	public $mLicense;
64
65
	/** User input variables from the root section **/
66
67
	public $mIgnoreWarning;
68
	public $mWatchthis;
69
	public $mCopyrightStatus;
70
	public $mCopyrightSource;
71
72
	/** Hidden variables **/
73
74
	public $mDestWarningAck;
75
76
	/** @var bool The user followed an "overwrite this file" link */
77
	public $mForReUpload;
78
79
	/** @var bool The user clicked "Cancel and return to upload form" button */
80
	public $mCancelUpload;
81
	public $mTokenOk;
82
83
	/** @var bool Subclasses can use this to determine whether a file was uploaded */
84
	public $mUploadSuccessful = false;
85
86
	/** Text injection points for hooks not using HTMLForm **/
87
	public $uploadFormTextTop;
88
	public $uploadFormTextAfterSummary;
89
90
	/**
91
	 * Initialize instance variables from request and create an Upload handler
92
	 */
93
	protected function loadRequest() {
94
		$this->mRequest = $request = $this->getRequest();
95
		$this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
96
		$this->mUpload = UploadBase::createFromRequest( $request );
97
		$this->mUploadClicked = $request->wasPosted()
98
			&& ( $request->getCheck( 'wpUpload' )
99
				|| $request->getCheck( 'wpUploadIgnoreWarning' ) );
100
101
		// Guess the desired name from the filename if not provided
102
		$this->mDesiredDestName = $request->getText( 'wpDestFile' );
103
		if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
104
			$this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
105
		}
106
		$this->mLicense = $request->getText( 'wpLicense' );
107
108
		$this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
109
		$this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
110
			|| $request->getCheck( 'wpUploadIgnoreWarning' );
111
		$this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
112
		$this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
113
		$this->mCopyrightSource = $request->getText( 'wpUploadSource' );
114
115
		$this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
116
117
		$commentDefault = '';
118
		$commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
119
		if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
120
			$commentDefault = $commentMsg->plain();
121
		}
122
		$this->mComment = $request->getText( 'wpUploadDescription', $commentDefault );
123
124
		$this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
125
			|| $request->getCheck( 'wpReUpload' ); // b/w compat
126
127
		// If it was posted check for the token (no remote POST'ing with user credentials)
128
		$token = $request->getVal( 'wpEditToken' );
129
		$this->mTokenOk = $this->getUser()->matchEditToken( $token );
130
131
		$this->uploadFormTextTop = '';
132
		$this->uploadFormTextAfterSummary = '';
133
	}
134
135
	/**
136
	 * This page can be shown if uploading is enabled.
137
	 * Handle permission checking elsewhere in order to be able to show
138
	 * custom error messages.
139
	 *
140
	 * @param User $user
141
	 * @return bool
142
	 */
143
	public function userCanExecute( User $user ) {
144
		return UploadBase::isEnabled() && parent::userCanExecute( $user );
145
	}
146
147
	/**
148
	 * Special page entry point
149
	 * @param string $par
150
	 * @throws ErrorPageError
151
	 * @throws Exception
152
	 * @throws FatalError
153
	 * @throws MWException
154
	 * @throws PermissionsError
155
	 * @throws ReadOnlyError
156
	 * @throws UserBlockedError
157
	 */
158
	public function execute( $par ) {
159
		$this->useTransactionalTimeLimit();
160
161
		$this->setHeaders();
162
		$this->outputHeader();
163
164
		# Check uploading enabled
165
		if ( !UploadBase::isEnabled() ) {
166
			throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
167
		}
168
169
		$this->addHelpLink( 'Help:Managing files' );
170
171
		# Check permissions
172
		$user = $this->getUser();
173
		$permissionRequired = UploadBase::isAllowed( $user );
174
		if ( $permissionRequired !== true ) {
175
			throw new PermissionsError( $permissionRequired );
0 ignored issues
show
Bug introduced by
It seems like $permissionRequired defined by \UploadBase::isAllowed($user) on line 173 can also be of type boolean; however, PermissionsError::__construct() does only seem to accept string, maybe add an additional type check?

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:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
176
		}
177
178
		# Check blocks
179
		if ( $user->isBlocked() ) {
180
			throw new UserBlockedError( $user->getBlock() );
0 ignored issues
show
Bug introduced by
It seems like $user->getBlock() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
181
		}
182
183
		# Check whether we actually want to allow changing stuff
184
		$this->checkReadOnly();
185
186
		$this->loadRequest();
187
188
		# Unsave the temporary file in case this was a cancelled upload
189
		if ( $this->mCancelUpload ) {
190
			if ( !$this->unsaveUploadedFile() ) {
191
				# Something went wrong, so unsaveUploadedFile showed a warning
192
				return;
193
			}
194
		}
195
196
		# Process upload or show a form
197
		if (
198
			$this->mTokenOk && !$this->mCancelUpload &&
199
			( $this->mUpload && $this->mUploadClicked )
200
		) {
201
			$this->processUpload();
202
		} else {
203
			# Backwards compatibility hook
204
			if ( !Hooks::run( 'UploadForm:initial', [ &$this ] ) ) {
205
				wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
206
207
				return;
208
			}
209
			$this->showUploadForm( $this->getUploadForm() );
210
		}
211
212
		# Cleanup
213
		if ( $this->mUpload ) {
214
			$this->mUpload->cleanupTempFile();
215
		}
216
	}
217
218
	/**
219
	 * Show the main upload form
220
	 *
221
	 * @param HTMLForm|string $form An HTMLForm instance or HTML string to show
222
	 */
223
	protected function showUploadForm( $form ) {
224
		# Add links if file was previously deleted
225
		if ( $this->mDesiredDestName ) {
226
			$this->showViewDeletedLinks();
227
		}
228
229
		if ( $form instanceof HTMLForm ) {
230
			$form->show();
231
		} else {
232
			$this->getOutput()->addHTML( $form );
233
		}
234
	}
235
236
	/**
237
	 * Get an UploadForm instance with title and text properly set.
238
	 *
239
	 * @param string $message HTML string to add to the form
240
	 * @param string $sessionKey Session key in case this is a stashed upload
241
	 * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box
242
	 * @return UploadForm
243
	 */
244
	protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
245
		# Initialize form
246
		$context = new DerivativeContext( $this->getContext() );
247
		$context->setTitle( $this->getPageTitle() ); // Remove subpage
248
		$form = new UploadForm( [
249
			'watch' => $this->getWatchCheck(),
250
			'forreupload' => $this->mForReUpload,
251
			'sessionkey' => $sessionKey,
252
			'hideignorewarning' => $hideIgnoreWarning,
253
			'destwarningack' => (bool)$this->mDestWarningAck,
254
255
			'description' => $this->mComment,
256
			'texttop' => $this->uploadFormTextTop,
257
			'textaftersummary' => $this->uploadFormTextAfterSummary,
258
			'destfile' => $this->mDesiredDestName,
259
		], $context );
260
261
		# Check the token, but only if necessary
262
		if (
263
			!$this->mTokenOk && !$this->mCancelUpload &&
264
			( $this->mUpload && $this->mUploadClicked )
265
		) {
266
			$form->addPreText( $this->msg( 'session_fail_preview' )->parse() );
267
		}
268
269
		# Give a notice if the user is uploading a file that has been deleted or moved
270
		# Note that this is independent from the message 'filewasdeleted'
271
		$desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
272
		$delNotice = ''; // empty by default
273
		if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
274
			LogEventsList::showLogExtract( $delNotice, [ 'delete', 'move' ],
275
				$desiredTitleObj,
276
				'', [ 'lim' => 10,
277
					'conds' => [ "log_action != 'revision'" ],
278
					'showIfEmpty' => false,
279
					'msgKey' => [ 'upload-recreate-warning' ] ]
280
			);
281
		}
282
		$form->addPreText( $delNotice );
0 ignored issues
show
Bug introduced by
It seems like $delNotice can also be of type object<OutputPage>; however, HTMLForm::addPreText() does only seem to accept string, maybe add an additional type check?

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:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
283
284
		# Add text to form
285
		$form->addPreText( '<div id="uploadtext">' .
286
			$this->msg( 'uploadtext', [ $this->mDesiredDestName ] )->parseAsBlock() .
287
			'</div>' );
288
		# Add upload error message
289
		$form->addPreText( $message );
290
291
		# Add footer to form
292
		$uploadFooter = $this->msg( 'uploadfooter' );
293
		if ( !$uploadFooter->isDisabled() ) {
294
			$form->addPostText( '<div id="mw-upload-footer-message">'
295
				. $uploadFooter->parseAsBlock() . "</div>\n" );
296
		}
297
298
		return $form;
299
	}
300
301
	/**
302
	 * Shows the "view X deleted revivions link""
303
	 */
304
	protected function showViewDeletedLinks() {
305
		$title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
306
		$user = $this->getUser();
307
		// Show a subtitle link to deleted revisions (to sysops et al only)
308
		if ( $title instanceof Title ) {
309
			$count = $title->isDeleted();
310
			if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
311
				$restorelink = Linker::linkKnown(
312
					SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
313
					$this->msg( 'restorelink' )->numParams( $count )->escaped()
314
				);
315
				$link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' )
316
					->rawParams( $restorelink )->parseAsBlock();
317
				$this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
318
			}
319
		}
320
	}
321
322
	/**
323
	 * Stashes the upload and shows the main upload form.
324
	 *
325
	 * Note: only errors that can be handled by changing the name or
326
	 * description should be redirected here. It should be assumed that the
327
	 * file itself is sane and has passed UploadBase::verifyFile. This
328
	 * essentially means that UploadBase::VERIFICATION_ERROR and
329
	 * UploadBase::EMPTY_FILE should not be passed here.
330
	 *
331
	 * @param string $message HTML message to be passed to mainUploadForm
332
	 */
333
	protected function showRecoverableUploadError( $message ) {
334
		$sessionKey = $this->mUpload->stashSession();
335
		$message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" .
336
			'<div class="error">' . $message . "</div>\n";
337
338
		$form = $this->getUploadForm( $message, $sessionKey );
339
		$form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() );
340
		$this->showUploadForm( $form );
341
	}
342
343
	/**
344
	 * Stashes the upload, shows the main form, but adds a "continue anyway button".
345
	 * Also checks whether there are actually warnings to display.
346
	 *
347
	 * @param array $warnings
348
	 * @return bool True if warnings were displayed, false if there are no
349
	 *   warnings and it should continue processing
350
	 */
351
	protected function showUploadWarning( $warnings ) {
352
		# If there are no warnings, or warnings we can ignore, return early.
353
		# mDestWarningAck is set when some javascript has shown the warning
354
		# to the user. mForReUpload is set when the user clicks the "upload a
355
		# new version" link.
356
		if ( !$warnings || ( count( $warnings ) == 1
0 ignored issues
show
Bug Best Practice introduced by
The expression $warnings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
357
			&& isset( $warnings['exists'] )
358
			&& ( $this->mDestWarningAck || $this->mForReUpload ) )
359
		) {
360
			return false;
361
		}
362
363
		$sessionKey = $this->mUpload->stashSession();
364
365
		$warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
366
			. '<div class="warningbox"><ul>';
367
		foreach ( $warnings as $warning => $args ) {
368
			if ( $warning == 'badfilename' ) {
369
				$this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
370
			}
371
			if ( $warning == 'exists' ) {
372
				$msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
373
			} elseif ( $warning == 'was-deleted' ) {
374
				# If the file existed before and was deleted, warn the user of this
375
				$ltitle = SpecialPage::getTitleFor( 'Log' );
376
				$llink = Linker::linkKnown(
377
					$ltitle,
378
					wfMessage( 'deletionlog' )->escaped(),
379
					[],
380
					[
381
						'type' => 'delete',
382
						'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(),
383
					]
384
				);
385
				$msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
386
			} elseif ( $warning == 'duplicate' ) {
387
				$msg = $this->getDupeWarning( $args );
388
			} elseif ( $warning == 'duplicate-archive' ) {
389
				if ( $args === '' ) {
390
					$msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
391
						. "</li>\n";
392
				} else {
393
					$msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
394
							Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
395
						. "</li>\n";
396
				}
397
			} else {
398
				if ( $args === true ) {
399
					$args = [];
400
				} elseif ( !is_array( $args ) ) {
401
					$args = [ $args ];
402
				}
403
				$msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n";
404
			}
405
			$warningHtml .= $msg;
406
		}
407
		$warningHtml .= "</ul></div>\n";
408
		$warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock();
409
410
		$form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
411
		$form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
412
		$form->addButton( [
413
			'name' => 'wpUploadIgnoreWarning',
414
			'value' => $this->msg( 'ignorewarning' )->text()
415
		] );
416
		$form->addButton( [
417
			'name' => 'wpCancelUpload',
418
			'value' => $this->msg( 'reuploaddesc' )->text()
419
		] );
420
421
		$this->showUploadForm( $form );
422
423
		# Indicate that we showed a form
424
		return true;
425
	}
426
427
	/**
428
	 * Show the upload form with error message, but do not stash the file.
429
	 *
430
	 * @param string $message HTML string
431
	 */
432
	protected function showUploadError( $message ) {
433
		$message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
434
			'<div class="error">' . $message . "</div>\n";
435
		$this->showUploadForm( $this->getUploadForm( $message ) );
436
	}
437
438
	/**
439
	 * Do the upload.
440
	 * Checks are made in SpecialUpload::execute()
441
	 */
442
	protected function processUpload() {
443
		// Fetch the file if required
444
		$status = $this->mUpload->fetchFile();
445
		if ( !$status->isOK() ) {
446
			$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
447
448
			return;
449
		}
450
451
		if ( !Hooks::run( 'UploadForm:BeforeProcessing', [ &$this ] ) ) {
452
			wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
453
			// This code path is deprecated. If you want to break upload processing
454
			// do so by hooking into the appropriate hooks in UploadBase::verifyUpload
455
			// and UploadBase::verifyFile.
456
			// If you use this hook to break uploading, the user will be returned
457
			// an empty form with no error message whatsoever.
458
			return;
459
		}
460
461
		// Upload verification
462
		$details = $this->mUpload->verifyUpload();
463
		if ( $details['status'] != UploadBase::OK ) {
464
			$this->processVerificationError( $details );
0 ignored issues
show
Bug introduced by
It seems like $details defined by $this->mUpload->verifyUpload() on line 462 can also be of type boolean; however, SpecialUpload::processVerificationError() does only seem to accept array, maybe add an additional type check?

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:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
465
466
			return;
467
		}
468
469
		// Verify permissions for this title
470
		$permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
471
		if ( $permErrors !== true ) {
472
			$code = array_shift( $permErrors[0] );
473
			$this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
474
475
			return;
476
		}
477
478
		$this->mLocalFile = $this->mUpload->getLocalFile();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->mUpload->getLocalFile() can also be of type object<UploadStashFile>. However, the property $mLocalFile is declared as type object<LocalFile>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
479
480
		// Check warnings if necessary
481
		if ( !$this->mIgnoreWarning ) {
482
			$warnings = $this->mUpload->checkWarnings();
483
			if ( $this->showUploadWarning( $warnings ) ) {
484
				return;
485
			}
486
		}
487
488
		// This is as late as we can throttle, after expected issues have been handled
489
		if ( UploadBase::isThrottled( $this->getUser() ) ) {
490
			$this->showRecoverableUploadError(
491
				$this->msg( 'actionthrottledtext' )->escaped()
492
			);
493
			return;
494
		}
495
496
		// Get the page text if this is not a reupload
497
		if ( !$this->mForReUpload ) {
498
			$pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
499
				$this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() );
500
		} else {
501
			$pageText = false;
502
		}
503
504
		$changeTags = $this->getRequest()->getVal( 'wpChangeTags' );
505
		if ( is_null( $changeTags ) || $changeTags === '' ) {
506
			$changeTags = [];
507
		} else {
508
			$changeTags = array_filter( array_map( 'trim', explode( ',', $changeTags ) ) );
509
		}
510
511
		if ( $changeTags ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $changeTags of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
512
			$changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
513
				$changeTags, $this->getUser() );
514
			if ( !$changeTagsStatus->isOK() ) {
515
				$this->showUploadError( $this->getOutput()->parse( $changeTagsStatus->getWikiText() ) );
516
517
				return;
518
			}
519
		}
520
521
		$status = $this->mUpload->performUpload(
522
			$this->mComment,
523
			$pageText,
0 ignored issues
show
Security Bug introduced by
It seems like $pageText defined by false on line 501 can also be of type false; however, UploadBase::performUpload() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
524
			$this->mWatchthis,
525
			$this->getUser(),
526
			$changeTags
527
		);
528
529
		if ( !$status->isGood() ) {
530
			$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
531
532
			return;
533
		}
534
535
		// Success, redirect to description page
536
		$this->mUploadSuccessful = true;
537
		Hooks::run( 'SpecialUploadComplete', [ &$this ] );
538
		$this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
539
	}
540
541
	/**
542
	 * Get the initial image page text based on a comment and optional file status information
543
	 * @param string $comment
544
	 * @param string $license
545
	 * @param string $copyStatus
546
	 * @param string $source
547
	 * @param Config $config Configuration object to load data from
548
	 * @return string
549
	 */
550
	public static function getInitialPageText( $comment = '', $license = '',
551
		$copyStatus = '', $source = '', Config $config = null
552
	) {
553 View Code Duplication
		if ( $config === null ) {
554
			wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
555
			$config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
0 ignored issues
show
Deprecated Code introduced by
The method ConfigFactory::getDefaultInstance() has been deprecated with message: since 1.27, use MediaWikiServices::getConfigFactory() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
556
		}
557
558
		$msg = [];
559
		$forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' );
560
		/* These messages are transcluded into the actual text of the description page.
561
		 * Thus, forcing them as content messages makes the upload to produce an int: template
562
		 * instead of hardcoding it there in the uploader language.
563
		 */
564
		foreach ( [ 'license-header', 'filedesc', 'filestatus', 'filesource' ] as $msgName ) {
565
			if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) {
566
				$msg[$msgName] = "{{int:$msgName}}";
567
			} else {
568
				$msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
569
			}
570
		}
571
572
		if ( $config->get( 'UseCopyrightUpload' ) ) {
573
			$licensetxt = '';
574
			if ( $license != '' ) {
575
				$licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
576
			}
577
			$pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
578
				'== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
579
				"$licensetxt" .
580
				'== ' . $msg['filesource'] . " ==\n" . $source;
581
		} else {
582
			if ( $license != '' ) {
583
				$filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
584
					$pageText = $filedesc .
585
					'== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
586
			} else {
587
				$pageText = $comment;
588
			}
589
		}
590
591
		return $pageText;
592
	}
593
594
	/**
595
	 * See if we should check the 'watch this page' checkbox on the form
596
	 * based on the user's preferences and whether we're being asked
597
	 * to create a new file or update an existing one.
598
	 *
599
	 * In the case where 'watch edits' is off but 'watch creations' is on,
600
	 * we'll leave the box unchecked.
601
	 *
602
	 * Note that the page target can be changed *on the form*, so our check
603
	 * state can get out of sync.
604
	 * @return bool|string
605
	 */
606
	protected function getWatchCheck() {
607
		if ( $this->getUser()->getOption( 'watchdefault' ) ) {
608
			// Watch all edits!
609
			return true;
610
		}
611
612
		$desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
613
		if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
614
			// Already watched, don't change that
615
			return true;
616
		}
617
618
		$local = wfLocalFile( $this->mDesiredDestName );
619
		if ( $local && $local->exists() ) {
620
			// We're uploading a new version of an existing file.
621
			// No creation, so don't watch it if we're not already.
622
			return false;
623
		} else {
624
			// New page should get watched if that's our option.
625
			return $this->getUser()->getOption( 'watchcreations' ) ||
626
				$this->getUser()->getOption( 'watchuploads' );
627
		}
628
	}
629
630
	/**
631
	 * Provides output to the user for a result of UploadBase::verifyUpload
632
	 *
633
	 * @param array $details Result of UploadBase::verifyUpload
634
	 * @throws MWException
635
	 */
636
	protected function processVerificationError( $details ) {
637
		switch ( $details['status'] ) {
638
639
			/** Statuses that only require name changing **/
640
			case UploadBase::MIN_LENGTH_PARTNAME:
641
				$this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() );
642
				break;
643
			case UploadBase::ILLEGAL_FILENAME:
644
				$this->showRecoverableUploadError( $this->msg( 'illegalfilename',
645
					$details['filtered'] )->parse() );
646
				break;
647
			case UploadBase::FILENAME_TOO_LONG:
648
				$this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() );
649
				break;
650
			case UploadBase::FILETYPE_MISSING:
651
				$this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() );
652
				break;
653
			case UploadBase::WINDOWS_NONASCII_FILENAME:
654
				$this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() );
655
				break;
656
657
			/** Statuses that require reuploading **/
658
			case UploadBase::EMPTY_FILE:
659
				$this->showUploadError( $this->msg( 'emptyfile' )->escaped() );
660
				break;
661
			case UploadBase::FILE_TOO_LARGE:
662
				$this->showUploadError( $this->msg( 'largefileserver' )->escaped() );
663
				break;
664
			case UploadBase::FILETYPE_BADTYPE:
665
				$msg = $this->msg( 'filetype-banned-type' );
666
				if ( isset( $details['blacklistedExt'] ) ) {
667
					$msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) );
668
				} else {
669
					$msg->params( $details['finalExt'] );
670
				}
671
				$extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
672
				$msg->params( $this->getLanguage()->commaList( $extensions ),
673
					count( $extensions ) );
674
675
				// Add PLURAL support for the first parameter. This results
676
				// in a bit unlogical parameter sequence, but does not break
677
				// old translations
678
				if ( isset( $details['blacklistedExt'] ) ) {
679
					$msg->params( count( $details['blacklistedExt'] ) );
680
				} else {
681
					$msg->params( 1 );
682
				}
683
684
				$this->showUploadError( $msg->parse() );
685
				break;
686
			case UploadBase::VERIFICATION_ERROR:
687
				unset( $details['status'] );
688
				$code = array_shift( $details['details'] );
689
				$this->showUploadError( $this->msg( $code, $details['details'] )->parse() );
690
				break;
691
			case UploadBase::HOOK_ABORTED:
692
				if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array
693
					$args = $details['error'];
694
					$error = array_shift( $args );
695
				} else {
696
					$error = $details['error'];
697
					$args = null;
698
				}
699
700
				$this->showUploadError( $this->msg( $error, $args )->parse() );
701
				break;
702
			default:
703
				throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
704
		}
705
	}
706
707
	/**
708
	 * Remove a temporarily kept file stashed by saveTempUploadedFile().
709
	 *
710
	 * @return bool Success
711
	 */
712
	protected function unsaveUploadedFile() {
713
		if ( !( $this->mUpload instanceof UploadFromStash ) ) {
714
			return true;
715
		}
716
		$success = $this->mUpload->unsaveUploadedFile();
717
		if ( !$success ) {
718
			$this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
719
720
			return false;
721
		} else {
722
			return true;
723
		}
724
	}
725
726
	/*** Functions for formatting warnings ***/
727
728
	/**
729
	 * Formats a result of UploadBase::getExistsWarning as HTML
730
	 * This check is static and can be done pre-upload via AJAX
731
	 *
732
	 * @param array $exists The result of UploadBase::getExistsWarning
733
	 * @return string Empty string if there is no warning or an HTML fragment
734
	 */
735
	public static function getExistsWarning( $exists ) {
736
		if ( !$exists ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
737
			return '';
738
		}
739
740
		$file = $exists['file'];
741
		$filename = $file->getTitle()->getPrefixedText();
742
		$warning = '';
743
744
		if ( $exists['warning'] == 'exists' ) {
745
			// Exact match
746
			$warning = wfMessage( 'fileexists', $filename )->parse();
747
		} elseif ( $exists['warning'] == 'page-exists' ) {
748
			// Page exists but file does not
749
			$warning = wfMessage( 'filepageexists', $filename )->parse();
750
		} elseif ( $exists['warning'] == 'exists-normalized' ) {
751
			$warning = wfMessage( 'fileexists-extension', $filename,
752
				$exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
753
		} elseif ( $exists['warning'] == 'thumb' ) {
754
			// Swapped argument order compared with other messages for backwards compatibility
755
			$warning = wfMessage( 'fileexists-thumbnail-yes',
756
				$exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
757
		} elseif ( $exists['warning'] == 'thumb-name' ) {
758
			// Image w/o '180px-' does not exists, but we do not like these filenames
759
			$name = $file->getName();
760
			$badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
761
			$warning = wfMessage( 'file-thumbnail-no', $badPart )->parse();
762
		} elseif ( $exists['warning'] == 'bad-prefix' ) {
763
			$warning = wfMessage( 'filename-bad-prefix', $exists['prefix'] )->parse();
764
		}
765
766
		return $warning;
767
	}
768
769
	/**
770
	 * Construct a warning and a gallery from an array of duplicate files.
771
	 * @param array $dupes
772
	 * @return string
773
	 */
774
	public function getDupeWarning( $dupes ) {
775
		if ( !$dupes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dupes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
776
			return '';
777
		}
778
779
		$gallery = ImageGalleryBase::factory( false, $this->getContext() );
780
		$gallery->setShowBytes( false );
781
		foreach ( $dupes as $file ) {
782
			$gallery->add( $file->getTitle() );
783
		}
784
785
		return '<li>' .
786
			$this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
787
			$gallery->toHTML() . "</li>\n";
788
	}
789
790
	protected function getGroupName() {
791
		return 'media';
792
	}
793
794
	/**
795
	 * Should we rotate images in the preview on Special:Upload.
796
	 *
797
	 * This controls js: mw.config.get( 'wgFileCanRotate' )
798
	 *
799
	 * @todo What about non-BitmapHandler handled files?
800
	 */
801
	public static function rotationEnabled() {
802
		$bitmapHandler = new BitmapHandler();
803
		return $bitmapHandler->autoRotateEnabled();
804
	}
805
}
806
807
/**
808
 * Sub class of HTMLForm that provides the form section of SpecialUpload
809
 */
810
class UploadForm extends HTMLForm {
811
	protected $mWatch;
812
	protected $mForReUpload;
813
	protected $mSessionKey;
814
	protected $mHideIgnoreWarning;
815
	protected $mDestWarningAck;
816
	protected $mDestFile;
817
818
	protected $mComment;
819
	protected $mTextTop;
820
	protected $mTextAfterSummary;
821
822
	protected $mSourceIds;
823
824
	protected $mMaxFileSize = [];
825
826
	protected $mMaxUploadSize = [];
827
828
	public function __construct( array $options = [], IContextSource $context = null ) {
829
		if ( $context instanceof IContextSource ) {
830
			$this->setContext( $context );
831
		}
832
833
		$this->mWatch = !empty( $options['watch'] );
834
		$this->mForReUpload = !empty( $options['forreupload'] );
835
		$this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
836
		$this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
837
		$this->mDestWarningAck = !empty( $options['destwarningack'] );
838
		$this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
839
840
		$this->mComment = isset( $options['description'] ) ?
841
			$options['description'] : '';
842
843
		$this->mTextTop = isset( $options['texttop'] )
844
			? $options['texttop'] : '';
845
846
		$this->mTextAfterSummary = isset( $options['textaftersummary'] )
847
			? $options['textaftersummary'] : '';
848
849
		$sourceDescriptor = $this->getSourceSection();
850
		$descriptor = $sourceDescriptor
851
			+ $this->getDescriptionSection()
852
			+ $this->getOptionsSection();
853
854
		Hooks::run( 'UploadFormInitDescriptor', [ &$descriptor ] );
855
		parent::__construct( $descriptor, $context, 'upload' );
856
857
		# Add a link to edit MediaWik:Licenses
858
		if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
859
			$this->getOutput()->addModuleStyles( 'mediawiki.special' );
860
			$licensesLink = Linker::linkKnown(
861
				$this->msg( 'licenses' )->inContentLanguage()->getTitle(),
862
				$this->msg( 'licenses-edit' )->escaped(),
863
				[],
864
				[ 'action' => 'edit' ]
865
			);
866
			$editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
867
			$this->addFooterText( $editLicenses, 'description' );
868
		}
869
870
		# Set some form properties
871
		$this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
872
		$this->setSubmitName( 'wpUpload' );
873
		# Used message keys: 'accesskey-upload', 'tooltip-upload'
874
		$this->setSubmitTooltip( 'upload' );
875
		$this->setId( 'mw-upload-form' );
876
877
		# Build a list of IDs for javascript insertion
878
		$this->mSourceIds = [];
879
		foreach ( $sourceDescriptor as $field ) {
880
			if ( !empty( $field['id'] ) ) {
881
				$this->mSourceIds[] = $field['id'];
882
			}
883
		}
884
	}
885
886
	/**
887
	 * Get the descriptor of the fieldset that contains the file source
888
	 * selection. The section is 'source'
889
	 *
890
	 * @return array Descriptor array
891
	 */
892
	protected function getSourceSection() {
893
		if ( $this->mSessionKey ) {
894
			return [
895
				'SessionKey' => [
896
					'type' => 'hidden',
897
					'default' => $this->mSessionKey,
898
				],
899
				'SourceType' => [
900
					'type' => 'hidden',
901
					'default' => 'Stash',
902
				],
903
			];
904
		}
905
906
		$canUploadByUrl = UploadFromUrl::isEnabled()
907
			&& ( UploadFromUrl::isAllowed( $this->getUser() ) === true )
908
			&& $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' );
909
		$radio = $canUploadByUrl;
910
		$selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
911
912
		$descriptor = [];
913
		if ( $this->mTextTop ) {
914
			$descriptor['UploadFormTextTop'] = [
915
				'type' => 'info',
916
				'section' => 'source',
917
				'default' => $this->mTextTop,
918
				'raw' => true,
919
			];
920
		}
921
922
		$this->mMaxUploadSize['file'] = min(
923
			UploadBase::getMaxUploadSize( 'file' ),
924
			UploadBase::getMaxPhpUploadSize()
925
		);
926
927
		$help = $this->msg( 'upload-maxfilesize',
928
				$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
929
			)->parse();
930
931
		// If the user can also upload by URL, there are 2 different file size limits.
932
		// This extra message helps stress which limit corresponds to what.
933
		if ( $canUploadByUrl ) {
934
			$help .= $this->msg( 'word-separator' )->escaped();
935
			$help .= $this->msg( 'upload_source_file' )->parse();
936
		}
937
938
		$descriptor['UploadFile'] = [
939
			'class' => 'UploadSourceField',
940
			'section' => 'source',
941
			'type' => 'file',
942
			'id' => 'wpUploadFile',
943
			'radio-id' => 'wpSourceTypeFile',
944
			'label-message' => 'sourcefilename',
945
			'upload-type' => 'File',
946
			'radio' => &$radio,
947
			'help' => $help,
948
			'checked' => $selectedSourceType == 'file',
949
		];
950
951
		if ( $canUploadByUrl ) {
952
			$this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
953
			$descriptor['UploadFileURL'] = [
954
				'class' => 'UploadSourceField',
955
				'section' => 'source',
956
				'id' => 'wpUploadFileURL',
957
				'radio-id' => 'wpSourceTypeurl',
958
				'label-message' => 'sourceurl',
959
				'upload-type' => 'url',
960
				'radio' => &$radio,
961
				'help' => $this->msg( 'upload-maxfilesize',
962
					$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
963
				)->parse() .
964
					$this->msg( 'word-separator' )->escaped() .
965
					$this->msg( 'upload_source_url' )->parse(),
966
				'checked' => $selectedSourceType == 'url',
967
			];
968
		}
969
		Hooks::run( 'UploadFormSourceDescriptors', [ &$descriptor, &$radio, $selectedSourceType ] );
970
971
		$descriptor['Extensions'] = [
972
			'type' => 'info',
973
			'section' => 'source',
974
			'default' => $this->getExtensionsMessage(),
975
			'raw' => true,
976
		];
977
978
		return $descriptor;
979
	}
980
981
	/**
982
	 * Get the messages indicating which extensions are preferred and prohibitted.
983
	 *
984
	 * @return string HTML string containing the message
985
	 */
986
	protected function getExtensionsMessage() {
987
		# Print a list of allowed file extensions, if so configured.  We ignore
988
		# MIME type here, it's incomprehensible to most people and too long.
989
		$config = $this->getConfig();
990
991
		if ( $config->get( 'CheckFileExtensions' ) ) {
992
			$fileExtensions = array_unique( $config->get( 'FileExtensions' ) );
993
			if ( $config->get( 'StrictFileExtensions' ) ) {
994
				# Everything not permitted is banned
995
				$extensionsList =
996
					'<div id="mw-upload-permitted">' .
997
					$this->msg( 'upload-permitted' )
998
						->params( $this->getLanguage()->commaList( $fileExtensions ) )
999
						->numParams( count( $fileExtensions ) )
1000
						->parseAsBlock() .
1001
					"</div>\n";
1002
			} else {
1003
				# We have to list both preferred and prohibited
1004
				$fileBlacklist = array_unique( $config->get( 'FileBlacklist' ) );
1005
				$extensionsList =
1006
					'<div id="mw-upload-preferred">' .
1007
						$this->msg( 'upload-preferred' )
1008
							->params( $this->getLanguage()->commaList( $fileExtensions ) )
1009
							->numParams( count( $fileExtensions ) )
1010
							->parseAsBlock() .
1011
					"</div>\n" .
1012
					'<div id="mw-upload-prohibited">' .
1013
						$this->msg( 'upload-prohibited' )
1014
							->params( $this->getLanguage()->commaList( $fileBlacklist ) )
1015
							->numParams( count( $fileBlacklist ) )
1016
							->parseAsBlock() .
1017
					"</div>\n";
1018
			}
1019
		} else {
1020
			# Everything is permitted.
1021
			$extensionsList = '';
1022
		}
1023
1024
		return $extensionsList;
1025
	}
1026
1027
	/**
1028
	 * Get the descriptor of the fieldset that contains the file description
1029
	 * input. The section is 'description'
1030
	 *
1031
	 * @return array Descriptor array
1032
	 */
1033
	protected function getDescriptionSection() {
1034
		$config = $this->getConfig();
1035
		if ( $this->mSessionKey ) {
1036
			$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
1037
			try {
1038
				$file = $stash->getFile( $this->mSessionKey );
1039
			} catch ( Exception $e ) {
1040
				$file = null;
1041
			}
1042
			if ( $file ) {
1043
				global $wgContLang;
1044
1045
				$mto = $file->transform( [ 'width' => 120 ] );
1046
				$this->addHeaderText(
1047
					'<div class="thumb t' . $wgContLang->alignEnd() . '">' .
1048
					Html::element( 'img', [
1049
						'src' => $mto->getUrl(),
1050
						'class' => 'thumbimage',
1051
					] ) . '</div>', 'description' );
1052
			}
1053
		}
1054
1055
		$descriptor = [
1056
			'DestFile' => [
1057
				'type' => 'text',
1058
				'section' => 'description',
1059
				'id' => 'wpDestFile',
1060
				'label-message' => 'destfilename',
1061
				'size' => 60,
1062
				'default' => $this->mDestFile,
1063
				# @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
1064
				'nodata' => strval( $this->mDestFile ) !== '',
1065
			],
1066
			'UploadDescription' => [
1067
				'type' => 'textarea',
1068
				'section' => 'description',
1069
				'id' => 'wpUploadDescription',
1070
				'label-message' => $this->mForReUpload
1071
					? 'filereuploadsummary'
1072
					: 'fileuploadsummary',
1073
				'default' => $this->mComment,
1074
				'cols' => $this->getUser()->getIntOption( 'cols' ),
1075
				'rows' => 8,
1076
			]
1077
		];
1078
		if ( $this->mTextAfterSummary ) {
1079
			$descriptor['UploadFormTextAfterSummary'] = [
1080
				'type' => 'info',
1081
				'section' => 'description',
1082
				'default' => $this->mTextAfterSummary,
1083
				'raw' => true,
1084
			];
1085
		}
1086
1087
		$descriptor += [
1088
			'EditTools' => [
1089
				'type' => 'edittools',
1090
				'section' => 'description',
1091
				'message' => 'edittools-upload',
1092
			]
1093
		];
1094
1095
		if ( $this->mForReUpload ) {
1096
			$descriptor['DestFile']['readonly'] = true;
1097
		} else {
1098
			$descriptor['License'] = [
1099
				'type' => 'select',
1100
				'class' => 'Licenses',
1101
				'section' => 'description',
1102
				'id' => 'wpLicense',
1103
				'label-message' => 'license',
1104
			];
1105
		}
1106
1107
		if ( $config->get( 'UseCopyrightUpload' ) ) {
1108
			$descriptor['UploadCopyStatus'] = [
1109
				'type' => 'text',
1110
				'section' => 'description',
1111
				'id' => 'wpUploadCopyStatus',
1112
				'label-message' => 'filestatus',
1113
			];
1114
			$descriptor['UploadSource'] = [
1115
				'type' => 'text',
1116
				'section' => 'description',
1117
				'id' => 'wpUploadSource',
1118
				'label-message' => 'filesource',
1119
			];
1120
		}
1121
1122
		return $descriptor;
1123
	}
1124
1125
	/**
1126
	 * Get the descriptor of the fieldset that contains the upload options,
1127
	 * such as "watch this file". The section is 'options'
1128
	 *
1129
	 * @return array Descriptor array
1130
	 */
1131
	protected function getOptionsSection() {
1132
		$user = $this->getUser();
1133
		if ( $user->isLoggedIn() ) {
1134
			$descriptor = [
1135
				'Watchthis' => [
1136
					'type' => 'check',
1137
					'id' => 'wpWatchthis',
1138
					'label-message' => 'watchthisupload',
1139
					'section' => 'options',
1140
					'default' => $this->mWatch,
1141
				]
1142
			];
1143
		}
1144
		if ( !$this->mHideIgnoreWarning ) {
1145
			$descriptor['IgnoreWarning'] = [
0 ignored issues
show
Bug introduced by
The variable $descriptor does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1146
				'type' => 'check',
1147
				'id' => 'wpIgnoreWarning',
1148
				'label-message' => 'ignorewarnings',
1149
				'section' => 'options',
1150
			];
1151
		}
1152
1153
		$descriptor['DestFileWarningAck'] = [
1154
			'type' => 'hidden',
1155
			'id' => 'wpDestFileWarningAck',
1156
			'default' => $this->mDestWarningAck ? '1' : '',
1157
		];
1158
1159
		if ( $this->mForReUpload ) {
1160
			$descriptor['ForReUpload'] = [
1161
				'type' => 'hidden',
1162
				'id' => 'wpForReUpload',
1163
				'default' => '1',
1164
			];
1165
		}
1166
1167
		return $descriptor;
1168
	}
1169
1170
	/**
1171
	 * Add the upload JS and show the form.
1172
	 */
1173
	public function show() {
1174
		$this->addUploadJS();
1175
		parent::show();
1176
	}
1177
1178
	/**
1179
	 * Add upload JS to the OutputPage
1180
	 */
1181
	protected function addUploadJS() {
1182
		$config = $this->getConfig();
1183
1184
		$useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' );
1185
		$useAjaxLicensePreview = $config->get( 'UseAjax' ) &&
1186
			$config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' );
1187
		$this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
1188
1189
		$scriptVars = [
1190
			'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
1191
			'wgAjaxLicensePreview' => $useAjaxLicensePreview,
1192
			'wgUploadAutoFill' => !$this->mForReUpload &&
1193
				// If we received mDestFile from the request, don't autofill
1194
				// the wpDestFile textbox
1195
				$this->mDestFile === '',
1196
			'wgUploadSourceIds' => $this->mSourceIds,
1197
			'wgCheckFileExtensions' => $config->get( 'CheckFileExtensions' ),
1198
			'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
1199
			'wgFileExtensions' => array_values( array_unique( $config->get( 'FileExtensions' ) ) ),
1200
			'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
1201
			'wgMaxUploadSize' => $this->mMaxUploadSize,
1202
			'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
1203
		];
1204
1205
		$out = $this->getOutput();
1206
		$out->addJsConfigVars( $scriptVars );
1207
1208
		$out->addModules( [
1209
			'mediawiki.action.edit', // For <charinsert> support
1210
			'mediawiki.special.upload', // Extras for thumbnail and license preview.
1211
		] );
1212
	}
1213
1214
	/**
1215
	 * Empty function; submission is handled elsewhere.
1216
	 *
1217
	 * @return bool False
1218
	 */
1219
	function trySubmit() {
1220
		return false;
1221
	}
1222
}
1223
1224
/**
1225
 * A form field that contains a radio box in the label
1226
 */
1227
class UploadSourceField extends HTMLTextField {
1228
1229
	/**
1230
	 * @param array $cellAttributes
1231
	 * @return string
1232
	 */
1233
	function getLabelHtml( $cellAttributes = [] ) {
1234
		$id = $this->mParams['id'];
1235
		$label = Html::rawElement( 'label', [ 'for' => $id ], $this->mLabel );
1236
1237
		if ( !empty( $this->mParams['radio'] ) ) {
1238
			if ( isset( $this->mParams['radio-id'] ) ) {
1239
				$radioId = $this->mParams['radio-id'];
1240
			} else {
1241
				// Old way. For the benefit of extensions that do not define
1242
				// the 'radio-id' key.
1243
				$radioId = 'wpSourceType' . $this->mParams['upload-type'];
1244
			}
1245
1246
			$attribs = [
1247
				'name' => 'wpSourceType',
1248
				'type' => 'radio',
1249
				'id' => $radioId,
1250
				'value' => $this->mParams['upload-type'],
1251
			];
1252
1253
			if ( !empty( $this->mParams['checked'] ) ) {
1254
				$attribs['checked'] = 'checked';
1255
			}
1256
1257
			$label .= Html::element( 'input', $attribs );
1258
		}
1259
1260
		return Html::rawElement( 'td', [ 'class' => 'mw-label' ] + $cellAttributes, $label );
1261
	}
1262
1263
	/**
1264
	 * @return int
1265
	 */
1266
	function getSize() {
1267
		return isset( $this->mParams['size'] )
1268
			? $this->mParams['size']
1269
			: 60;
1270
	}
1271
}
1272