Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/page/ImagePage.php (17 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Special handling for file description pages.
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
 */
22
23
/**
24
 * Class for viewing MediaWiki file description pages
25
 *
26
 * @ingroup Media
27
 */
28
class ImagePage extends Article {
29
	/** @var File */
30
	private $displayImg;
31
32
	/** @var FileRepo */
33
	private $repo;
34
35
	/** @var bool */
36
	private $fileLoaded;
37
38
	/** @var bool */
39
	protected $mExtraDescription = false;
40
41
	/**
42
	 * @var WikiFilePage
43
	 */
44
	protected $mPage;
45
46
	/**
47
	 * @param Title $title
48
	 * @return WikiFilePage
49
	 */
50
	protected function newPage( Title $title ) {
51
		// Overload mPage with a file-specific page
52
		return new WikiFilePage( $title );
53
	}
54
55
	/**
56
	 * @param File $file
57
	 * @return void
58
	 */
59
	public function setFile( $file ) {
60
		$this->mPage->setFile( $file );
61
		$this->displayImg = $file;
62
		$this->fileLoaded = true;
63
	}
64
65
	protected function loadFile() {
66
		if ( $this->fileLoaded ) {
67
			return;
68
		}
69
		$this->fileLoaded = true;
70
71
		$this->displayImg = $img = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like $img = false of type false is incompatible with the declared type object<File> of property $displayImg.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
72
		Hooks::run( 'ImagePageFindFile', [ $this, &$img, &$this->displayImg ] );
73
		if ( !$img ) { // not set by hook?
74
			$img = wfFindFile( $this->getTitle() );
75
			if ( !$img ) {
76
				$img = wfLocalFile( $this->getTitle() );
77
			}
78
		}
79
		$this->mPage->setFile( $img );
0 ignored issues
show
It seems like $img can also be of type boolean or null; however, WikiFilePage::setFile() does only seem to accept object<File>, 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...
80
		if ( !$this->displayImg ) { // not set by hook?
81
			$this->displayImg = $img;
0 ignored issues
show
Documentation Bug introduced by
It seems like $img can also be of type boolean. However, the property $displayImg is declared as type object<File>. 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...
82
		}
83
		$this->repo = $img->getRepo();
84
	}
85
86
	/**
87
	 * Handler for action=render
88
	 * Include body text only; none of the image extras
89
	 */
90
	public function render() {
91
		$this->getContext()->getOutput()->setArticleBodyOnly( true );
92
		parent::view();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (view() instead of render()). Are you sure this is correct? If so, you might want to change this to $this->view().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
93
	}
94
95
	public function view() {
96
		global $wgShowEXIF;
97
98
		$out = $this->getContext()->getOutput();
99
		$request = $this->getContext()->getRequest();
100
		$diff = $request->getVal( 'diff' );
101
		$diffOnly = $request->getBool(
102
			'diffonly',
103
			$this->getContext()->getUser()->getOption( 'diffonly' )
104
		);
105
106
		if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
107
			parent::view();
108
			return;
109
		}
110
111
		$this->loadFile();
112
113
		if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
114
			if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) {
115
				// mTitle is the same as the redirect target so ask Article
116
				// to perform the redirect for us.
117
				$request->setVal( 'diffonly', 'true' );
118
				parent::view();
119
				return;
120
			} else {
121
				// mTitle is not the same as the redirect target so it is
122
				// probably the redirect page itself. Fake the redirect symbol
123
				$out->setPageTitle( $this->getTitle()->getPrefixedText() );
124
				$out->addHTML( $this->viewRedirect(
125
					Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
126
					/* $appendSubtitle */ true,
127
					/* $forceKnown */ true )
128
				);
129
				$this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
130
				return;
131
			}
132
		}
133
134
		if ( $wgShowEXIF && $this->displayImg->exists() ) {
135
			// @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
136
			$formattedMetadata = $this->displayImg->formatMetadata( $this->getContext() );
137
			$showmeta = $formattedMetadata !== false;
138
		} else {
139
			$showmeta = false;
140
		}
141
142
		if ( !$diff && $this->displayImg->exists() ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $diff of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
143
			$out->addHTML( $this->showTOC( $showmeta ) );
144
		}
145
146
		if ( !$diff ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $diff of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
147
			$this->openShowImage();
148
		}
149
150
		# No need to display noarticletext, we use our own message, output in openShowImage()
151
		if ( $this->mPage->getId() ) {
152
			# NS_FILE is in the user language, but this section (the actual wikitext)
153
			# should be in page content language
154
			$pageLang = $this->getTitle()->getPageViewLanguage();
155
			$out->addHTML( Xml::openElement( 'div', [ 'id' => 'mw-imagepage-content',
156
				'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
157
				'class' => 'mw-content-' . $pageLang->getDir() ] ) );
158
159
			parent::view();
160
161
			$out->addHTML( Xml::closeElement( 'div' ) );
162
		} else {
163
			# Just need to set the right headers
164
			$out->setArticleFlag( true );
165
			$out->setPageTitle( $this->getTitle()->getPrefixedText() );
166
			$this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
167
		}
168
169
		# Show shared description, if needed
170
		if ( $this->mExtraDescription ) {
171
			$fol = $this->getContext()->msg( 'shareddescriptionfollows' );
172
			if ( !$fol->isDisabled() ) {
173
				$out->addWikiText( $fol->plain() );
174
			}
175
			$out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
176
		}
177
178
		$this->closeShowImage();
179
		$this->imageHistory();
