1<?php 2/** 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along 14 * with this program; if not, write to the Free Software Foundation, Inc., 15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 * http://www.gnu.org/copyleft/gpl.html 17 * 18 * @file 19 * @ingroup Media 20 */ 21use MediaWiki\MediaWikiServices; 22 23/** 24 * @defgroup Media Media 25 * 26 * Media handlers and other classes relating to Multimedia support, 27 * with the exception of FileRepo and FileBackend, which have their own groups. 28 */ 29 30/** 31 * Base media handler class 32 * 33 * @stable to extend 34 * 35 * @ingroup Media 36 */ 37abstract class MediaHandler { 38 public const TRANSFORM_LATER = 1; 39 public const METADATA_GOOD = true; 40 public const METADATA_BAD = false; 41 public const METADATA_COMPATIBLE = 2; // for old but backwards compatible. 42 /** 43 * Max length of error logged by logErrorForExternalProcess() 44 */ 45 private const MAX_ERR_LOG_SIZE = 65535; 46 47 /** 48 * Get a MediaHandler for a given MIME type from the instance cache 49 * 50 * @param string $type 51 * @return MediaHandler|bool 52 */ 53 public static function getHandler( $type ) { 54 return MediaWikiServices::getInstance() 55 ->getMediaHandlerFactory()->getHandler( $type ); 56 } 57 58 /** 59 * Get an associative array mapping magic word IDs to parameter names. 60 * Will be used by the parser to identify parameters. 61 */ 62 abstract public function getParamMap(); 63 64 /** 65 * Validate a thumbnail parameter at parse time. 66 * Return true to accept the parameter, and false to reject it. 67 * If you return false, the parser will do something quiet and forgiving. 68 * 69 * @param string $name 70 * @param mixed $value 71 */ 72 abstract public function validateParam( $name, $value ); 73 74 /** 75 * Merge a parameter array into a string appropriate for inclusion in filenames 76 * 77 * @param array $params Array of parameters that have been through normaliseParams. 78 * @return string 79 */ 80 abstract public function makeParamString( $params ); 81 82 /** 83 * Parse a param string made with makeParamString back into an array 84 * 85 * @param string $str The parameter string without file name (e.g. 122px) 86 * @return array|bool Array of parameters or false on failure. 87 */ 88 abstract public function parseParamString( $str ); 89 90 /** 91 * Changes the parameter array as necessary, ready for transformation. 92 * Should be idempotent. 93 * Returns false if the parameters are unacceptable and the transform should fail 94 * @param File $image 95 * @param array &$params 96 */ 97 abstract public function normaliseParams( $image, &$params ); 98 99 /** 100 * Get an image size array like that returned by getimagesize(), or false if it 101 * can't be determined. 102 * 103 * This function is used for determining the width, height and bitdepth directly 104 * from an image. The results are stored in the database in the img_width, 105 * img_height, img_bits fields. 106 * 107 * @note If this is a multipage file, return the width and height of the 108 * first page. 109 * 110 * @deprecated since 1.37, override getSizeAndMetadata instead 111 * 112 * @param File|FSFile $image The image object, or false if there isn't one. 113 * Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!) 114 * @param string $path The filename 115 * @return array|false Follow the format of PHP getimagesize() internal function. 116 * See https://www.php.net/getimagesize. MediaWiki will only ever use the 117 * first two array keys (the width and height), and the 'bits' associative 118 * key. All other array keys are ignored. Returning a 'bits' key is optional 119 * as not all formats have a notion of "bitdepth". Returns false on failure. 120 */ 121 public function getImageSize( $image, $path ) { 122 return false; 123 } 124 125 /** 126 * Get image size information and metadata array. 127 * 128 * If this returns null, the caller will fall back to getImageSize() and 129 * getMetadata(). 130 * 131 * If getImageSize() or getMetadata() are implemented in the most derived 132 * class, they will be used instead of this function. To override this 133 * behaviour, override useLegacyMetadata(). 134 * 135 * @stable to override 136 * @since 1.37 137 * 138 * @param MediaHandlerState $state An object for saving process-local state. 139 * This is normally a File object which will be passed back to other 140 * MediaHandler methods like pageCount(), if they are called in the same 141 * request. The handler can use this object to save its state. 142 * @param string $path The filename 143 * @return array|null Null to fall back to getImageSize(), or an array with 144 * the following keys. All keys are optional. 145 * - width: The width. If multipage, return the first page width. (optional) 146 * - height: The height. If multipage, return the first page height. (optional) 147 * - bits: The number of bits for each color (optional) 148 * - metadata: A JSON-serializable array of metadata (optional) 149 */ 150 public function getSizeAndMetadata( $state, $path ) { 151 return null; 152 } 153 154 /** 155 * Get handler-specific metadata which will be saved in the img_metadata field. 156 * @deprecated since 1.37 override getSizeAndMetadata() instead 157 * 158 * @param File|FSFile $image The image object, or false if there isn't one. 159 * Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!) 160 * @param string $path The filename 161 * @return string A string of metadata in php serialized form (Run through serialize()) 162 */ 163 public function getMetadata( $image, $path ) { 164 return ''; 165 } 166 167 /** 168 * If this returns true, the new method getSizeAndMetadata() will not be 169 * called. The legacy methods getMetadata() and getImageSize() will be used 170 * instead. 171 * 172 * @since 1.37 173 * @stable to override 174 * @return bool 175 */ 176 protected function useLegacyMetadata() { 177 return $this->hasMostDerivedMethod( 'getMetadata' ) 178 || $this->hasMostDerivedMethod( 'getImageSize' ); 179 } 180 181 /** 182 * Check whether a method is implemented in the most derived class. 183 * 184 * @since 1.37 185 * @param string $name 186 * @return bool 187 */ 188 protected function hasMostDerivedMethod( $name ) { 189 $rc = new ReflectionClass( $this ); 190 $rm = new ReflectionMethod( $this, $name ); 191 return $rm->getDeclaringClass()->getName() === $rc->getName(); 192 } 193 194 /** 195 * Get the metadata array and the image size, with b/c fallback. 196 * 197 * The legacy methods will be used if useLegacyMetadata() returns true or 198 * if getSizeAndMetadata() returns null. 199 * 200 * Absent metadata will be normalized to an empty array. Absent width and 201 * height will be normalized to zero. 202 * 203 * @param File|FSFile $file This must be a File or FSFile to support the 204 * legacy methods. When the legacy methods are removed, this will be 205 * narrowed to MediaHandlerState. 206 * @param string $path 207 * @return array|false|null False on failure, or an array with the following keys: 208 * - width: The width. If multipage, return the first page width. 209 * - height: The height. If multipage, return the first page height. 210 * - bits: The number of bits for each color (optional) 211 * - metadata: A JSON-serializable array of metadata 212 * @since 1.37 213 */ 214 final public function getSizeAndMetadataWithFallback( $file, $path ) { 215 if ( !$this->useLegacyMetadata() ) { 216 if ( $file instanceof MediaHandlerState ) { 217 $state = $file; 218 } else { 219 $state = new TrivialMediaHandlerState; 220 } 221 $info = $this->getSizeAndMetadata( $state, $path ); 222 if ( $info === false ) { 223 return false; 224 } 225 if ( $info !== null ) { 226 $info += [ 'width' => 0, 'height' => 0, 'metadata' => [] ]; 227 if ( !is_array( $info['metadata'] ) ) { 228 throw new InvalidArgumentException( 'Media handler ' . 229 static::class . ' returned ' . gettype( $info['metadata'] ) . 230 ' for metadata, should be array' ); 231 } 232 return $info; 233 } 234 } 235 236 $blob = $this->getMetadata( $file, $path ); 237 // @phan-suppress-next-line PhanParamTooMany 238 $size = $this->getImageSize( 239 $file, 240 $path, 241 $blob // Secret TimedMediaHandler parameter 242 ); 243 if ( $blob === false && $size === false ) { 244 return false; 245 } 246 if ( $size ) { 247 $info = [ 248 'width' => $size[0] ?? 0, 249 'height' => $size[1] ?? 0 250 ]; 251 if ( isset( $size['bits'] ) ) { 252 $info['bits'] = $size['bits']; 253 } 254 } else { 255 $info = [ 'width' => 0, 'height' => 0 ]; 256 } 257 if ( $blob !== false ) { 258 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged 259 $metadata = @unserialize( $blob ); 260 if ( $metadata === false ) { 261 // Unserialize error 262 $metadata = [ '_error' => $blob ]; 263 } elseif ( !is_array( $metadata ) ) { 264 $metadata = []; 265 } 266 $info['metadata'] = $metadata; 267 } else { 268 $info['metadata'] = []; 269 } 270 return $info; 271 } 272 273 /** 274 * Get metadata version. 275 * 276 * This is not used for validating metadata, this is used for the api when returning 277 * metadata, since api content formats should stay the same over time, and so things 278 * using ForeignApiRepo can keep backwards compatibility 279 * 280 * All core media handlers share a common version number, and extensions can 281 * use the GetMetadataVersion hook to append to the array (they should append a unique 282 * string so not to get confusing). If there was a media handler named 'foo' with metadata 283 * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata 284 * version is 2, the end version string would look like '2;foo=3'. 285 * 286 * @stable to override 287 * 288 * @return string Version string 289 */ 290 public static function getMetadataVersion() { 291 $version = [ '2' ]; // core metadata version 292 Hooks::runner()->onGetMetadataVersion( $version ); 293 294 return implode( ';', $version ); 295 } 296 297 /** 298 * Convert metadata version. 299 * 300 * By default just returns $metadata, but can be used to allow 301 * media handlers to convert between metadata versions. 302 * @stable to override 303 * 304 * @param array $metadata Metadata array 305 * @param int $version Target version 306 * @return array Serialized metadata in specified version, or $metadata on fail. 307 */ 308 public function convertMetadataVersion( $metadata, $version = 1 ) { 309 return $metadata; 310 } 311 312 /** 313 * Get a string describing the type of metadata, for display purposes. 314 * @stable to override 315 * 316 * @note This method is currently unused. 317 * @param File $image 318 * @return string 319 */ 320 public function getMetadataType( $image ) { 321 return false; 322 } 323 324 /** 325 * Check if the metadata string is valid for this handler. 326 * If it returns MediaHandler::METADATA_BAD (or false), Image 327 * will reload the metadata from the file and update the database. 328 * MediaHandler::METADATA_GOOD for if the metadata is a-ok, 329 * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards 330 * compatible (which may or may not trigger a metadata reload). 331 * 332 * @note Returning self::METADATA_BAD will trigger a metadata reload from 333 * file on page view. Always returning this from a broken file, or suddenly 334 * triggering as bad metadata for a large number of files can cause 335 * performance problems. 336 * 337 * @deprecated since 1.37 use isFileMetadataValid 338 * @param File $image 339 * @param string $metadata The metadata in serialized form 340 * @return bool|int 341 */ 342 public function isMetadataValid( $image, $metadata ) { 343 return self::METADATA_GOOD; 344 } 345 346 /** 347 * Check if the metadata is valid for this handler. 348 * If it returns MediaHandler::METADATA_BAD (or false), Image 349 * will reload the metadata from the file and update the database. 350 * MediaHandler::METADATA_GOOD for if the metadata is a-ok, 351 * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards 352 * compatible (which may or may not trigger a metadata reload). 353 * 354 * @note Returning self::METADATA_BAD will trigger a metadata reload from 355 * file on page view. Always returning this from a broken file, or suddenly 356 * triggering as bad metadata for a large number of files can cause 357 * performance problems. 358 * 359 * This was introduced in 1.37 to replace isMetadataValid(), which took a 360 * serialized string as a parameter. Handlers overriding this method are 361 * expected to use accessors to get the metadata out of the File. The 362 * reasons for the change were to get rid of serialization, and to allow 363 * handlers to partially load metadata with getMetadataItem(). For example 364 * a handler could just validate a version number. 365 * 366 * @stable to override 367 * @since 1.37 368 * @param File $image 369 * @return bool|int 370 */ 371 public function isFileMetadataValid( $image ) { 372 return self::METADATA_GOOD; 373 } 374 375 /** 376 * Get an array of standard (FormatMetadata type) metadata values. 377 * 378 * The returned data is largely the same as that from getMetadata(), 379 * but formatted in a standard, stable, handler-independent way. 380 * The idea being that some values like ImageDescription or Artist 381 * are universal and should be retrievable in a handler generic way. 382 * 383 * The specific properties are the type of properties that can be 384 * handled by the FormatMetadata class. These values are exposed to the 385 * user via the filemetadata parser function. 386 * 387 * Details of the response format of this function can be found at 388 * https://www.mediawiki.org/wiki/Manual:File_metadata_handling 389 * tl/dr: the response is an associative array of 390 * properties keyed by name, but the value can be complex. You probably 391 * want to call one of the FormatMetadata::flatten* functions on the 392 * property values before using them, or call 393 * FormatMetadata::getFormattedData() on the full response array, which 394 * transforms all values into prettified, human-readable text. 395 * 396 * Subclasses overriding this function must return a value which is a 397 * valid API response fragment (all associative array keys are valid 398 * XML tagnames). 399 * 400 * Note, if the file simply has no metadata, but the handler supports 401 * this interface, it should return an empty array, not false. 402 * 403 * @stable to override 404 * 405 * @param File $file 406 * @return array|bool False if interface not supported 407 * @since 1.23 408 */ 409 public function getCommonMetaArray( File $file ) { 410 return false; 411 } 412 413 /** 414 * Get a MediaTransformOutput object representing an alternate of the transformed 415 * output which will call an intermediary thumbnail assist script. 416 * 417 * Used when the repository has a thumbnailScriptUrl option configured. 418 * 419 * Return false to fall back to the regular getTransform(). 420 * 421 * @stable to override 422 * 423 * @param File $image 424 * @param string $script 425 * @param array $params 426 * @return bool|ThumbnailImage 427 */ 428 public function getScriptedTransform( $image, $script, $params ) { 429 return false; 430 } 431 432 /** 433 * Get a MediaTransformOutput object representing the transformed output. Does not 434 * actually do the transform. 435 * 436 * @stable to override 437 * 438 * @param File $image 439 * @param string $dstPath Filesystem destination path 440 * @param string $dstUrl Destination URL to use in output HTML 441 * @param array $params Arbitrary set of parameters validated by $this->validateParam() 442 * @return MediaTransformOutput 443 */ 444 final public function getTransform( $image, $dstPath, $dstUrl, $params ) { 445 return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER ); 446 } 447 448 /** 449 * Get a MediaTransformOutput object representing the transformed output. Does the 450 * transform unless $flags contains self::TRANSFORM_LATER. 451 * 452 * @stable to override 453 * 454 * @param File $image 455 * @param string $dstPath Filesystem destination path 456 * @param string $dstUrl Destination URL to use in output HTML 457 * @param array $params Arbitrary set of parameters validated by $this->validateParam() 458 * Note: These parameters have *not* gone through $this->normaliseParams() 459 * @param int $flags A bitfield, may contain self::TRANSFORM_LATER 460 * @return MediaTransformOutput 461 */ 462 abstract public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ); 463 464 /** 465 * Get the thumbnail extension and MIME type for a given source MIME type 466 * 467 * @stable to override 468 * 469 * @param string $ext Extension of original file 470 * @param string $mime MIME type of original file 471 * @param array|null $params Handler specific rendering parameters 472 * @return array Thumbnail extension and MIME type 473 */ 474 public function getThumbType( $ext, $mime, $params = null ) { 475 $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer(); 476 if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) { 477 // The extension is not valid for this MIME type and we do 478 // recognize the MIME type 479 $knownExt = $magic->getExtensionFromMimeTypeOrNull( $mime ); 480 if ( $knownExt !== null ) { 481 return [ $knownExt, $mime ]; 482 } 483 } 484 485 // The extension is correct (true) or the MIME type is unknown to 486 // MediaWiki (null) 487 return [ $ext, $mime ]; 488 } 489 490 /** 491 * True if the handled types can be transformed 492 * 493 * @stable to override 494 * 495 * @param File $file 496 * @return bool 497 */ 498 public function canRender( $file ) { 499 return true; 500 } 501 502 /** 503 * True if handled types cannot be displayed directly in a browser 504 * but can be rendered 505 * 506 * @stable to override 507 * 508 * @param File $file 509 * @return bool 510 */ 511 public function mustRender( $file ) { 512 return false; 513 } 514 515 /** 516 * True if the type has multi-page capabilities 517 * 518 * @stable to override 519 * 520 * @param File $file 521 * @return bool 522 */ 523 public function isMultiPage( $file ) { 524 return false; 525 } 526 527 /** 528 * Page count for a multi-page document, false if unsupported or unknown 529 * 530 * @stable to override 531 * 532 * @param File $file 533 * @return int|false 534 */ 535 public function pageCount( File $file ) { 536 return false; 537 } 538 539 /** 540 * The material is vectorized and thus scaling is lossless 541 * 542 * @stable to override 543 * 544 * @param File $file 545 * @return bool 546 */ 547 public function isVectorized( $file ) { 548 return false; 549 } 550 551 /** 552 * The material is an image, and is animated. 553 * In particular, video material need not return true. 554 * @note Before 1.20, this was a method of ImageHandler only 555 * 556 * @stable to override 557 * 558 * @param File $file 559 * @return bool 560 */ 561 public function isAnimatedImage( $file ) { 562 return false; 563 } 564 565 /** 566 * If the material is animated, we can animate the thumbnail 567 * @since 1.20 568 * 569 * @stable to override 570 * 571 * @param File $file 572 * @return bool If material is not animated, handler may return any value. 573 */ 574 public function canAnimateThumbnail( $file ) { 575 return true; 576 } 577 578 /** 579 * False if the handler is disabled for all files 580 * @stable to override 581 * 582 * @return bool 583 */ 584 public function isEnabled() { 585 return true; 586 } 587 588 /** 589 * Get an associative array of page dimensions 590 * Currently "width" and "height" are understood, but this might be 591 * expanded in the future. 592 * Returns false if unknown. 593 * 594 * For a single page document format (!isMultipage()), this should return 595 * false. 596 * 597 * @note For non-paged media, use getImageSize. 598 * 599 * @stable to override 600 * 601 * @param File $image 602 * @param int $page What page to get dimensions of 603 * @return array|bool 604 */ 605 public function getPageDimensions( File $image, $page ) { 606 return false; 607 } 608 609 /** 610 * Generic getter for text layer. 611 * Currently overloaded by PDF and DjVu handlers 612 * @stable to override 613 * 614 * @param File $image 615 * @param int $page Page number to get information for 616 * @return bool|string Page text or false when no text found or if 617 * unsupported. 618 */ 619 public function getPageText( File $image, $page ) { 620 return false; 621 } 622 623 /** 624 * Get the text of the entire document. 625 * @param File $file 626 * @return bool|string The text of the document or false if unsupported. 627 */ 628 public function getEntireText( File $file ) { 629 $numPages = $file->pageCount(); 630 if ( !$numPages ) { 631 // Not a multipage document 632 return $this->getPageText( $file, 1 ); 633 } 634 $document = ''; 635 for ( $i = 1; $i <= $numPages; $i++ ) { 636 $curPage = $this->getPageText( $file, $i ); 637 if ( is_string( $curPage ) ) { 638 $document .= $curPage . "\n"; 639 } 640 } 641 if ( $document !== '' ) { 642 return $document; 643 } 644 return false; 645 } 646 647 /** 648 * Get an array structure that looks like this: 649 * 650 * [ 651 * 'visible' => [ 652 * 'Human-readable name' => 'Human readable value', 653 * ... 654 * ], 655 * 'collapsed' => [ 656 * 'Human-readable name' => 'Human readable value', 657 * ... 658 * ] 659 * ] 660 * The UI will format this into a table where the visible fields are always 661 * visible, and the collapsed fields are optionally visible. 662 * 663 * The function should return false if there is no metadata to display. 664 */ 665 666 /** 667 * @todo FIXME: This interface is not very flexible. The media handler 668 * should generate HTML instead. It can do all the formatting according 669 * to some standard. That makes it possible to do things like visual 670 * indication of grouped and chained streams in ogg container files. 671 * @stable to override 672 * 673 * @param File $image 674 * @param IContextSource|false $context 675 * @return array|false 676 */ 677 public function formatMetadata( $image, $context = false ) { 678 return false; 679 } 680 681 /** sorts the visible/invisible field. 682 * Split off from ImageHandler::formatMetadata, as used by more than 683 * one type of handler. 684 * 685 * This is used by the media handlers that use the FormatMetadata class 686 * 687 * @stable to override 688 * 689 * @param array $metadataArray 690 * @param IContextSource|false $context 691 * @return array[] Array for use displaying metadata. 692 */ 693 protected function formatMetadataHelper( $metadataArray, $context = false ) { 694 $result = [ 695 'visible' => [], 696 'collapsed' => [] 697 ]; 698 699 // Allow this MediaHandler to override formatting on certain values 700 foreach ( $metadataArray as $tag => $vals ) { 701 $v = $this->formatTag( $tag, $vals, $context ); 702 if ( $v === false ) { 703 // Use default formatting 704 continue; 705 } 706 if ( $v === null ) { 707 // Remove this tag, don't format it for display 708 unset( $metadataArray[$tag] ); 709 } else { 710 // Allow subclass to override default formatting. 711 $metadataArray[$tag] = [ '_formatted' => $v ]; 712 if ( isset( $v['_type'] ) ) { 713 $metadataArray[$tag]['_type'] = $v['_type']; 714 unset( $metadataArray[$tag]['_formatted']['_type'] ); 715 } 716 } 717 } 718 719 $formatted = FormatMetadata::getFormattedData( $metadataArray, $context ); 720 // Sort fields into visible and collapsed 721 $visibleFields = $this->visibleMetadataFields(); 722 foreach ( $formatted as $name => $value ) { 723 $tag = strtolower( $name ); 724 self::addMeta( $result, 725 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed', 726 'exif', 727 $tag, 728 $value 729 ); 730 } 731 732 return $result; 733 } 734 735 /** 736 * Override default formatting for the given metadata field. 737 * 738 * @stable to override 739 * 740 * @param string $key The metadata field key 741 * @param string|array $vals The unformatted value of this metadata field 742 * @param bool|IContextSource $context Context to use (optional) 743 * @return false|null|string|array False to use default formatting, null 744 * to remove this tag from the formatted list; otherwise return 745 * a formatted HTML string (or array of them). 746 */ 747 protected function formatTag( string $key, $vals, $context = false ) { 748 return false; // Use default formatting 749 } 750 751 /** 752 * Get a list of metadata items which should be displayed when 753 * the metadata table is collapsed. 754 * 755 * @stable to override 756 * 757 * @return string[] 758 */ 759 protected function visibleMetadataFields() { 760 return FormatMetadata::getVisibleFields(); 761 } 762 763 /** 764 * This is used to generate an array element for each metadata value 765 * That array is then used to generate the table of metadata values 766 * on the image page 767 * 768 * @param array &$array An array containing elements for each type of visibility 769 * and each of those elements being an array of metadata items. This function adds 770 * a value to that array. 771 * @param string $visibility ('visible' or 'collapsed') if this value is hidden 772 * by default. 773 * @param string $type Type of metadata tag (currently always 'exif') 774 * @param string $id The name of the metadata tag (like 'artist' for example). 775 * its name in the table displayed is the message "$type-$id" (Ex exif-artist ). 776 * @param string $value Thingy goes into a wikitext table; it used to be escaped but 777 * that was incompatible with previous practise of customized display 778 * with wikitext formatting via messages such as 'exif-model-value'. 779 * So the escaping is taken back out, but generally this seems a confusing 780 * interface. 781 * @param bool|string $param Value to pass to the message for the name of the field 782 * as $1. Currently this parameter doesn't seem to ever be used. 783 * 784 * Note, everything here is passed through the parser later on (!) 785 */ 786 protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) { 787 $msg = wfMessage( "$type-$id", $param ); 788 if ( $msg->exists() ) { 789 $name = $msg->text(); 790 } else { 791 // This is for future compatibility when using instant commons. 792 // So as to not display as ugly a name if a new metadata 793 // property is defined that we don't know about 794 // (not a major issue since such a property would be collapsed 795 // by default). 796 wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id ); 797 $name = wfEscapeWikiText( $id ); 798 } 799 $array[$visibility][] = [ 800 'id' => "$type-$id", 801 'name' => $name, 802 'value' => $value 803 ]; 804 } 805 806 /** 807 * Short description. Shown on Special:Search results. 808 * 809 * @stable to override 810 * 811 * @param File $file 812 * @return string 813 */ 814 public function getShortDesc( $file ) { 815 return self::getGeneralShortDesc( $file ); 816 } 817 818 /** 819 * Long description. Shown under image on image description page surounded by (). 820 * 821 * @stable to override 822 * 823 * @param File $file 824 * @return string 825 */ 826 public function getLongDesc( $file ) { 827 return self::getGeneralLongDesc( $file ); 828 } 829 830 /** 831 * Used instead of getShortDesc if there is no handler registered for file. 832 * 833 * @param File $file 834 * @return string 835 */ 836 public static function getGeneralShortDesc( $file ) { 837 global $wgLang; 838 839 return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ); 840 } 841 842 /** 843 * Used instead of getLongDesc if there is no handler registered for file. 844 * 845 * @param File $file 846 * @return string 847 */ 848 public static function getGeneralLongDesc( $file ) { 849 return wfMessage( 'file-info' )->sizeParams( $file->getSize() ) 850 ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse(); 851 } 852 853 /** 854 * Calculate the largest thumbnail width for a given original file size 855 * such that the thumbnail's height is at most $maxHeight. 856 * @param int $boxWidth Width of the thumbnail box. 857 * @param int $boxHeight Height of the thumbnail box. 858 * @param int $maxHeight Maximum height expected for the thumbnail. 859 * @return int 860 */ 861 public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) { 862 $idealWidth = $boxWidth * $maxHeight / $boxHeight; 863 $roundedUp = ceil( $idealWidth ); 864 if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) { 865 return floor( $idealWidth ); 866 } else { 867 return $roundedUp; 868 } 869 } 870 871 /** 872 * Shown in file history box on image description page. 873 * 874 * @stable to override 875 * 876 * @param File $file 877 * @return string Dimensions 878 */ 879 public function getDimensionsString( $file ) { 880 return ''; 881 } 882 883 /** 884 * Modify the parser object post-transform. 885 * 886 * This is often used to do $parser->addOutputHook(), 887 * in order to add some javascript to render a viewer. 888 * See TimedMediaHandler or OggHandler for an example. 889 * 890 * @stable to override 891 * 892 * @param Parser $parser 893 * @param File $file 894 */ 895 public function parserTransformHook( $parser, $file ) { 896 } 897 898 /** 899 * File validation hook called on upload. 900 * 901 * If the file at the given local path is not valid, or its MIME type does not 902 * match the handler class, a Status object should be returned containing 903 * relevant errors. 904 * 905 * @stable to override 906 * 907 * @param string $fileName The local path to the file. 908 * @return Status 909 */ 910 public function verifyUpload( $fileName ) { 911 return Status::newGood(); 912 } 913 914 /** 915 * Check for zero-sized thumbnails. These can be generated when 916 * no disk space is available or some other error occurs 917 * 918 * @stable to override 919 * 920 * @param string $dstPath The location of the suspect file 921 * @param int $retval Return value of some shell process, file will be deleted if this is non-zero 922 * @return bool True if removed, false otherwise 923 */ 924 public function removeBadFile( $dstPath, $retval = 0 ) { 925 if ( file_exists( $dstPath ) ) { 926 $thumbstat = stat( $dstPath ); 927 if ( $thumbstat['size'] == 0 || $retval != 0 ) { 928 $result = unlink( $dstPath ); 929 930 if ( $result ) { 931 wfDebugLog( 'thumbnail', 932 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded', 933 $thumbstat['size'], $dstPath ) ); 934 } else { 935 wfDebugLog( 'thumbnail', 936 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed', 937 $thumbstat['size'], $dstPath ) ); 938 } 939 940 return true; 941 } 942 } 943 944 return false; 945 } 946 947 /** 948 * Remove files from the purge list. 949 * 950 * This is used by some video handlers to prevent ?action=purge 951 * from removing a transcoded video, which is expensive to 952 * regenerate. 953 * 954 * @see LocalFile::purgeThumbnails 955 * @stable to override 956 * 957 * @param array &$files 958 * @param array $options Purge options. Currently will always be 959 * an array with a single key 'forThumbRefresh' set to true. 960 */ 961 public function filterThumbnailPurgeList( &$files, $options ) { 962 // Do nothing 963 } 964 965 /** 966 * True if the handler can rotate the media 967 * @since 1.24 non-static. From 1.21-1.23 was static 968 * @stable to override 969 * 970 * @return bool 971 */ 972 public function canRotate() { 973 return false; 974 } 975 976 /** 977 * On supporting image formats, try to read out the low-level orientation 978 * of the file and return the angle that the file needs to be rotated to 979 * be viewed. 980 * 981 * This information is only useful when manipulating the original file; 982 * the width and height we normally work with is logical, and will match 983 * any produced output views. 984 * 985 * For files we don't know, we return 0. 986 * 987 * @stable to override 988 * 989 * @param File $file 990 * @return int 0, 90, 180 or 270 991 */ 992 public function getRotation( $file ) { 993 return 0; 994 } 995 996 /** 997 * Log an error that occurred in an external process 998 * 999 * Moved from BitmapHandler to MediaHandler with MediaWiki 1.23 1000 * 1001 * @since 1.23 1002 * @param int $retval 1003 * @param string $err Error reported by command. Anything longer than 1004 * MediaHandler::MAX_ERR_LOG_SIZE is stripped off. 1005 * @param string $cmd 1006 */ 1007 protected function logErrorForExternalProcess( $retval, $err, $cmd ) { 1008 # Keep error output limited (T59985) 1009 $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) ); 1010 1011 wfDebugLog( 'thumbnail', 1012 sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', 1013 wfHostname(), $retval, $errMessage, $cmd ) ); 1014 } 1015 1016 /** 1017 * Get list of languages file can be viewed in. 1018 * 1019 * @stable to override 1020 * 1021 * @param File $file 1022 * @return string[] Array of language codes, or empty array if unsupported. 1023 * @since 1.23 1024 */ 1025 public function getAvailableLanguages( File $file ) { 1026 return []; 1027 } 1028 1029 /** 1030 * When overridden in a descendant class, returns a language code most suiting 1031 * 1032 * @stable to override 1033 * 1034 * @since 1.32 1035 * 1036 * @param string $userPreferredLanguage Language code requesed 1037 * @param string[] $availableLanguages Languages present in the file 1038 * @return string|null Language code picked or null if not supported/available 1039 */ 1040 public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) { 1041 return null; 1042 } 1043 1044 /** 1045 * On file types that support renderings in multiple languages, 1046 * which language is used by default if unspecified. 1047 * 1048 * If getAvailableLanguages returns a non-empty array, this must return 1049 * a valid language code. Otherwise can return null if files of this 1050 * type do not support alternative language renderings. 1051 * 1052 * @stable to override 1053 * 1054 * @param File $file 1055 * @return string|null Language code or null if multi-language not supported for filetype. 1056 * @since 1.23 1057 */ 1058 public function getDefaultRenderLanguage( File $file ) { 1059 return null; 1060 } 1061 1062 /** 1063 * If its an audio file, return the length of the file. Otherwise 0. 1064 * 1065 * File::getLength() existed for a long time, but was calling a method 1066 * that only existed in some subclasses of this class (The TMH ones). 1067 * 1068 * @stable to override 1069 * 1070 * @param File $file 1071 * @return float Length in seconds 1072 * @since 1.23 1073 */ 1074 public function getLength( $file ) { 1075 return 0.0; 1076 } 1077 1078 /** 1079 * True if creating thumbnails from the file is large or otherwise resource-intensive. 1080 * @stable to override 1081 * 1082 * @param File $file 1083 * @return bool 1084 */ 1085 public function isExpensiveToThumbnail( $file ) { 1086 return false; 1087 } 1088 1089 /** 1090 * Returns whether or not this handler supports the chained generation of thumbnails according 1091 * to buckets 1092 * @stable to override 1093 * 1094 * @return bool 1095 * @since 1.24 1096 */ 1097 public function supportsBucketing() { 1098 return false; 1099 } 1100 1101 /** 1102 * Returns a normalised params array for which parameters have been cleaned up for bucketing 1103 * purposes 1104 * @stable to override 1105 * 1106 * @param array $params 1107 * @return array 1108 */ 1109 public function sanitizeParamsForBucketing( $params ) { 1110 return $params; 1111 } 1112 1113 /** 1114 * Gets configuration for the file warning message. Return value of 1115 * the following structure: 1116 * [ 1117 * // Required, module with messages loaded for the client 1118 * 'module' => 'example.filewarning.messages', 1119 * // Required, array of names of messages 1120 * 'messages' => [ 1121 * // Required, main warning message 1122 * 'main' => 'example-filewarning-main', 1123 * // Optional, header for warning dialog 1124 * 'header' => 'example-filewarning-header', 1125 * // Optional, footer for warning dialog 1126 * 'footer' => 'example-filewarning-footer', 1127 * // Optional, text for more-information link (see below) 1128 * 'info' => 'example-filewarning-info', 1129 * ], 1130 * // Optional, link for more information 1131 * 'link' => 'http://example.com', 1132 * ] 1133 * 1134 * Returns null if no warning is necessary. 1135 * @stable to override 1136 * @param File $file 1137 * @return array|null 1138 */ 1139 public function getWarningConfig( $file ) { 1140 return null; 1141 } 1142 1143 /** 1144 * Converts a dimensions array about a potentially multipage document from an 1145 * exhaustive list of ordered page numbers to a list of page ranges 1146 * @param array[] $pagesByDimensions 1147 * @return string 1148 * @since 1.30 1149 */ 1150 public static function getPageRangesByDimensions( $pagesByDimensions ) { 1151 $pageRangesByDimensions = []; 1152 1153 foreach ( $pagesByDimensions as $dimensions => $pageList ) { 1154 $ranges = []; 1155 $firstPage = $pageList[0]; 1156 $lastPage = $firstPage - 1; 1157 1158 foreach ( $pageList as $page ) { 1159 if ( $page > $lastPage + 1 ) { 1160 if ( $firstPage != $lastPage ) { 1161 $ranges[] = "$firstPage-$lastPage"; 1162 } else { 1163 $ranges[] = "$firstPage"; 1164 } 1165 1166 $firstPage = $page; 1167 } 1168 1169 $lastPage = $page; 1170 } 1171 1172 if ( $firstPage != $lastPage ) { 1173 $ranges[] = "$firstPage-$lastPage"; 1174 } else { 1175 $ranges[] = "$firstPage"; 1176 } 1177 1178 $pageRangesByDimensions[ $dimensions ] = $ranges; 1179 } 1180 1181 $dimensionsString = []; 1182 foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) { 1183 $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges ); 1184 } 1185 1186 return implode( '/', $dimensionsString ); 1187 } 1188 1189 /** 1190 * Get useful response headers for GET/HEAD requests for a file with the given metadata 1191 * @stable to override 1192 * 1193 * @param array $metadata Contains this handler's unserialized getMetadata() for a file 1194 * @return array 1195 * @since 1.30 1196 */ 1197 public function getContentHeaders( $metadata ) { 1198 return [ 'X-Content-Dimensions' => '' ]; // T175689 1199 } 1200 1201 /** 1202 * If this returns true, LocalFile may split metadata up and store its 1203 * constituent items separately. This only makes sense if the handler calls 1204 * File::getMetadataItem() or File::getMetadataItems() instead of 1205 * requesting the whole array at once. 1206 * 1207 * @return bool 1208 */ 1209 public function useSplitMetadata() { 1210 return false; 1211 } 1212} 1213