Completed
Branch master (8d5465)
by
unknown
31:25
created

ImagePage::newFromID()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
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
Bug introduced by
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
Bug introduced by
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...
Bug introduced by
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
	/**
222
	 * @return File
223
	 */
224
	public function getDisplayedFile() {
225
		$this->loadFile();
226
		return $this->displayImg;
227
	}
228
229
	/**
230
	 * Create the TOC
231
	 *
232
	 * @param bool $metadata Whether or not to show the metadata link
233
	 * @return string
234
	 */
235
	protected function showTOC( $metadata ) {
236
		$r = [
237
			'<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>',
238
			'<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>',
239
			'<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>',
240
		];
241
242
		Hooks::run( 'ImagePageShowTOC', [ $this, &$r ] );
243
244
		if ( $metadata ) {
245
			$r[] = '<li><a href="#metadata">' .
246
				$this->getContext()->msg( 'metadata' )->escaped() .
247
				'</a></li>';
248
		}
249
250
		return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
251
	}
252
253
	/**
254
	 * Make a table with metadata to be shown in the output page.
255
	 *
256
	 * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
257
	 *
258
	 * @param array $metadata The array containing the Exif data
259
	 * @return string The metadata table. This is treated as Wikitext (!)
260
	 */
261
	protected function makeMetadataTable( $metadata ) {
262
		$r = "<div class=\"mw-imagepage-section-metadata\">";
263
		$r .= $this->getContext()->msg( 'metadata-help' )->plain();
264
		$r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
265
		foreach ( $metadata as $type => $stuff ) {
266
			foreach ( $stuff as $v ) {
267
				# @todo FIXME: Why is this using escapeId for a class?!
268
				$class = Sanitizer::escapeId( $v['id'] );
269
				if ( $type == 'collapsed' ) {
270
					// Handled by mediawiki.action.view.metadata module.
271
					$class .= ' collapsable';
272
				}
273
				$r .= "<tr class=\"$class\">\n";
274
				$r .= "<th>{$v['name']}</th>\n";
275
				$r .= "<td>{$v['value']}</td>\n</tr>";
276
			}
277
		}
278
		$r .= "</table>\n</div>\n";
279
		return $r;
280
	}
281
282
	/**
283
	 * Overloading Article's getContentObject method.
284
	 *
285
	 * Omit noarticletext if sharedupload; text will be fetched from the
286
	 * shared upload server if possible.
287
	 * @return string
288
	 */
289
	public function getContentObject() {
290
		$this->loadFile();
291
		if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getId() ) {
292
			return null;
293
		}
294
		return parent::getContentObject();
295
	}
296
297
	protected function openShowImage() {
298
		global $wgEnableUploads, $wgSend404Code, $wgSVGMaxSize;
299
300
		$this->loadFile();
301
		$out = $this->getContext()->getOutput();
302
		$user = $this->getContext()->getUser();
303
		$lang = $this->getContext()->getLanguage();
304
		$dirmark = $lang->getDirMarkEntity();
305
		$request = $this->getContext()->getRequest();
306
307
		$max = $this->getImageLimitsFromOption( $user, 'imagesize' );
308
		$maxWidth = $max[0];
309
		$maxHeight = $max[1];
310
311
		if ( $this->displayImg->exists() ) {
312
			# image
313
			$page = $request->getIntOrNull( 'page' );
314
			if ( is_null( $page ) ) {
315
				$params = [];
316
				$page = 1;
317
			} else {
318
				$params = [ 'page' => $page ];
319
			}
320
321
			$renderLang = $request->getVal( 'lang' );
322
			if ( !is_null( $renderLang ) ) {
323
				$handler = $this->displayImg->getHandler();
324
				if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
325
					$params['lang'] = $renderLang;
326
				} else {
327
					$renderLang = null;
328
				}
329
			}
330
331
			$width_orig = $this->displayImg->getWidth( $page );
332
			$width = $width_orig;
333
			$height_orig = $this->displayImg->getHeight( $page );
334
			$height = $height_orig;
335
336
			$filename = wfEscapeWikiText( $this->displayImg->getName() );
337
			$linktext = $filename;
338
339
			Hooks::run( 'ImageOpenShowImageInlineBefore', [ &$this, &$out ] );
340
341
			if ( $this->displayImg->allowInlineDisplay() ) {
342
				# image
343
				# "Download high res version" link below the image
344
				# $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig,
345
				#   Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
346
				# We'll show a thumbnail of this image
347
				if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) {
348
					list( $width, $height ) = $this->getDisplayWidthHeight(
349
						$maxWidth, $maxHeight, $width, $height
0 ignored issues
show
Bug introduced by
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...
Bug introduced by
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...
350
					);
351
					$linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
352
353
					$thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
0 ignored issues
show
Bug introduced by
It seems like $width_orig defined by $this->displayImg->getWidth($page) on line 331 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...
Bug introduced by
It seems like $height_orig defined by $this->displayImg->getHeight($page) on line 333 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...
354
					# Generate thumbnails or thumbnail links as needed...
355
					$otherSizes = [];
356
					foreach ( $thumbSizes as $size ) {
357
						// We include a thumbnail size in the list, if it is
358
						// less than or equal to the original size of the image
359
						// asset ($width_orig/$height_orig). We also exclude
360
						// the current thumbnail's size ($width/$height)
361
						// since that is added to the message separately, so
362
						// it can be denoted as the current size being shown.
363
						// Vectorized images are limited by $wgSVGMaxSize big,
364
						// so all thumbs less than or equal that are shown.
365
						if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
366
								|| ( $this->displayImg->isVectorized()
367
									&& max( $size[0], $size[1] ) <= $wgSVGMaxSize )
368
							)
369
							&& $size[0] != $width && $size[1] != $height
370
						) {
371
							$sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
372
							if ( $sizeLink ) {
373
								$otherSizes[] = $sizeLink;
374
							}
375
						}
376
					}
