Completed
Branch master (bbf110)
by
unknown
25:51
created

SpecialUpload::showUploadWarning()   D

Complexity

Conditions 16
Paths 35

Size

Total Lines 85
Code Lines 56

Duplication

Lines 5
Ratio 5.88 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 56
c 2
b 0
f 0
nc 35
nop 1
dl 5
loc 85
rs 4.8736

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 );
0 ignored issues
show
Bug introduced by
It seems like $permissionRequired defined by \UploadBase::isAllowed($user) on line 176 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...
179
		}
180
181
		# Check blocks
182
		if ( $user->isBlocked() ) {
183
			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...
184
		}
185
186
		// Global blocks
187
		if ( $user->isBlockedGlobally() ) {
188
			throw new UserBlockedError( $user->getGlobalBlock() );
0 ignored issues
show
Bug introduced by
It seems like $user->getGlobalBlock() 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...
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
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...
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 == 'was-deleted' ) {
398
				# If the file existed before and was deleted, warn the user of this
399
				$ltitle = SpecialPage::getTitleFor( 'Log' );
400
				$llink = $linkRenderer->makeKnownLink(
401
					$ltitle,
402
					wfMessage( 'deletionlog' )->text(),
403
					[],
404
					[
405
						'type' => 'delete',
406
						'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(),
407
					]
408
				);
409
				$msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
410
			} elseif ( $warning == 'duplicate' ) {
411
				$msg = $this->getDupeWarning( $args );
412
			} elseif ( $warning == 'duplicate-archive' ) {
413
				if ( $args === '' ) {
414
					$msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
415
						. "</li>\n";
416
				} else {
417
					$msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
418
							Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
419
						. "</li>\n";
420
				}
421
			} else {
422 View Code Duplication
				if ( $args === true ) {
423
					$args = [];
424
				} elseif ( !is_array( $args ) ) {
425
					$args = [ $args ];
426
				}
427
				$msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n";
428
			}
429
			$warningHtml .= $msg;
430
		}
431
		$warningHtml .= "</ul></div>\n";
432
		$warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock();
433
434
		$form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
435
		$form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
436
		$form->addButton( [
437
			'name' => 'wpUploadIgnoreWarning',
438
			'value' => $this->msg( 'ignorewarning' )->text()
439
		] );
440
		$form->addButton( [
441
			'name' => 'wpCancelUpload',
442
			'value' => $this->msg( 'reuploaddesc' )->text()
443
		] );
444
445
		$this->showUploadForm( $form );
446
447
		# Indicate that we showed a form
448
		return true;
449
	}
450
451
	/**
452
	 * Show the upload form with error message, but do not stash the file.
453
	 *
454
	 * @param string $message HTML string
455
	 */
456
	protected function showUploadError( $message ) {
457
		$message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
458
			'<div class="error">' . $message . "</div>\n";
459
		$this->showUploadForm( $this->getUploadForm( $message ) );
460
	}
461
462
	/**
463
	 * Do the upload.
464
	 * Checks are made in SpecialUpload::execute()
465
	 */
466
	protected function processUpload() {
467
		// Fetch the file if required
468
		$status = $this->mUpload->fetchFile();
469
		if ( !$status->isOK() ) {
470
			$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
471
472
			return;
473
		}
474
475
		if ( !Hooks::run( 'UploadForm:BeforeProcessing', [ &$this ] ) ) {
476
			wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
477
			// This code path is deprecated. If you want to break upload processing
478
			// do so by hooking into the appropriate hooks in UploadBase::verifyUpload
479
			// and UploadBase::verifyFile.
480
			// If you use this hook to break uploading, the user will be returned
481
			// an empty form with no error message whatsoever.
482
			return;
483
		}
484
485
		// Upload verification
486
		$details = $this->mUpload->verifyUpload();
487
		if ( $details['status'] != UploadBase::OK ) {
488
			$this->processVerificationError( $details );
0 ignored issues
show
Bug introduced by
It seems like $details defined by $this->mUpload->verifyUpload() on line 486 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...
489
490
			return;
491
		}
492
493
		// Verify permissions for this title
494
		$permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
495
		if ( $permErrors !== true ) {
496
			$code = array_shift( $permErrors[0] );
497
			$this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
498
499
			return;
500
		}
501
502
		$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...
503
504
		// Check warnings if necessary
505
		if ( !$this->mIgnoreWarning ) {
506
			$warnings = $this->mUpload->checkWarnings();
507
			if ( $this->showUploadWarning( $warnings ) ) {
508
				return;
509
			}
510
		}
511
512
		// This is as late as we can throttle, after expected issues have been handled
513
		if ( UploadBase::isThrottled( $this->getUser() ) ) {
514
			$this->showRecoverableUploadError(
515
				$this->msg( 'actionthrottledtext' )->escaped()
516
			);
517
			return;
518
		}
519
520
		// Get the page text if this is not a reupload
521
		if ( !$this->mForReUpload ) {
522
			$pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
523
				$this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() );
524
		} else {
525
			$pageText = false;
526
		}
