Generic.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <?php
  2. /**
  3. * Media-handling base classes and generic functionality
  4. * @file
  5. * @ingroup Media
  6. */
  7. /**
  8. * Base media handler class
  9. *
  10. * @ingroup Media
  11. */
  12. abstract class MediaHandler {
  13. const TRANSFORM_LATER = 1;
  14. /**
  15. * Instance cache
  16. */
  17. static $handlers = array();
  18. /**
  19. * Get a MediaHandler for a given MIME type from the instance cache
  20. */
  21. static function getHandler( $type ) {
  22. global $wgMediaHandlers;
  23. if ( !isset( $wgMediaHandlers[$type] ) ) {
  24. wfDebug( __METHOD__ . ": no handler found for $type.\n");
  25. return false;
  26. }
  27. $class = $wgMediaHandlers[$type];
  28. if ( !isset( self::$handlers[$class] ) ) {
  29. self::$handlers[$class] = new $class;
  30. if ( !self::$handlers[$class]->isEnabled() ) {
  31. self::$handlers[$class] = false;
  32. }
  33. }
  34. return self::$handlers[$class];
  35. }
  36. /**
  37. * Get an associative array mapping magic word IDs to parameter names.
  38. * Will be used by the parser to identify parameters.
  39. */
  40. abstract function getParamMap();
  41. /*
  42. * Validate a thumbnail parameter at parse time.
  43. * Return true to accept the parameter, and false to reject it.
  44. * If you return false, the parser will do something quiet and forgiving.
  45. */
  46. abstract function validateParam( $name, $value );
  47. /**
  48. * Merge a parameter array into a string appropriate for inclusion in filenames
  49. */
  50. abstract function makeParamString( $params );
  51. /**
  52. * Parse a param string made with makeParamString back into an array
  53. */
  54. abstract function parseParamString( $str );
  55. /**
  56. * Changes the parameter array as necessary, ready for transformation.
  57. * Should be idempotent.
  58. * Returns false if the parameters are unacceptable and the transform should fail
  59. */
  60. abstract function normaliseParams( $image, &$params );
  61. /**
  62. * Get an image size array like that returned by getimagesize(), or false if it
  63. * can't be determined.
  64. *
  65. * @param Image $image The image object, or false if there isn't one
  66. * @param string $fileName The filename
  67. * @return array
  68. */
  69. abstract function getImageSize( $image, $path );
  70. /**
  71. * Get handler-specific metadata which will be saved in the img_metadata field.
  72. *
  73. * @param Image $image The image object, or false if there isn't one
  74. * @param string $fileName The filename
  75. * @return string
  76. */
  77. function getMetadata( $image, $path ) { return ''; }
  78. /**
  79. * Get a string describing the type of metadata, for display purposes.
  80. */
  81. function getMetadataType( $image ) { return false; }
  82. /**
  83. * Check if the metadata string is valid for this handler.
  84. * If it returns false, Image will reload the metadata from the file and update the database
  85. */
  86. function isMetadataValid( $image, $metadata ) { return true; }
  87. /**
  88. * Get a MediaTransformOutput object representing an alternate of the transformed
  89. * output which will call an intermediary thumbnail assist script.
  90. *
  91. * Used when the repository has a thumbnailScriptUrl option configured.
  92. *
  93. * Return false to fall back to the regular getTransform().
  94. */
  95. function getScriptedTransform( $image, $script, $params ) {
  96. return false;
  97. }
  98. /**
  99. * Get a MediaTransformOutput object representing the transformed output. Does not
  100. * actually do the transform.
  101. *
  102. * @param Image $image The image object
  103. * @param string $dstPath Filesystem destination path
  104. * @param string $dstUrl Destination URL to use in output HTML
  105. * @param array $params Arbitrary set of parameters validated by $this->validateParam()
  106. */
  107. function getTransform( $image, $dstPath, $dstUrl, $params ) {
  108. return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
  109. }
  110. /**
  111. * Get a MediaTransformOutput object representing the transformed output. Does the
  112. * transform unless $flags contains self::TRANSFORM_LATER.
  113. *
  114. * @param Image $image The image object
  115. * @param string $dstPath Filesystem destination path
  116. * @param string $dstUrl Destination URL to use in output HTML
  117. * @param array $params Arbitrary set of parameters validated by $this->validateParam()
  118. * @param integer $flags A bitfield, may contain self::TRANSFORM_LATER
  119. */
  120. abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
  121. /**
  122. * Get the thumbnail extension and MIME type for a given source MIME type
  123. * @return array thumbnail extension and MIME type
  124. */
  125. function getThumbType( $ext, $mime ) {
  126. return array( $ext, $mime );
  127. }
  128. /**
  129. * True if the handled types can be transformed
  130. */
  131. function canRender( $file ) { return true; }
  132. /**
  133. * True if handled types cannot be displayed directly in a browser
  134. * but can be rendered
  135. */
  136. function mustRender( $file ) { return false; }
  137. /**
  138. * True if the type has multi-page capabilities
  139. */
  140. function isMultiPage( $file ) { return false; }
  141. /**
  142. * Page count for a multi-page document, false if unsupported or unknown
  143. */
  144. function pageCount( $file ) { return false; }
  145. /**
  146. * False if the handler is disabled for all files
  147. */
  148. function isEnabled() { return true; }
  149. /**
  150. * Get an associative array of page dimensions
  151. * Currently "width" and "height" are understood, but this might be
  152. * expanded in the future.
  153. * Returns false if unknown or if the document is not multi-page.
  154. */
  155. function getPageDimensions( $image, $page ) {
  156. $gis = $this->getImageSize( $image, $image->getPath() );
  157. return array(
  158. 'width' => $gis[0],
  159. 'height' => $gis[1]
  160. );
  161. }
  162. /**
  163. * Get an array structure that looks like this:
  164. *
  165. * array(
  166. * 'visible' => array(
  167. * 'Human-readable name' => 'Human readable value',
  168. * ...
  169. * ),
  170. * 'collapsed' => array(
  171. * 'Human-readable name' => 'Human readable value',
  172. * ...
  173. * )
  174. * )
  175. * The UI will format this into a table where the visible fields are always
  176. * visible, and the collapsed fields are optionally visible.
  177. *
  178. * The function should return false if there is no metadata to display.
  179. */
  180. /**
  181. * FIXME: I don't really like this interface, it's not very flexible
  182. * I think the media handler should generate HTML instead. It can do
  183. * all the formatting according to some standard. That makes it possible
  184. * to do things like visual indication of grouped and chained streams
  185. * in ogg container files.
  186. */
  187. function formatMetadata( $image ) {
  188. return false;
  189. }
  190. /**
  191. * @fixme document this!
  192. * 'value' thingy goes into a wikitext table; it used to be escaped but
  193. * that was incompatible with previous practice of customized display
  194. * with wikitext formatting via messages such as 'exif-model-value'.
  195. * So the escaping is taken back out, but generally this seems a confusing
  196. * interface.
  197. */
  198. protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
  199. $array[$visibility][] = array(
  200. 'id' => "$type-$id",
  201. 'name' => wfMsg( "$type-$id", $param ),
  202. 'value' => $value
  203. );
  204. }
  205. function getShortDesc( $file ) {
  206. global $wgLang;
  207. $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
  208. $wgLang->formatNum( $file->getSize() ) ) . ')';
  209. return "$nbytes";
  210. }
  211. function getLongDesc( $file ) {
  212. global $wgUser;
  213. $sk = $wgUser->getSkin();
  214. return wfMsgExt( 'file-info', 'parseinline',
  215. $sk->formatSize( $file->getSize() ),
  216. $file->getMimeType() );
  217. }
  218. static function getGeneralShortDesc( $file ) {
  219. global $wgLang;
  220. $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
  221. $wgLang->formatNum( $file->getSize() ) ) . ')';
  222. return "$nbytes";
  223. }
  224. static function getGeneralLongDesc( $file ) {
  225. global $wgUser;
  226. $sk = $wgUser->getSkin();
  227. return wfMsgExt( 'file-info', 'parseinline',
  228. $sk->formatSize( $file->getSize() ),
  229. $file->getMimeType() );
  230. }
  231. function getDimensionsString( $file ) {
  232. return '';
  233. }
  234. /**
  235. * Modify the parser object post-transform
  236. */
  237. function parserTransformHook( $parser, $file ) {}
  238. /**
  239. * Check for zero-sized thumbnails. These can be generated when
  240. * no disk space is available or some other error occurs
  241. *
  242. * @param $dstPath The location of the suspect file
  243. * @param $retval Return value of some shell process, file will be deleted if this is non-zero
  244. * @return true if removed, false otherwise
  245. */
  246. function removeBadFile( $dstPath, $retval = 0 ) {
  247. if( file_exists( $dstPath ) ) {
  248. $thumbstat = stat( $dstPath );
  249. if( $thumbstat['size'] == 0 || $retval != 0 ) {
  250. wfDebugLog( 'thumbnail',
  251. sprintf( 'Removing bad %d-byte thumbnail "%s"',
  252. $thumbstat['size'], $dstPath ) );
  253. unlink( $dstPath );
  254. return true;
  255. }
  256. }
  257. return false;
  258. }
  259. }
  260. /**
  261. * Media handler abstract base class for images
  262. *
  263. * @ingroup Media
  264. */
  265. abstract class ImageHandler extends MediaHandler {
  266. function canRender( $file ) {
  267. if ( $file->getWidth() && $file->getHeight() ) {
  268. return true;
  269. } else {
  270. return false;
  271. }
  272. }
  273. function getParamMap() {
  274. return array( 'img_width' => 'width' );
  275. }
  276. function validateParam( $name, $value ) {
  277. if ( in_array( $name, array( 'width', 'height' ) ) ) {
  278. if ( $value <= 0 ) {
  279. return false;
  280. } else {
  281. return true;
  282. }
  283. } else {
  284. return false;
  285. }
  286. }
  287. function makeParamString( $params ) {
  288. if ( isset( $params['physicalWidth'] ) ) {
  289. $width = $params['physicalWidth'];
  290. } elseif ( isset( $params['width'] ) ) {
  291. $width = $params['width'];
  292. } else {
  293. throw new MWException( 'No width specified to '.__METHOD__ );
  294. }
  295. # Removed for ProofreadPage
  296. #$width = intval( $width );
  297. return "{$width}px";
  298. }
  299. function parseParamString( $str ) {
  300. $m = false;
  301. if ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
  302. return array( 'width' => $m[1] );
  303. } else {
  304. return false;
  305. }
  306. }
  307. function getScriptParams( $params ) {
  308. return array( 'width' => $params['width'] );
  309. }
  310. function normaliseParams( $image, &$params ) {
  311. $mimeType = $image->getMimeType();
  312. if ( !isset( $params['width'] ) ) {
  313. return false;
  314. }
  315. if ( !isset( $params['page'] ) ) {
  316. $params['page'] = 1;
  317. }
  318. $srcWidth = $image->getWidth( $params['page'] );
  319. $srcHeight = $image->getHeight( $params['page'] );
  320. if ( isset( $params['height'] ) && $params['height'] != -1 ) {
  321. if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
  322. $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
  323. }
  324. }
  325. $params['height'] = File::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
  326. if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
  327. return false;
  328. }
  329. return true;
  330. }
  331. /**
  332. * Get a transform output object without actually doing the transform
  333. */
  334. function getTransform( $image, $dstPath, $dstUrl, $params ) {
  335. return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
  336. }
  337. /**
  338. * Validate thumbnail parameters and fill in the correct height
  339. *
  340. * @param integer &$width Specified width (input/output)
  341. * @param integer &$height Height (output only)
  342. * @return false to indicate that an error should be returned to the user.
  343. */
  344. function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
  345. $width = intval( $width );
  346. # Sanity check $width
  347. if( $width <= 0) {
  348. wfDebug( __METHOD__.": Invalid destination width: $width\n" );
  349. return false;
  350. }
  351. if ( $srcWidth <= 0 ) {
  352. wfDebug( __METHOD__.": Invalid source width: $srcWidth\n" );
  353. return false;
  354. }
  355. $height = File::scaleHeight( $srcWidth, $srcHeight, $width );
  356. return true;
  357. }
  358. function getScriptedTransform( $image, $script, $params ) {
  359. if ( !$this->normaliseParams( $image, $params ) ) {
  360. return false;
  361. }
  362. $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) );
  363. $page = isset( $params['page'] ) ? $params['page'] : false;
  364. if( $image->mustRender() || $params['width'] < $image->getWidth() ) {
  365. return new ThumbnailImage( $image, $url, $params['width'], $params['height'], $page );
  366. }
  367. }
  368. function getImageSize( $image, $path ) {
  369. wfSuppressWarnings();
  370. $gis = getimagesize( $path );
  371. wfRestoreWarnings();
  372. return $gis;
  373. }
  374. function getShortDesc( $file ) {
  375. global $wgLang;
  376. $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
  377. $wgLang->formatNum( $file->getSize() ) );
  378. $widthheight = wfMsgHtml( 'widthheight', $wgLang->formatNum( $file->getWidth() ) ,$wgLang->formatNum( $file->getHeight() ) );
  379. return "$widthheight ($nbytes)";
  380. }
  381. function getLongDesc( $file ) {
  382. global $wgLang;
  383. return wfMsgExt('file-info-size', 'parseinline',
  384. $wgLang->formatNum( $file->getWidth() ),
  385. $wgLang->formatNum( $file->getHeight() ),
  386. $wgLang->formatSize( $file->getSize() ),
  387. $file->getMimeType() );
  388. }
  389. function getDimensionsString( $file ) {
  390. global $wgLang;
  391. $pages = $file->pageCount();
  392. $width = $wgLang->formatNum( $file->getWidth() );
  393. $height = $wgLang->formatNum( $file->getHeight() );
  394. $pagesFmt = $wgLang->formatNum( $pages );
  395. if ( $pages > 1 ) {
  396. return wfMsgExt( 'widthheightpage', 'parsemag', $width, $height, $pagesFmt );
  397. } else {
  398. return wfMsg( 'widthheight', $width, $height );
  399. }
  400. }
  401. }