377
					$otherSizes = array_unique( $otherSizes );
378
379
					$sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
380
					$msgsmall = $this->getThumbPrevText( $params, $sizeLinkBigImagePreview );
381
					if ( count( $otherSizes ) ) {
382
						$msgsmall .= ' ' .
383
						Html::rawElement(
384
							'span',
385
							[ 'class' => 'mw-filepage-other-resolutions' ],
386
							$this->getContext()->msg( 'show-big-image-other' )
387
								->rawParams( $lang->pipeList( $otherSizes ) )
388
								->params( count( $otherSizes ) )
389
								->parse()
390
						);
391
					}
392
				} elseif ( $width == 0 && $height == 0 ) {
393
					# Some sort of audio file that doesn't have dimensions
394
					# Don't output a no hi res message for such a file
395
					$msgsmall = '';
396
				} else {
397
					# Image is small enough to show full size on image page
398
					$msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
399
				}
400
401
				$params['width'] = $width;
402
				$params['height'] = $height;
403
				$thumbnail = $this->displayImg->transform( $params );
404
				Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
405
406
				$anchorclose = Html::rawElement(
407
					'div',
408
					[ 'class' => 'mw-filepage-resolutioninfo' ],
409
					$msgsmall
410
				);
411
412
				$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
413
				if ( $isMulti ) {
414
					$out->addModules( 'mediawiki.page.image.pagination' );
415
					$out->addHTML( '<table class="multipageimage"><tr><td>' );
416
				}
417
418
				if ( $thumbnail ) {
419
					$options = [
420
						'alt' => $this->displayImg->getTitle()->getPrefixedText(),
421
						'file-link' => true,
422
					];
423
					$out->addHTML( '<div class="fullImageLink" id="file">' .
424
						$thumbnail->toHtml( $options ) .
425
						$anchorclose . "</div>\n" );
426
				}
427
428
				if ( $isMulti ) {
429
					$count = $this->displayImg->pageCount();
430
431
					if ( $page > 1 ) {
432
						$label = $out->parse( $this->getContext()->msg( 'imgmultipageprev' )->text(), false );
433
						// on the client side, this link is generated in ajaxifyPageNavigation()
434
						// in the mediawiki.page.image.pagination module
435
						$link = Linker::linkKnown(
436
							$this->getTitle(),
437
							$label,
438
							[],
439
							[ 'page' => $page - 1 ]
440
						);
441
						$thumb1 = Linker::makeThumbLinkObj(
442
							$this->getTitle(),
443
							$this->displayImg,
444
							$link,
445
							$label,
446
							'none',
447
							[ 'page' => $page - 1 ]
448
						);
449
					} else {
450
						$thumb1 = '';
451
					}
452
453
					if ( $page < $count ) {
454
						$label = $this->getContext()->msg( 'imgmultipagenext' )->text();
455
						$link = Linker::linkKnown(
456
							$this->getTitle(),
457
							$label,
458
							[],
459
							[ 'page' => $page + 1 ]
460
						);
461
						$thumb2 = Linker::makeThumbLinkObj(
462
							$this->getTitle(),
463
							$this->displayImg,
464
							$link,
465
							$label,
466
							'none',
467
							[ 'page' => $page + 1 ]
468
						);
469
					} else {
470
						$thumb2 = '';
471
					}
472
473
					global $wgScript;
474
475
					$formParams = [
476
						'name' => 'pageselector',
477
						'action' => $wgScript,
478
					];
479
					$options = [];
480
					for ( $i = 1; $i <= $count; $i++ ) {
481
						$options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
482
					}
483
					$select = Xml::tags( 'select',
484
						[ 'id' => 'pageselector', 'name' => 'page' ],
485
						implode( "\n", $options ) );
486
487
					$out->addHTML(
488
						'</td><td><div class="multipageimagenavbox">' .
489
						Xml::openElement( 'form', $formParams ) .
490
						Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
491
						$this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() .
492
						Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) .
493
						Xml::closeElement( 'form' ) .
494
						"<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
495
					);
