ThumbnailImage.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. /**
  3. * Base class for the output of file transformation methods.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Media
  22. */
  23. /**
  24. * Media transform output for images
  25. *
  26. * @ingroup Media
  27. */
  28. class ThumbnailImage extends MediaTransformOutput {
  29. private static $firstNonIconImageRendered = false;
  30. /**
  31. * Get a thumbnail object from a file and parameters.
  32. * If $path is set to null, the output file is treated as a source copy.
  33. * If $path is set to false, no output file will be created.
  34. * $parameters should include, as a minimum, (file) 'width' and 'height'.
  35. * It may also include a 'page' parameter for multipage files.
  36. *
  37. * @param File $file
  38. * @param string $url URL path to the thumb
  39. * @param string|bool $path Filesystem path to the thumb
  40. * @param array $parameters Associative array of parameters
  41. */
  42. function __construct( $file, $url, $path = false, $parameters = [] ) {
  43. # Previous parameters:
  44. # $file, $url, $width, $height, $path = false, $page = false
  45. $defaults = [
  46. 'page' => false,
  47. 'lang' => false
  48. ];
  49. if ( is_array( $parameters ) ) {
  50. $actualParams = $parameters + $defaults;
  51. } else {
  52. # Using old format, should convert. Later a warning could be added here.
  53. $numArgs = func_num_args();
  54. $actualParams = [
  55. 'width' => $path,
  56. 'height' => $parameters,
  57. 'page' => ( $numArgs > 5 ) ? func_get_arg( 5 ) : false
  58. ] + $defaults;
  59. $path = ( $numArgs > 4 ) ? func_get_arg( 4 ) : false;
  60. }
  61. $this->file = $file;
  62. $this->url = $url;
  63. $this->path = $path;
  64. # These should be integers when they get here.
  65. # If not, there's a bug somewhere. But let's at
  66. # least produce valid HTML code regardless.
  67. $this->width = round( $actualParams['width'] );
  68. $this->height = round( $actualParams['height'] );
  69. $this->page = $actualParams['page'];
  70. $this->lang = $actualParams['lang'];
  71. }
  72. /**
  73. * Return HTML <img ... /> tag for the thumbnail, will include
  74. * width and height attributes and a blank alt text (as required).
  75. *
  76. * @param array $options Associative array of options. Boolean options
  77. * should be indicated with a value of true for true, and false or
  78. * absent for false.
  79. *
  80. * alt HTML alt attribute
  81. * title HTML title attribute
  82. * desc-link Boolean, show a description link
  83. * file-link Boolean, show a file download link
  84. * valign vertical-align property, if the output is an inline element
  85. * img-class Class applied to the \<img\> tag, if there is such a tag
  86. * desc-query String, description link query params
  87. * override-width Override width attribute. Should generally not set
  88. * override-height Override height attribute. Should generally not set
  89. * no-dimensions Boolean, skip width and height attributes (useful if
  90. * set in CSS)
  91. * custom-url-link Custom URL to link to
  92. * custom-title-link Custom Title object to link to
  93. * custom target-link Value of the target attribute, for custom-target-link
  94. * parser-extlink-* Attributes added by parser for external links:
  95. * parser-extlink-rel: add rel="nofollow"
  96. * parser-extlink-target: link target, but overridden by custom-target-link
  97. *
  98. * For images, desc-link and file-link are implemented as a click-through. For
  99. * sounds and videos, they may be displayed in other ways.
  100. *
  101. * @throws MWException
  102. * @return string
  103. */
  104. function toHtml( $options = [] ) {
  105. global $wgPriorityHints, $wgPriorityHintsRatio, $wgElementTiming, $wgNativeImageLazyLoading;
  106. if ( func_num_args() == 2 ) {
  107. throw new MWException( __METHOD__ . ' called in the old style' );
  108. }
  109. $alt = $options['alt'] ?? '';
  110. $query = $options['desc-query'] ?? '';
  111. $attribs = [
  112. 'alt' => $alt,
  113. 'src' => $this->url,
  114. 'decoding' => 'async',
  115. ];
  116. if ( $wgNativeImageLazyLoading ) {
  117. $attribs['loading'] = 'lazy';
  118. }
  119. $elementTimingName = 'thumbnail';
  120. if ( $wgPriorityHints
  121. && !self::$firstNonIconImageRendered
  122. && $this->width * $this->height > 100 * 100 ) {
  123. self::$firstNonIconImageRendered = true;
  124. // Generate a random number between 0.01 and 1.0, included
  125. $random = rand( 1, 100 ) / 100.0;
  126. if ( $random <= $wgPriorityHintsRatio ) {
  127. $attribs['importance'] = 'high';
  128. $elementTimingName = 'thumbnail-high';
  129. } else {
  130. // This lets us track that the thumbnail *would* have gotten high priority but didn't.
  131. $elementTimingName = 'thumbnail-top';
  132. }
  133. }
  134. if ( $wgElementTiming ) {
  135. $attribs['elementtiming'] = $elementTimingName;
  136. }
  137. if ( !empty( $options['custom-url-link'] ) ) {
  138. $linkAttribs = [ 'href' => $options['custom-url-link'] ];
  139. if ( !empty( $options['title'] ) ) {
  140. $linkAttribs['title'] = $options['title'];
  141. }
  142. if ( !empty( $options['custom-target-link'] ) ) {
  143. $linkAttribs['target'] = $options['custom-target-link'];
  144. } elseif ( !empty( $options['parser-extlink-target'] ) ) {
  145. $linkAttribs['target'] = $options['parser-extlink-target'];
  146. }
  147. if ( !empty( $options['parser-extlink-rel'] ) ) {
  148. $linkAttribs['rel'] = $options['parser-extlink-rel'];
  149. }
  150. } elseif ( !empty( $options['custom-title-link'] ) ) {
  151. /** @var Title $title */
  152. $title = $options['custom-title-link'];
  153. $linkAttribs = [
  154. 'href' => $title->getLinkURL(),
  155. 'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
  156. ];
  157. } elseif ( !empty( $options['desc-link'] ) ) {
  158. $linkAttribs = $this->getDescLinkAttribs(
  159. empty( $options['title'] ) ? null : $options['title'],
  160. $query
  161. );
  162. } elseif ( !empty( $options['file-link'] ) ) {
  163. $linkAttribs = [ 'href' => $this->file->getUrl() ];
  164. } else {
  165. $linkAttribs = false;
  166. if ( !empty( $options['title'] ) ) {
  167. $attribs['title'] = $options['title'];
  168. }
  169. }
  170. if ( empty( $options['no-dimensions'] ) ) {
  171. $attribs['width'] = $this->width;
  172. $attribs['height'] = $this->height;
  173. }
  174. if ( !empty( $options['valign'] ) ) {
  175. $attribs['style'] = "vertical-align: {$options['valign']}";
  176. }
  177. if ( !empty( $options['img-class'] ) ) {
  178. $attribs['class'] = $options['img-class'];
  179. }
  180. if ( isset( $options['override-height'] ) ) {
  181. $attribs['height'] = $options['override-height'];
  182. }
  183. if ( isset( $options['override-width'] ) ) {
  184. $attribs['width'] = $options['override-width'];
  185. }
  186. // Additional densities for responsive images, if specified.
  187. // If any of these urls is the same as src url, it'll be excluded.
  188. $responsiveUrls = array_diff( $this->responsiveUrls, [ $this->url ] );
  189. if ( !empty( $responsiveUrls ) ) {
  190. $attribs['srcset'] = Html::srcSet( $responsiveUrls );
  191. }
  192. Hooks::run( 'ThumbnailBeforeProduceHTML', [ $this, &$attribs, &$linkAttribs ] );
  193. return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
  194. }
  195. }