MediaWiki master
MediaHandler.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Media;
8
9use InvalidArgumentException;
17use ReflectionClass;
18use ReflectionMethod;
20
34abstract class MediaHandler {
35 public const TRANSFORM_LATER = 1;
36 public const METADATA_GOOD = true;
37 public const METADATA_BAD = false;
38 public const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
42 private const MAX_ERR_LOG_SIZE = 65535;
43
50 public static function getHandler( $type ) {
52 ->getMediaHandlerFactory()->getHandler( $type );
53 }
54
60 abstract public function getParamMap();
61
71 abstract public function validateParam( $name, $value );
72
79 abstract public function makeParamString( $params );
80
87 abstract public function parseParamString( $str );
88
97 abstract public function normaliseParams( $image, &$params );
98
121 public function getImageSize( $image, $path ) {
122 return false;
123 }
124
150 public function getSizeAndMetadata( $state, $path ) {
151 return null;
152 }
153
163 public function getMetadata( $image, $path ) {
164 return '';
165 }
166
176 protected function useLegacyMetadata() {
177 return $this->hasMostDerivedMethod( 'getMetadata' )
178 || $this->hasMostDerivedMethod( 'getImageSize' );
179 }
180
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
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 ' . get_debug_type( $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
290 public static function getMetadataVersion() {
291 $version = [ '2' ]; // core metadata version
292 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onGetMetadataVersion( $version );
293
294 return implode( ';', $version );
295 }
296
308 public function convertMetadataVersion( $metadata, $version = 1 ) {
309 return $metadata;
310 }
311
320 public function getMetadataType( $image ) {
321 return false;
322 }
323
342 public function isMetadataValid( $image, $metadata ) {
343 return self::METADATA_GOOD;
344 }
345
371 public function isFileMetadataValid( $image ) {
372 return self::METADATA_GOOD;
373 }
374
409 public function getCommonMetaArray( File $file ) {
410 return false;
411 }
412
428 public function getScriptedTransform( $image, $script, $params ) {
429 return false;
430 }
431
444 final public function getTransform( $image, $dstPath, $dstUrl, $params ) {
445 return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
446 }
447
462 abstract public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
463
474 public function getThumbType( $ext, $mime, $params = null ) {
475 $magic = 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
498 public function canRender( $file ) {
499 return true;
500 }
501
511 public function mustRender( $file ) {
512 return false;
513 }
514
523 public function isMultiPage( $file ) {
524 return false;
525 }
526
535 public function pageCount( File $file ) {
536 return false;
537 }
538
547 public function isVectorized( $file ) {
548 return false;
549 }
550
561 public function isAnimatedImage( $file ) {
562 return false;
563 }
564
574 public function canAnimateThumbnail( $file ) {
575 return true;
576 }
577
584 public function isEnabled() {
585 return true;
586 }
587
605 public function getPageDimensions( File $image, $page ) {
606 return false;
607 }
608
619 public function getPageText( File $image, $page ) {
620 return false;
621 }
622
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
677 public function formatMetadata( $image, $context = false ) {
678 return false;
679 }
680
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
747 protected function formatTag( string $key, $vals, $context = false ) {
748 return false; // Use default formatting
749 }
750
759 protected function visibleMetadataFields() {
761 }
762
786 protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
787 $msg = wfMessage( "$type-$id", (string)$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
820 public function getShortDesc( $file ) {
821 return self::getGeneralShortDesc( $file );
822 }
823
838 public function getLongDesc( $file ) {
839 return self::getGeneralLongDesc( $file );
840 }
841
848 public static function getGeneralShortDesc( $file ) {
849 global $wgLang;
850
851 return htmlspecialchars( $wgLang->formatSize( $file->getSize() ), ENT_QUOTES );
852 }
853
860 public static function getGeneralLongDesc( $file ) {
861 return wfMessage( 'file-info' )
862 ->sizeParams( $file->getSize() )
863 ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )
864 ->parse();
865 }
866
875 public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
876 $idealWidth = $boxWidth * $maxHeight / $boxHeight;
877 $roundedUp = ceil( $idealWidth );
878 if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
879 return (int)floor( $idealWidth );
880 }
881 return $roundedUp;
882 }
883
892 public function getDimensionsString( $file ) {
893 return '';
894 }
895
908 public function parserTransformHook( $parser, $file ) {
909 }
910
923 public function verifyUpload( $fileName ) {
924 return Status::newGood();
925 }
926
937 public function removeBadFile( $dstPath, $retval = 0 ) {
938 if ( file_exists( $dstPath ) ) {
939 $thumbstat = stat( $dstPath );
940 if ( $thumbstat['size'] == 0 || $retval != 0 ) {
941 $result = unlink( $dstPath );
942
943 if ( $result ) {
944 wfDebugLog( 'thumbnail',
945 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
946 $thumbstat['size'], $dstPath ) );
947 } else {
948 wfDebugLog( 'thumbnail',
949 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
950 $thumbstat['size'], $dstPath ) );
951 }
952
953 return true;
954 }
955 }
956
957 return false;
958 }
959
974 public function filterThumbnailPurgeList( &$files, $options ) {
975 // Do nothing
976 }
977
985 public function canRotate() {
986 return false;
987 }
988
1005 public function getRotation( $file ) {
1006 return 0;
1007 }
1008
1020 protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
1021 # Keep error output limited (T59985)
1022 $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
1023
1024 wfDebugLog( 'thumbnail',
1025 sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1026 wfHostname(), $retval, $errMessage, $cmd ) );
1027 }
1028
1038 public function getAvailableLanguages( File $file ) {
1039 return [];
1040 }
1041
1053 public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) {
1054 return null;
1055 }
1056
1072 public function getDefaultRenderLanguage( File $file ) {
1073 return null;
1074 }
1075
1088 public function getLength( $file ) {
1089 return 0.0;
1090 }
1091
1099 public function isExpensiveToThumbnail( $file ) {
1100 return false;
1101 }
1102
1111 public function supportsBucketing() {
1112 return false;
1113 }
1114
1123 public function sanitizeParamsForBucketing( $params ) {
1124 return $params;
1125 }
1126
1153 public function getWarningConfig( $file ) {
1154 return null;
1155 }
1156
1164 public static function getPageRangesByDimensions( $pagesByDimensions ) {
1165 $pageRangesByDimensions = [];
1166
1167 foreach ( $pagesByDimensions as $dimensions => $pageList ) {
1168 $ranges = [];
1169 $firstPage = $pageList[0];
1170 $lastPage = $firstPage - 1;
1171
1172 foreach ( $pageList as $page ) {
1173 if ( $page > $lastPage + 1 ) {
1174 if ( $firstPage !== $lastPage ) {
1175 $ranges[] = "$firstPage-$lastPage";
1176 } else {
1177 $ranges[] = "$firstPage";
1178 }
1179
1180 $firstPage = $page;
1181 }
1182
1183 $lastPage = $page;
1184 }
1185
1186 if ( $firstPage != $lastPage ) {
1187 $ranges[] = "$firstPage-$lastPage";
1188 } else {
1189 $ranges[] = "$firstPage";
1190 }
1191
1192 $pageRangesByDimensions[ $dimensions ] = $ranges;
1193 }
1194
1195 $dimensionsString = [];
1196 foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) {
1197 $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges );
1198 }
1199
1200 return implode( '/', $dimensionsString );
1201 }
1202
1211 public function getContentHeaders( $metadata ) {
1212 return [ 'X-Content-Dimensions' => '' ]; // T175689
1213 }
1214
1223 public function useSplitMetadata() {
1224 return false;
1225 }
1226}
1227
1229class_alias( MediaHandler::class, 'MediaHandler' );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
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.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:551
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:79
pageCount()
Returns the number of pages of a multipage document, or false for documents which aren't multipage do...
Definition File.php:2183
Local file in the wiki's own database.
Definition LocalFile.php:81
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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.
parserTransformHook( $parser, $file)
Modify the parser object post-transform.
getTransform( $image, $dstPath, $dstUrl, $params)
Get a MediaTransformOutput object representing the transformed output.
static getGeneralLongDesc( $file)
Used instead of getLongDesc if there is no handler registered for file.
getPageText(File $image, $page)
Generic getter for text layer.
filterThumbnailPurgeList(&$files, $options)
Remove files from the purge list.
getThumbType( $ext, $mime, $params=null)
Get the thumbnail extension and MIME type for a given source MIME type.
getScriptedTransform( $image, $script, $params)
Get a MediaTransformOutput object representing an alternate of the transformed output which will call...
getContentHeaders( $metadata)
Get useful response headers for GET/HEAD requests for a file with the given metadata.
canAnimateThumbnail( $file)
If the material is animated, we can animate the thumbnail.
getLength( $file)
If it's an audio file, return the length of the file.
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
getParamMap()
Get an associative array mapping magic word IDs to parameter names.
static getMetadataVersion()
Get metadata version.
static getGeneralShortDesc( $file)
Used instead of getShortDesc if there is no handler registered for file.
convertMetadataVersion( $metadata, $version=1)
Convert metadata version.
visibleMetadataFields()
Get a list of metadata items which should be displayed when the metadata table is collapsed.
getMetadataType( $image)
Get a string describing the type of metadata, for display purposes.
doTransform( $image, $dstPath, $dstUrl, $params, $flags=0)
Get a MediaTransformOutput object representing the transformed output.
getEntireText(File $file)
Get the text of the entire document.
mustRender( $file)
True if handled types cannot be displayed directly in a browser but can be rendered.
isExpensiveToThumbnail( $file)
True if creating thumbnails from the file is large or otherwise resource-intensive.
getMetadata( $image, $path)
Get handler-specific metadata which will be saved in the img_metadata field.
isAnimatedImage( $file)
The material is an image, and is animated.
getAvailableLanguages(File $file)
Get list of languages file can be viewed in.
getDimensionsString( $file)
Shown in file history box on image description page.
canRotate()
True if the handler can rotate the media.
getWarningConfig( $file)
Gets configuration for the file warning message.
getImageSize( $image, $path)
Get an image size array like that returned by getimagesize(), or false if it can't be determined.
getPageDimensions(File $image, $page)
Get an associative array of page dimensions Currently "width" and "height" are understood,...
static getHandler( $type)
Get a MediaHandler for a given MIME type from the instance cache.
getSizeAndMetadataWithFallback( $file, $path)
Get the metadata array and the image size, with b/c fallback.
formatTag(string $key, $vals, $context=false)
Override default formatting for the given metadata field.
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...
getMatchedLanguage( $userPreferredLanguage, array $availableLanguages)
When overridden in a descendant class, returns a language code most suiting.
isVectorized( $file)
The material is vectorized and thus scaling is lossless.
useSplitMetadata()
If this returns true, LocalFile may split metadata up and store its constituent items separately.
isFileMetadataValid( $image)
Check if the metadata is valid for this handler.
static getPageRangesByDimensions( $pagesByDimensions)
Converts a dimensions array about a potentially multipage document from an exhaustive list of ordered...
verifyUpload( $fileName)
File validation hook called on upload.
supportsBucketing()
Returns whether or not this handler supports the chained generation of thumbnails according to bucket...
getSizeAndMetadata( $state, $path)
Get image size information and metadata array.
getDefaultRenderLanguage(File $file)
On file types that support renderings in multiple languages, which language is used by default if uns...
isEnabled()
False if the handler is disabled for all files.
getShortDesc( $file)
Short description.
validateParam( $name, $value)
Validate a thumbnail parameter at parse time.
normaliseParams( $image, &$params)
Changes the parameter array as necessary, ready for transformation.
getCommonMetaArray(File $file)
Get an array of standard (FormatMetadata type) metadata values.
makeParamString( $params)
Merge a parameter array into a string appropriate for inclusion in filenames.
parseParamString( $str)
Parse a param string made with makeParamString back into an array.
static fitBoxWidth( $boxWidth, $boxHeight, $maxHeight)
Calculate the largest thumbnail width for a given original file size such that the thumbnail's height...
getLongDesc( $file)
Long description.
sanitizeParamsForBucketing( $params)
Returns a normalised params array for which parameters have been cleaned up for bucketing purposes.
hasMostDerivedMethod( $name)
Check whether a method is implemented in the most derived class.
canRender( $file)
True if the handled types can be transformed.
removeBadFile( $dstPath, $retval=0)
Check for zero-sized thumbnails.
useLegacyMetadata()
If this returns true, the new method getSizeAndMetadata() will not be called.
getRotation( $file)
On supporting image formats, try to read out the low-level orientation of the file and return the ang...
logErrorForExternalProcess( $retval, $err, $cmd)
Log an error that occurred in an external process.
formatMetadataHelper( $metadataArray, $context=false)
sorts the visible/invisible field.
isMultiPage( $file)
True if the type has multi-page capabilities.
formatMetadata( $image, $context=false)
Get an array structure that looks like this:
pageCount(File $file)
Page count for a multi-page document, false if unsupported or unknown.
Trivial implementation of MediaHandlerState.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:134
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Class representing a non-directory file on the file system.
Definition FSFile.php:20
Interface for objects which can provide a MediaWiki context on request.
An interface to support process-local caching of handler data associated with a given file.