496
				}
497
			} elseif ( $this->displayImg->isSafeFile() ) {
498
				# if direct link is allowed but it's not a renderable image, show an icon.
499
				$icon = $this->displayImg->iconThumb();
500
501
				$out->addHTML( '<div class="fullImageLink" id="file">' .
502
					$icon->toHtml( [ 'file-link' => true ] ) .
503
					"</div>\n" );
504
			}
505
506
			$longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text();
507
508
			$handler = $this->displayImg->getHandler();
509
510
			// If this is a filetype with potential issues, warn the user.
511
			if ( $handler ) {
512
				$warningConfig = $handler->getWarningConfig( $this->displayImg );
513
514
				if ( $warningConfig !== null ) {
515
					// The warning will be displayed via CSS and JavaScript.
516
					// We just need to tell the client side what message to use.
517
					$output = $this->getContext()->getOutput();
518
					$output->addJsConfigVars( 'wgFileWarning', $warningConfig );
519
					$output->addModules( $warningConfig['module'] );
520
					$output->addModules( 'mediawiki.filewarning' );
521
				}
522
			}
523
524
			$medialink = "[[Media:$filename|$linktext]]";
525
526
			if ( !$this->displayImg->isSafeFile() ) {
527
				$warning = $this->getContext()->msg( 'mediawarning' )->plain();
528
				// dirmark is needed here to separate the file name, which
529
				// most likely ends in Latin characters, from the description,
530
				// which may begin with the file type. In RTL environment
531
				// this will get messy.
532
				// The dirmark, however, must not be immediately adjacent
533
				// to the filename, because it can get copied with it.
534
				// See bug 25277.
535
				// @codingStandardsIgnoreStart Ignore long line
536
				$out->addWikiText( <<<EOT
537
<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
538
<div class="mediaWarning">$warning</div>
539
EOT
540
				);
541
				// @codingStandardsIgnoreEnd
542
			} else {
543
				$out->addWikiText( <<<EOT
544
<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
545
</div>
546
EOT
547
				);
548
			}
549
550
			$renderLangOptions = $this->displayImg->getAvailableLanguages();
551
			if ( count( $renderLangOptions ) >= 1 ) {
552
				$currentLanguage = $renderLang;
553
				$defaultLang = $this->displayImg->getDefaultRenderLanguage();
554
				if ( is_null( $currentLanguage ) ) {
555
					$currentLanguage = $defaultLang;
556
				}
557
				$out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
558
			}
559
560
			// Add cannot animate thumbnail warning
561
			if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
562
				// Include the extension so wiki admins can
563
				// customize it on a per file-type basis
564
				// (aka say things like use format X instead).
565
				// additionally have a specific message for
566
				// file-no-thumb-animation-gif
567
				$ext = $this->displayImg->getExtension();
568
				$noAnimMesg = wfMessageFallback(
569
					'file-no-thumb-animation-' . $ext,
570
					'file-no-thumb-animation'
571
				)->plain();
572
573
				$out->addWikiText( <<<EOT
574
<div class="mw-noanimatethumb">{$noAnimMesg}</div>
575
EOT
576
				);
577
			}
578
579
			if ( !$this->displayImg->isLocal() ) {
580
				$this->printSharedImageText();
581
			}
