Completed
Branch master (939199)
by
unknown
39:35
created

includes/page/ImagePage.php (2 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;
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 );
80
		if ( !$this->displayImg ) { // not set by hook?
81
			$this->displayImg = $img;
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();
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() ) {
143
			$out->addHTML( $this->showTOC( $showmeta ) );
144
		}
145
146
		if ( !$diff ) {
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 ) {
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
350
					);
351
					$linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
352
353
					$thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
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() )
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 ) {
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 );
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 ) {
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