180
		// TODO: Cleanup the following
181
182
		$out->addHTML( Xml::element( 'h2',
183
			[ 'id' => 'filelinks' ],
184
				$this->getContext()->msg( 'imagelinks' )->text() ) . "\n" );
185
		$this->imageDupes();
186
		# @todo FIXME: For some freaky reason, we can't redirect to foreign images.
187
		# Yet we return metadata about the target. Definitely an issue in the FileRepo
188
		$this->imageLinks();
189
190
		# Allow extensions to add something after the image links
191
		$html = '';
192
		Hooks::run( 'ImagePageAfterImageLinks', [ $this, &$html ] );
193
		if ( $html ) {
194
			$out->addHTML( $html );
195
		}
196
197
		if ( $showmeta ) {
198
			$out->addHTML( Xml::element(
199
				'h2',
200
				[ 'id' => 'metadata' ],
201
					$this->getContext()->msg( 'metadata' )->text() ) . "\n" );
202
			$out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
0 ignored issues
show
The variable $formattedMetadata 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...
It seems like $formattedMetadata defined by $this->displayImg->forma...ta($this->getContext()) on line 136 can also be of type boolean; however, ImagePage::makeMetadataTable() 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...
203
			$out->addModules( [ 'mediawiki.action.view.metadata' ] );
204
		}
205
206
		// Add remote Filepage.css
207
		if ( !$this->repo->isLocal() ) {
208
			$css = $this->repo->getDescriptionStylesheetUrl();
209
			if ( $css ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $css of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
210
				$out->addStyle( $css );
211
			}
212
		}
213
214
		$out->addModuleStyles( [
215
			'filepage', // always show the local local Filepage.css, bug 29277
216
			'mediawiki.action.view.filepage', // Add MediaWiki styles for a file page
217
		] );
218
	}
219
220
	/**
221
	 * @return File
222
	 */
223
	public function getDisplayedFile() {
224
		$this->loadFile();
225
		return $this->displayImg;
226
	}
227
228
	/**
229
	 * Create the TOC
230
	 *
231
	 * @param bool $metadata Whether or not to show the metadata link
232
	 * @return string
233
	 */
234
	protected function showTOC( $metadata ) {
235
		$r = [
236
			'<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>',
237
			'<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>',
238
			'<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>',
239
		];
240
241
		Hooks::run( 'ImagePageShowTOC', [ $this, &$r ] );
242
243
		if ( $metadata ) {
244
			$r[] = '<li><a href="#metadata">' .
245
				$this->getContext()->msg( 'metadata' )->escaped() .
246
				'</a></li>';
247
		}
248
249
		return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
250
	}
251
252
	/**
253
	 * Make a table with metadata to be shown in the output page.
254
	 *
255
	 * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
256
	 *
257
	 * @param array $metadata The array containing the Exif data
258
	 * @return string The metadata table. This is treated as Wikitext (!)
259
	 */
260
	protected function makeMetadataTable( $metadata ) {
261
		$r = "<div class=\"mw-imagepage-section-metadata\">";
262
		$r .= $this->getContext()->msg( 'metadata-help' )->plain();
263
		$r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
264
		foreach ( $metadata as $type => $stuff ) {
265
			foreach ( $stuff as $v ) {
266
				# @todo FIXME: Why is this using escapeId for a class?!
267
				$class = Sanitizer::escapeId( $v['id'] );
268
				if ( $type == 'collapsed' ) {
269
					// Handled by mediawiki.action.view.metadata module.
270
					$class .= ' collapsable';
271
				}
272
				$r .= "<tr class=\"$class\">\n";
273
				$r .= "<th>{$v['name']}</th>\n";
274
				$r .= "<td>{$v['value']}</td>\n</tr>";
275
			}
276
		}
277
		$r .= "</table>\n</div>\n";
278
		return $r;
279
	}
280
281
	/**
282
	 * Overloading Article's getContentObject method.
283
	 *
284
	 * Omit noarticletext if sharedupload; text will be fetched from the
285
	 * shared upload server if possible.
286
	 * @return string
287
	 */
288
	public function getContentObject() {
289
		$this->loadFile();
290
		if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getId() ) {
291
			return null;
292
		}
293
		return parent::getContentObject();
294
	}