527
528
		$changeTags = $this->getRequest()->getVal( 'wpChangeTags' );
529
		if ( is_null( $changeTags ) || $changeTags === '' ) {
530
			$changeTags = [];
531
		} else {
532
			$changeTags = array_filter( array_map( 'trim', explode( ',', $changeTags ) ) );
533
		}
534
535
		if ( $changeTags ) {
536
			$changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
537
				$changeTags, $this->getUser() );
538
			if ( !$changeTagsStatus->isOK() ) {
539
				$this->showUploadError( $this->getOutput()->parse( $changeTagsStatus->getWikiText() ) );
540
541
				return;
542
			}
543
		}
544
545
		$status = $this->mUpload->performUpload(
546
			$this->mComment,
547
			$pageText,
0 ignored issues
show
Security Bug introduced by
It seems like $pageText defined by false on line 525 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...
548
			$this->mWatchthis,
549
			$this->getUser(),
550
			$changeTags
551
		);
552
553
		if ( !$status->isGood() ) {
554
			$this->showRecoverableUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
555
556
			return;
557
		}
558
559
		// Success, redirect to description page
560
		$this->mUploadSuccessful = true;
561
		Hooks::run( 'SpecialUploadComplete', [ &$this ] );
562
		$this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
563
	}
564
565
	/**
566
	 * Get the initial image page text based on a comment and optional file status information
567
	 * @param string $comment
568
	 * @param string $license
569
	 * @param string $copyStatus
570
	 * @param string $source
571
	 * @param Config $config Configuration object to load data from
572
	 * @return string
573
	 */
574
	public static function getInitialPageText( $comment = '', $license = '',
575
		$copyStatus = '', $source = '', Config $config = null
576
	) {
577 View Code Duplication
		if ( $config === null ) {
578
			wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
579
			$config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
580
		}
581
582
		$msg = [];
583
		$forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' );
584
		/* These messages are transcluded into the actual text of the description page.
585
		 * Thus, forcing them as content messages makes the upload to produce an int: template
586
		 * instead of hardcoding it there in the uploader language.
587
		 */
588
		foreach ( [ 'license-header', 'filedesc', 'filestatus', 'filesource' ] as $msgName ) {
589
			if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) {
590
				$msg[$msgName] = "{{int:$msgName}}";
591
			} else {
592
				$msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
593
			}
594
		}
595
596
		if ( $config->get( 'UseCopyrightUpload' ) ) {
597
			$licensetxt = '';
598
			if ( $license != '' ) {
599
				$licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
600
			}
601
			$pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
602
				'== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
603
				"$licensetxt" .
604
				'== ' . $msg['filesource'] . " ==\n" . $source;
605
		} else {
606
			if ( $license != '' ) {
607
				$filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
608
					$pageText = $filedesc .
609
					'== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
610
			} else {
611
				$pageText = $comment;
612
			}
613
		}
614
615
		return $pageText;
616
	}
