MediaWiki  master
MediaHandler.php
Go to the documentation of this file.
1 <?php
23 
38 abstract class MediaHandler {
39  public const TRANSFORM_LATER = 1;
40  public const METADATA_GOOD = true;
41  public const METADATA_BAD = false;
42  public const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
46  private const MAX_ERR_LOG_SIZE = 65535;
47 
54  public static function getHandler( $type ) {
55  return MediaWikiServices::getInstance()
56  ->getMediaHandlerFactory()->getHandler( $type );
57  }
58 
64  abstract public function getParamMap();
65 
75  abstract public function validateParam( $name, $value );
76 
83  abstract public function makeParamString( $params );
84 
91  abstract public function parseParamString( $str );
92 
101  abstract public function normaliseParams( $image, &$params );
102 
125  public function getImageSize( $image, $path ) {
126  return false;
127  }
128 
154  public function getSizeAndMetadata( $state, $path ) {
155  return null;
156  }
157 
167  public function getMetadata( $image, $path ) {
168  return '';
169  }
170 
180  protected function useLegacyMetadata() {
181  return $this->hasMostDerivedMethod( 'getMetadata' )
182  || $this->hasMostDerivedMethod( 'getImageSize' );
183  }
184 
192  protected function hasMostDerivedMethod( $name ) {
193  $rc = new ReflectionClass( $this );
194  $rm = new ReflectionMethod( $this, $name );
195  return $rm->getDeclaringClass()->getName() === $rc->getName();
196  }
197 
218  final public function getSizeAndMetadataWithFallback( $file, $path ) {
219  if ( !$this->useLegacyMetadata() ) {
220  if ( $file instanceof MediaHandlerState ) {
221  $state = $file;
222  } else {
223  $state = new TrivialMediaHandlerState;
224  }
225  $info = $this->getSizeAndMetadata( $state, $path );
226  if ( $info === false ) {
227  return false;
228  }
229  if ( $info !== null ) {
230  $info += [ 'width' => 0, 'height' => 0, 'metadata' => [] ];
231  if ( !is_array( $info['metadata'] ) ) {
232  throw new InvalidArgumentException( 'Media handler ' .
233  static::class . ' returned ' . gettype( $info['metadata'] ) .
234  ' for metadata, should be array' );
235  }
236  return $info;
237  }
238  }
239 
240  $blob = $this->getMetadata( $file, $path );
241  // @phan-suppress-next-line PhanParamTooMany
242  $size = $this->getImageSize(
243  $file,
244  $path,
245  $blob // Secret TimedMediaHandler parameter
246  );
247  if ( $blob === false && $size === false ) {
248  return false;
249  }
250  if ( $size ) {
251  $info = [
252  'width' => $size[0] ?? 0,
253  'height' => $size[1] ?? 0
254  ];
255  if ( isset( $size['bits'] ) ) {
256  $info['bits'] = $size['bits'];
257  }
258  } else {
259  $info = [ 'width' => 0, 'height' => 0 ];
260  }
261  if ( $blob !== false ) {
262  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
263  $metadata = @unserialize( $blob );
264  if ( $metadata === false ) {
265  // Unserialize error
266  $metadata = [ '_error' => $blob ];
267  } elseif ( !is_array( $metadata ) ) {
268  $metadata = [];
269  }
270  $info['metadata'] = $metadata;
271  } else {
272  $info['metadata'] = [];
273  }
274  return $info;
275  }
276 
294  public static function getMetadataVersion() {
295  $version = [ '2' ]; // core metadata version
296  Hooks::runner()->onGetMetadataVersion( $version );
297 
298  return implode( ';', $version );
299  }
300 
312  public function convertMetadataVersion( $metadata, $version = 1 ) {
313  return $metadata;
314  }
315 
324  public function getMetadataType( $image ) {
325  return false;
326  }
327 
346  public function isMetadataValid( $image, $metadata ) {
347  return self::METADATA_GOOD;
348  }
349 
375  public function isFileMetadataValid( $image ) {
376  return self::METADATA_GOOD;
377  }
378 
413  public function getCommonMetaArray( File $file ) {
414  return false;
415  }
416 
432  public function getScriptedTransform( $image, $script, $params ) {
433  return false;
434  }
435 
448  final public function getTransform( $image, $dstPath, $dstUrl, $params ) {
449  return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
450  }
451 
466  abstract public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
467 
478  public function getThumbType( $ext, $mime, $params = null ) {
479  $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
480  if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
481  // The extension is not valid for this MIME type and we do
482  // recognize the MIME type
483  $knownExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
484  if ( $knownExt !== null ) {
485  return [ $knownExt, $mime ];
486  }
487  }
488 
489  // The extension is correct (true) or the MIME type is unknown to
490  // MediaWiki (null)
491  return [ $ext, $mime ];
492  }
493 
502  public function canRender( $file ) {
503  return true;
504  }
505 
515  public function mustRender( $file ) {
516  return false;
517  }
518 
527  public function isMultiPage( $file ) {
528  return false;
529  }
530 
539  public function pageCount( File $file ) {
540  return false;
541  }
542 
551  public function isVectorized( $file ) {
552  return false;
553  }
554 
565  public function isAnimatedImage( $file ) {
566  return false;
567  }
568 
578  public function canAnimateThumbnail( $file ) {
579  return true;
580  }
581 
588  public function isEnabled() {
589  return true;
590  }
591 
609  public function getPageDimensions( File $image, $page ) {
610  return false;
611  }
612 
623  public function getPageText( File $image, $page ) {
624  return false;
625  }
626 
632  public function getEntireText( File $file ) {
633  $numPages = $file->pageCount();
634  if ( !$numPages ) {
635  // Not a multipage document
636  return $this->getPageText( $file, 1 );
637  }
638  $document = '';
639  for ( $i = 1; $i <= $numPages; $i++ ) {
640  $curPage = $this->getPageText( $file, $i );
641  if ( is_string( $curPage ) ) {
642  $document .= $curPage . "\n";
643  }
644  }
645  if ( $document !== '' ) {
646  return $document;
647  }
648  return false;
649  }
650 
681  public function formatMetadata( $image, $context = false ) {
682  return false;
683  }
684 
697  protected function formatMetadataHelper( $metadataArray, $context = false ) {
698  $result = [
699  'visible' => [],
700  'collapsed' => []
701  ];
702 
703  // Allow this MediaHandler to override formatting on certain values
704  foreach ( $metadataArray as $tag => $vals ) {
705  $v = $this->formatTag( $tag, $vals, $context );
706  if ( $v === false ) {
707  // Use default formatting
708  continue;
709  }
710  if ( $v === null ) {
711  // Remove this tag, don't format it for display
712  unset( $metadataArray[$tag] );
713  } else {
714  // Allow subclass to override default formatting.
715  $metadataArray[$tag] = [ '_formatted' => $v ];
716  if ( isset( $v['_type'] ) ) {
717  $metadataArray[$tag]['_type'] = $v['_type'];
718  unset( $metadataArray[$tag]['_formatted']['_type'] );
719  }
720  }
721  }
722 
723  $formatted = FormatMetadata::getFormattedData( $metadataArray, $context );
724  // Sort fields into visible and collapsed
725  $visibleFields = $this->visibleMetadataFields();
726  foreach ( $formatted as $name => $value ) {
727  $tag = strtolower( $name );
728  self::addMeta( $result,
729  in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
730  'exif',
731  $tag,
732  $value
733  );
734  }
735 
736  return $result;
737  }
738 
751  protected function formatTag( string $key, $vals, $context = false ) {
752  return false; // Use default formatting
753  }
754 
763  protected function visibleMetadataFields() {
765  }
766 
790  protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
791  $msg = wfMessage( "$type-$id", $param );
792  if ( $msg->exists() ) {
793  $name = $msg->text();
794  } else {
795  // This is for future compatibility when using instant commons.
796  // So as to not display as ugly a name if a new metadata
797  // property is defined that we don't know about
798  // (not a major issue since such a property would be collapsed
799  // by default).
800  wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id );
801  $name = wfEscapeWikiText( $id );
802  }
803  $array[$visibility][] = [
804  'id' => "$type-$id",
805  'name' => $name,
806  'value' => $value
807  ];
808  }
809 
818  public function getShortDesc( $file ) {
820  }
821 
830  public function getLongDesc( $file ) {
832  }
833 
840  public static function getGeneralShortDesc( $file ) {
841  global $wgLang;
842 
843  return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
844  }
845 
852  public static function getGeneralLongDesc( $file ) {
853  return wfMessage( 'file-info' )->sizeParams( $file->getSize() )
854  ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse();
855  }
856 
865  public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
866  $idealWidth = $boxWidth * $maxHeight / $boxHeight;
867  $roundedUp = ceil( $idealWidth );
868  if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
869  return (int)floor( $idealWidth );
870  }
871  return $roundedUp;
872  }
873 
882  public function getDimensionsString( $file ) {
883  return '';
884  }
885 
898  public function parserTransformHook( $parser, $file ) {
899  }
900 
913  public function verifyUpload( $fileName ) {
914  return Status::newGood();
915  }
916 
927  public function removeBadFile( $dstPath, $retval = 0 ) {
928  if ( file_exists( $dstPath ) ) {
929  $thumbstat = stat( $dstPath );
930  if ( $thumbstat['size'] == 0 || $retval != 0 ) {
931  $result = unlink( $dstPath );
932 
933  if ( $result ) {
934  wfDebugLog( 'thumbnail',
935  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
936  $thumbstat['size'], $dstPath ) );
937  } else {
938  wfDebugLog( 'thumbnail',
939  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
940  $thumbstat['size'], $dstPath ) );
941  }
942 
943  return true;
944  }
945  }
946 
947  return false;
948  }
949 
964  public function filterThumbnailPurgeList( &$files, $options ) {
965  // Do nothing
966  }
967 
975  public function canRotate() {
976  return false;
977  }
978 
995  public function getRotation( $file ) {
996  return 0;
997  }
998 
1010  protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
1011  # Keep error output limited (T59985)
1012  $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
1013 
1014  wfDebugLog( 'thumbnail',
1015  sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1016  wfHostname(), $retval, $errMessage, $cmd ) );
1017  }
1018 
1028  public function getAvailableLanguages( File $file ) {
1029  return [];
1030  }
1031 
1043  public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) {
1044  return null;
1045  }
1046 
1062  public function getDefaultRenderLanguage( File $file ) {
1063  return null;
1064  }
1065 
1078  public function getLength( $file ) {
1079  return 0.0;
1080  }
1081 
1089  public function isExpensiveToThumbnail( $file ) {
1090  return false;
1091  }
1092 
1101  public function supportsBucketing() {
1102  return false;
1103  }
1104 
1113  public function sanitizeParamsForBucketing( $params ) {
1114  return $params;
1115  }
1116 
1143  public function getWarningConfig( $file ) {
1144  return null;
1145  }
1146 
1154  public static function getPageRangesByDimensions( $pagesByDimensions ) {
1155  $pageRangesByDimensions = [];
1156 
1157  foreach ( $pagesByDimensions as $dimensions => $pageList ) {
1158  $ranges = [];
1159  $firstPage = $pageList[0];
1160  $lastPage = $firstPage - 1;
1161 
1162  foreach ( $pageList as $page ) {
1163  if ( $page > $lastPage + 1 ) {
1164  if ( $firstPage !== $lastPage ) {
1165  $ranges[] = "$firstPage-$lastPage";
1166  } else {
1167  $ranges[] = "$firstPage";
1168  }
1169 
1170  $firstPage = $page;
1171  }
1172 
1173  $lastPage = $page;
1174  }
1175 
1176  if ( $firstPage != $lastPage ) {
1177  $ranges[] = "$firstPage-$lastPage";
1178  } else {
1179  $ranges[] = "$firstPage";
1180  }
1181 
1182  $pageRangesByDimensions[ $dimensions ] = $ranges;
1183  }
1184 
1185  $dimensionsString = [];
1186  foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) {
1187  $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges );
1188  }
1189 
1190  return implode( '/', $dimensionsString );
1191  }
1192 
1201  public function getContentHeaders( $metadata ) {
1202  return [ 'X-Content-Dimensions' => '' ]; // T175689
1203  }
1204 
1213  public function useSplitMetadata() {
1214  return false;
1215  }
1216 }
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
Definition: Setup.php:527
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:68
static getVisibleFields()
Get a list of fields that are visible by default.
static getFormattedData( $tags, $context=false)
Numbers given by Exif user agents are often magical, that is they should be replaced by a detailed ex...
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:170
Base media handler class.
normaliseParams( $image, &$params)
Changes the parameter array as necessary, ready for transformation.
getRotation( $file)
On supporting image formats, try to read out the low-level orientation of the file and return the ang...
const METADATA_COMPATIBLE
canRender( $file)
True if the handled types can be transformed.
getSizeAndMetadata( $state, $path)
Get image size information and metadata array.
canAnimateThumbnail( $file)
If the material is animated, we can animate the thumbnail.
useLegacyMetadata()
If this returns true, the new method getSizeAndMetadata() will not be called.
verifyUpload( $fileName)
File validation hook called on upload.
visibleMetadataFields()
Get a list of metadata items which should be displayed when the metadata table is collapsed.
static addMeta(&$array, $visibility, $type, $id, $value, $param=false)
This is used to generate an array element for each metadata value That array is then used to generate...
supportsBucketing()
Returns whether or not this handler supports the chained generation of thumbnails according to bucket...
useSplitMetadata()
If this returns true, LocalFile may split metadata up and store its constituent items separately.
parserTransformHook( $parser, $file)
Modify the parser object post-transform.
static getGeneralLongDesc( $file)
Used instead of getLongDesc if there is no handler registered for file.
formatTag(string $key, $vals, $context=false)
Override default formatting for the given metadata field.
canRotate()
True if the handler can rotate the media.
sanitizeParamsForBucketing( $params)
Returns a normalised params array for which parameters have been cleaned up for bucketing purposes.
getDefaultRenderLanguage(File $file)
On file types that support renderings in multiple languages, which language is used by default if uns...
getLength( $file)
If it's an audio file, return the length of the file.
getTransform( $image, $dstPath, $dstUrl, $params)
Get a MediaTransformOutput object representing the transformed output.
getDimensionsString( $file)
Shown in file history box on image description page.
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
isFileMetadataValid( $image)
Check if the metadata is valid for this handler.
getCommonMetaArray(File $file)
Get an array of standard (FormatMetadata type) metadata values.
doTransform( $image, $dstPath, $dstUrl, $params, $flags=0)
Get a MediaTransformOutput object representing the transformed output.
getMetadata( $image, $path)
Get handler-specific metadata which will be saved in the img_metadata field.
getPageText(File $image, $page)
Generic getter for text layer.
getSizeAndMetadataWithFallback( $file, $path)
Get the metadata array and the image size, with b/c fallback.
isAnimatedImage( $file)
The material is an image, and is animated.
const METADATA_BAD
isVectorized( $file)
The material is vectorized and thus scaling is lossless.
getImageSize( $image, $path)
Get an image size array like that returned by getimagesize(), or false if it can't be determined.
convertMetadataVersion( $metadata, $version=1)
Convert metadata version.
formatMetadata( $image, $context=false)
Get an array structure that looks like this:
getWarningConfig( $file)
Gets configuration for the file warning message.
logErrorForExternalProcess( $retval, $err, $cmd)
Log an error that occurred in an external process.
isEnabled()
False if the handler is disabled for all files.
parseParamString( $str)
Parse a param string made with makeParamString back into an array.
static getGeneralShortDesc( $file)
Used instead of getShortDesc if there is no handler registered for file.
isMultiPage( $file)
True if the type has multi-page capabilities.
getLongDesc( $file)
Long description.
static fitBoxWidth( $boxWidth, $boxHeight, $maxHeight)
Calculate the largest thumbnail width for a given original file size such that the thumbnail's height...
mustRender( $file)
True if handled types cannot be displayed directly in a browser but can be rendered.
getScriptedTransform( $image, $script, $params)
Get a MediaTransformOutput object representing an alternate of the transformed output which will call...
getMetadataType( $image)
Get a string describing the type of metadata, for display purposes.
isExpensiveToThumbnail( $file)
True if creating thumbnails from the file is large or otherwise resource-intensive.
getEntireText(File $file)
Get the text of the entire document.
getAvailableLanguages(File $file)
Get list of languages file can be viewed in.
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
getShortDesc( $file)
Short description.
makeParamString( $params)
Merge a parameter array into a string appropriate for inclusion in filenames.
formatMetadataHelper( $metadataArray, $context=false)
sorts the visible/invisible field.
validateParam( $name, $value)
Validate a thumbnail parameter at parse time.
static getPageRangesByDimensions( $pagesByDimensions)
Converts a dimensions array about a potentially multipage document from an exhaustive list of ordered...
getPageDimensions(File $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood,...
getContentHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
const TRANSFORM_LATER
getParamMap()
Get an associative array mapping magic word IDs to parameter names.
const METADATA_GOOD
getThumbType( $ext, $mime, $params=null)
Get the thumbnail extension and MIME type for a given source MIME type.
removeBadFile( $dstPath, $retval=0)
Check for zero-sized thumbnails.
hasMostDerivedMethod( $name)
Check whether a method is implemented in the most derived class.
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
getMatchedLanguage( $userPreferredLanguage, array $availableLanguages)
When overridden in a descendant class, returns a language code most suiting.
static getMetadataVersion()
Get metadata version.
pageCount(File $file)
Page count for a multi-page document, false if unsupported or unknown.
Service locator for MediaWiki core services.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
Trivial implementation of MediaHandlerState.
An interface to support process-local caching of handler data associated with a given file.
$mime
Definition: router.php:60
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
if(!is_readable( $file)) $ext
Definition: router.php:48