582
		} else {
583
			# Image does not exist
584 View Code Duplication
			if ( !$this->getId() ) {
585
				# No article exists either
586
				# Show deletion log to be consistent with normal articles
587
				LogEventsList::showLogExtract(
588
					$out,
589
					[ 'delete', 'move' ],
590
					$this->getTitle()->getPrefixedText(),
591
					'',
592
					[ 'lim' => 10,
593
						'conds' => [ "log_action != 'revision'" ],
594
						'showIfEmpty' => false,
595
						'msgKey' => [ 'moveddeleted-notice' ]
596
					]
597
				);
598
			}
599
600
			if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) {
601
				// Only show an upload link if the user can upload
602
				$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
603
				$nofile = [
604
					'filepage-nofile-link',
605
					$uploadTitle->getFullURL( [ 'wpDestFile' => $this->mPage->getFile()->getName() ] )
606
				];
607
			} else {
608
				$nofile = 'filepage-nofile';
609
			}
610
			// Note, if there is an image description page, but
611
			// no image, then this setRobotPolicy is overridden
612
			// by Article::View().
613
			$out->setRobotPolicy( 'noindex,nofollow' );
614
			$out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
615
			if ( !$this->getId() && $wgSend404Code ) {
616
				// If there is no image, no shared image, and no description page,
617
				// output a 404, to be consistent with Article::showMissingArticle.
618
				$request->response()->statusHeader( 404 );
619
			}
620
		}
621
		$out->setFileVersion( $this->displayImg );
622
	}
623
624
	/**
625
	 * Make the text under the image to say what size preview
626
	 *
627
	 * @param $params array parameters for thumbnail
628
	 * @param $sizeLinkBigImagePreview HTML for the current size
629
	 * @return string HTML output
630
	 */
631
	private function getThumbPrevText( $params, $sizeLinkBigImagePreview ) {
632
		if ( $sizeLinkBigImagePreview ) {
633
			// Show a different message of preview is different format from original.
634
			$previewTypeDiffers = false;
635
			$origExt = $thumbExt = $this->displayImg->getExtension();
636
			if ( $this->displayImg->getHandler() ) {
637
				$origMime = $this->displayImg->getMimeType();
638
				$typeParams = $params;
639
				$this->displayImg->getHandler()->normaliseParams( $this->displayImg, $typeParams );
640
				list( $thumbExt, $thumbMime ) = $this->displayImg->getHandler()->getThumbType(
641
					$origExt, $origMime, $typeParams );
642
				if ( $thumbMime !== $origMime ) {
643
					$previewTypeDiffers = true;
644
				}
645
			}
646
			if ( $previewTypeDiffers ) {
647
				return $this->getContext()->msg( 'show-big-image-preview-differ' )->
648
					rawParams( $sizeLinkBigImagePreview )->
649
					params( strtoupper( $origExt ) )->
650
					params( strtoupper( $thumbExt ) )->
651
					parse();
652
			} else {
653
				return $this->getContext()->msg( 'show-big-image-preview' )->
654
					rawParams( $sizeLinkBigImagePreview )->
655
				parse();
656
			}
657
		} else {
658
			return '';
659
		}
660
	}
661
662
	/**
663
	 * Creates an thumbnail of specified size and returns an HTML link to it
664
	 * @param array $params Scaler parameters
665
	 * @param int $width
666
	 * @param int $height
667
	 * @return string
668
	 */
669
	private function makeSizeLink( $params, $width, $height ) {
670
		$params['width'] = $width;
671
		$params['height'] = $height;
672
		$thumbnail = $this->displayImg->transform( $params );
673
		if ( $thumbnail && !$thumbnail->isError() ) {
674
			return Html::rawElement( 'a', [
675
				'href' => $thumbnail->getUrl(),
676
				'class' => 'mw-thumbnail-link'
677
				], $this->getContext()->msg( 'show-big-image-size' )->numParams(
678
					$thumbnail->getWidth(), $thumbnail->getHeight()
679
				)->parse() );
680
		} else {
681
			return '';
682
		}
683
	}
684
685
	/**
686
	 * Show a notice that the file is from a shared repository
687
	 */
688
	protected function printSharedImageText() {
689
		$out = $this->getContext()->getOutput();
690
		$this->loadFile();
691
692
		$descUrl = $this->mPage->getFile()->getDescriptionUrl();
693
		$descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
694
695
		/* Add canonical to head if there is no local page for this shared file */
696
		if ( $descUrl && $this->mPage->getId() == 0 ) {
697
			$out->setCanonicalUrl( $descUrl );
698
		}
699
700
		$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
701
		$repo = $this->mPage->getFile()->getRepo()->getDisplayName();
702
703
		if ( $descUrl &&
704
			$descText &&
705
			$this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-'
706
		) {
707
			$out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-here', $repo, $descUrl ] );
708
		} elseif ( $descUrl &&
709
			$this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-'
710
		) {
711
			$out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-there', $repo, $descUrl ] );
712
		} else {
713
			$out->wrapWikiMsg( $wrap, [ 'sharedupload', $repo ], ''/*BACKCOMPAT*/ );
714
		}
715
716
		if ( $descText ) {
717
			$this->mExtraDescription = $descText;
718
		}
719
	}