617
618
	/**
619
	 * See if we should check the 'watch this page' checkbox on the form
620
	 * based on the user's preferences and whether we're being asked
621
	 * to create a new file or update an existing one.
622
	 *
623
	 * In the case where 'watch edits' is off but 'watch creations' is on,
624
	 * we'll leave the box unchecked.
625
	 *
626
	 * Note that the page target can be changed *on the form*, so our check
627
	 * state can get out of sync.
628
	 * @return bool|string
629
	 */
630
	protected function getWatchCheck() {
631
		if ( $this->getUser()->getOption( 'watchdefault' ) ) {
632
			// Watch all edits!
633
			return true;
634
		}
635
636
		$desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
637
		if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
638
			// Already watched, don't change that
639
			return true;
640
		}
641
642
		$local = wfLocalFile( $this->mDesiredDestName );
643
		if ( $local && $local->exists() ) {
644
			// We're uploading a new version of an existing file.
645
			// No creation, so don't watch it if we're not already.
646
			return false;
647
		} else {
648
			// New page should get watched if that's our option.
649
			return $this->getUser()->getOption( 'watchcreations' ) ||
650
				$this->getUser()->getOption( 'watchuploads' );
651
		}
652
	}
653
654
	/**
655
	 * Provides output to the user for a result of UploadBase::verifyUpload
656
	 *
657
	 * @param array $details Result of UploadBase::verifyUpload
658
	 * @throws MWException
659
	 */
660
	protected function processVerificationError( $details ) {
661
		switch ( $details['status'] ) {
662
663
			/** Statuses that only require name changing **/
664
			case UploadBase::MIN_LENGTH_PARTNAME:
665
				$this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() );
666
				break;
667
			case UploadBase::ILLEGAL_FILENAME:
668
				$this->showRecoverableUploadError( $this->msg( 'illegalfilename',
669
					$details['filtered'] )->parse() );
670
				break;
671
			case UploadBase::FILENAME_TOO_LONG:
672
				$this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() );
673
				break;
674
			case UploadBase::FILETYPE_MISSING:
675
				$this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() );
676
				break;
677
			case UploadBase::WINDOWS_NONASCII_FILENAME:
678
				$this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() );
679
				break;
680
681
			/** Statuses that require reuploading **/
682
			case UploadBase::EMPTY_FILE:
683
				$this->showUploadError( $this->msg( 'emptyfile' )->escaped() );
684
				break;
685
			case UploadBase::FILE_TOO_LARGE:
686
				$this->showUploadError( $this->msg( 'largefileserver' )->escaped() );
687
				break;
688
			case UploadBase::FILETYPE_BADTYPE:
689
				$msg = $this->msg( 'filetype-banned-type' );
690
				if ( isset( $details['blacklistedExt'] ) ) {
691
					$msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) );
692
				} else {
693
					$msg->params( $details['finalExt'] );
694
				}
695
				$extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
696
				$msg->params( $this->getLanguage()->commaList( $extensions ),
697
					count( $extensions ) );
698
699
				// Add PLURAL support for the first parameter. This results
700
				// in a bit unlogical parameter sequence, but does not break
701
				// old translations
702
				if ( isset( $details['blacklistedExt'] ) ) {
703
					$msg->params( count( $details['blacklistedExt'] ) );
704
				} else {
705
					$msg->params( 1 );
706
				}
707
708
				$this->showUploadError( $msg->parse() );
709
				break;
710
			case UploadBase::VERIFICATION_ERROR:
711
				unset( $details['status'] );
712
				$code = array_shift( $details['details'] );
713
				$this->showUploadError( $this->msg( $code, $details['details'] )->parse() );
714
				break;
715
			case UploadBase::HOOK_ABORTED:
716
				if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array
717
					$args = $details['error'];
718
					$error = array_shift( $args );
719
				} else {
720
					$error = $details['error'];
721
					$args = null;
722
				}
723
724
				$this->showUploadError( $this->msg( $error, $args )->parse() );
725
				break;
726
			default:
727
				throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
728
		}
729
	}
730
731
	/**
732
	 * Remove a temporarily kept file stashed by saveTempUploadedFile().
733
	 *
734
	 * @return bool Success
735
	 */