295
296
	protected function openShowImage() {
297
		global $wgEnableUploads, $wgSend404Code, $wgSVGMaxSize;
298
299
		$this->loadFile();
300
		$out = $this->getContext()->getOutput();
301
		$user = $this->getContext()->getUser();
302
		$lang = $this->getContext()->getLanguage();
303
		$dirmark = $lang->getDirMarkEntity();
304
		$request = $this->getContext()->getRequest();
305
306
		$max = $this->getImageLimitsFromOption( $user, 'imagesize' );
307
		$maxWidth = $max[0];
308
		$maxHeight = $max[1];
309
310
		if ( $this->displayImg->exists() ) {
311
			# image
312
			$page = $request->getIntOrNull( 'page' );
313
			if ( is_null( $page ) ) {
314
				$params = [];
315
				$page = 1;
316
			} else {
317
				$params = [ 'page' => $page ];
318
			}
319
320
			$renderLang = $request->getVal( 'lang' );
321
			if ( !is_null( $renderLang ) ) {
322
				$handler = $this->displayImg->getHandler();
323
				if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
324
					$params['lang'] = $renderLang;
325
				} else {
326
					$renderLang = null;
327
				}
328
			}
329
330
			$width_orig = $this->displayImg->getWidth( $page );
331
			$width = $width_orig;
332
			$height_orig = $this->displayImg->getHeight( $page );
333
			$height = $height_orig;
334
335
			$filename = wfEscapeWikiText( $this->displayImg->getName() );
336
			$linktext = $filename;
337
338
			Hooks::run( 'ImageOpenShowImageInlineBefore', [ &$this, &$out ] );
339
340
			if ( $this->displayImg->allowInlineDisplay() ) {
341
				# image
342
				# "Download high res version" link below the image
343
				# $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig,
344
				#   Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
345
				# We'll show a thumbnail of this image
346
				if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) {
347
					list( $width, $height ) = $this->getDisplayWidthHeight(
348
						$maxWidth, $maxHeight, $width, $height
0 ignored issues
show
It seems like $width can also be of type boolean; however, ImagePage::getDisplayWidthHeight() does only seem to accept integer, 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...
It seems like $height can also be of type boolean; however, ImagePage::getDisplayWidthHeight() does only seem to accept integer, 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...
349
					);
350
					$linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
351
352
					$thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
0 ignored issues
show
It seems like $width_orig defined by $this->displayImg->getWidth($page) on line 330 can also be of type boolean; however, ImagePage::getThumbSizes() does only seem to accept integer, 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...
It seems like $height_orig defined by $this->displayImg->getHeight($page) on line 332 can also be of type boolean; however, ImagePage::getThumbSizes() does only seem to accept integer, 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...
353
					# Generate thumbnails or thumbnail links as needed...
354
					$otherSizes = [];
355
					foreach ( $thumbSizes as $size ) {
356
						// We include a thumbnail size in the list, if it is
357
						// less than or equal to the original size of the image
358
						// asset ($width_orig/$height_orig). We also exclude
359
						// the current thumbnail's size ($width/$height)
360
						// since that is added to the message separately, so
361
						// it can be denoted as the current size being shown.
362
						// Vectorized images are limited by $wgSVGMaxSize big,
363
						// so all thumbs less than or equal that are shown.
364
						if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
365
								|| ( $this->displayImg->isVectorized()
366
									&& max( $size[0], $size[1] ) <= $wgSVGMaxSize )
367
							)
368
							&& $size[0] != $width && $size[1] != $height
369
						) {
370
							$sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
371
							if ( $sizeLink ) {
372
								$otherSizes[] = $sizeLink;
373
							}
374
						}
375
					}
376
					$otherSizes = array_unique( $otherSizes );
377
378
					$sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
379
					$msgsmall = $this->getThumbPrevText( $params, $sizeLinkBigImagePreview );
380
					if ( count( $otherSizes ) ) {
381
						$msgsmall .= ' ' .
382
						Html::rawElement(
383
							'span',
384
							[ 'class' => 'mw-filepage-other-resolutions' ],
385
							$this->getContext()->msg( 'show-big-image-other' )
386
								->rawParams( $lang->pipeList( $otherSizes ) )
387
								->params( count( $otherSizes ) )
388
								->parse()
389
						);
390
					}
391
				} elseif ( $width == 0 && $height == 0 ) {
392
					# Some sort of audio file that doesn't have dimensions
393
					# Don't output a no hi res message for such a file
394
					$msgsmall = '';
395
				} else {
396
					# Image is small enough to show full size on image page
397
					$msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
398
				}
399
400
				$params['width'] = $width;
401
				$params['height'] = $height;
402
				$thumbnail = $this->displayImg->transform( $params );
403
				Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
404
405
				$anchorclose = Html::rawElement(
406
					'div',
407
					[ 'class' => 'mw-filepage-resolutioninfo' ],
408
					$msgsmall
409
				);
410
411
				$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
412
				if ( $isMulti ) {
413
					$out->addModules( 'mediawiki.page.image.pagination' );
414
					$out->addHTML( '<table class="multipageimage"><tr><td>' );
415
				}
416
417
				if ( $thumbnail ) {
418
					$options = [
419
						'alt' => $this->displayImg->getTitle()->getPrefixedText(),
420
						'file-link' => true,
421
					];
422
					$out->addHTML( '<div class="fullImageLink" id="file">' .
423
						$thumbnail->toHtml( $options ) .
424
						$anchorclose . "</div>\n" );
425
				}
426
427
				if ( $isMulti ) {
428
					$count = $this->displayImg->pageCount();
429
430
					if ( $page > 1 ) {
431
						$label = $out->parse( $this->getContext()->msg( 'imgmultipageprev' )->text(), false );
432
						// on the client side, this link is generated in ajaxifyPageNavigation()
433
						// in the mediawiki.page.image.pagination module
434
						$link = Linker::linkKnown(
435
							$this->getTitle(),
436
							$label,
437
							[],
438
							[ 'page' => $page - 1 ]
439
						);
440
						$thumb1 = Linker::makeThumbLinkObj(
441
							$this->getTitle(),
442
							$this->displayImg,
443
							$link,
444
							$label,
445
							'none',
446
							[ 'page' => $page - 1 ]
447
						);
448
					} else {
449
						$thumb1 = '';
450
					}
451
452
					if ( $page < $count ) {
453
						$label = $this->getContext()->msg( 'imgmultipagenext' )->text();
454
						$link = Linker::linkKnown(
455
							$this->getTitle(),
456
							$label,
457
							[],
458
							[ 'page' => $page + 1 ]
459
						);
460
						$thumb2 = Linker::makeThumbLinkObj(
461
							$this->getTitle(),
462
							$this->displayImg,
463
							$link,
464
							$label,
465
							'none',
466
							[ 'page' => $page + 1 ]
467
						);
468
					} else {
469
						$thumb2 = '';
470
					}
471
472
					global $wgScript;
473
474
					$formParams = [
475
						'name' => 'pageselector',
476
						'action' => $wgScript,
477
					];
478
					$options = [];
479
					for ( $i = 1; $i <= $count; $i++ ) {
480
						$options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
481
					}
482
					$select = Xml::tags( 'select',
483
						[ 'id' => 'pageselector', 'name' => 'page' ],
484
						implode( "\n", $options ) );
485
486
					$out->addHTML(
487
						'</td><td><div class="multipageimagenavbox">' .
488
						Xml::openElement( 'form', $formParams ) .
489
						Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
490
						$this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() .
491
						Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) .
492
						Xml::closeElement( 'form' ) .
493
						"<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
494
					);