720
721
	public function getUploadUrl() {
722
		$this->loadFile();
723
		$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
724
		return $uploadTitle->getFullURL( [
725
			'wpDestFile' => $this->mPage->getFile()->getName(),
726
			'wpForReUpload' => 1
727
		] );
728
	}
729
730
	/**
731
	 * Print out the various links at the bottom of the image page, e.g. reupload,
732
	 * external editing (and instructions link) etc.
733
	 */
734
	protected function uploadLinksBox() {
735
		global $wgEnableUploads;
736
737
		if ( !$wgEnableUploads ) {
738
			return;
739
		}
740
741
		$this->loadFile();
742
		if ( !$this->mPage->getFile()->isLocal() ) {
743
			return;
744
		}
745
746
		$out = $this->getContext()->getOutput();
747
		$out->addHTML( "<ul>\n" );
748
749
		# "Upload a new version of this file" link
750
		$canUpload = $this->getTitle()->quickUserCan( 'upload', $this->getContext()->getUser() );
751
		if ( $canUpload && UploadBase::userCanReUpload(
752
				$this->getContext()->getUser(),
753
				$this->mPage->getFile() )
0 ignored issues
show
Bug introduced by
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...
754
		) {
755
			$ulink = Linker::makeExternalLink(
756
				$this->getUploadUrl(),
757
				$this->getContext()->msg( 'uploadnewversion-linktext' )->text()
758
			);
759
			$out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
760
				. "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
761
		} else {
762
			$out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
763
				. $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
764
		}
765
766
		$out->addHTML( "</ul>\n" );
767
	}
768
769
	/**
770
	 * For overloading
771
	 */
772
	protected function closeShowImage() {
773
	}
774
775
	/**
776
	 * If the page we've just displayed is in the "Image" namespace,
777
	 * we follow it with an upload history of the image and its usage.
778
	 */
779
	protected function imageHistory() {
780
		$this->loadFile();
781
		$out = $this->getContext()->getOutput();
782
		$pager = new ImageHistoryPseudoPager( $this );
783
		$out->addHTML( $pager->getBody() );
784
		$out->preventClickjacking( $pager->getPreventClickjacking() );
785
786
		$this->mPage->getFile()->resetHistory(); // free db resources
787
788
		# Exist check because we don't want to show this on pages where an image
789
		# doesn't exist along with the noimage message, that would suck. -ævar
790
		if ( $this->mPage->getFile()->exists() ) {
791
			$this->uploadLinksBox();
792
		}
793
	}
794
795
	/**
796
	 * @param string $target
797
	 * @param int $limit
798
	 * @return ResultWrapper
799
	 */
800
	protected function queryImageLinks( $target, $limit ) {
801
		$dbr = wfGetDB( DB_REPLICA );
802
803
		return $dbr->select(
804
			[ 'imagelinks', 'page' ],
805
			[ 'page_namespace', 'page_title', 'il_to' ],
806
			[ 'il_to' => $target, 'il_from = page_id' ],
807
			__METHOD__,
808
			[ 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ]
809
		);
810
	}
811
812
	protected function imageLinks() {
813
		$limit = 100;
814
815
		$out = $this->getContext()->getOutput();
816
817
		$rows = [];
818
		$redirects = [];
819
		foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
820
			$redirects[$redir->getDBkey()] = [];
821
			$rows[] = (object)[
822
				'page_namespace' => NS_FILE,
823
				'page_title' => $redir->getDBkey(),
824
			];
825
		}
826
827
		$res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
828
		foreach ( $res as $row ) {
829
			$rows[] = $row;
830
		}
831
		$count = count( $rows );
832
833
		$hasMore = $count > $limit;
834
		if ( !$hasMore && count( $redirects ) ) {
835
			$res = $this->queryImageLinks( array_keys( $redirects ),
836
				$limit - count( $rows ) + 1 );
837
			foreach ( $res as $row ) {
838
				$redirects[$row->il_to][] = $row;
839
				$count++;
840
			}
841
			$hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
842
		}
843
844
		if ( $count == 0 ) {
845
			$out->wrapWikiMsg(
846
				Html::rawElement( 'div',
847
					[ 'id' => 'mw-imagepage-nolinkstoimage' ], "\n$1\n" ),
848
				'nolinkstoimage'
849
			);
850
			return;
851
		}