736
	protected function unsaveUploadedFile() {
737
		if ( !( $this->mUpload instanceof UploadFromStash ) ) {
738
			return true;
739
		}
740
		$success = $this->mUpload->unsaveUploadedFile();
741
		if ( !$success ) {
742
			$this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
743
744
			return false;
745
		} else {
746
			return true;
747
		}
748
	}
749
750
	/*** Functions for formatting warnings ***/
751
752
	/**
753
	 * Formats a result of UploadBase::getExistsWarning as HTML
754
	 * This check is static and can be done pre-upload via AJAX
755
	 *
756
	 * @param array $exists The result of UploadBase::getExistsWarning
757
	 * @return string Empty string if there is no warning or an HTML fragment
758
	 */
759
	public static function getExistsWarning( $exists ) {
760
		if ( !$exists ) {
761
			return '';
762
		}
763
764
		$file = $exists['file'];
765
		$filename = $file->getTitle()->getPrefixedText();
766
		$warning = '';
767
768
		if ( $exists['warning'] == 'exists' ) {
769
			// Exact match
770
			$warning = wfMessage( 'fileexists', $filename )->parse();
771
		} elseif ( $exists['warning'] == 'page-exists' ) {
772
			// Page exists but file does not
773
			$warning = wfMessage( 'filepageexists', $filename )->parse();
774
		} elseif ( $exists['warning'] == 'exists-normalized' ) {
775
			$warning = wfMessage( 'fileexists-extension', $filename,
776
				$exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
777
		} elseif ( $exists['warning'] == 'thumb' ) {
778
			// Swapped argument order compared with other messages for backwards compatibility
779
			$warning = wfMessage( 'fileexists-thumbnail-yes',
780
				$exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
781
		} elseif ( $exists['warning'] == 'thumb-name' ) {
782
			// Image w/o '180px-' does not exists, but we do not like these filenames
783
			$name = $file->getName();
784
			$badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
785
			$warning = wfMessage( 'file-thumbnail-no', $badPart )->parse();
786
		} elseif ( $exists['warning'] == 'bad-prefix' ) {
787
			$warning = wfMessage( 'filename-bad-prefix', $exists['prefix'] )->parse();
788
		}
789
790
		return $warning;
791
	}
792
793
	/**
794
	 * Construct a warning and a gallery from an array of duplicate files.
795
	 * @param array $dupes
796
	 * @return string
797
	 */
798
	public function getDupeWarning( $dupes ) {
799
		if ( !$dupes ) {
800
			return '';
801
		}
802
803
		$gallery = ImageGalleryBase::factory( false, $this->getContext() );
804
		$gallery->setShowBytes( false );
805
		foreach ( $dupes as $file ) {
806
			$gallery->add( $file->getTitle() );
807
		}
808
809
		return '<li>' .
810
			$this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
811
			$gallery->toHTML() . "</li>\n";
812
	}
813
814
	protected function getGroupName() {
815
		return 'media';
816
	}
817
818
	/**
819
	 * Should we rotate images in the preview on Special:Upload.
820
	 *
821
	 * This controls js: mw.config.get( 'wgFileCanRotate' )
822
	 *
823
	 * @todo What about non-BitmapHandler handled files?
824
	 */
825
	public static function rotationEnabled() {
826
		$bitmapHandler = new BitmapHandler();
827
		return $bitmapHandler->autoRotateEnabled();
828
	}
829
}
830
831
/**
832
 * Sub class of HTMLForm that provides the form section of SpecialUpload
833
 */
834
class UploadForm extends HTMLForm {
835
	protected $mWatch;
836
	protected $mForReUpload;
837
	protected $mSessionKey;
838
	protected $mHideIgnoreWarning;
839
	protected $mDestWarningAck;
840
	protected $mDestFile;
841
842
	protected $mComment;
843
	protected $mTextTop;
844
	protected $mTextAfterSummary;
845
846
	protected $mSourceIds;
847
848
	protected $mMaxFileSize = [];
849
850
	protected $mMaxUploadSize = [];
851
852
	public function __construct( array $options = [], IContextSource $context = null,
853
		LinkRenderer $linkRenderer = null
854
	) {
855
		if ( $context instanceof IContextSource ) {
856
			$this->setContext( $context );
857
		}
858
859
		if ( !$linkRenderer ) {
860
			$linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
861
		}
862
863
		$this->mWatch = !empty( $options['watch'] );
864
		$this->mForReUpload = !empty( $options['forreupload'] );
865
		$this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
866
		$this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
867
		$this->mDestWarningAck = !empty( $options['destwarningack'] );
868
		$this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
869
870
		$this->mComment = isset( $options['description'] ) ?
871
			$options['description'] : '';
872
873
		$this->mTextTop = isset( $options['texttop'] )
874
			? $options['texttop'] : '';
875
876
		$this->mTextAfterSummary = isset( $options['textaftersummary'] )
877
			? $options['textaftersummary'] : '';
878
879
		$sourceDescriptor = $this->getSourceSection();
880
		$descriptor = $sourceDescriptor
881
			+ $this->getDescriptionSection()
882
			+ $this->getOptionsSection();
883
884
		Hooks::run( 'UploadFormInitDescriptor', [ &$descriptor ] );
885
		parent::__construct( $descriptor, $context, 'upload' );
886
887
		# Add a link to edit MediaWiki:Licenses
888
		if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
889
			$this->getOutput()->addModuleStyles( 'mediawiki.special.upload.styles' );
890
			$licensesLink = $linkRenderer->makeKnownLink(
891
				$this->msg( 'licenses' )->inContentLanguage()->getTitle(),
892
				$this->msg( 'licenses-edit' )->text(),
893
				[],
894
				[ 'action' => 'edit' ]
895
			);
896
			$editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
897
			$this->addFooterText( $editLicenses, 'description' );
898
		}
899
900
		# Set some form properties
901
		$this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
902
		$this->setSubmitName( 'wpUpload' );
903
		# Used message keys: 'accesskey-upload', 'tooltip-upload'
904
		$this->setSubmitTooltip( 'upload' );
905
		$this->setId( 'mw-upload-form' );
906
907
		# Build a list of IDs for javascript insertion
908
		$this->mSourceIds = [];
909
		foreach ( $sourceDescriptor as $field ) {
910
			if ( !empty( $field['id'] ) ) {
911
				$this->mSourceIds[] = $field['id'];
912
			}
913
		}
914
	}
915
916
	/**
917
	 * Get the descriptor of the fieldset that contains the file source
918
	 * selection. The section is 'source'
919
	 *
920
	 * @return array Descriptor array
921
	 */
922
	protected function getSourceSection() {
923
		if ( $this->mSessionKey ) {
924
			return [
925
				'SessionKey' => [
926
					'type' => 'hidden',
927
					'default' => $this->mSessionKey,
928
				],
929
				'SourceType' => [
930
					'type' => 'hidden',
931
					'default' => 'Stash',
932
				],
933
			];
934
		}
935
936
		$canUploadByUrl = UploadFromUrl::isEnabled()
937
			&& ( UploadFromUrl::isAllowed( $this->getUser() ) === true )
938
			&& $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' );
939
		$radio = $canUploadByUrl;
940
		$selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
941
942
		$descriptor = [];
943
		if ( $this->mTextTop ) {
944
			$descriptor['UploadFormTextTop'] = [
945
				'type' => 'info',
946
				'section' => 'source',
947
				'default' => $this->mTextTop,
948
				'raw' => true,
949
			];
950
		}
951
952
		$this->mMaxUploadSize['file'] = min(
953
			UploadBase::getMaxUploadSize( 'file' ),
954
			UploadBase::getMaxPhpUploadSize()
955
		);
956
957
		$help = $this->msg( 'upload-maxfilesize',
958
				$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
959
			)->parse();
960
961
		// If the user can also upload by URL, there are 2 different file size limits.
962
		// This extra message helps stress which limit corresponds to what.
963
		if ( $canUploadByUrl ) {
964
			$help .= $this->msg( 'word-separator' )->escaped();
965
			$help .= $this->msg( 'upload_source_file' )->parse();
966
		}
967
968
		$descriptor['UploadFile'] = [
969
			'class' => 'UploadSourceField',
970
			'section' => 'source',
971
			'type' => 'file',
972
			'id' => 'wpUploadFile',
973
			'radio-id' => 'wpSourceTypeFile',
974
			'label-message' => 'sourcefilename',
975
			'upload-type' => 'File',
976
			'radio' => &$radio,
977
			'help' => $help,
978
			'checked' => $selectedSourceType == 'file',
979
		];
980
981
		if ( $canUploadByUrl ) {
982
			$this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
983
			$descriptor['UploadFileURL'] = [
984
				'class' => 'UploadSourceField',
985
				'section' => 'source',
986
				'id' => 'wpUploadFileURL',
987
				'radio-id' => 'wpSourceTypeurl',
988
				'label-message' => 'sourceurl',
989
				'upload-type' => 'url',
990
				'radio' => &$radio,
991
				'help' => $this->msg( 'upload-maxfilesize',
992
					$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
993
				)->parse() .
994
					$this->msg( 'word-separator' )->escaped() .
995
					$this->msg( 'upload_source_url' )->parse(),
996
				'checked' => $selectedSourceType == 'url',
997
			];
998
		}
999
		Hooks::run( 'UploadFormSourceDescriptors', [ &$descriptor, &$radio, $selectedSourceType ] );
1000
1001
		$descriptor['Extensions'] = [
1002
			'type' => 'info',
1003
			'section' => 'source',
1004
			'default' => $this->getExtensionsMessage(),
1005
			'raw' => true,
1006
		];
1007
1008
		return $descriptor;
1009
	}
1010
1011
	/**
1012
	 * Get the messages indicating which extensions are preferred and prohibitted.
1013
	 *
1014
	 * @return string HTML string containing the message
1015
	 */
1016
	protected function getExtensionsMessage() {
1017
		# Print a list of allowed file extensions, if so configured.  We ignore
1018
		# MIME type here, it's incomprehensible to most people and too long.
1019
		$config = $this->getConfig();
1020
1021
		if ( $config->get( 'CheckFileExtensions' ) ) {
1022
			$fileExtensions = array_unique( $config->get( 'FileExtensions' ) );
1023
			if ( $config->get( 'StrictFileExtensions' ) ) {
1024
				# Everything not permitted is banned
1025
				$extensionsList =
1026
					'<div id="mw-upload-permitted">' .
1027
					$this->msg( 'upload-permitted' )
1028
						->params( $this->getLanguage()->commaList( $fileExtensions ) )
1029
						->numParams( count( $fileExtensions ) )
1030
						->parseAsBlock() .
1031
					"</div>\n";
1032
			} else {
1033
				# We have to list both preferred and prohibited
1034
				$fileBlacklist = array_unique( $config->get( 'FileBlacklist' ) );
1035
				$extensionsList =
1036
					'<div id="mw-upload-preferred">' .
1037
						$this->msg( 'upload-preferred' )
1038
							->params( $this->getLanguage()->commaList( $fileExtensions ) )
1039
							->numParams( count( $fileExtensions ) )
1040
							->parseAsBlock() .
1041
					"</div>\n" .
1042
					'<div id="mw-upload-prohibited">' .
1043
						$this->msg( 'upload-prohibited' )
1044
							->params( $this->getLanguage()->commaList( $fileBlacklist ) )
1045
							->numParams( count( $fileBlacklist ) )
1046
							->parseAsBlock() .
1047
					"</div>\n";
1048
			}
1049
		} else {
1050
			# Everything is permitted.
1051
			$extensionsList = '';
1052
		}
1053
1054
		return $extensionsList;
1055
	}
1056
1057
	/**
1058
	 * Get the descriptor of the fieldset that contains the file description
1059
	 * input. The section is 'description'
1060
	 *
1061
	 * @return array Descriptor array
1062
	 */
1063
	protected function getDescriptionSection() {
1064
		$config = $this->getConfig();
1065
		if ( $this->mSessionKey ) {
1066
			$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
1067
			try {
1068
				$file = $stash->getFile( $this->mSessionKey );
1069
			} catch ( Exception $e ) {
1070
				$file = null;
1071
			}
1072
			if ( $file ) {
1073
				global $wgContLang;
1074
1075
				$mto = $file->transform( [ 'width' => 120 ] );
1076
				$this->addHeaderText(
1077
					'<div class="thumb t' . $wgContLang->alignEnd() . '">' .
1078
					Html::element( 'img', [
1079
						'src' => $mto->getUrl(),
1080
						'class' => 'thumbimage',
1081
					] ) . '</div>', 'description' );
1082
			}
1083
		}
1084
1085
		$descriptor = [
1086
			'DestFile' => [
1087
				'type' => 'text',
1088
				'section' => 'description',
1089
				'id' => 'wpDestFile',
1090
				'label-message' => 'destfilename',
1091
				'size' => 60,
1092
				'default' => $this->mDestFile,
1093
				# @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
1094
				'nodata' => strval( $this->mDestFile ) !== '',
1095
			],
1096
			'UploadDescription' => [
1097
				'type' => 'textarea',
1098
				'section' => 'description',
1099
				'id' => 'wpUploadDescription',
1100
				'label-message' => $this->mForReUpload
1101
					? 'filereuploadsummary'
1102
					: 'fileuploadsummary',
1103
				'default' => $this->mComment,
1104
				'cols' => $this->getUser()->getIntOption( 'cols' ),
1105
				'rows' => 8,
1106
			]
1107
		];
1108
		if ( $this->mTextAfterSummary ) {
1109
			$descriptor['UploadFormTextAfterSummary'] = [
1110
				'type' => 'info',
1111
				'section' => 'description',
1112
				'default' => $this->mTextAfterSummary,
1113
				'raw' => true,
1114
			];
1115
		}
1116
1117
		$descriptor += [
1118
			'EditTools' => [
1119
				'type' => 'edittools',
1120
				'section' => 'description',
1121
				'message' => 'edittools-upload',
1122
			]
1123
		];
1124
1125
		if ( $this->mForReUpload ) {
1126
			$descriptor['DestFile']['readonly'] = true;
1127
		} else {
1128
			$descriptor['License'] = [
1129
				'type' => 'select',
1130
				'class' => 'Licenses',
1131
				'section' => 'description',
1132
				'id' => 'wpLicense',
1133
				'label-message' => 'license',
1134
			];
1135
		}
1136
1137
		if ( $config->get( 'UseCopyrightUpload' ) ) {
1138
			$descriptor['UploadCopyStatus'] = [
1139
				'type' => 'text',
1140
				'section' => 'description',
1141
				'id' => 'wpUploadCopyStatus',
1142
				'label-message' => 'filestatus',
1143
			];
1144
			$descriptor['UploadSource'] = [
1145
				'type' => 'text',
1146
				'section' => 'description',
1147
				'id' => 'wpUploadSource',
1148
				'label-message' => 'filesource',
1149
			];
1150
		}
1151
1152
		return $descriptor;
1153
	}
1154
1155
	/**
1156
	 * Get the descriptor of the fieldset that contains the upload options,
1157
	 * such as "watch this file". The section is 'options'
1158
	 *
1159
	 * @return array Descriptor array
1160
	 */
1161
	protected function getOptionsSection() {
1162
		$user = $this->getUser();
1163
		if ( $user->isLoggedIn() ) {
1164
			$descriptor = [
1165
				'Watchthis' => [
1166
					'type' => 'check',
1167
					'id' => 'wpWatchthis',
1168
					'label-message' => 'watchthisupload',
1169
					'section' => 'options',
1170
					'default' => $this->mWatch,
1171
				]
1172
			];
1173
		}
1174
		if ( !$this->mHideIgnoreWarning ) {
1175
			$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...
1176
				'type' => 'check',
1177
				'id' => 'wpIgnoreWarning',
1178
				'label-message' => 'ignorewarnings',
1179
				'section' => 'options',
1180
			];
1181
		}
1182
1183
		$descriptor['DestFileWarningAck'] = [
1184
			'type' => 'hidden',
1185
			'id' => 'wpDestFileWarningAck',
1186
			'default' => $this->mDestWarningAck ? '1' : '',
1187
		];
1188
1189
		if ( $this->mForReUpload ) {
1190
			$descriptor['ForReUpload'] = [
1191
				'type' => 'hidden',
1192
				'id' => 'wpForReUpload',
1193
				'default' => '1',
1194
			];
1195
		}
1196
1197
		return $descriptor;
1198
	}
