MediaWiki fundraising/REL1_35
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 $showmeta = $formattedMetadata !== false;
133 } else {
134 $showmeta = false;
135 }
136
137 if ( !$diff && $this->displayImg->exists() ) {
138 $out->addHTML( $this->showTOC( $showmeta ) );
139 }
140
141 if ( !$diff ) {
142 $this->openShowImage();
143 }
144
145 # No need to display noarticletext, we use our own message, output in openShowImage()
146 if ( $this->getPage()->getId() ) {
147 # NS_FILE is in the user language, but this section (the actual wikitext)
148 # should be in page content language
149 $pageLang = $this->getTitle()->getPageViewLanguage();
150 $out->addHTML( Xml::openElement( 'div', [ 'id' => 'mw-imagepage-content',
151 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
152 'class' => 'mw-content-' . $pageLang->getDir() ] ) );
153
154 parent::view();
155
156 $out->addHTML( Xml::closeElement( 'div' ) );
157 } else {
158 # Just need to set the right headers
159 $out->setArticleFlag( true );
160 $out->setPageTitle( $this->getTitle()->getPrefixedText() );
161 $this->getPage()->doViewUpdates(
162 $this->getContext()->getUser(),
163 $this->getOldID()
164 );
165 }
166
167 # Show shared description, if needed
168 if ( $this->mExtraDescription ) {
169 $fol = $this->getContext()->msg( 'shareddescriptionfollows' );
170 if ( !$fol->isDisabled() ) {
171 $out->addWikiTextAsInterface( $fol->plain() );
172 }
173 $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
174 }
175
176 $this->closeShowImage();
177 $this->imageHistory();
178 // TODO: Cleanup the following
179
180 $out->addHTML( Xml::element( 'h2',
181 [ 'id' => 'filelinks' ],
182 $this->getContext()->msg( 'imagelinks' )->text() ) . "\n" );
183 $this->imageDupes();
184 # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
185 # Yet we return metadata about the target. Definitely an issue in the FileRepo
186 $this->imageLinks();
187
188 # Allow extensions to add something after the image links
189 $html = '';
190 $this->getHookRunner()->onImagePageAfterImageLinks( $this, $html );
191 if ( $html ) {
192 $out->addHTML( $html );
193 }
194
195 if ( $showmeta ) {
196 '@phan-var array $formattedMetadata';
197 $out->addHTML( Xml::element(
198 'h2',
199 [ 'id' => 'metadata' ],
200 $this->getContext()->msg( 'metadata' )->text() ) . "\n" );
201 $out->wrapWikiTextAsInterface(
202 'mw-imagepage-section-metadata',
203 $this->makeMetadataTable( $formattedMetadata )
204 );
205 $out->addModules( [ 'mediawiki.action.view.metadata' ] );
206 }
207
208 // Add remote Filepage.css
209 if ( !$this->repo->isLocal() ) {
210 $css = $this->repo->getDescriptionStylesheetUrl();
211 if ( $css ) {
212 $out->addStyle( $css );
213 }
214 }
215
216 $out->addModuleStyles( [
217 'filepage', // always show the local local Filepage.css, T31277
218 'mediawiki.action.view.filepage', // Add MediaWiki styles for a file page
219 ] );
220 }
221
225 public function getDisplayedFile() {
226 $this->loadFile();
227 return $this->displayImg;
228 }
229
236 protected function showTOC( $metadata ) {
237 $r = [
238 '<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>',
239 '<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>',
240 '<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>',
241 ];
242
243 $this->getHookRunner()->onImagePageShowTOC( $this, $r );
244
245 if ( $metadata ) {
246 $r[] = '<li><a href="#metadata">' .
247 $this->getContext()->msg( 'metadata' )->escaped() .
248 '</a></li>';
249 }
250
251 return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
252 }
253
262 protected function makeMetadataTable( $metadata ) {
263 $r = $this->getContext()->msg( 'metadata-help' )->plain();
264 // Intial state is collapsed
265 // see filepage.css and mediawiki.action.view.metadata module.
266 $r .= "<table id=\"mw_metadata\" class=\"mw_metadata collapsed\">\n";
267 foreach ( $metadata as $type => $stuff ) {
268 foreach ( $stuff as $v ) {
269 $class = str_replace( ' ', '_', $v['id'] );
270 if ( $type == 'collapsed' ) {
271 $class .= ' mw-metadata-collapsible';
272 }
273 $r .= Html::rawElement( 'tr',
274 [ 'class' => $class ],
275 Html::rawElement( 'th', [], $v['name'] )
276 . Html::rawElement( 'td', [], $v['value'] )
277 );
278 }
279 }
280 $r .= "</table>\n";
281 return $r;
282 }
283
293 public function getEmptyPageParserOutput( ParserOptions $options ) {
294 $this->loadFile();
295 if (
296 $this->getFile()
297 && !$this->getFile()->isLocal()
298 && !$this->getPage()->getId()
299 ) {
300 return new ParserOutput();
301 }
302 return parent::getEmptyPageParserOutput( $options );
303 }
304
313 private function getLanguageForRendering( WebRequest $request, File $file ) {
314 $handler = $file->getHandler();
315 if ( !$handler ) {
316 return null;
317 }
318
319 $config = MediaWikiServices::getInstance()->getMainConfig();
320 $requestLanguage = $request->getVal( 'lang', $config->get( 'LanguageCode' ) );
321 if ( $handler->validateParam( 'lang', $requestLanguage ) ) {
322 return $file->getMatchedLanguage( $requestLanguage );
323 }
324
325 return $handler->getDefaultRenderLanguage( $file );
326 }
327
328 protected function openShowImage() {
330
331 $this->loadFile();
332 $out = $this->getContext()->getOutput();
333 $user = $this->getContext()->getUser();
334 $lang = $this->getContext()->getLanguage();
335 $dirmark = $lang->getDirMarkEntity();
336 $request = $this->getContext()->getRequest();
337
338 if ( $this->displayImg->exists() ) {
339 list( $maxWidth, $maxHeight ) = $this->getImageLimitsFromOption( $user, 'imagesize' );
340
341 # image
342 $page = $request->getIntOrNull( 'page' );
343 if ( $page === null ) {
344 $params = [];
345 $page = 1;
346 } else {
347 $params = [ 'page' => $page ];
348 }
349
350 $renderLang = $this->getLanguageForRendering( $request, $this->displayImg );
351 if ( $renderLang !== null ) {
352 $params['lang'] = $renderLang;
353 }
354
355 $width_orig = $this->displayImg->getWidth( $page );
356 $width = $width_orig;
357 $height_orig = $this->displayImg->getHeight( $page );
358 $height = $height_orig;
359
360 $filename = wfEscapeWikiText( $this->displayImg->getName() );
361 $linktext = $filename;
362
363 $this->getHookRunner()->onImageOpenShowImageInlineBefore( $this, $out );
364
365 if ( $this->displayImg->allowInlineDisplay() ) {
366 # image
367 # "Download high res version" link below the image
368 # $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig,
369 # Language::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
370 # We'll show a thumbnail of this image
371 if ( $width > $maxWidth ||
372 $height > $maxHeight ||
373 $this->displayImg->isVectorized()
374 ) {
375 list( $width, $height ) = $this->displayImg->getDisplayWidthHeight(
376 $maxWidth, $maxHeight, $page
377 );
378 $linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
379
380 $thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
381 # Generate thumbnails or thumbnail links as needed...
382 $otherSizes = [];
383 foreach ( $thumbSizes as $size ) {
384 // We include a thumbnail size in the list, if it is
385 // less than or equal to the original size of the image
386 // asset ($width_orig/$height_orig). We also exclude
387 // the current thumbnail's size ($width/$height)
388 // since that is added to the message separately, so
389 // it can be denoted as the current size being shown.
390 // Vectorized images are limited by $wgSVGMaxSize big,
391 // so all thumbs less than or equal that are shown.
392 if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
393 || ( $this->displayImg->isVectorized()
394 && max( $size[0], $size[1] ) <= $wgSVGMaxSize )
395 )
396 && $size[0] != $width && $size[1] != $height
397 ) {
398 $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
399 if ( $sizeLink ) {
400 $otherSizes[] = $sizeLink;
401 }
402 }
403 }
404 $otherSizes = array_unique( $otherSizes );
405
406 $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
407 $msgsmall = $this->getThumbPrevText( $params, $sizeLinkBigImagePreview );
408 if ( count( $otherSizes ) ) {
409 $msgsmall .= ' ' .
410 Html::rawElement(
411 'span',
412 [ 'class' => 'mw-filepage-other-resolutions' ],
413 $this->getContext()->msg( 'show-big-image-other' )
414 ->rawParams( $lang->pipeList( $otherSizes ) )
415 ->params( count( $otherSizes ) )
416 ->parse()
417 );
418 }
419 } elseif ( $width == 0 && $height == 0 ) {
420 # Some sort of audio file that doesn't have dimensions
421 # Don't output a no hi res message for such a file
422 $msgsmall = '';
423 } else {
424 # Image is small enough to show full size on image page
425 $msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
426 }
427
428 $params['width'] = $width;
429 $params['height'] = $height;
430 // Allow the MediaHandler to handle query string parameters on the file page,
431 // e.g. start time for videos (T203994)
432 $params['imagePageParams'] = $request->getQueryValuesOnly();
433 $thumbnail = $this->displayImg->transform( $params );
434 Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
435
436 $anchorclose = Html::rawElement(
437 'div',
438 [ 'class' => 'mw-filepage-resolutioninfo' ],
439 $msgsmall
440 );
441
442 $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
443 if ( $isMulti ) {
444 $out->addModules( 'mediawiki.page.image.pagination' );
445 $out->addHTML( '<table class="multipageimage"><tr><td>' );
446 }
447
448 if ( $thumbnail ) {
449 $options = [
450 'alt' => $this->displayImg->getTitle()->getPrefixedText(),
451 'file-link' => true,
452 ];
453 $out->addHTML( '<div class="fullImageLink" id="file">' .
454 $thumbnail->toHtml( $options ) .
455 $anchorclose . "</div>\n" );
456 }
457
458 if ( $isMulti ) {
459 $count = $this->displayImg->pageCount();
460 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
461
462 if ( $page > 1 ) {
463 $label = $this->getContext()->msg( 'imgmultipageprev' )->text();
464 // on the client side, this link is generated in ajaxifyPageNavigation()
465 // in the mediawiki.page.image.pagination module
467 $this->getTitle(),
468 $label,
469 [],
470 [ 'page' => $page - 1 ]
471 );
472 $thumb1 = Linker::makeThumbLinkObj(
473 $this->getTitle(),
474 $this->displayImg,
475 $link,
476 $label,
477 'none',
478 [ 'page' => $page - 1 ]
479 );
480 } else {
481 $thumb1 = '';
482 }
483
484 if ( $page < $count ) {
485 $label = $this->getContext()->msg( 'imgmultipagenext' )->text();
487 $this->getTitle(),
488 $label,
489 [],
490 [ 'page' => $page + 1 ]
491 );
492 $thumb2 = Linker::makeThumbLinkObj(
493 $this->getTitle(),
494 $this->displayImg,
495 $link,
496 $label,
497 'none',
498 [ 'page' => $page + 1 ]
499 );
500 } else {
501 $thumb2 = '';
502 }
503
504 global $wgScript;
505
506 $formParams = [
507 'name' => 'pageselector',
508 'action' => $wgScript,
509 ];
510 $options = [];
511 for ( $i = 1; $i <= $count; $i++ ) {
512 $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
513 }
514 $select = Xml::tags( 'select',
515 [ 'id' => 'pageselector', 'name' => 'page' ],
516 implode( "\n", $options ) );
517
518 $out->addHTML(
519 '</td><td><div class="multipageimagenavbox">' .
520 Xml::openElement( 'form', $formParams ) .
521 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
522 $this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() .
523 $this->getContext()->msg( 'word-separator' )->escaped() .
524 Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) .
525 Xml::closeElement( 'form' ) .
526 "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
527 );
528 }
529 } elseif ( $this->displayImg->isSafeFile() ) {
530 # if direct link is allowed but it's not a renderable image, show an icon.
531 $icon = $this->displayImg->iconThumb();
532
533 $out->addHTML( '<div class="fullImageLink" id="file">' .
534 $icon->toHtml( [ 'file-link' => true ] ) .
535 "</div>\n" );
536 }
537
538 $longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text();
539
540 $handler = $this->displayImg->getHandler();
541
542 // If this is a filetype with potential issues, warn the user.
543 if ( $handler ) {
544 $warningConfig = $handler->getWarningConfig( $this->displayImg );
545
546 if ( $warningConfig !== null ) {
547 // The warning will be displayed via CSS and JavaScript.
548 // We just need to tell the client side what message to use.
549 $output = $this->getContext()->getOutput();
550 $output->addJsConfigVars( 'wgFileWarning', $warningConfig );
551 $output->addModules( $warningConfig['module'] );
552 $output->addModules( 'mediawiki.filewarning' );
553 }
554 }
555
556 $medialink = "[[Media:$filename|$linktext]]";
557
558 if ( !$this->displayImg->isSafeFile() ) {
559 $warning = $this->getContext()->msg( 'mediawarning' )->plain();
560 // dirmark is needed here to separate the file name, which
561 // most likely ends in Latin characters, from the description,
562 // which may begin with the file type. In RTL environment
563 // this will get messy.
564 // The dirmark, however, must not be immediately adjacent
565 // to the filename, because it can get copied with it.
566 // See T27277.
567 // phpcs:disable Generic.Files.LineLength
568 $out->wrapWikiTextAsInterface( 'fullMedia', <<<EOT
569<span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span>
570EOT
571 );
572 // phpcs:enable
573 $out->wrapWikiTextAsInterface( 'mediaWarning', $warning );
574 } else {
575 $out->wrapWikiTextAsInterface( 'fullMedia', <<<EOT
576{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
577EOT
578 );
579 }
580
581 $renderLangOptions = $this->displayImg->getAvailableLanguages();
582 if ( count( $renderLangOptions ) >= 1 ) {
583 $out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $renderLang ) );
584 }
585
586 // Add cannot animate thumbnail warning
587 if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
588 // Include the extension so wiki admins can
589 // customize it on a per file-type basis
590 // (aka say things like use format X instead).
591 // additionally have a specific message for
592 // file-no-thumb-animation-gif
593 $ext = $this->displayImg->getExtension();
594 $noAnimMesg = wfMessageFallback(
595 'file-no-thumb-animation-' . $ext,
596 'file-no-thumb-animation'
597 )->plain();
598
599 $out->wrapWikiTextAsInterface( 'mw-noanimatethumb', $noAnimMesg );
600 }
601
602 if ( !$this->displayImg->isLocal() ) {
603 $this->printSharedImageText();
604 }
605 } else {
606 # Image does not exist
607 if ( !$this->getPage()->getId() ) {
609
610 # No article exists either
611 # Show deletion log to be consistent with normal articles
612 LogEventsList::showLogExtract(
613 $out,
614 [ 'delete', 'move', 'protect' ],
615 $this->getTitle()->getPrefixedText(),
616 '',
617 [ 'lim' => 10,
618 'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
619 'showIfEmpty' => false,
620 'msgKey' => [ 'moveddeleted-notice' ]
621 ]
622 );
623 }
624
625 if ( $wgEnableUploads && MediaWikiServices::getInstance()
627 ->userHasRight( $user, 'upload' )
628 ) {
629 // Only show an upload link if the user can upload
630 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
631 $nofile = [
632 'filepage-nofile-link',
633 $uploadTitle->getFullURL( [
634 'wpDestFile' => $this->getFile()->getName()
635 ] )
636 ];
637 } else {
638 $nofile = 'filepage-nofile';
639 }
640 // Note, if there is an image description page, but
641 // no image, then this setRobotPolicy is overridden
642 // by Article::View().
643 $out->setRobotPolicy( 'noindex,nofollow' );
644 $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
645 if ( !$this->getPage()->getId() && $wgSend404Code ) {
646 // If there is no image, no shared image, and no description page,
647 // output a 404, to be consistent with Article::showMissingArticle.
648 $request->response()->statusHeader( 404 );
649 }
650 }
651 $out->setFileVersion( $this->displayImg );
652 }
653
661 protected function getThumbPrevText( $params, $sizeLinkBigImagePreview ) {
662 if ( $sizeLinkBigImagePreview ) {
663 // Show a different message of preview is different format from original.
664 $previewTypeDiffers = false;
665 $origExt = $thumbExt = $this->displayImg->getExtension();
666 if ( $this->displayImg->getHandler() ) {
667 $origMime = $this->displayImg->getMimeType();
668 $typeParams = $params;
669 $this->displayImg->getHandler()->normaliseParams( $this->displayImg, $typeParams );
670 list( $thumbExt, $thumbMime ) = $this->displayImg->getHandler()->getThumbType(
671 $origExt, $origMime, $typeParams );
672 if ( $thumbMime !== $origMime ) {
673 $previewTypeDiffers = true;
674 }
675 }
676 if ( $previewTypeDiffers ) {
677 return $this->getContext()->msg( 'show-big-image-preview-differ' )->
678 rawParams( $sizeLinkBigImagePreview )->
679 params( strtoupper( $origExt ) )->
680 params( strtoupper( $thumbExt ) )->
681 parse();
682 } else {
683 return $this->getContext()->msg( 'show-big-image-preview' )->
684 rawParams( $sizeLinkBigImagePreview )->
685 parse();
686 }
687 } else {
688 return '';
689 }
690 }
691
699 protected function makeSizeLink( $params, $width, $height ) {
700 $params['width'] = $width;
701 $params['height'] = $height;
702 $thumbnail = $this->displayImg->transform( $params );
703 if ( $thumbnail && !$thumbnail->isError() ) {
704 return Html::rawElement( 'a', [
705 'href' => $thumbnail->getUrl(),
706 'class' => 'mw-thumbnail-link'
707 ], $this->getContext()->msg( 'show-big-image-size' )->numParams(
708 $thumbnail->getWidth(), $thumbnail->getHeight()
709 )->parse() );
710 } else {
711 return '';
712 }
713 }
714
718 protected function printSharedImageText() {
719 $out = $this->getContext()->getOutput();
720 $this->loadFile();
721
722 $descUrl = $this->getFile()->getDescriptionUrl();
723 $descText = $this->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
724
725 /* Add canonical to head if there is no local page for this shared file */
726 if ( $descUrl && !$this->getPage()->getId() ) {
727 $out->setCanonicalUrl( $descUrl );
728 }
729
730 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
731 $repo = $this->getFile()->getRepo()->getDisplayName();
732
733 if ( $descUrl &&
734 $descText &&
735 $this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-'
736 ) {
737 $out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-here', $repo, $descUrl ] );
738 } elseif ( $descUrl &&
739 $this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-'
740 ) {
741 $out->wrapWikiMsg( $wrap, [ 'sharedupload-desc-there', $repo, $descUrl ] );
742 } else {
743 $out->wrapWikiMsg( $wrap, [ 'sharedupload', $repo ], ''/*BACKCOMPAT*/ );
744 }
745
746 if ( $descText ) {
747 $this->mExtraDescription = $descText;
748 }
749 }
750
751 public function getUploadUrl() {
752 $this->loadFile();
753 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
754 return $uploadTitle->getFullURL( [
755 'wpDestFile' => $this->getFile()->getName(),
756 'wpForReUpload' => 1
757 ] );
758 }
759
763 protected function uploadLinksBox() {
764 if ( !$this->getContext()->getConfig()->get( 'EnableUploads' ) ) {
765 return;
766 }
767
768 $this->loadFile();
769 if ( !$this->getFile()->isLocal() ) {
770 return;
771 }
772
773 $canUpload = MediaWikiServices::getInstance()->getPermissionManager()
774 ->quickUserCan( 'upload', $this->getContext()->getUser(), $this->getTitle() );
775 if ( $canUpload && UploadBase::userCanReUpload(
776 $this->getContext()->getUser(),
777 $this->getFile() )
778 ) {
779 // "Upload a new version of this file" link
781 $this->getUploadUrl(),
782 $this->getContext()->msg( 'uploadnewversion-linktext' )->text()
783 );
784 $attrs = [ 'class' => 'plainlinks', 'id' => 'mw-imagepage-reupload-link' ];
785 $linkPara = Html::rawElement( 'p', $attrs, $ulink );
786 } else {
787 // "You cannot overwrite this file." message
788 $attrs = [ 'id' => 'mw-imagepage-upload-disallowed' ];
789 $msg = $this->getContext()->msg( 'upload-disallowed-here' )->text();
790 $linkPara = Html::element( 'p', $attrs, $msg );
791 }
792
793 $uploadLinks = Html::rawElement( 'div', [ 'class' => 'mw-imagepage-upload-links' ], $linkPara );
794 $this->getContext()->getOutput()->addHTML( $uploadLinks );
795 }
796
800 protected function closeShowImage() {
801 }
802
807 protected function imageHistory() {
808 $this->loadFile();
809 $out = $this->getContext()->getOutput();
810 $pager = new ImageHistoryPseudoPager( $this );
811 $out->addHTML( $pager->getBody() );
812 $out->preventClickjacking( $pager->getPreventClickjacking() );
813
814 $this->getFile()->resetHistory(); // free db resources
815
816 # Exist check because we don't want to show this on pages where an image
817 # doesn't exist along with the noimage message, that would suck. -ævar
818 if ( $this->getFile()->exists() ) {
819 $this->uploadLinksBox();
820 }
821 }
822
828 protected function queryImageLinks( $target, $limit ) {
830
831 return $dbr->select(
832 [ 'imagelinks', 'page' ],
833 [ 'page_namespace', 'page_title', 'il_to' ],
834 [ 'il_to' => $target, 'il_from = page_id' ],
835 __METHOD__,
836 [ 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ]
837 );
838 }
839
840 protected function imageLinks() {
841 $limit = 100;
842
843 $out = $this->getContext()->getOutput();
844
845 $rows = [];
846 $redirects = [];
847 foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
848 $redirects[$redir->getDBkey()] = [];
849 $rows[] = (object)[
850 'page_namespace' => NS_FILE,
851 'page_title' => $redir->getDBkey(),
852 ];
853 }
854
855 $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
856 foreach ( $res as $row ) {
857 $rows[] = $row;
858 }
859 $count = count( $rows );
860
861 $hasMore = $count > $limit;
862 if ( !$hasMore && count( $redirects ) ) {
863 $res = $this->queryImageLinks( array_keys( $redirects ),
864 $limit - count( $rows ) + 1 );
865 foreach ( $res as $row ) {
866 $redirects[$row->il_to][] = $row;
867 $count++;
868 }
869 $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
870 }
871
872 if ( $count == 0 ) {
873 $out->wrapWikiMsg(
874 Html::rawElement( 'div',
875 [ 'id' => 'mw-imagepage-nolinkstoimage' ], "\n$1\n" ),
876 'nolinkstoimage'
877 );
878 return;
879 }
880
881 $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
882 if ( !$hasMore ) {
883 $out->addWikiMsg( 'linkstoimage', $count );
884 } else {
885 // More links than the limit. Add a link to [[Special:Whatlinkshere]]
886 $out->addWikiMsg( 'linkstoimage-more',
887 $this->getContext()->getLanguage()->formatNum( $limit ),
888 $this->getTitle()->getPrefixedDBkey()
889 );
890 }
891
892 $out->addHTML(
893 Html::openElement( 'ul',
894 [ 'class' => 'mw-imagepage-linkstoimage' ] ) . "\n"
895 );
896 // Sort the list by namespace:title
897 usort( $rows, [ $this, 'compare' ] );
898
899 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
900
901 // Create links for every element
902 $currentCount = 0;
903 foreach ( $rows as $element ) {
904 $currentCount++;
905 if ( $currentCount > $limit ) {
906 break;
907 }
908
909 $query = [];
910 # Add a redirect=no to make redirect pages reachable
911 if ( isset( $redirects[$element->page_title] ) ) {
912 $query['redirect'] = 'no';
913 }
915 Title::makeTitle( $element->page_namespace, $element->page_title ),
916 null, [], $query
917 );
918 if ( !isset( $redirects[$element->page_title] ) ) {
919 # No redirects
920 $liContents = $link;
921 } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
922 # Redirect without usages
923 $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )
924 ->rawParams( $link, '' )
925 ->parse();
926 } else {
927 # Redirect with usages
928 $li = '';
929 foreach ( $redirects[$element->page_title] as $row ) {
930 $currentCount++;
931 if ( $currentCount > $limit ) {
932 break;
933 }
934
936 Title::makeTitle( $row->page_namespace, $row->page_title ) );
937 $li .= Html::rawElement(
938 'li',
939 [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
940 $link2
941 ) . "\n";
942 }
943
944 $ul = Html::rawElement(
945 'ul',
946 [ 'class' => 'mw-imagepage-redirectstofile' ],
947 $li
948 ) . "\n";
949 $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
950 $link, $ul )->parse();
951 }
952 $out->addHTML( Html::rawElement(
953 'li',
954 [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
955 $liContents
956 ) . "\n"
957 );
958
959 }
960 $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
961 $res->free();
962
963 // Add a links to [[Special:Whatlinkshere]]
964 if ( $currentCount > $limit ) {
965 $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
966 }
967 $out->addHTML( Html::closeElement( 'div' ) . "\n" );
968 }
969
970 protected function imageDupes() {
971 $this->loadFile();
972 $out = $this->getContext()->getOutput();
973
974 $dupes = $this->getPage()->getDuplicates();
975 if ( count( $dupes ) == 0 ) {
976 return;
977 }
978
979 $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
980 $out->addWikiMsg( 'duplicatesoffile',
981 $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
982 );
983 $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
984
985 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
986
990 foreach ( $dupes as $file ) {
991 $fromSrc = '';
992 if ( $file->isLocal() ) {
993 $link = $linkRenderer->makeKnownLink( $file->getTitle() );
994 } else {
995 $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
996 $file->getTitle()->getPrefixedText() );
997 $fromSrc = $this->getContext()->msg(
998 'shared-repo-from',
999 $file->getRepo()->getDisplayName()
1000 )->escaped();
1001 }
1002 $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
1003 }
1004 $out->addHTML( "</ul></div>\n" );
1005 }
1006
1010 public function delete() {
1011 $file = $this->getFile();
1012 if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
1013 // Standard article deletion
1014 parent::delete();
1015 return;
1016 }
1017 '@phan-var LocalFile $file';
1018
1019 $deleter = new FileDeleteForm( $file, $this->getContext()->getUser() );
1020 $deleter->execute();
1021 }
1022
1028 public function showError( $description ) {
1029 $out = $this->getContext()->getOutput();
1030 $out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
1031 $out->setRobotPolicy( 'noindex,nofollow' );
1032 $out->setArticleRelated( false );
1033 $out->enableClientCache( false );
1034 $out->addWikiTextAsInterface( $description );
1035 }
1036
1045 protected function compare( $a, $b ) {
1046 return $a->page_namespace <=> $b->page_namespace
1047 ?: strcmp( $a->page_title, $b->page_title );
1048 }
1049
1059 public function getImageLimitsFromOption( $user, $optionName ) {
1060 return MediaFileTrait::getImageLimitsFromOption( $user, $optionName );
1061 }
1062
1070 protected function doRenderLangOpt( array $langChoices, $renderLang ) {
1071 global $wgScript;
1072 $opts = '';
1073
1074 $matchedRenderLang = $this->displayImg->getMatchedLanguage( $renderLang );
1075
1076 foreach ( $langChoices as $lang ) {
1077 $opts .= $this->createXmlOptionStringForLanguage(
1078 $lang,
1079 $matchedRenderLang === $lang
1080 );
1081 }
1082
1083 // Allow for the default case in an svg <switch> that is displayed if no
1084 // systemLanguage attribute matches
1085 $opts .= "\n" .
1086 Xml::option(
1087 $this->getContext()->msg( 'img-lang-default' )->text(),
1088 'und',
1089 $matchedRenderLang === null
1090 );
1091
1092 $select = Html::rawElement(
1093 'select',
1094 [ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
1095 $opts
1096 );
1097 $submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
1098
1099 $formContents = $this->getContext()->msg( 'img-lang-info' )
1100 ->rawParams( $select, $submit )
1101 ->parse();
1102 $formContents .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1103
1104 $langSelectLine = Html::rawElement( 'div', [ 'id' => 'mw-imglangselector-line' ],
1105 Html::rawElement( 'form', [ 'action' => $wgScript ], $formContents )
1106 );
1107 return $langSelectLine;
1108 }
1109
1115 private function createXmlOptionStringForLanguage( $lang, $selected ) {
1116 $code = LanguageCode::bcp47( $lang );
1117 $name = MediaWikiServices::getInstance()
1118 ->getLanguageNameUtils()
1119 ->getLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1120 if ( $name !== '' ) {
1121 $display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
1122 } else {
1123 $display = $code;
1124 }
1125 return "\n" .
1126 Xml::option(
1127 $display,
1128 $lang,
1129 $selected
1130 );
1131 }
1132
1142 protected function getThumbSizes( $origWidth, $origHeight ) {
1143 global $wgImageLimits;
1144 if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1145 $thumbSizes = $wgImageLimits;
1146 // Also include the full sized resolution in the list, so
1147 // that users know they can get it. This will link to the
1148 // original file asset if mustRender() === false. In the case
1149 // that we mustRender, some users have indicated that they would
1150 // find it useful to have the full size image in the rendered
1151 // image format.
1152 $thumbSizes[] = [ $origWidth, $origHeight ];
1153 } else {
1154 # Creating thumb links triggers thumbnail generation.
1155 # Just generate the thumb for the current users prefs.
1156 $thumbSizes = [
1157 $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' )
1158 ];
1159 if ( !$this->displayImg->mustRender() ) {
1160 // We can safely include a link to the "full-size" preview,
1161 // without actually rendering.
1162 $thumbSizes[] = [ $origWidth, $origHeight ];
1163 }
1164 }
1165 return $thumbSizes;
1166 }
1167
1172 public function getFile() {
1173 return $this->getPage()->getFile();
1174 }
1175
1180 public function isLocal() {
1181 return $this->getPage()->isLocal();
1182 }
1183
1188 public function getDuplicates() {
1189 return $this->getPage()->getDuplicates();
1190 }
1191
1196 public function getForeignCategories() {
1197 return $this->getPage()->getForeignCategories();
1198 }
1199
1200}
getPermissionManager()
getUser()
$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.
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:46
getOldID()
Definition Article.php:360
LinkRenderer $linkRenderer
Definition Article.php:130
getPage()
Get the WikiPage object of this instance.
Definition Article.php:265
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
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
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.
bool $mExtraDescription
Definition ImagePage.php:46
getEmptyPageParserOutput(ParserOptions $options)
Overloading Article's getEmptyPageParserOutput method.
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:639
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:490
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:846
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
MediaWikiServices is the service locator for the application scope of MediaWiki.
Set options of the Parser.
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:42
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.
const NS_FILE
Definition Defines.php:76
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