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  } else {
871  return $roundedUp;
872  }
873  }
874 
883  public function getDimensionsString( $file ) {
884  return '';
885  }
886 
899  public function parserTransformHook( $parser, $file ) {
900  }
901 
914  public function verifyUpload( $fileName ) {
915  return Status::newGood();
916  }
917 
928  public function removeBadFile( $dstPath, $retval = 0 ) {
929  if ( file_exists( $dstPath ) ) {
930  $thumbstat = stat( $dstPath );
931  if ( $thumbstat['size'] == 0 || $retval != 0 ) {
932  $result = unlink( $dstPath );
933 
934  if ( $result ) {
935  wfDebugLog( 'thumbnail',
936  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
937  $thumbstat['size'], $dstPath ) );
938  } else {
939  wfDebugLog( 'thumbnail',
940  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
941  $thumbstat['size'], $dstPath ) );
942  }
943 
944  return true;
945  }
946  }
947 
948  return false;
949  }
950 
965  public function filterThumbnailPurgeList( &$files, $options ) {
966  // Do nothing
967  }
968 
976  public function canRotate() {
977  return false;
978  }
979 
996  public function getRotation( $file ) {
997  return 0;
998  }
999 
1011  protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
1012  # Keep error output limited (T59985)
1013  $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
1014 
1015  wfDebugLog( 'thumbnail',
1016  sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1017  wfHostname(), $retval, $errMessage, $cmd ) );
1018  }
1019 
1029  public function getAvailableLanguages( File $file ) {
1030  return [];
1031  }
1032 
1044  public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) {
1045  return null;
1046  }
1047 
1063  public function getDefaultRenderLanguage( File $file ) {
1064  return null;
1065  }
1066 
1079  public function getLength( $file ) {
1080  return 0.0;
1081  }
1082 
1090  public function isExpensiveToThumbnail( $file ) {
1091  return false;
1092  }
1093 
1102  public function supportsBucketing() {
1103  return false;
1104  }
1105 
1114  public function sanitizeParamsForBucketing( $params ) {
1115  return $params;
1116  }
1117 
1144  public function getWarningConfig( $file ) {
1145  return null;
1146  }
1147 
1155  public static function getPageRangesByDimensions( $pagesByDimensions ) {
1156  $pageRangesByDimensions = [];
1157 
1158  foreach ( $pagesByDimensions as $dimensions => $pageList ) {
1159  $ranges = [];
1160  $firstPage = $pageList[0];
1161  $lastPage = $firstPage - 1;
1162 
1163  foreach ( $pageList as $page ) {
1164  if ( $page > $lastPage + 1 ) {
1165  if ( $firstPage != $lastPage ) {
1166  $ranges[] = "$firstPage-$lastPage";
1167  } else {
1168  $ranges[] = "$firstPage";
1169  }
1170 
1171  $firstPage = $page;
1172  }
1173 
1174  $lastPage = $page;
1175  }
1176 
1177  if ( $firstPage != $lastPage ) {
1178  $ranges[] = "$firstPage-$lastPage";
1179  } else {
1180  $ranges[] = "$firstPage";
1181  }
1182 
1183  $pageRangesByDimensions[ $dimensions ] = $ranges;
1184  }
1185 
1186  $dimensionsString = [];
1187  foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) {
1188  $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges );
1189  }
1190 
1191  return implode( '/', $dimensionsString );
1192  }
1193 
1202  public function getContentHeaders( $metadata ) {
1203  return [ 'X-Content-Dimensions' => '' ]; // T175689
1204  }
1205 
1214  public function useSplitMetadata() {
1215  return false;
1216  }
1217 }
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:493
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:67
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:173
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 its 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