MediaWiki  master
ImagePage.php
Go to the documentation of this file.
1 <?php
25 
33 class 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  Hooks::run( 'ImagePageFindFile', [ $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  Hooks::run( 'ImagePageAfterImageLinks', [ $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  Hooks::run( 'ImagePageShowTOC', [ $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  // Avoid PHP 7.1 warning from passing $this by reference
364  $imagePage = $this;
365 
366  Hooks::run( 'ImageOpenShowImageInlineBefore', [ &$imagePage, &$out ] );
367 
368  if ( $this->displayImg->allowInlineDisplay() ) {
369  # image
370  # "Download high res version" link below the image
371  # $msgsize = $this->getContext()->msg( 'file-info-size', $width_orig, $height_orig,
372  # Language::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
373  # We'll show a thumbnail of this image
374  if ( $width > $maxWidth ||
375  $height > $maxHeight ||
376  $this->displayImg->isVectorized()
377  ) {
378  list( $width, $height ) = $this->displayImg->getDisplayWidthHeight(
379  $maxWidth, $maxHeight, $page
380  );
381  $linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
382 
383  $thumbSizes = $this->getThumbSizes( $width_orig, $height_orig );
384  # Generate thumbnails or thumbnail links as needed...
385  $otherSizes = [];
386  foreach ( $thumbSizes as $size ) {
387  // We include a thumbnail size in the list, if it is
388  // less than or equal to the original size of the image
389  // asset ($width_orig/$height_orig). We also exclude
390  // the current thumbnail's size ($width/$height)
391  // since that is added to the message separately, so
392  // it can be denoted as the current size being shown.
393  // Vectorized images are limited by $wgSVGMaxSize big,
394  // so all thumbs less than or equal that are shown.
395  if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
396  || ( $this->displayImg->isVectorized()
397  && max( $size[0], $size[1] ) <= $wgSVGMaxSize )
398  )
399  && $size[0] != $width && $size[1] != $height
400  ) {
401  $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
402  if ( $sizeLink ) {
403  $otherSizes[] = $sizeLink;
404  }
405  }
406  }
407  $otherSizes = array_unique( $otherSizes );
408 
409  $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
410  $msgsmall = $this->getThumbPrevText( $params, $sizeLinkBigImagePreview );
411  if ( count( $otherSizes ) ) {
412  $msgsmall .= ' ' .
413  Html::rawElement(
414  'span',
415  [ 'class' => 'mw-filepage-other-resolutions' ],
416  $this->getContext()->msg( 'show-big-image-other' )
417  ->rawParams( $lang->pipeList( $otherSizes ) )
418  ->params( count( $otherSizes ) )
419  ->parse()
420  );
421  }
422  } elseif ( $width == 0 && $height == 0 ) {
423  # Some sort of audio file that doesn't have dimensions
424  # Don't output a no hi res message for such a file
425  $msgsmall = '';
426  } else {
427  # Image is small enough to show full size on image page
428  $msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
429  }
430 
431  $params['width'] = $width;
432  $params['height'] = $height;
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
466  $link = $linkRenderer->makeKnownLink(
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();
486  $link = $linkRenderer->makeKnownLink(
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>
570 EOT
571  );
572  // phpcs:enable
573  $out->wrapWikiTextAsInterface( 'mediaWarning', $warning );
574  } else {
575  $out->wrapWikiTextAsInterface( 'fullMedia', <<<EOT
576 {$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
577 EOT
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() ) {
608  $dbr = wfGetDB( DB_REPLICA );
609 
610  # No article exists either
611  # Show deletion log to be consistent with normal articles
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()
626  ->getPermissionManager()
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
780  $ulink = Linker::makeExternalLink(
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 ) {
829  $dbr = wfGetDB( DB_REPLICA );
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  $count = 0;
897 
898  // Sort the list by namespace:title
899  usort( $rows, [ $this, 'compare' ] );
900 
901  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
902 
903  // Create links for every element
904  $currentCount = 0;
905  foreach ( $rows as $element ) {
906  $currentCount++;
907  if ( $currentCount > $limit ) {
908  break;
909  }
910 
911  $query = [];
912  # Add a redirect=no to make redirect pages reachable
913  if ( isset( $redirects[$element->page_title] ) ) {
914  $query['redirect'] = 'no';
915  }
916  $link = $linkRenderer->makeKnownLink(
917  Title::makeTitle( $element->page_namespace, $element->page_title ),
918  null, [], $query
919  );
920  if ( !isset( $redirects[$element->page_title] ) ) {
921  # No redirects
922  $liContents = $link;
923  } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
924  # Redirect without usages
925  $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )
926  ->rawParams( $link, '' )
927  ->parse();
928  } else {
929  # Redirect with usages
930  $li = '';
931  foreach ( $redirects[$element->page_title] as $row ) {
932  $currentCount++;
933  if ( $currentCount > $limit ) {
934  break;
935  }
936 
937  $link2 = $linkRenderer->makeKnownLink(
938  Title::makeTitle( $row->page_namespace, $row->page_title ) );
939  $li .= Html::rawElement(
940  'li',
941  [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
942  $link2
943  ) . "\n";
944  }
945 
946  $ul = Html::rawElement(
947  'ul',
948  [ 'class' => 'mw-imagepage-redirectstofile' ],
949  $li
950  ) . "\n";
951  $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
952  $link, $ul )->parse();
953  }
954  $out->addHTML( Html::rawElement(
955  'li',
956  [ 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ],
957  $liContents
958  ) . "\n"
959  );
960 
961  }
962  $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
963  $res->free();
964 
965  // Add a links to [[Special:Whatlinkshere]]
966  if ( $count > $limit ) {
967  $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
968  }
969  $out->addHTML( Html::closeElement( 'div' ) . "\n" );
970  }
971 
972  protected function imageDupes() {
973  $this->loadFile();
974  $out = $this->getContext()->getOutput();
975 
976  $dupes = $this->getPage()->getDuplicates();
977  if ( count( $dupes ) == 0 ) {
978  return;
979  }
980 
981  $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
982  $out->addWikiMsg( 'duplicatesoffile',
983  $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
984  );
985  $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
986 
987  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
988 
992  foreach ( $dupes as $file ) {
993  $fromSrc = '';
994  if ( $file->isLocal() ) {
995  $link = $linkRenderer->makeKnownLink( $file->getTitle() );
996  } else {
997  $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
998  $file->getTitle()->getPrefixedText() );
999  $fromSrc = $this->getContext()->msg(
1000  'shared-repo-from',
1001  $file->getRepo()->getDisplayName()
1002  )->escaped();
1003  }
1004  $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
1005  }
1006  $out->addHTML( "</ul></div>\n" );
1007  }
1008 
1012  public function delete() {
1013  $file = $this->getFile();
1014  if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
1015  // Standard article deletion
1016  parent::delete();
1017  return;
1018  }
1019  '@phan-var LocalFile $file';
1020 
1021  $deleter = new FileDeleteForm( $file, $this->getContext()->getUser() );
1022  $deleter->execute();
1023  }
1024 
1030  function showError( $description ) {
1031  $out = $this->getContext()->getOutput();
1032  $out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
1033  $out->setRobotPolicy( 'noindex,nofollow' );
1034  $out->setArticleRelated( false );
1035  $out->enableClientCache( false );
1036  $out->addWikiTextAsInterface( $description );
1037  }
1038 
1047  protected function compare( $a, $b ) {
1048  return $a->page_namespace <=> $b->page_namespace
1049  ?: strcmp( $a->page_title, $b->page_title );
1050  }
1051 
1061  public function getImageLimitsFromOption( $user, $optionName ) {
1062  return MediaFileTrait::getImageLimitsFromOption( $user, $optionName );
1063  }
1064 
1072  protected function doRenderLangOpt( array $langChoices, $renderLang ) {
1073  global $wgScript;
1074  $opts = '';
1075 
1076  $matchedRenderLang = $this->displayImg->getMatchedLanguage( $renderLang );
1077 
1078  foreach ( $langChoices as $lang ) {
1079  $opts .= $this->createXmlOptionStringForLanguage(
1080  $lang,
1081  $matchedRenderLang === $lang
1082  );
1083  }
1084 
1085  // Allow for the default case in an svg <switch> that is displayed if no
1086  // systemLanguage attribute matches
1087  $opts .= "\n" .
1088  Xml::option(
1089  $this->getContext()->msg( 'img-lang-default' )->text(),
1090  'und',
1091  $matchedRenderLang === null
1092  );
1093 
1094  $select = Html::rawElement(
1095  'select',
1096  [ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
1097  $opts
1098  );
1099  $submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
1100 
1101  $formContents = $this->getContext()->msg( 'img-lang-info' )
1102  ->rawParams( $select, $submit )
1103  ->parse();
1104  $formContents .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1105 
1106  $langSelectLine = Html::rawElement( 'div', [ 'id' => 'mw-imglangselector-line' ],
1107  Html::rawElement( 'form', [ 'action' => $wgScript ], $formContents )
1108  );
1109  return $langSelectLine;
1110  }
1111 
1117  private function createXmlOptionStringForLanguage( $lang, $selected ) {
1118  $code = LanguageCode::bcp47( $lang );
1119  $name = MediaWikiServices::getInstance()
1120  ->getLanguageNameUtils()
1121  ->getLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1122  if ( $name !== '' ) {
1123  $display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
1124  } else {
1125  $display = $code;
1126  }
1127  return "\n" .
1128  Xml::option(
1129  $display,
1130  $lang,
1131  $selected
1132  );
1133  }
1134 
1144  protected function getThumbSizes( $origWidth, $origHeight ) {
1145  global $wgImageLimits;
1146  if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1147  $thumbSizes = $wgImageLimits;
1148  // Also include the full sized resolution in the list, so
1149  // that users know they can get it. This will link to the
1150  // original file asset if mustRender() === false. In the case
1151  // that we mustRender, some users have indicated that they would
1152  // find it useful to have the full size image in the rendered
1153  // image format.
1154  $thumbSizes[] = [ $origWidth, $origHeight ];
1155  } else {
1156  # Creating thumb links triggers thumbnail generation.
1157  # Just generate the thumb for the current users prefs.
1158  $thumbSizes = [
1159  $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' )
1160  ];
1161  if ( !$this->displayImg->mustRender() ) {
1162  // We can safely include a link to the "full-size" preview,
1163  // without actually rendering.
1164  $thumbSizes[] = [ $origWidth, $origHeight ];
1165  }
1166  }
1167  return $thumbSizes;
1168  }
1169 
1174  public function getFile() {
1175  return $this->getPage()->getFile();
1176  }
1177 
1182  public function isLocal() {
1183  return $this->getPage()->isLocal();
1184  }
1185 
1190  public function getDuplicates() {
1191  return $this->getPage()->getDuplicates();
1192  }
1193 
1198  public function getForeignCategories() {
1199  return $this->getPage()->getForeignCategories();
1200  }
1201 
1202 }
$wgSend404Code
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
Definition: DefaultSettings.php:3556
ImagePage\makeSizeLink
makeSizeLink( $params, $width, $height)
Creates an thumbnail of specified size and returns an HTML link to it.
Definition: ImagePage.php:699
ImagePage\getForeignCategories
getForeignCategories()
Definition: ImagePage.php:1198
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
wfMessageFallback
wfMessageFallback(... $keys)
This function accepts multiple message keys and returns a message instance for the first message whic...
Definition: GlobalFunctions.php:1224
ParserOutput
Definition: ParserOutput.php:25
ImagePage\getUploadUrl
getUploadUrl()
Definition: ImagePage.php:751
ImagePage\createXmlOptionStringForLanguage
createXmlOptionStringForLanguage( $lang, $selected)
Definition: ImagePage.php:1117
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
ImagePage\closeShowImage
closeShowImage()
For overloading.
Definition: ImagePage.php:800
ImagePage\openShowImage
openShowImage()
Definition: ImagePage.php:328
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
ImagePage\getFile
getFile()
Definition: ImagePage.php:1174
$wgScript
$wgScript
The URL path to index.php.
Definition: DefaultSettings.php:186
$wgSVGMaxSize
$wgSVGMaxSize
Don't scale a SVG larger than this.
Definition: DefaultSettings.php:1245
ImagePage\$fileLoaded
bool $fileLoaded
Definition: ImagePage.php:43
ImagePage\uploadLinksBox
uploadLinksBox()
Add the re-upload link (or message about not being able to re-upload) to the output.
Definition: ImagePage.php:763
NS_FILE
const NS_FILE
Definition: Defines.php:75
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
Xml\option
static option( $text, $value=null, $selected=false, $attribs=[])
Convenience function to build an HTML drop-down list item.
Definition: Xml.php:486
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:83
Linker\processResponsiveImages
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition: Linker.php:643
ImagePage
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:33
$res
$res
Definition: testCompression.php:57
Linker\makeThumbLinkObj
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:494
ImagePage\setFile
setFile( $file)
Definition: ImagePage.php:61
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
Article\$linkRenderer
LinkRenderer $linkRenderer
Definition: Article.php:135
ImagePage\$repo
FileRepo $repo
Definition: ImagePage.php:40
$dbr
$dbr
Definition: testCompression.php:54
ImagePage\getLanguageForRendering
getLanguageForRendering(WebRequest $request, File $file)
Returns language code to be used for dispaying the image, based on request context and languages avai...
Definition: ImagePage.php:313
FileRepo
Base class for file repositories.
Definition: FileRepo.php:41
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
Article\getTitle
getTitle()
Get the title object of the article.
Definition: Article.php:242
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
ImagePage\showTOC
showTOC( $metadata)
Create the TOC.
Definition: ImagePage.php:236
Article\exists
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2428
ImagePage\imageLinks
imageLinks()
Definition: ImagePage.php:840
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2500
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ImagePage\newPage
newPage(Title $title)
Definition: ImagePage.php:52
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:252
Article\getContext
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2279
FileDeleteForm
File deletion user interface.
Definition: FileDeleteForm.php:31
$title
$title
Definition: testCompression.php:38
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:849
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:595
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:634
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ImagePage\showError
showError( $description)
Display an error with a wikitext description.
Definition: ImagePage.php:1030
ImagePage\view
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: ImagePage.php:90
ImagePage\imageDupes
imageDupes()
Definition: ImagePage.php:972
ImagePage\makeMetadataTable
makeMetadataTable( $metadata)
Make a table with metadata to be shown in the output page.
Definition: ImagePage.php:262
ImagePage\getThumbPrevText
getThumbPrevText( $params, $sizeLinkBigImagePreview)
Make the text under the image to say what size preview.
Definition: ImagePage.php:661
ImagePage\getThumbSizes
getThumbSizes( $origWidth, $origHeight)
Get alternative thumbnail sizes.
Definition: ImagePage.php:1144
$ul
$ul
Definition: upgradeLogging.php:180
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:130
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1488
ImagePage\getDisplayedFile
getDisplayedFile()
Definition: ImagePage.php:225
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
$wgImageLimits
$wgImageLimits
Limit images on image description pages to a user-selectable limit.
Definition: DefaultSettings.php:1493
Title
Represents a title within MediaWiki.
Definition: Title.php:42
ImagePage\queryImageLinks
queryImageLinks( $target, $limit)
Definition: ImagePage.php:828
Article\getUser
getUser( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2651
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
WebRequest\getVal
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
Definition: WebRequest.php:487
ImagePage\$displayImg
File false $displayImg
Definition: ImagePage.php:37
ImagePage\doRenderLangOpt
doRenderLangOpt(array $langChoices, $renderLang)
Output a drop-down box for language options for the file.
Definition: ImagePage.php:1072
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:175
$wgEnableUploads
$wgEnableUploads
Allow users to upload files.
Definition: DefaultSettings.php:429
ImagePage\compare
compare( $a, $b)
Callback for usort() to do link sorts by (namespace, title) Function copied from Title::compare()
Definition: ImagePage.php:1047
Article\getId
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2546
ImagePage\getImageLimitsFromOption
getImageLimitsFromOption( $user, $optionName)
Returns the corresponding $wgImageLimits entry for the selected user option.
Definition: ImagePage.php:1061
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:31
ImageHistoryPseudoPager
Definition: ImageHistoryPseudoPager.php:23
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:45
ImagePage\loadFile
loadFile()
Definition: ImagePage.php:67
ImagePage\isLocal
isLocal()
Definition: ImagePage.php:1182
Article\getOldID
getOldID()
Definition: Article.php:347
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
ImagePage\$mExtraDescription
bool $mExtraDescription
Definition: ImagePage.php:46
ImagePage\getEmptyPageParserOutput
getEmptyPageParserOutput(ParserOptions $options)
Overloading Article's getEmptyPageParserOutput method.
Definition: ImagePage.php:293
$wgShowEXIF
$wgShowEXIF
Show Exif data, on by default if available.
Definition: DefaultSettings.php:831
ImagePage\getDuplicates
getDuplicates()
Definition: ImagePage.php:1190
ImagePage\printSharedImageText
printSharedImageText()
Show a notice that the file is from a shared repository.
Definition: ImagePage.php:718
ImagePage\imageHistory
imageHistory()
If the page we've just displayed is in the "Image" namespace, we follow it with an upload history of ...
Definition: ImagePage.php:807
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:461
$type
$type
Definition: testCompression.php:52