1199
1200
	/**
1201
	 * Add the upload JS and show the form.
1202
	 */
1203
	public function show() {
1204
		$this->addUploadJS();
1205
		parent::show();
1206
	}
1207
1208
	/**
1209
	 * Add upload JS to the OutputPage
1210
	 */
1211
	protected function addUploadJS() {
1212
		$config = $this->getConfig();
1213
1214
		$useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' );
1215
		$useAjaxLicensePreview = $config->get( 'UseAjax' ) &&
1216
			$config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' );
1217
		$this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
1218
1219
		$scriptVars = [
1220
			'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
1221
			'wgAjaxLicensePreview' => $useAjaxLicensePreview,
1222
			'wgUploadAutoFill' => !$this->mForReUpload &&
1223
				// If we received mDestFile from the request, don't autofill
1224
				// the wpDestFile textbox
1225
				$this->mDestFile === '',
1226
			'wgUploadSourceIds' => $this->mSourceIds,
1227
			'wgCheckFileExtensions' => $config->get( 'CheckFileExtensions' ),
1228
			'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
1229
			'wgFileExtensions' => array_values( array_unique( $config->get( 'FileExtensions' ) ) ),
1230
			'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
1231
			'wgMaxUploadSize' => $this->mMaxUploadSize,
1232
			'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
1233
		];
1234
1235
		$out = $this->getOutput();
1236
		$out->addJsConfigVars( $scriptVars );
1237
1238
		$out->addModules( [
1239
			'mediawiki.action.edit', // For <charinsert> support
1240
			'mediawiki.special.upload', // Extras for thumbnail and license preview.
1241
		] );
1242
	}