495
				}
496
			} elseif ( $this->displayImg->isSafeFile() ) {
497
				# if direct link is allowed but it's not a renderable image, show an icon.
498
				$icon = $this->displayImg->iconThumb();
499
500
				$out->addHTML( '<div class="fullImageLink" id="file">' .
501
					$icon->toHtml( [ 'file-link' => true ] ) .
502
					"</div>\n" );
503
			}
504
505
			$longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text();
506
507
			$handler = $this->displayImg->getHandler();
508
509
			// If this is a filetype with potential issues, warn the user.
510
			if ( $handler ) {
511
				$warningConfig = $handler->getWarningConfig( $this->displayImg );
512
513
				if ( $warningConfig !== null ) {
514
					// The warning will be displayed via CSS and JavaScript.
515
					// We just need to tell the client side what message to use.
516
					$output = $this->getContext()->getOutput();
517
					$output->addJsConfigVars( 'wgFileWarning', $warningConfig );
518
					$output->addModules( $warningConfig['module'] );
519
					$output->addModules( 'mediawiki.filewarning' );
520
				}
521
			}
522
523
			$medialink = "[[Media:$filename|$linktext]]";
524
525
			if ( !$this->displayImg->isSafeFile() ) {
526
				$warning = $this->getContext()->msg( 'mediawarning' )->plain();
527
				// dirmark is needed here to separate the file name, which
528
				// most likely ends in Latin characters, from the description,
529
				// which may begin with the file type. In RTL environment
530
				// this will get messy.
531
				// The dirmark, however, must not be immediately adjacent
532
				// to the filename, because it can get copied with it.
533
				// See bug 25277.
534
				// @codingStandardsIgnoreStart Ignore long line
535
				$out->addWikiText( <<<EOT
536
<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
537
<div class="mediaWarning">$warning</div>
538
EOT
539
				);
540
				// @codingStandardsIgnoreEnd
541
			} else {
542
				$out->addWikiText( <<<EOT
543
<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
544
</div>
545
EOT
546
				);
547
			}
548
549
			$renderLangOptions = $this->displayImg->getAvailableLanguages();
550
			if ( count( $renderLangOptions ) >= 1 ) {
551
				$currentLanguage = $renderLang;
552
				$defaultLang = $this->displayImg->getDefaultRenderLanguage();
553
				if ( is_null( $currentLanguage ) ) {
554
					$currentLanguage = $defaultLang;
555
				}
556
				$out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
557
			}
558
559
			// Add cannot animate thumbnail warning
560
			if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
561
				// Include the extension so wiki admins can
562
				// customize it on a per file-type basis
563
				// (aka say things like use format X instead).
564
				// additionally have a specific message for
565
				// file-no-thumb-animation-gif
566
				$ext = $this->displayImg->getExtension();
567
				$noAnimMesg = wfMessageFallback(
568
					'file-no-thumb-animation-' . $ext,
569
					'file-no-thumb-animation'
570
				)->plain();
571
572
				$out->addWikiText( <<<EOT
573
<div class="mw-noanimatethumb">{$noAnimMesg}</div>
574
EOT
575
				);
576
			}
577
578
			if ( !$this->displayImg->isLocal() ) {
579
				$this->printSharedImageText();
580
			}
581
		} else {
582
			# Image does not exist
583 View Code Duplication
			if ( !$this->getId() ) {
584
				# No article exists either
585
				# Show deletion log to be consistent with normal articles
586
				LogEventsList::showLogExtract(
587
					$out,
588
					[ 'delete', 'move' ],
589
					$this->getTitle()->getPrefixedText(),
590
					'',
591
					[ 'lim' => 10,
592
						'conds' => [ "log_action != 'revision'" ],
593
						'showIfEmpty' => false,
594
						'msgKey' => [ 'moveddeleted-notice' ]
595
					]
596
				);
597
			}
598
599
			if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) {
600
				// Only show an upload link if the user can upload
601
				$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
602
				$nofile = [
603
					'filepage-nofile-link',
604
					$uploadTitle->getFullURL( [ 'wpDestFile' => $this->mPage->getFile()->getName() ] )
605
				];
606
			} else {
607
				$nofile = 'filepage-nofile';
608
			}
609
			// Note, if there is an image description page, but
610
			// no image, then this setRobotPolicy is overridden
611
			// by Article::View().
612
			$out->setRobotPolicy( 'noindex,nofollow' );
613
			$out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
614
			if ( !$this->getId() && $wgSend404Code ) {
615
				// If there is no image, no shared image, and no description page,
616
				// output a 404, to be consistent with Article::showMissingArticle.
617
				$request->response()->statusHeader( 404 );
618
			}
