Completed
Branch master (726f70)
by
unknown
25:29
created

UploadForm::__construct()   F

Complexity

Conditions 11
Paths 768

Size

Total Lines 63
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 40
nc 768
nop 3
dl 0
loc 63
rs 3.9772
c 1
b 0
f 0

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