852
853
		$out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
854
		if ( !$hasMore ) {
855
			$out->addWikiMsg( 'linkstoimage', $count );
856
		} else {
857
			// More links than the limit. Add a link to [[Special:Whatlinkshere]]
858
			$out->addWikiMsg( 'linkstoimage-more',
859
				$this->getContext()->getLanguage()->formatNum( $limit ),
860
				$this->getTitle()->getPrefixedDBkey()
861
			);
862
		}
863
864
		$out->addHTML(
865
			Html::openElement( 'ul',
866
				[ 'class' => 'mw-imagepage-linkstoimage' ] ) . "\n"
867
		);
868
		$count = 0;
869
870
		// Sort the list by namespace:title
871
		usort( $rows, [ $this, 'compare' ] );
872
873
		// Create links for every element
874
		$currentCount = 0;
875
		foreach ( $rows as $element ) {
876
			$currentCount++;
877
			if ( $currentCount > $limit ) {
878
				break;
879
			}
880
881
			$query = [];
882
			# Add a redirect=no to make redirect pages reachable
883
			if ( isset( $redirects[$element->page_title] ) ) {
884
				$query['redirect'] = 'no';
885
			}
886
			$link = Linker::linkKnown(
887
				Title::makeTitle( $element->page_namespace, $element->page_title ),
888
				null, [], $query
889
			);
890
			if ( !isset( $redirects[$element->page_title] ) ) {
891
				# No redirects
892
				$liContents = $link;
893
			} elseif ( count( $redirects[$element->page_title] ) === 0 ) {
894
				# Redirect without usages
895
				$liContents = $this->getContext()->msg( 'linkstoimage-redirect' )
896
					->rawParams( $link, '' )
897
					->parse();
898
			} else {
899
				# Redirect with usages
900
				$li = '';
901
				foreach ( $redirects[$element->page_title] as $row ) {
902
					$currentCount++;
903
					if ( $currentCount > $limit ) {
904
						break;
905
					}
906
907
					$link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
908
					$li .= Html::rawElement(
909
						'li',
910
						[ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
911
						$link2
912
						) . "\n";
913
				}
914
915
				$ul = Html::rawElement(
916
					'ul',
917
					[ 'class' => 'mw-imagepage-redirectstofile' ],
918
					$li
919
					) . "\n";
920
				$liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
921
					$link, $ul )->parse();
922
			}
923
			$out->addHTML( Html::rawElement(
924
					'li',
925
					[ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
926
					$liContents
927
				) . "\n"
928
			);
929
930
		};
931
		$out->addHTML( Html::closeElement( 'ul' ) . "\n" );
932
		$res->free();
933
934
		// Add a links to [[Special:Whatlinkshere]]
935
		if ( $count > $limit ) {
936
			$out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
937
		}
938
		$out->addHTML( Html::closeElement( 'div' ) . "\n" );
939
	}
940
941
	protected function imageDupes() {
942
		$this->loadFile();
943
		$out = $this->getContext()->getOutput();
944
945
		$dupes = $this->mPage->getDuplicates();
946
		if ( count( $dupes ) == 0 ) {
947
			return;
948
		}
949
950
		$out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
951
		$out->addWikiMsg( 'duplicatesoffile',
952
			$this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
953
		);
954
		$out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
955
956
		/**
957
		 * @var $file File
958
		 */
959
		foreach ( $dupes as $file ) {
0 ignored issues
show
Bug introduced by
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...
960
			$fromSrc = '';
961
			if ( $file->isLocal() ) {
962
				$link = Linker::linkKnown( $file->getTitle() );
963
			} else {
964
				$link = Linker::makeExternalLink( $file->getDescriptionUrl(),
965
					$file->getTitle()->getPrefixedText() );
966
				$fromSrc = $this->getContext()->msg(
967
					'shared-repo-from',
968
					$file->getRepo()->getDisplayName()
969
				)->text();
970
			}
971
			$out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
972
		}
973
		$out->addHTML( "</ul></div>\n" );
974
	}
975
976
	/**
977
	 * Delete the file, or an earlier version of it
978
	 */
979
	public function delete() {
980
		$file = $this->mPage->getFile();
981
		if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
982
			// Standard article deletion
983
			parent::delete();
984
			return;
985
		}
986
987
		$deleter = new FileDeleteForm( $file );
0 ignored issues
show
Bug introduced by
It seems like $file defined by $this->mPage->getFile() on line 980 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...
988
		$deleter->execute();
989
	}
990
991
	/**
992
	 * Display an error with a wikitext description
993
	 *
994
	 * @param string $description
995
	 */