619
		}
620
		$out->setFileVersion( $this->displayImg );
621
	}
622
623
	/**
624
	 * Make the text under the image to say what size preview
625
	 *
626
	 * @param $params array parameters for thumbnail
627
	 * @param $sizeLinkBigImagePreview HTML for the current size
628
	 * @return string HTML output
629
	 */
630
	private function getThumbPrevText( $params, $sizeLinkBigImagePreview ) {
631
		if ( $sizeLinkBigImagePreview ) {
632
			// Show a different message of preview is different format from original.
633
			$previewTypeDiffers = false;
634
			$origExt = $thumbExt = $this->displayImg->getExtension();
635
			if ( $this->displayImg->getHandler() ) {
636
				$origMime = $this->displayImg->getMimeType();
637
				$typeParams = $params;
638
				$this->displayImg->getHandler()->normaliseParams( $this->displayImg, $typeParams );
639
				list( $thumbExt, $thumbMime ) = $this->displayImg->getHandler()->getThumbType(
640
					$origExt, $origMime, $typeParams );
641
				if ( $thumbMime !== $origMime ) {
642
					$previewTypeDiffers = true;
643
				}
644
			}
645
			if ( $previewTypeDiffers ) {
646
				return $this->getContext()->msg( 'show-big-image-preview-differ' )->
647
					rawParams( $sizeLinkBigImagePreview )->
648
					params( strtoupper( $origExt ) )->
649
					params( strtoupper( $thumbExt ) )->
650
					parse();
651
			} else {
652
				return $this->getContext()->msg( 'show-big-image-preview' )->
653
					rawParams( $sizeLinkBigImagePreview )->
654
				parse();
655
			}
656
		} else {
657
			return '';
658
		}
659
	}
660
661
	/**
662
	 * Creates an thumbnail of specified size and returns an HTML link to it
663
	 * @param array $params Scaler parameters
664
	 * @param int $width
665
	 * @param int $height
666
	 * @return string
667
	 */
668
	private function makeSizeLink( $params, $width, $height ) {
669
		$params['width'] = $width;
670
		$params['height'] = $height;
671
		$thumbnail = $this->displayImg->transform( $params );
672
		if ( $thumbnail && !$thumbnail->isError() ) {
673
			return Html::rawElement( 'a', [
674
				'href' => $thumbnail->getUrl(),
675
				'class' => 'mw-thumbnail-link'
676
				], $this->getContext()->msg( 'show-big-image-size' )->numParams(
677
					$thumbnail->getWidth(), $thumbnail->getHeight()
678
				)->parse() );
679
		} else {
680
			return '';
681
		}
682
	}
683
684
	/**
685
	 * Show a notice that the file is from a shared repository
686
	 */
687
	protected function printSharedImageText() {
688
		$out = $this->getContext()->getOutput();
689
		$this->loadFile();
690
691
		$descUrl = $this->mPage->getFile()->getDescriptionUrl();
692
		$descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
693
694
		/* Add canonical to head if there is no local page for this shared file */
695
		if ( $descUrl && $this->mPage->getId() == 0 ) {
696
			$out->setCanonicalUrl( $descUrl );
697
		}
698
699
		$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
700
		$repo = $this->mPage->getFile()->getRepo()->getDisplayName();
701
702
		if ( $descUrl &&
703
			$descText &&
704
			$this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-'
705
		) {
706
			$out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-here', $repo, $descUrl ] );
707
		} elseif ( $descUrl &&
708
			$this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-'
709
		) {
710
			$out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-there', $repo, $descUrl ] );
711
		} else {
712
			$out->wrapWikiMsg( $wrap, [ 'sharedupload', $repo ], ''/*BACKCOMPAT*/ );
713
		}
714
715
		if ( $descText ) {
716
			$this->mExtraDescription = $descText;
717
		}
718
	}
719
720
	public function getUploadUrl() {
721
		$this->loadFile();
722
		$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
723
		return $uploadTitle->getFullURL( [
724
			'wpDestFile' => $this->mPage->getFile()->getName(),
725
			'wpForReUpload' => 1
726
		] );
727
	}
728
729
	/**
730
	 * Print out the various links at the bottom of the image page, e.g. reupload,
731
	 * external editing (and instructions link) etc.
732
	 */
733
	protected function uploadLinksBox() {
734
		global $wgEnableUploads;
735
736
		if ( !$wgEnableUploads ) {
737
			return;
738
		}
739
740
		$this->loadFile();
741
		if ( !$this->mPage->getFile()->isLocal() ) {
742
			return;
743
		}
744
745
		$out = $this->getContext()->getOutput();
746
		$out->addHTML( "<ul>\n" );
747
748
		# "Upload a new version of this file" link
749
		$canUpload = $this->getTitle()->quickUserCan( 'upload', $this->getContext()->getUser() );
750
		if ( $canUpload && UploadBase::userCanReUpload(
751
				$this->getContext()->getUser(),
752
				$this->mPage->getFile() )
0 ignored issues
show
It seems like $this->mPage->getFile() targeting WikiFilePage::getFile() can also be of type boolean; however, UploadBase::userCanReUpload() does only seem to accept object<File>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
753
		) {
754
			$ulink = Linker::makeExternalLink(
755
				$this->getUploadUrl(),
756
				$this->getContext()->msg( 'uploadnewversion-linktext' )->text()
757
			);
758
			$out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
759
				. "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
760
		} else {
761
			$out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
762
				. $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
763
		}
764
765
		$out->addHTML( "</ul>\n" );
766
	}
