MediaWiki REL1_36
ImagePage.php
Go to the documentation of this file.
1<?php
25
33class ImagePage extends Article {
34 use MediaFileTrait;
35
37 private $displayImg;
38
40 private $repo;
41
43 private $fileLoaded;
44
46 protected $mExtraDescription = false;
47
52 protected function newPage( Title $title ) {
53 // Overload mPage with a file-specific page
54 return new WikiFilePage( $title );
55 }
56
61 public function setFile( $file ) {
62 $this->getPage()->setFile( $file );
63 $this->displayImg = $file;
64 $this->fileLoaded = true;
65 }
66
67 protected function loadFile() {
68 if ( $this->fileLoaded ) {
69 return;
70 }
71 $this->fileLoaded = true;
72
73 $this->displayImg = $img = false;
74
75 $this->getHookRunner()->onImagePageFindFile( $this, $img, $this->displayImg );
76 if ( !$img ) { // not set by hook?
77 $services = MediaWikiServices::getInstance();
78 $img = $services->getRepoGroup()->findFile( $this->getTitle() );
79 if ( !$img ) {
80 $img = $services->getRepoGroup()->getLocalRepo()->newFile( $this->getTitle() );
81 }
82 }
83 $this->getPage()->setFile( $img );
84 if ( !$this->displayImg ) { // not set by hook?
85 $this->displayImg = $img;
86 }
87 $this->repo = $img->getRepo();
88 }
89
90 public function view() {
91 global $wgShowEXIF;
92
93 // For action=render, include body text only; none of the image extras
94 if ( $this->viewIsRenderAction ) {
95 parent::view();
96 return;
97 }
98
99 $out = $this->getContext()->getOutput();
100 $request = $this->getContext()->getRequest();
101 $diff = $request->getVal( 'diff' );
102 $diffOnly = $request->getBool(
103 'diffonly',
104 $this->getContext()->getUser()->getOption( 'diffonly' )
105 );
106
107 if ( $this->getTitle()->getNamespace() !== NS_FILE || ( $diff !== null && $diffOnly ) ) {
108 parent::view();
109 return;
110 }
111
112 $this->loadFile();
113
114 if (
115 $this->getTitle()->getNamespace() === NS_FILE
116 && $this->getFile()->getRedirected()
117 ) {
118 if (
119 $this->getTitle()->getDBkey() == $this->getFile()->getName()
120 || $diff !== null
121 ) {
122 $request->setVal( 'diffonly', 'true' );
123 }
124
125 parent::view();
126 return;
127 }
128
129 if ( $wgShowEXIF && $this->displayImg->exists() ) {
130 // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
131 $formattedMetadata = $this->displayImg->formatMetadata( $this->getContext() );
132 } else {
133 $formattedMetadata = false;
134 }
135
136 if ( !$diff && $this->displayImg->exists() ) {
137 $out->addHTML( $this->showTOC( (bool)$formattedMetadata ) );
138 }
139
140 if ( !$diff ) {
141 $this->openShowImage();
142 }
143
144 # No need to display noarticletext, we use our own message, output in openShowImage()
145 if ( $this->getPage()->getId() ) {
146 # NS_FILE is in the user language, but this section (the actual wikitext)
147 # should be in page content language
148 $pageLang = $this->getTitle()->getPageViewLanguage();
149 $out->addHTML( Xml::openElement( 'div', [ 'id' => 'mw-imagepage-content',
150 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
151 'class' => 'mw-content-' . $pageLang->getDir() ] ) );
152
153 parent::view();
154
155 $out->addHTML( Xml::closeElement( 'div' ) );
156 } else {
157 # Just need to set the right headers
158 $out->setArticleFlag( true );
159 $out->setPageTitle( $this->getTitle()->getPrefixedText() );
160 $this->getPage()->doViewUpdates(
161 $this->getContext()->getUser(),
162 $this->getOldID()
163 );
164 }
165
166 # Show shared description, if needed
167 if ( $this->mExtraDescription ) {
168 $fol = $this->getContext()->msg( 'shareddescriptionfollows' );
169 if ( !$fol->isDisabled() ) {
170 $out->addWikiTextAsInterface( $fol->plain() );
171 }
172 $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
173 }
174
175 $this->closeShowImage();
176 $this->imageHistory();
177 // TODO: Cleanup the following
178
179 $out->addHTML( Xml::element( 'h2',
180 [ 'id' => 'filelinks' ],
181 $this->getContext()->msg( 'imagelinks' )->text() ) . "\n" );
182 $this->imageDupes();
183 # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
184 # Yet we return metadata about the target. Definitely an issue in the FileRepo
185 $this->imageLinks();
186
187 # Allow extensions to add something after the image links
188 $html = '';
189 $this->getHookRunner()->onImagePageAfterImageLinks( $this, $html );
190 if ( $html ) {
191 $out->addHTML( $html );
192 }
193
194 if ( $formattedMetadata ) {
195 $out->addHTML( Xml::element(
196 'h2',
197 [ 'id' => 'metadata' ],
198 $this->getContext()->msg( 'metadata' )->text() ) . "\n" );
199 $out->wrapWikiTextAsInterface(
200 'mw-imagepage-section-metadata',
201 $this->makeMetadataTable( $formattedMetadata )
202 );
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, T31277
216 'mediawiki.action.view.filepage', // Add MediaWiki styles for a file page
217 ] );
218 }
219
223 public function getDisplayedFile() {
224 $this->loadFile();
225 return $this->displayImg;
226 }
227
234 protected function showTOC( $metadata ) {
235 $r = [
236 '<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>',
237 '<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>',
238 '<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>',
239 ];
240
241 $this->getHookRunner()->onImagePageShowTOC( $this, $r );
242
243 if ( $metadata ) {
244 $r[] = '<li><a href="#metadata">' .
245 $this->getContext()->msg( 'metadata' )->escaped() .
246 '</a></li>';
247 }
248
249 return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
250 }
251
260 protected function makeMetadataTable( $metadata ) {
261 $r = $this->getContext()->msg( 'metadata-help' )->plain();
262 // Initial state is collapsed
263 // see filepage.css and mediawiki.action.view.metadata module.
264 $r .= "<table id=\"mw_metadata\" class=\"mw_metadata collapsed\">\n";
265 foreach ( $metadata as $type => $stuff ) {
266 foreach ( $stuff as $v ) {
267 $class = str_replace( ' ', '_', $v['id'] );
268 if ( $type == 'collapsed' ) {
269 $class .= ' mw-metadata-collapsible';
270 }
271 $r .= Html::rawElement( 'tr',
272 [ 'class' => $class ],
273 Html::rawElement( 'th', [], $v['name'] )
274 . Html::rawElement( 'td', [], $v['value'] )
275 );
276 }
277 }
278 $r .= "</table>\n";
279 return $r;
280 }
281
290 private function getLanguageForRendering( WebRequest $request, File $file ) {
291 $handler = $file->getHandler();
292 if ( !$handler ) {
293 return null;
294 }
295
296 $config = MediaWikiServices::getInstance()->getMainConfig();
297 $requestLanguage = $request->getVal( 'lang', $config->get( 'LanguageCode' ) );
298 if ( $handler->validateParam( 'lang', $requestLanguage ) ) {
299 return $file->getMatchedLanguage( $requestLanguage );
300 }
301
302 return $handler->getDefaultRenderLanguage( $file );
303 }
304
305 protected function openShowImage() {
307
308 $this->loadFile();
309 $out = $this->getContext()->getOutput();
310 $user = $this->getContext()->getUser();
311 $lang = $this->getContext()->getLanguage();
312 $dirmark = $lang->getDirMarkEntity();
313 $request = $this->getContext()->getRequest();
314
315 if ( $this->displayImg->exists() ) {
316 list( $maxWidth, $maxHeight ) = $this->getImageLimitsFromOption( $user, 'imagesize' );
317
318 # image
319 $page = $request->getIntOrNull( 'page' );
320 if ( $page === null ) {
321 $params = [];
322 $page = 1;
323 } else {
324 $params = [ 'page' => $page ];
325 }
326
327 $renderLang = $this->getLanguageForRendering( $request, $this->displayImg );
328 if ( $renderLang !== null ) {
329 $params['lang'] = $renderLang;
330 }
331
332 $width_orig = $this->displayImg->getWidth( $page );
333 $width = $width_orig;
334 $height_orig = $this->displayImg->getHeight( $page );
335 $height = $height_orig;
336
337 $filename = wfEscapeWikiText( $this->displayImg->getName() );
338 $linktext = $filename;
339
340 $this->getHookRunner()->onImageOpenShowImageInlineBefore( $this, $out );
341
342 if ( $this->displayImg->allowInlineDisplay() ) {
343 # image
344 # "Download high res version" link below the image
345 # $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig,
346 # Language::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
347 # We'll show a thumbnail of this image
348 if ( $width > $maxWidth ||
349 $height > $maxHeight ||
350 $this->displayImg->isVectorized()
351 ) {
352 list( $width, $height ) = $this->displayImg->getDisplayWidthHeight(
353 $maxWidth, $maxHeight, $page
354 );
355 $linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
356
357 $thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
358 # Generate thumbnails or thumbnail links as needed...
359 $otherSizes = [];
360 foreach ( $thumbSizes as $size ) {
361 // We include a thumbnail size in the list, if it is
362 // less than or equal to the original size of the image
363 // asset ($width_orig/$height_orig). We also exclude
364 // the current thumbnail's size ($width/$height)
365 // since that is added to the message separately, so
366 // it can be denoted as the current size being shown.
367 // Vectorized images are limited by $wgSVGMaxSize big,
368 // so all thumbs less than or equal that are shown.
369 if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
370 || ( $this->displayImg->isVectorized()
371 && max( $size[0], $size[1] ) <= $wgSVGMaxSize )
372 )
373 && $size[0] != $width && $size[1] != $height
374 ) {
375 $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
376 if ( $sizeLink ) {
377 $otherSizes[] = $sizeLink;
378 }
379 }
380 }
381 $otherSizes = array_unique( $otherSizes );
382
383 $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
384 $msgsmall = $this->getThumbPrevText( $params, $sizeLinkBigImagePreview );
385 if ( count( $otherSizes ) ) {
386 $msgsmall .= ' ' .
387 Html::rawElement(
388 'span',
389 [ 'class' => 'mw-filepage-other-resolutions' ],
390 $this->getContext()->msg( 'show-big-image-other' )
391 ->rawParams( $lang->pipeList( $otherSizes ) )
392 ->params( count( $otherSizes ) )
393 ->parse()
394 );
395 }
396 } elseif ( $width == 0 && $height == 0 ) {
397 # Some sort of audio file that doesn't have dimensions
398 # Don't output a no hi res message for such a file
399 $msgsmall = '';
400 } else {
401 # Image is small enough to show full size on image page
402 $msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
403 }
404
405 $params['width'] = $width;
406 $params['height'] = $height;
407 // Allow the MediaHandler to handle query string parameters on the file page,
408 // e.g. start time for videos (T203994)
409 $params['imagePageParams'] = $request->getQueryValuesOnly();
410 $thumbnail = $this->displayImg->transform( $params );
411 Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
412
413 $anchorclose = Html::rawElement(
414 'div',
415 [ 'class' => 'mw-filepage-resolutioninfo' ],
416 $msgsmall
417 );
418
419 $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
420 if ( $isMulti ) {
421 $out->addModules( 'mediawiki.page.image.pagination' );
422 $out->addHTML( '<table class="multipageimage"><tr><td>' );
423 }
424
425 if ( $thumbnail ) {
426 $options = [
427 'alt' => $this->displayImg->getTitle()->getPrefixedText(),
428 'file-link' => true,
429 ];
430 $out->addHTML( '<div class="fullImageLink" id="file">' .
431 $thumbnail->toHtml( $options ) .
432 $anchorclose . "</div>\n" );
433 }
434
435 if ( $isMulti ) {
436 $count = $this->displayImg->pageCount();
437 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
438
439 if ( $page > 1 ) {
440 $label = $this->getContext()->msg( 'imgmultipageprev' )->text();
441 // on the client side, this link is generated in ajaxifyPageNavigation()
442 // in the mediawiki.page.image.pagination module
444 $this->getTitle(),
445 $label,
446 [],
447 [ 'page' => $page - 1 ]
448 );
449 // @phan-suppress-next-line SecurityCheck-DoubleEscaped link getting a key, false positive
450 $thumb1 = Linker::makeThumbLinkObj(
451 $this->getTitle(),
452 $this->displayImg,
453 $link,
454 $label,
455 'none',
456 [ 'page' => $page - 1 ]
457 );
458 } else {
459 $thumb1 = '';
460 }
461
462 if ( $page < $count ) {
463 $label = $this->getContext()->msg( 'imgmultipagenext' )->text();
465 $this->getTitle(),
466 $label,
467 [],
468 [ 'page' => $page + 1 ]
469 );
470 // @phan-suppress-next-line SecurityCheck-DoubleEscaped link getting a key, false positive
471 $thumb2 = Linker::makeThumbLinkObj(
472 $this->getTitle(),
473 $this->displayImg,
474 $link,
475 $label,
476 'none',
477 [ 'page' => $page + 1 ]
478 );
479 } else {
480 $thumb2 = '';
481 }
482
483 global $wgScript;
484
485 $formParams = [
486 'name' => 'pageselector',
487 'action' => $wgScript,
488 ];
489 $options = [];
490 for ( $i = 1; $i <= $count; $i++ ) {
491 $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
492 }
493 $select = Xml::tags( 'select',
494 [ 'id' => 'pageselector', 'name' => 'page' ],
495 implode( "\n", $options ) );
496
497 $out->addHTML(
498 '</td><td><div class="multipageimagenavbox">' .
499 Xml::openElement( 'form', $formParams ) .
500 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
501 $this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() .
502 $this->getContext()->msg( 'word-separator' )->escaped() .
503 Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) .
504 Xml::closeElement( 'form' ) .
505 "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
506 );
507 }
508 } elseif ( $this->displayImg->isSafeFile() ) {
509 # if direct link is allowed but it's not a renderable image, show an icon.
510 $icon = $this->displayImg->iconThumb();
511
512 $out->addHTML( '<div class="fullImageLink" id="file">' .
513 $icon->toHtml( [ 'file-link' => true ] ) .
514 "</div>\n" );
515 }
516
517 $longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text();
518
519 $handler = $this->displayImg->getHandler();
520
521 // If this is a filetype with potential issues, warn the user.
522 if ( $handler ) {
523 $warningConfig = $handler->getWarningConfig( $this->displayImg );
524
525 if ( $warningConfig !== null ) {
526 // The warning will be displayed via CSS and JavaScript.
527 // We just need to tell the client side what message to use.
528 $output = $this->getContext()->getOutput();
529 $output->addJsConfigVars( 'wgFileWarning', $warningConfig );
530 $output->addModules( $warningConfig['module'] );
531 $output->addModules( 'mediawiki.filewarning' );
532 }
533 }
534
535 $medialink = "[[Media:$filename|$linktext]]";
536
537 if ( !$this->displayImg->isSafeFile() ) {
538 $warning = $this->getContext()->msg( 'mediawarning' )->plain();
539 // dirmark is needed here to separate the file name, which
540 // most likely ends in Latin characters, from the description,
541 // which may begin with the file type. In RTL environment
542 // this will get messy.
543 // The dirmark, however, must not be immediately adjacent
544 // to the filename, because it can get copied with it.
545 // See T27277.
546 // phpcs:disable Generic.Files.LineLength
547 $out->wrapWikiTextAsInterface( 'fullMedia', <<<EOT
548<span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span>
549EOT
550 );
551 // phpcs:enable
552 $out->wrapWikiTextAsInterface( 'mediaWarning', $warning );
553 } else {
554 $out->wrapWikiTextAsInterface( 'fullMedia', <<<EOT
555{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
556EOT
557 );
558 }
559
560 $renderLangOptions = $this->displayImg->getAvailableLanguages();
561 if ( count( $renderLangOptions ) >= 1 ) {
562 $out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $renderLang ) );
563 }
564
565 // Add cannot animate thumbnail warning
566 if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
567 // Include the extension so wiki admins can
568 // customize it on a per file-type basis
569 // (aka say things like use format X instead).
570 // additionally have a specific message for
571 // file-no-thumb-animation-gif
572 $ext = $this->displayImg->getExtension();
573 $noAnimMesg = wfMessageFallback(
574 'file-no-thumb-animation-' . $ext,
575 'file-no-thumb-animation'
576 )->plain();
577
578 $out->wrapWikiTextAsInterface( 'mw-noanimatethumb', $noAnimMesg );
579 }
580
581 if ( !$this->displayImg->isLocal() ) {
582 $this->printSharedImageText();
583 }
584 } else {
585 # Image does not exist
586 if ( !$this->getPage()->getId() ) {
588
589 # No article exists either
590 # Show deletion log to be consistent with normal articles
591 LogEventsList::showLogExtract(
592 $out,
593 [ 'delete', 'move', 'protect' ],
594 $this->getTitle()->getPrefixedText(),
595 '',
596 [ 'lim' => 10,
597 'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
598 'showIfEmpty' => false,
599 'msgKey' => [ 'moveddeleted-notice' ]
600 ]
601 );
602 }
603
604 if ( $wgEnableUploads &&
605 $this->getContext()->getAuthority()->isAllowed( 'upload' )
606 ) {
607 // Only show an upload link if the user can upload
608 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
609 $nofile = [
610 'filepage-nofile-link',
611 $uploadTitle->getFullURL( [
612 'wpDestFile' => $this->getFile()->getName()
613 ] )
614 ];
615 } else {
616 $nofile = 'filepage-nofile';
617 }
618 // Note, if there is an image description page, but
619 // no image, then this setRobotPolicy is overridden
620 // by Article::View().
621 $out->setRobotPolicy( 'noindex,nofollow' );
622 $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
623 if ( !$this->getPage()->getId() && $wgSend404Code ) {
624 // If there is no image, no shared image, and no description page,
625 // output a 404, to be consistent with Article::showMissingArticle.
626 $request->response()->statusHeader( 404 );
627 }
628 }
629 $out->setFileVersion( $this->displayImg );
630 }
631
639 protected function getThumbPrevText( $params, $sizeLinkBigImagePreview ) {
640 if ( $sizeLinkBigImagePreview ) {
641 // Show a different message of preview is different format from original.
642 $previewTypeDiffers = false;
643 $origExt = $thumbExt = $this->displayImg->getExtension();
644 if ( $this->displayImg->getHandler() ) {
645 $origMime = $this->displayImg->getMimeType();
646 $typeParams = $params;
647 $this->displayImg->getHandler()->normaliseParams( $this->displayImg, $typeParams );
648 list( $thumbExt, $thumbMime ) = $this->displayImg->getHandler()->getThumbType(
649 $origExt, $origMime, $typeParams );
650 if ( $thumbMime !== $origMime ) {
651 $previewTypeDiffers = true;
652 }
653 }
654 if ( $previewTypeDiffers ) {
655 return $this->getContext()->msg( 'show-big-image-preview-differ' )->
656 rawParams( $sizeLinkBigImagePreview )->
657 params( strtoupper( $origExt ) )->
658 params( strtoupper( $thumbExt ) )->
659 parse();
660 } else {
661 return $this->getContext()->msg( 'show-big-image-preview' )->
662 rawParams( $sizeLinkBigImagePreview )->
663 parse();
664 }
665 } else {
666 return '';
667 }
668 }
669
677 protected function makeSizeLink( $params, $width, $height ) {
678 $params['width'] = $width;
679 $params['height'] = $height;
680 $thumbnail = $this->displayImg->transform( $params );
681 if ( $thumbnail && !$thumbnail->isError() ) {
682 return Html::rawElement( 'a', [
683 'href' => $thumbnail->getUrl(),
684 'class' => 'mw-thumbnail-link'
685 ], $this->getContext()->msg( 'show-big-image-size' )->numParams(
686 $thumbnail->getWidth(), $thumbnail->getHeight()
687 )->parse() );
688 } else {
689 return '';
690 }
691 }
692
696 protected function printSharedImageText() {
697 $out = $this->getContext()->getOutput();
698 $this->loadFile();
699
700 $descUrl = $this->getFile()->getDescriptionUrl();
701 $descText = $this->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
702
703 /* Add canonical to head if there is no local page for this shared file */
704 if ( $descUrl && !$this->getPage()->getId() ) {
705 $out->setCanonicalUrl( $descUrl );
706 }
707
708 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
709 $repo = $this->getFile()->getRepo()->getDisplayName();
710
711 if ( $descUrl &&
712 $descText &&
713 $this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-'
714 ) {
715 $out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-here', $repo, $descUrl ] );
716 } elseif ( $descUrl &&
717 $this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-'
718 ) {
719 $out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-there', $repo, $descUrl ] );
720 } else {
721 $out->wrapWikiMsg( $wrap, [ 'sharedupload', $repo ], ''/*BACKCOMPAT*/ );
722 }
723
724 if ( $descText ) {
725 $this->mExtraDescription = $descText;
726 }
727 }
728
729 public function getUploadUrl() {
730 $this->loadFile();
731 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
732 return $uploadTitle->getFullURL( [
733 'wpDestFile' => $this->getFile()->getName(),
734 'wpForReUpload' => 1
735 ] );
736 }
737
741 protected function uploadLinksBox() {
742 if ( !$this->getContext()->getConfig()->get( 'EnableUploads' ) ) {
743 return;
744 }
745
746 $this->loadFile();
747 if ( !$this->getFile()->isLocal() ) {
748 return;
749 }
750
751 $canUpload = $this->getContext()->getAuthority()
752 ->probablyCan( 'upload', $this->getTitle() );
753 if ( $canUpload && UploadBase::userCanReUpload(
754 $this->getContext()->getAuthority(),
755 $this->getFile() )
756 ) {
757 // "Upload a new version of this file" link
759 $this->getUploadUrl(),
760 $this->getContext()->msg( 'uploadnewversion-linktext' )->text()
761 );
762 $attrs = [ 'class' => 'plainlinks', 'id' => 'mw-imagepage-reupload-link' ];
763 $linkPara = Html::rawElement( 'p', $attrs, $ulink );
764 } else {
765 // "You cannot overwrite this file." message
766 $attrs = [ 'id' => 'mw-imagepage-upload-disallowed' ];
767 $msg = $this->getContext()->msg( 'upload-disallowed-here' )->text();
768 $linkPara = Html::element( 'p', $attrs, $msg );
769 }
770
771 $uploadLinks = Html::rawElement( 'div', [ 'class' => 'mw-imagepage-upload-links' ], $linkPara );
772 $this->getContext()->getOutput()->addHTML( $uploadLinks );
773 }
774
778 protected function closeShowImage() {
779 }
780
785 protected function imageHistory() {
786 $this->loadFile();
787 $out = $this->getContext()->getOutput();
788 $pager = new ImageHistoryPseudoPager(
789 $this,
790 MediaWikiServices::getInstance()->getLinkBatchFactory()
791 );
792 $out->addHTML( $pager->getBody() );
793 $out->preventClickjacking( $pager->getPreventClickjacking() );
794
795 $this->getFile()->resetHistory(); // free db resources
796
797 # Exist check because we don't want to show this on pages where an image
798 # doesn't exist along with the noimage message, that would suck. -ævar
799 if ( $this->getFile()->exists() ) {
800 $this->uploadLinksBox();
801 }
802 }
803
809 protected function queryImageLinks( $target, $limit ) {
811
812 return $dbr->select(
813 [ 'imagelinks', 'page' ],
814 [ 'page_namespace', 'page_title', 'il_to' ],
815 [ 'il_to' => $target, 'il_from = page_id' ],
816 __METHOD__,
817 [ 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ]
818 );
819 }
820
821 protected function imageLinks() {
822 $limit = 100;
823
824 $out = $this->getContext()->getOutput();
825
826 $rows = [];
827 $redirects = [];
828 foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
829 $redirects[$redir->getDBkey()] = [];
830 $rows[] = (object)[
831 'page_namespace' => NS_FILE,
832 'page_title' => $redir->getDBkey(),
833 ];
834 }
835
836 $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
837 foreach ( $res as $row ) {
838 $rows[] = $row;
839 }
840 $count = count( $rows );
841
842 $hasMore = $count > $limit;
843 if ( !$hasMore && count( $redirects ) ) {
844 $res = $this->queryImageLinks( array_keys( $redirects ),
845 $limit - count( $rows ) + 1 );
846 foreach ( $res as $row ) {
847 $redirects[$row->il_to][] = $row;
848 $count++;
849 }
850 $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
851 }
852
853 if ( $count == 0 ) {
854 $out->wrapWikiMsg(
855 Html::rawElement( 'div',
856 [ 'id' => 'mw-imagepage-nolinkstoimage' ], "\n$1\n" ),
857 'nolinkstoimage'
858 );
859 return;
860 }
861
862 $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
863 if ( !$hasMore ) {
864 $out->addWikiMsg( 'linkstoimage', $count );
865 } else {
866 // More links than the limit. Add a link to [[Special:Whatlinkshere]]
867 $out->addWikiMsg( 'linkstoimage-more',
868 $this->getContext()->getLanguage()->formatNum( $limit ),
869 $this->getTitle()->getPrefixedDBkey()
870 );
871 }
872
873 $out->addHTML(
874 Html::openElement( 'ul',
875 [ 'class' => 'mw-imagepage-linkstoimage' ] ) . "\n"
876 );
877 // Sort the list by namespace:title
878 usort( $rows, [ $this, 'compare' ] );
879
880 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
881
882 // Create links for every element
883 $currentCount = 0;
884 foreach ( $rows as $element ) {
885 $currentCount++;
886 if ( $currentCount > $limit ) {
887 break;
888 }
889
890 $query = [];
891 # Add a redirect=no to make redirect pages reachable
892 if ( isset( $redirects[$element->page_title] ) ) {
893 $query['redirect'] = 'no';
894 }
896 Title::makeTitle( $element->page_namespace, $element->page_title ),
897 null, [], $query
898 );
899 if ( !isset( $redirects[$element->page_title] ) ) {
900 # No redirects
901 $liContents = $link;
902 } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
903 # Redirect without usages
904 $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )
905 ->rawParams( $link, '' )
906 ->parse();
907 } else {
908 # Redirect with usages
909 $li = '';
910 foreach ( $redirects[$element->page_title] as $row ) {
911 $currentCount++;
912 if ( $currentCount > $limit ) {
913 break;
914 }
915
917 Title::makeTitle( $row->page_namespace, $row->page_title ) );
918 $li .= Html::rawElement(
919 'li',
920 [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
921 $link2
922 ) . "\n";
923 }
924
925 $ul = Html::rawElement(
926 'ul',
927 [ 'class' => 'mw-imagepage-redirectstofile' ],
928 $li
929 ) . "\n";
930 $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
931 $link, $ul )->parse();
932 }
933 $out->addHTML( Html::rawElement(
934 'li',
935 [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
936 $liContents
937 ) . "\n"
938 );
939
940 }
941 $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
942 $res->free();
943
944 // Add a links to [[Special:Whatlinkshere]]
945 if ( $currentCount > $limit ) {
946 $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
947 }
948 $out->addHTML( Html::closeElement( 'div' ) . "\n" );
949 }
950
951 protected function imageDupes() {
952 $this->loadFile();
953 $out = $this->getContext()->getOutput();
954
955 $dupes = $this->getPage()->getDuplicates();
956 if ( count( $dupes ) == 0 ) {
957 return;
958 }
959
960 $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
961 $out->addWikiMsg( 'duplicatesoffile',
962 $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
963 );
964 $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
965
966 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
967
971 foreach ( $dupes as $file ) {
972 $fromSrc = '';
973 if ( $file->isLocal() ) {
974 $link = $linkRenderer->makeKnownLink( $file->getTitle() );
975 } else {
976 $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
977 $file->getTitle()->getPrefixedText() );
978 $fromSrc = $this->getContext()->msg(
979 'shared-repo-from',
980 $file->getRepo()->getDisplayName()
981 )->escaped();
982 }
983 $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
984 }
985 $out->addHTML( "</ul></div>\n" );
986 }
987
991 public function delete() {
992 $file = $this->getFile();
993 if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
994 // Standard article deletion
995 parent::delete();
996 return;
997 }
998 '@phan-var LocalFile $file';
999
1000 $context = $this->getContext();
1001 $deleter = new FileDeleteForm(
1002 $file,
1003 $context->getUser(),
1004 $context->getOutput()
1005 );
1006 $deleter->execute();
1007 }
1008
1014 public function showError( $description ) {
1015 $out = $this->getContext()->getOutput();
1016 $out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
1017 $out->setRobotPolicy( 'noindex,nofollow' );
1018 $out->setArticleRelated( false );
1019 $out->enableClientCache( false );
1020 $out->addWikiTextAsInterface( $description );
1021 }
1022
1031 protected function compare( $a, $b ) {
1032 return $a->page_namespace <=> $b->page_namespace
1033 ?: strcmp( $a->page_title, $b->page_title );
1034 }
1035
1045 public function getImageLimitsFromOption( $user, $optionName ) {
1046 return MediaFileTrait::getImageLimitsFromOption( $user, $optionName );
1047 }
1048
1056 protected function doRenderLangOpt( array $langChoices, $renderLang ) {
1057 global $wgScript;
1058 $opts = '';
1059
1060 $matchedRenderLang = $this->displayImg->getMatchedLanguage( $renderLang );
1061
1062 foreach ( $langChoices as $lang ) {
1063 $opts .= $this->createXmlOptionStringForLanguage(
1064 $lang,
1065 $matchedRenderLang === $lang
1066 );
1067 }
1068
1069 // Allow for the default case in an svg <switch> that is displayed if no
1070 // systemLanguage attribute matches
1071 $opts .= "\n" .
1072 Xml::option(
1073 $this->getContext()->msg( 'img-lang-default' )->text(),
1074 'und',
1075 $matchedRenderLang === null
1076 );
1077
1078 $select = Html::rawElement(
1079 'select',
1080 [ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
1081 $opts
1082 );
1083 $submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
1084
1085 $formContents = $this->getContext()->msg( 'img-lang-info' )
1086 ->rawParams( $select, $submit )
1087 ->parse();
1088 $formContents .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1089
1090 $langSelectLine = Html::rawElement( 'div', [ 'id' => 'mw-imglangselector-line' ],
1091 Html::rawElement( 'form', [ 'action' => $wgScript ], $formContents )
1092 );
1093 return $langSelectLine;
1094 }
1095
1101 private function createXmlOptionStringForLanguage( $lang, $selected ) {
1102 $code = LanguageCode::bcp47( $lang );
1103 $name = MediaWikiServices::getInstance()
1104 ->getLanguageNameUtils()
1105 ->getLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1106 if ( $name !== '' ) {
1107 $display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
1108 } else {
1109 $display = $code;
1110 }
1111 return "\n" .
1112 Xml::option(
1113 $display,
1114 $lang,
1115 $selected
1116 );
1117 }
1118
1128 protected function getThumbSizes( $origWidth, $origHeight ) {
1129 global $wgImageLimits;
1130 if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1131 $thumbSizes = $wgImageLimits;
1132 // Also include the full sized resolution in the list, so
1133 // that users know they can get it. This will link to the
1134 // original file asset if mustRender() === false. In the case
1135 // that we mustRender, some users have indicated that they would
1136 // find it useful to have the full size image in the rendered
1137 // image format.
1138 $thumbSizes[] = [ $origWidth, $origHeight ];
1139 } else {
1140 # Creating thumb links triggers thumbnail generation.
1141 # Just generate the thumb for the current users prefs.
1142 $thumbSizes = [
1143 $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' )
1144 ];
1145 if ( !$this->displayImg->mustRender() ) {
1146 // We can safely include a link to the "full-size" preview,
1147 // without actually rendering.
1148 $thumbSizes[] = [ $origWidth, $origHeight ];
1149 }
1150 }
1151 return $thumbSizes;
1152 }
1153
1158 public function getFile() {
1159 return $this->getPage()->getFile();
1160 }
1161
1166 public function isLocal() {
1167 return $this->getPage()->isLocal();
1168 }
1169
1174 public function getDuplicates() {
1175 return $this->getPage()->getDuplicates();
1176 }
1177
1182 public function getForeignCategories() {
1183 return $this->getPage()->getForeignCategories();
1184 }
1185
1186}
getAuthority()
$wgScript
The URL path to index.php.
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
$wgImageLimits
Limit images on image description pages to a user-selectable limit.
$wgSVGMaxSize
Don't scale a SVG larger than this.
$wgEnableUploads
Allow users to upload files.
$wgShowEXIF
Show Exif data, on by default if available.
const NS_FILE
Definition Defines.php:70
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMessageFallback(... $keys)
This function accepts multiple message keys and returns a message instance for the first message whic...
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
getFile()
Get the file for this page, if one exists.
getContext()
Class for viewing MediaWiki article and history.
Definition Article.php:45
getOldID()
Definition Article.php:301
LinkRenderer $linkRenderer
Definition Article.php:94
getPage()
Get the WikiPage object of this instance.
Definition Article.php:221
File deletion user interface.
Base class for file repositories.
Definition FileRepo.php:41
getDisplayName()
Get the human-readable name of the repo.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:63
Class for viewing MediaWiki file description pages.
Definition ImagePage.php:33
getLanguageForRendering(WebRequest $request, File $file)
Returns language code to be used for dispaying the image, based on request context and languages avai...
showError( $description)
Display an error with a wikitext description.
imageHistory()
If the page we've just displayed is in the "Image" namespace, we follow it with an upload history of ...
compare( $a, $b)
Callback for usort() to do link sorts by (namespace, title) Function copied from Title::compare()
createXmlOptionStringForLanguage( $lang, $selected)
getThumbSizes( $origWidth, $origHeight)
Get alternative thumbnail sizes.
getImageLimitsFromOption( $user, $optionName)
Returns the corresponding $wgImageLimits entry for the selected user option.
getForeignCategories()
newPage(Title $title)
Definition ImagePage.php:52
string false $mExtraDescription
Guaranteed to be HTML, {.
Definition ImagePage.php:46
makeSizeLink( $params, $width, $height)
Creates an thumbnail of specified size and returns an HTML link to it.
setFile( $file)
Definition ImagePage.php:61
File false $displayImg
Only temporary false, most code can assume this is a File.
Definition ImagePage.php:37
doRenderLangOpt(array $langChoices, $renderLang)
Output a drop-down box for language options for the file.
FileRepo $repo
Definition ImagePage.php:40
makeMetadataTable( $metadata)
Make a table with metadata to be shown in the output page.
queryImageLinks( $target, $limit)
bool $fileLoaded
Definition ImagePage.php:43
uploadLinksBox()
Add the re-upload link (or message about not being able to re-upload) to the output.
printSharedImageText()
Show a notice that the file is from a shared repository.
getDisplayedFile()
closeShowImage()
For overloading.
showTOC( $metadata)
Create the TOC.
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition ImagePage.php:90
getThumbPrevText( $params, $sizeLinkBigImagePreview)
Make the text under the image to say what size preview.
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:641
static makeThumbLinkObj(LinkTarget $title, $file, $label='', $alt='', $align='right', $params=[], $framed=false, $manualthumb="")
Make HTML for a thumbnail including image, border and caption.
Definition Linker.php:492
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:847
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:48
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
Special handling for file pages.
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition defines.php:25
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!is_readable( $file)) $ext
Definition router.php:48
if(!isset( $args[0])) $lang