1243
1244
	/**
1245
	 * Empty function; submission is handled elsewhere.
1246
	 *
1247
	 * @return bool False
1248
	 */
1249
	function trySubmit() {
1250
		return false;
1251
	}
1252
}
1253
1254
/**
1255
 * A form field that contains a radio box in the label
1256
 */
1257
class UploadSourceField extends HTMLTextField {
1258
1259
	/**
1260
	 * @param array $cellAttributes
1261
	 * @return string
1262
	 */
1263
	function getLabelHtml( $cellAttributes = [] ) {
1264
		$id = $this->mParams['id'];
1265
		$label = Html::rawElement( 'label', [ 'for' => $id ], $this->mLabel );
1266
1267
		if ( !empty( $this->mParams['radio'] ) ) {
1268
			if ( isset( $this->mParams['radio-id'] ) ) {
1269
				$radioId = $this->mParams['radio-id'];
1270
			} else {
1271
				// Old way. For the benefit of extensions that do not define
1272
				// the 'radio-id' key.
1273
				$radioId = 'wpSourceType' . $this->mParams['upload-type'];
1274
			}
1275
1276
			$attribs = [
1277
				'name' => 'wpSourceType',
1278
				'type' => 'radio',
1279
				'id' => $radioId,
1280
				'value' => $this->mParams['upload-type'],
1281
			];
1282
1283
			if ( !empty( $this->mParams['checked'] ) ) {
1284
				$attribs['checked'] = 'checked';
1285
			}
1286
1287
			$label .= Html::element( 'input', $attribs );
1288
		}
1289
1290
		return Html::rawElement( 'td', [ 'class' => 'mw-label' ] + $cellAttributes, $label );
1291
	}
1292
1293
	/**
1294
	 * @return int
1295
	 */
1296
	function getSize() {
1297
		return isset( $this->mParams['size'] )
1298
			? $this->mParams['size']
1299
			: 60;
1300
	}
1301
}
1302