767
768
	/**
769
	 * For overloading
770
	 */
771
	protected function closeShowImage() {
772
	}
773
774
	/**
775
	 * If the page we've just displayed is in the "Image" namespace,
776
	 * we follow it with an upload history of the image and its usage.
777
	 */
778
	protected function imageHistory() {
779
		$this->loadFile();
780
		$out = $this->getContext()->getOutput();
781
		$pager = new ImageHistoryPseudoPager( $this );
782
		$out->addHTML( $pager->getBody() );
783
		$out->preventClickjacking( $pager->getPreventClickjacking() );
784
785
		$this->mPage->getFile()->resetHistory(); // free db resources
786
787
		# Exist check because we don't want to show this on pages where an image
788
		# doesn't exist along with the noimage message, that would suck. -ævar
789
		if ( $this->mPage->getFile()->exists() ) {
790
			$this->uploadLinksBox();
791
		}
792
	}
793
794
	/**
795
	 * @param string $target
796
	 * @param int $limit
797
	 * @return ResultWrapper
798
	 */
799
	protected function queryImageLinks( $target, $limit ) {
800
		$dbr = wfGetDB( DB_REPLICA );
801
802
		return $dbr->select(
803
			[ 'imagelinks', 'page' ],
804
			[ 'page_namespace', 'page_title', 'il_to' ],
805
			[ 'il_to' => $target, 'il_from = page_id' ],
806
			__METHOD__,
807
			[ 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ]
808
		);
809
	}
810
811
	protected function imageLinks() {
812
		$limit = 100;
813
814
		$out = $this->getContext()->getOutput();
815
816
		$rows = [];
817
		$redirects = [];
818
		foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
819
			$redirects[$redir->getDBkey()] = [];
820
			$rows[] = (object)[
821
				'page_namespace' => NS_FILE,
822
				'page_title' => $redir->getDBkey(),
823
			];
824
		}
825
826
		$res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
827
		foreach ( $res as $row ) {
828
			$rows[] = $row;
829
		}
830
		$count = count( $rows );
831
832
		$hasMore = $count > $limit;
833
		if ( !$hasMore && count( $redirects ) ) {
834
			$res = $this->queryImageLinks( array_keys( $redirects ),
835
				$limit - count( $rows ) + 1 );
836
			foreach ( $res as $row ) {
837
				$redirects[$row->il_to][] = $row;
838
				$count++;
839
			}
840
			$hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
841
		}
842
843
		if ( $count == 0 ) {
844
			$out->wrapWikiMsg(
845
				Html::rawElement( 'div',
846
					[ 'id' => 'mw-imagepage-nolinkstoimage' ], "\n$1\n" ),
847
				'nolinkstoimage'
848
			);
849
			return;
850
		}
851
852
		$out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
853
		if ( !$hasMore ) {
854
			$out->addWikiMsg( 'linkstoimage', $count );
855
		} else {
856
			// More links than the limit. Add a link to [[Special:Whatlinkshere]]
857
			$out->addWikiMsg( 'linkstoimage-more',
858
				$this->getContext()->getLanguage()->formatNum( $limit ),
859
				$this->getTitle()->getPrefixedDBkey()
860
			);
861
		}
862
863
		$out->addHTML(
864
			Html::openElement( 'ul',
865
				[ 'class' => 'mw-imagepage-linkstoimage' ] ) . "\n"
866
		);
867
		$count = 0;
868
869
		// Sort the list by namespace:title
870
		usort( $rows, [ $this, 'compare' ] );
871
872
		// Create links for every element
873
		$currentCount = 0;
874
		foreach ( $rows as $element ) {
875
			$currentCount++;
876
			if ( $currentCount > $limit ) {
877
				break;
878
			}
879
880
			$query = [];
881
			# Add a redirect=no to make redirect pages reachable
882
			if ( isset( $redirects[$element->page_title] ) ) {
883
				$query['redirect'] = 'no';
884
			}
885
			$link = Linker::linkKnown(
886
				Title::makeTitle( $element->page_namespace, $element->page_title ),
887
				null, [], $query
888
			);
889
			if ( !isset( $redirects[$element->page_title] ) ) {
890
				# No redirects
891
				$liContents = $link;
892
			} elseif ( count( $redirects[$element->page_title] ) === 0 ) {
893
				# Redirect without usages
894
				$liContents = $this->getContext()->msg( 'linkstoimage-redirect' )
895
					->rawParams( $link, '' )
896
					->parse();
897
			} else {
898
				# Redirect with usages
899
				$li = '';
900
				foreach ( $redirects[$element->page_title] as $row ) {
901
					$currentCount++;
902
					if ( $currentCount > $limit ) {
903
						break;
904
					}
905
906
					$link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
907
					$li .= Html::rawElement(
908
						'li',
909
						[ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
910
						$link2
911
						) . "\n";
912
				}
913
914
				$ul = Html::rawElement(
915
					'ul',
916
					[ 'class' => 'mw-imagepage-redirectstofile' ],
917
					$li
918
					) . "\n";
919
				$liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
920
					$link, $ul )->parse();
921
			}
922
			$out->addHTML( Html::rawElement(
923
					'li',
924
					[ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
925
					$liContents
926
				) . "\n"
927
			);
928
929
		};
930
		$out->addHTML( Html::closeElement( 'ul' ) . "\n" );
931
		$res->free();
932
933
		// Add a links to [[Special:Whatlinkshere]]
934
		if ( $count > $limit ) {
935
			$out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
936
		}
937
		$out->addHTML( Html::closeElement( 'div' ) . "\n" );
938
	}
