MediaWiki  master
MediaHandler.php
Go to the documentation of this file.
1 <?php
25 
40 abstract class MediaHandler {
41  public const TRANSFORM_LATER = 1;
42  public const METADATA_GOOD = true;
43  public const METADATA_BAD = false;
44  public const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
48  private const MAX_ERR_LOG_SIZE = 65535;
49 
56  public static function getHandler( $type ) {
57  return MediaWikiServices::getInstance()
58  ->getMediaHandlerFactory()->getHandler( $type );
59  }
60 
66  abstract public function getParamMap();
67 
77  abstract public function validateParam( $name, $value );
78 
85  abstract public function makeParamString( $params );
86 
93  abstract public function parseParamString( $str );
94 
103  abstract public function normaliseParams( $image, &$params );
104 
127  public function getImageSize( $image, $path ) {
128  return false;
129  }
130 
156  public function getSizeAndMetadata( $state, $path ) {
157  return null;
158  }
159 
169  public function getMetadata( $image, $path ) {
170  return '';
171  }
172 
182  protected function useLegacyMetadata() {
183  return $this->hasMostDerivedMethod( 'getMetadata' )
184  || $this->hasMostDerivedMethod( 'getImageSize' );
185  }
186 
194  protected function hasMostDerivedMethod( $name ) {
195  $rc = new ReflectionClass( $this );
196  $rm = new ReflectionMethod( $this, $name );
197  return $rm->getDeclaringClass()->getName() === $rc->getName();
198  }
199 
220  final public function getSizeAndMetadataWithFallback( $file, $path ) {
221  if ( !$this->useLegacyMetadata() ) {
222  if ( $file instanceof MediaHandlerState ) {
223  $state = $file;
224  } else {
225  $state = new TrivialMediaHandlerState;
226  }
227  $info = $this->getSizeAndMetadata( $state, $path );
228  if ( $info === false ) {
229  return false;
230  }
231  if ( $info !== null ) {
232  $info += [ 'width' => 0, 'height' => 0, 'metadata' => [] ];
233  if ( !is_array( $info['metadata'] ) ) {
234  throw new InvalidArgumentException( 'Media handler ' .
235  static::class . ' returned ' . gettype( $info['metadata'] ) .
236  ' for metadata, should be array' );
237  }
238  return $info;
239  }
240  }
241 
242  $blob = $this->getMetadata( $file, $path );
243  // @phan-suppress-next-line PhanParamTooMany
244  $size = $this->getImageSize(
245  $file,
246  $path,
247  $blob // Secret TimedMediaHandler parameter
248  );
249  if ( $blob === false && $size === false ) {
250  return false;
251  }
252  if ( $size ) {
253  $info = [
254  'width' => $size[0] ?? 0,
255  'height' => $size[1] ?? 0
256  ];
257  if ( isset( $size['bits'] ) ) {
258  $info['bits'] = $size['bits'];
259  }
260  } else {
261  $info = [ 'width' => 0, 'height' => 0 ];
262  }
263  if ( $blob !== false ) {
264  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
265  $metadata = @unserialize( $blob );
266  if ( $metadata === false ) {
267  // Unserialize error
268  $metadata = [ '_error' => $blob ];
269  } elseif ( !is_array( $metadata ) ) {
270  $metadata = [];
271  }
272  $info['metadata'] = $metadata;
273  } else {
274  $info['metadata'] = [];
275  }
276  return $info;
277  }
278 
296  public static function getMetadataVersion() {
297  $version = [ '2' ]; // core metadata version
298  ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onGetMetadataVersion( $version );
299 
300  return implode( ';', $version );
301  }
302 
314  public function convertMetadataVersion( $metadata, $version = 1 ) {
315  return $metadata;
316  }
317 
326  public function getMetadataType( $image ) {
327  return false;
328  }
329 
348  public function isMetadataValid( $image, $metadata ) {
349  return self::METADATA_GOOD;
350  }
351 
377  public function isFileMetadataValid( $image ) {
378  return self::METADATA_GOOD;
379  }
380 
415  public function getCommonMetaArray( File $file ) {
416  return false;
417  }
418 
434  public function getScriptedTransform( $image, $script, $params ) {
435  return false;
436  }
437 
450  final public function getTransform( $image, $dstPath, $dstUrl, $params ) {
451  return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
452  }
453 
468  abstract public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
469 
480  public function getThumbType( $ext, $mime, $params = null ) {
481  $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
482  if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
483  // The extension is not valid for this MIME type and we do
484  // recognize the MIME type
485  $knownExt = $magic->getExtensionFromMimeTypeOrNull( $mime );
486  if ( $knownExt !== null ) {
487  return [ $knownExt, $mime ];
488  }
489  }
490 
491  // The extension is correct (true) or the MIME type is unknown to
492  // MediaWiki (null)
493  return [ $ext, $mime ];
494  }
495 
504  public function canRender( $file ) {
505  return true;
506  }
507 
517  public function mustRender( $file ) {
518  return false;
519  }
520 
529  public function isMultiPage( $file ) {
530  return false;
531  }
532 
541  public function pageCount( File $file ) {
542  return false;
543  }
544 
553  public function isVectorized( $file ) {
554  return false;
555  }
556 
567  public function isAnimatedImage( $file ) {
568  return false;
569  }
570 
580  public function canAnimateThumbnail( $file ) {
581  return true;
582  }
583 
590  public function isEnabled() {
591  return true;
592  }
593 
611  public function getPageDimensions( File $image, $page ) {
612  return false;
613  }
614 
625  public function getPageText( File $image, $page ) {
626  return false;
627  }
628 
634  public function getEntireText( File $file ) {
635  $numPages = $file->pageCount();
636  if ( !$numPages ) {
637  // Not a multipage document
638  return $this->getPageText( $file, 1 );
639  }
640  $document = '';
641  for ( $i = 1; $i <= $numPages; $i++ ) {
642  $curPage = $this->getPageText( $file, $i );
643  if ( is_string( $curPage ) ) {
644  $document .= $curPage . "\n";
645  }
646  }
647  if ( $document !== '' ) {
648  return $document;
649  }
650  return false;
651  }
652 
683  public function formatMetadata( $image, $context = false ) {
684  return false;
685  }
686 
699  protected function formatMetadataHelper( $metadataArray, $context = false ) {
700  $result = [
701  'visible' => [],
702  'collapsed' => []
703  ];
704 
705  // Allow this MediaHandler to override formatting on certain values
706  foreach ( $metadataArray as $tag => $vals ) {
707  $v = $this->formatTag( $tag, $vals, $context );
708  if ( $v === false ) {
709  // Use default formatting
710  continue;
711  }
712  if ( $v === null ) {
713  // Remove this tag, don't format it for display
714  unset( $metadataArray[$tag] );
715  } else {
716  // Allow subclass to override default formatting.
717  $metadataArray[$tag] = [ '_formatted' => $v ];
718  if ( isset( $v['_type'] ) ) {
719  $metadataArray[$tag]['_type'] = $v['_type'];
720  unset( $metadataArray[$tag]['_formatted']['_type'] );
721  }
722  }
723  }
724 
725  $formatted = FormatMetadata::getFormattedData( $metadataArray, $context );
726  // Sort fields into visible and collapsed
727  $visibleFields = $this->visibleMetadataFields();
728  foreach ( $formatted as $name => $value ) {
729  $tag = strtolower( $name );
730  self::addMeta( $result,
731  in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
732  'exif',
733  $tag,
734  $value
735  );
736  }
737 
738  return $result;
739  }
740 
753  protected function formatTag( string $key, $vals, $context = false ) {
754  return false; // Use default formatting
755  }
756 
765  protected function visibleMetadataFields() {
767  }
768 
792  protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
793  $msg = wfMessage( "$type-$id", $param );
794  if ( $msg->exists() ) {
795  $name = $msg->text();
796  } else {
797  // This is for future compatibility when using instant commons.
798  // So as to not display as ugly a name if a new metadata
799  // property is defined that we don't know about
800  // (not a major issue since such a property would be collapsed
801  // by default).
802  wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id );
803  $name = wfEscapeWikiText( $id );
804  }
805  $array[$visibility][] = [
806  'id' => "$type-$id",
807  'name' => $name,
808  'value' => $value
809  ];
810  }
811 
820  public function getShortDesc( $file ) {
822  }
823 
832  public function getLongDesc( $file ) {
834  }
835 
842  public static function getGeneralShortDesc( $file ) {
843  global $wgLang;
844 
845  return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
846  }
847 
854  public static function getGeneralLongDesc( $file ) {
855  return wfMessage( 'file-info' )->sizeParams( $file->getSize() )
856  ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse();
857  }
858 
867  public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
868  $idealWidth = $boxWidth * $maxHeight / $boxHeight;
869  $roundedUp = ceil( $idealWidth );
870  if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
871  return (int)floor( $idealWidth );
872  }
873  return $roundedUp;
874  }
875 
884  public function getDimensionsString( $file ) {
885  return '';
886  }
887 
900  public function parserTransformHook( $parser, $file ) {
901  }
902 
915  public function verifyUpload( $fileName ) {
916  return Status::newGood();
917  }
918 
929  public function removeBadFile( $dstPath, $retval = 0 ) {
930  if ( file_exists( $dstPath ) ) {
931  $thumbstat = stat( $dstPath );
932  if ( $thumbstat['size'] == 0 || $retval != 0 ) {
933  $result = unlink( $dstPath );
934 
935  if ( $result ) {
936  wfDebugLog( 'thumbnail',
937  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
938  $thumbstat['size'], $dstPath ) );
939  } else {
940  wfDebugLog( 'thumbnail',
941  sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
942  $thumbstat['size'], $dstPath ) );
943  }
944 
945  return true;
946  }
947  }
948 
949  return false;
950  }
951 
966  public function filterThumbnailPurgeList( &$files, $options ) {
967  // Do nothing
968  }
969 
977  public function canRotate() {
978  return false;
979  }
980 
997  public function getRotation( $file ) {
998  return 0;
999  }
1000 
1012  protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
1013  # Keep error output limited (T59985)
1014  $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
1015 
1016  wfDebugLog( 'thumbnail',
1017  sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1018  wfHostname(), $retval, $errMessage, $cmd ) );
1019  }
1020 
1030  public function getAvailableLanguages( File $file ) {
1031  return [];
1032  }
1033 
1045  public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) {
1046  return null;
1047  }
1048 
1064  public function getDefaultRenderLanguage( File $file ) {
1065  return null;
1066  }
1067 
1080  public function getLength( $file ) {
1081  return 0.0;
1082  }
1083 
1091  public function isExpensiveToThumbnail( $file ) {
1092  return false;
1093  }
1094 
1103  public function supportsBucketing() {
1104  return false;
1105  }
1106 
1115  public function sanitizeParamsForBucketing( $params ) {
1116  return $params;
1117  }
1118 
1145  public function getWarningConfig( $file ) {
1146  return null;
1147  }
1148 
1156  public static function getPageRangesByDimensions( $pagesByDimensions ) {
1157  $pageRangesByDimensions = [];
1158 
1159  foreach ( $pagesByDimensions as $dimensions => $pageList ) {
1160  $ranges = [];
1161  $firstPage = $pageList[0];
1162  $lastPage = $firstPage - 1;
1163 
1164  foreach ( $pageList as $page ) {
1165  if ( $page > $lastPage + 1 ) {
1166  if ( $firstPage !== $lastPage ) {
1167  $ranges[] = "$firstPage-$lastPage";
1168  } else {
1169  $ranges[] = "$firstPage";
1170  }
1171 
1172  $firstPage = $page;
1173  }
1174 
1175  $lastPage = $page;
1176  }
1177 
1178  if ( $firstPage != $lastPage ) {
1179  $ranges[] = "$firstPage-$lastPage";
1180  } else {
1181  $ranges[] = "$firstPage";
1182  }
1183 
1184  $pageRangesByDimensions[ $dimensions ] = $ranges;
1185  }
1186 
1187  $dimensionsString = [];
1188  foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) {
1189  $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges );
1190  }
1191 
1192  return implode( '/', $dimensionsString );
1193  }
1194 
1203  public function getContentHeaders( $metadata ) {
1204  return [ 'X-Content-Dimensions' => '' ]; // T175689
1205  }
1206 
1215  public function useSplitMetadata() {
1216  return false;
1217  }
1218 }
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:535
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:70
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...
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.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
Service locator for MediaWiki core services.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
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