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