939
940
	protected function imageDupes() {
941
		$this->loadFile();
942
		$out = $this->getContext()->getOutput();
943
944
		$dupes = $this->mPage->getDuplicates();
945
		if ( count( $dupes ) == 0 ) {
946
			return;
947
		}
948
949
		$out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
950
		$out->addWikiMsg( 'duplicatesoffile',
951
			$this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
952
		);
953
		$out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
954
955
		/**
956
		 * @var $file File
957
		 */
958
		foreach ( $dupes as $file ) {
0 ignored issues
show
The expression $dupes of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
959
			$fromSrc = '';
960
			if ( $file->isLocal() ) {
961
				$link = Linker::linkKnown( $file->getTitle() );
962
			} else {
963
				$link = Linker::makeExternalLink( $file->getDescriptionUrl(),
964
					$file->getTitle()->getPrefixedText() );
965
				$fromSrc = $this->getContext()->msg(
966
					'shared-repo-from',
967
					$file->getRepo()->getDisplayName()
968
				)->text();
969
			}
970
			$out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
971
		}
972
		$out->addHTML( "</ul></div>\n" );
973
	}
974
975
	/**
976
	 * Delete the file, or an earlier version of it
977
	 */
978
	public function delete() {
979
		$file = $this->mPage->getFile();
980
		if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
981
			// Standard article deletion
982
			parent::delete();
983
			return;
984
		}
985
986
		$deleter = new FileDeleteForm( $file );
0 ignored issues
show
It seems like $file defined by $this->mPage->getFile() on line 979 can also be of type boolean; however, FileDeleteForm::__construct() does only seem to accept object<File>, 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...
987
		$deleter->execute();
988
	}
989
990
	/**
991
	 * Display an error with a wikitext description
992
	 *
993
	 * @param string $description
994
	 */
995
	function showError( $description ) {
996
		$out = $this->getContext()->getOutput();
997
		$out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
998
		$out->setRobotPolicy( 'noindex,nofollow' );
999
		$out->setArticleRelated( false );
1000
		$out->enableClientCache( false );
1001
		$out->addWikiText( $description );
1002
	}
1003
1004
	/**
1005
	 * Callback for usort() to do link sorts by (namespace, title)
1006
	 * Function copied from Title::compare()
1007
	 *
1008
	 * @param object $a Object page to compare with
1009
	 * @param object $b Object page to compare with
1010
	 * @return int Result of string comparison, or namespace comparison
1011
	 */
1012
	protected function compare( $a, $b ) {
1013
		if ( $a->page_namespace == $b->page_namespace ) {
1014
			return strcmp( $a->page_title, $b->page_title );
1015
		} else {
1016
			return $a->page_namespace - $b->page_namespace;
1017
		}
1018
	}
1019
1020
	/**
1021
	 * Returns the corresponding $wgImageLimits entry for the selected user option
1022
	 *
1023
	 * @param User $user
1024
	 * @param string $optionName Name of a option to check, typically imagesize or thumbsize
1025
	 * @return array
1026
	 * @since 1.21
1027
	 */
1028
	public function getImageLimitsFromOption( $user, $optionName ) {
1029
		global $wgImageLimits;
1030
1031
		$option = $user->getIntOption( $optionName );
1032
		if ( !isset( $wgImageLimits[$option] ) ) {
1033
			$option = User::getDefaultOption( $optionName );
1034
		}
1035
1036
		// The user offset might still be incorrect, specially if
1037
		// $wgImageLimits got changed (see bug #8858).
1038
		if ( !isset( $wgImageLimits[$option] ) ) {
1039
			// Default to the first offset in $wgImageLimits
1040
			$option = 0;
1041
		}
1042
1043
		return isset( $wgImageLimits[$option] )
1044
			? $wgImageLimits[$option]
1045
			: [ 800, 600 ]; // if nothing is set, fallback to a hardcoded default
1046
	}
1047
1048
	/**
1049
	 * Output a drop-down box for language options for the file
1050
	 *
1051
	 * @param array $langChoices Array of string language codes
1052
	 * @param string $curLang Language code file is being viewed in.
1053
	 * @param string $defaultLang Language code that image is rendered in by default
1054
	 * @return string HTML to insert underneath image.
1055
	 */
1056
	protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
1057
		global $wgScript;
1058
		sort( $langChoices );
1059
		$curLang = wfBCP47( $curLang );
1060
		$defaultLang = wfBCP47( $defaultLang );
1061
		$opts = '';
1062
		$haveCurrentLang = false;
1063
		$haveDefaultLang = false;
1064
1065
		// We make a list of all the language choices in the file.
1066
		// Additionally if the default language to render this file
1067
		// is not included as being in this file (for example, in svgs
1068
		// usually the fallback content is the english content) also
1069
		// include a choice for that. Last of all, if we're viewing
1070
		// the file in a language not on the list, add it as a choice.
1071
		foreach ( $langChoices as $lang ) {
1072
			$code = wfBCP47( $lang );
1073
			$name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1074 View Code Duplication
			if ( $name !== '' ) {
1075
				$display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
1076
			} else {
1077
				$display = $code;
1078
			}
1079
			$opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
1080
			if ( $curLang === $code ) {
1081
				$haveCurrentLang = true;
1082
			}
1083
			if ( $defaultLang === $code ) {
1084
				$haveDefaultLang = true;
1085
			}
1086
		}