996
	function showError( $description ) {
997
		$out = $this->getContext()->getOutput();
998
		$out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
999
		$out->setRobotPolicy( 'noindex,nofollow' );
1000
		$out->setArticleRelated( false );
1001
		$out->enableClientCache( false );
1002
		$out->addWikiText( $description );
1003
	}
1004
1005
	/**
1006
	 * Callback for usort() to do link sorts by (namespace, title)
1007
	 * Function copied from Title::compare()
1008
	 *
1009
	 * @param object $a Object page to compare with
1010
	 * @param object $b Object page to compare with
1011
	 * @return int Result of string comparison, or namespace comparison
1012
	 */
1013
	protected function compare( $a, $b ) {
1014
		if ( $a->page_namespace == $b->page_namespace ) {
1015
			return strcmp( $a->page_title, $b->page_title );
1016
		} else {
1017
			return $a->page_namespace - $b->page_namespace;
1018
		}
1019
	}
1020
1021
	/**
1022
	 * Returns the corresponding $wgImageLimits entry for the selected user option
1023
	 *
1024
	 * @param User $user
1025
	 * @param string $optionName Name of a option to check, typically imagesize or thumbsize
1026
	 * @return array
1027
	 * @since 1.21
1028
	 */
1029
	public function getImageLimitsFromOption( $user, $optionName ) {
1030
		global $wgImageLimits;
1031
1032
		$option = $user->getIntOption( $optionName );
1033
		if ( !isset( $wgImageLimits[$option] ) ) {
1034
			$option = User::getDefaultOption( $optionName );
1035
		}
1036
1037
		// The user offset might still be incorrect, specially if
1038
		// $wgImageLimits got changed (see bug #8858).
1039
		if ( !isset( $wgImageLimits[$option] ) ) {
1040
			// Default to the first offset in $wgImageLimits
1041
			$option = 0;
1042
		}
1043
1044
		return isset( $wgImageLimits[$option] )
1045
			? $wgImageLimits[$option]
1046
			: [ 800, 600 ]; // if nothing is set, fallback to a hardcoded default
1047
	}
1048
1049
	/**
1050
	 * Output a drop-down box for language options for the file
1051
	 *
1052
	 * @param array $langChoices Array of string language codes
1053
	 * @param string $curLang Language code file is being viewed in.
1054
	 * @param string $defaultLang Language code that image is rendered in by default
1055
	 * @return string HTML to insert underneath image.
1056
	 */
1057
	protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
1058
		global $wgScript;
1059
		sort( $langChoices );
1060
		$curLang = wfBCP47( $curLang );
1061
		$defaultLang = wfBCP47( $defaultLang );
1062
		$opts = '';
1063
		$haveCurrentLang = false;
1064
		$haveDefaultLang = false;
1065
1066
		// We make a list of all the language choices in the file.
1067
		// Additionally if the default language to render this file
1068
		// is not included as being in this file (for example, in svgs
1069
		// usually the fallback content is the english content) also
1070
		// include a choice for that. Last of all, if we're viewing
1071
		// the file in a language not on the list, add it as a choice.
1072
		foreach ( $langChoices as $lang ) {
1073
			$code = wfBCP47( $lang );
1074
			$name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1075 View Code Duplication
			if ( $name !== '' ) {
1076
				$display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
1077
			} else {
1078
				$display = $code;
1079
			}
1080
			$opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
1081
			if ( $curLang === $code ) {
1082
				$haveCurrentLang = true;
1083
			}
1084
			if ( $defaultLang === $code ) {
1085
				$haveDefaultLang = true;
1086
			}
1087
		}
1088
		if ( !$haveDefaultLang ) {
1089
			// Its hard to know if the content is really in the default language, or
1090
			// if its just unmarked content that could be in any language.
1091
			$opts = Xml::option(
1092
					$this->getContext()->msg( 'img-lang-default' )->text(),
1093
				$defaultLang,
1094
				$defaultLang === $curLang
1095
			) . $opts;
1096
		}
1097
		if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
1098
			$name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
1099 View Code Duplication
			if ( $name !== '' ) {
1100
				$display = $this->getContext()->msg( 'img-lang-opt', $curLang, $name )->text();
1101
			} else {
1102
				$display = $curLang;
1103
			}
1104
			$opts = Xml::option( $display, $curLang, true ) . $opts;
1105
		}