1087
		if ( !$haveDefaultLang ) {
1088
			// Its hard to know if the content is really in the default language, or
1089
			// if its just unmarked content that could be in any language.
1090
			$opts = Xml::option(
1091
					$this->getContext()->msg( 'img-lang-default' )->text(),
1092
				$defaultLang,
1093
				$defaultLang === $curLang
1094
			) . $opts;
1095
		}
1096
		if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
1097
			$name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
1098 View Code Duplication
			if ( $name !== '' ) {
1099
				$display = $this->getContext()->msg( 'img-lang-opt', $curLang, $name )->text();
1100
			} else {
1101
				$display = $curLang;
1102
			}
1103
			$opts = Xml::option( $display, $curLang, true ) . $opts;
1104
		}
1105
1106
		$select = Html::rawElement(
1107
			'select',
1108
			[ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
1109
			$opts
1110
		);
1111
		$submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
1112
1113
		$formContents = $this->getContext()->msg( 'img-lang-info' )
1114
			->rawParams( $select, $submit )
1115
			->parse();
1116
		$formContents .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1117
1118
		$langSelectLine = Html::rawElement( 'div', [ 'id' => 'mw-imglangselector-line' ],
1119
			Html::rawElement( 'form', [ 'action' => $wgScript ], $formContents )
1120
		);
1121
		return $langSelectLine;
1122
	}
1123
1124
	/**
1125
	 * Get the width and height to display image at.
1126
	 *
1127
	 * @note This method assumes that it is only called if one
1128
	 *  of the dimensions are bigger than the max, or if the
1129
	 *  image is vectorized.
1130
	 *
1131
	 * @param int $maxWidth Max width to display at
1132
	 * @param int $maxHeight Max height to display at
1133
	 * @param int $width Actual width of the image
1134
	 * @param int $height Actual height of the image
1135
	 * @throws MWException
1136
	 * @return array Array (width, height)
1137
	 */
1138
	protected function getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ) {
1139
		if ( !$maxWidth || !$maxHeight ) {
1140
			// should never happen
1141
			throw new MWException( 'Using a choice from $wgImageLimits that is 0x0' );
1142
		}
1143
1144
		if ( !$width || !$height ) {
1145
			return [ 0, 0 ];
1146
		}
1147
1148
		# Calculate the thumbnail size.
1149
		if ( $width <= $maxWidth && $height <= $maxHeight ) {
0 ignored issues
show
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1150
			// Vectorized image, do nothing.
1151
		} elseif ( $width / $height >= $maxWidth / $maxHeight ) {
1152
			# The limiting factor is the width, not the height.
1153
			$height = round( $height * $maxWidth / $width );
1154
			$width = $maxWidth;
1155
			# Note that $height <= $maxHeight now.
1156
		} else {
1157
			$newwidth = floor( $width * $maxHeight / $height );
1158
			$height = round( $height * $newwidth / $width );
1159
			$width = $newwidth;
1160
			# Note that $height <= $maxHeight now, but might not be identical
1161
			# because of rounding.
1162
		}
1163
		return [ $width, $height ];
1164
	}
1165
1166
	/**
1167
	 * Get alternative thumbnail sizes.
1168
	 *
1169
	 * @note This will only list several alternatives if thumbnails are rendered on 404
1170
	 * @param int $origWidth Actual width of image
1171
	 * @param int $origHeight Actual height of image
1172
	 * @return array An array of [width, height] pairs.
1173
	 */
1174
	protected function getThumbSizes( $origWidth, $origHeight ) {
1175
		global $wgImageLimits;
1176
		if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1177
			$thumbSizes = $wgImageLimits;
1178
			// Also include the full sized resolution in the list, so
1179
			// that users know they can get it. This will link to the
1180
			// original file asset if mustRender() === false. In the case
1181
			// that we mustRender, some users have indicated that they would
1182
			// find it useful to have the full size image in the rendered
1183
			// image format.
1184
			$thumbSizes[] = [ $origWidth, $origHeight ];
1185
		} else {
1186
			# Creating thumb links triggers thumbnail generation.
1187
			# Just generate the thumb for the current users prefs.
1188
			$thumbSizes = [
1189
				$this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' )
1190
			];
1191
			if ( !$this->displayImg->mustRender() ) {
1192
				// We can safely include a link to the "full-size" preview,
1193
				// without actually rendering.
1194
				$thumbSizes[] = [ $origWidth, $origHeight ];
1195
			}
1196
		}
1197
		return $thumbSizes;
1198
	}
1199
1200
	/**
1201
	 * @see WikiFilePage::getFile
1202
	 * @return bool|File
1203
	 */
1204
	public function getFile() {
1205
		return $this->mPage->getFile();
1206
	}
1207
1208
	/**
1209
	 * @see WikiFilePage::isLocal
1210
	 * @return bool
1211
	 */
1212
	public function isLocal() {
1213
		return $this->mPage->isLocal();
1214
	}
1215
1216
	/**
1217
	 * @see WikiFilePage::getDuplicates
1218
	 * @return array|null
1219
	 */
1220
	public function getDuplicates() {
1221
		return $this->mPage->getDuplicates();
1222
	}
1223
1224
	/**
1225
	 * @see WikiFilePage::getForeignCategories
1226
	 * @return TitleArray|Title[]
1227
	 */
1228
	public function getForeignCategories() {
1229
		$this->mPage->getForeignCategories();
1230
	}
1231
1232
}
1233