1106
1107
		$select = Html::rawElement(
1108
			'select',
1109
			[ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
1110
			$opts
1111
		);
1112
		$submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
1113
1114
		$formContents = $this->getContext()->msg( 'img-lang-info' )
1115
			->rawParams( $select, $submit )
1116
			->parse();
1117
		$formContents .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1118
1119
		$langSelectLine = Html::rawElement( 'div', [ 'id' => 'mw-imglangselector-line' ],
1120
			Html::rawElement( 'form', [ 'action' => $wgScript ], $formContents )
1121
		);
1122
		return $langSelectLine;
1123
	}
1124
1125
	/**
1126
	 * Get the width and height to display image at.
1127
	 *
1128
	 * @note This method assumes that it is only called if one
1129
	 *  of the dimensions are bigger than the max, or if the
1130
	 *  image is vectorized.
1131
	 *
1132
	 * @param int $maxWidth Max width to display at
1133
	 * @param int $maxHeight Max height to display at
1134
	 * @param int $width Actual width of the image
1135
	 * @param int $height Actual height of the image
1136
	 * @throws MWException
1137
	 * @return array Array (width, height)
1138
	 */
1139
	protected function getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ) {
1140
		if ( !$maxWidth || !$maxHeight ) {
1141
			// should never happen
1142
			throw new MWException( 'Using a choice from $wgImageLimits that is 0x0' );
1143
		}
1144
1145
		if ( !$width || !$height ) {
1146
			return [ 0, 0 ];
1147
		}
1148
1149
		# Calculate the thumbnail size.
1150
		if ( $width <= $maxWidth && $height <= $maxHeight ) {
0 ignored issues
show
Unused Code introduced by
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...
1151
			// Vectorized image, do nothing.
1152
		} elseif ( $width / $height >= $maxWidth / $maxHeight ) {
1153
			# The limiting factor is the width, not the height.
1154
			$height = round( $height * $maxWidth / $width );
1155
			$width = $maxWidth;
1156
			# Note that $height <= $maxHeight now.
1157
		} else {
1158
			$newwidth = floor( $width * $maxHeight / $height );
1159
			$height = round( $height * $newwidth / $width );
1160
			$width = $newwidth;
1161
			# Note that $height <= $maxHeight now, but might not be identical
1162
			# because of rounding.
1163
		}
1164
		return [ $width, $height ];
1165
	}
1166
1167
	/**
1168
	 * Get alternative thumbnail sizes.
1169
	 *
1170
	 * @note This will only list several alternatives if thumbnails are rendered on 404
1171
	 * @param int $origWidth Actual width of image
1172
	 * @param int $origHeight Actual height of image
1173
	 * @return array An array of [width, height] pairs.
1174
	 */
1175
	protected function getThumbSizes( $origWidth, $origHeight ) {
1176
		global $wgImageLimits;
1177
		if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1178
			$thumbSizes = $wgImageLimits;
1179
			// Also include the full sized resolution in the list, so
1180
			// that users know they can get it. This will link to the
1181
			// original file asset if mustRender() === false. In the case
1182
			// that we mustRender, some users have indicated that they would
1183
			// find it useful to have the full size image in the rendered
1184
			// image format.
1185
			$thumbSizes[] = [ $origWidth, $origHeight ];
1186
		} else {
1187
			# Creating thumb links triggers thumbnail generation.
1188
			# Just generate the thumb for the current users prefs.
1189
			$thumbSizes = [
1190
				$this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' )
1191
			];
1192
			if ( !$this->displayImg->mustRender() ) {
1193
				// We can safely include a link to the "full-size" preview,
1194
				// without actually rendering.
1195
				$thumbSizes[] = [ $origWidth, $origHeight ];
1196
			}
1197
		}
1198
		return $thumbSizes;
1199
	}
1200
1201
	/**
1202
	 * @see WikiFilePage::getFile
1203
	 * @return bool|File
1204
	 */
1205
	public function getFile() {
1206
		return $this->mPage->getFile();
1207
	}
1208
1209
	/**
1210
	 * @see WikiFilePage::isLocal
1211
	 * @return bool
1212
	 */
1213
	public function isLocal() {
1214
		return $this->mPage->isLocal();
1215
	}
1216
1217
	/**
1218
	 * @see WikiFilePage::getDuplicates
1219
	 * @return array|null
1220
	 */
1221
	public function getDuplicates() {
1222
		return $this->mPage->getDuplicates();
1223
	}
1224
1225
	/**
1226
	 * @see WikiFilePage::getForeignCategories
1227
	 * @return TitleArray|Title[]
1228
	 */
1229
	public function getForeignCategories() {
1230
		$this->mPage->getForeignCategories();
1231
	}
1232
1233
}
1234