summaryrefslogtreecommitdiff
path: root/includes/media
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2014-12-27 15:41:37 +0100
committerPierre Schmitz <pierre@archlinux.de>2014-12-31 11:43:28 +0100
commitc1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch)
tree2b38796e738dd74cb42ecd9bfd151803108386bc /includes/media
parentb88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff)
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/media')
-rw-r--r--includes/media/BMP.php14
-rw-r--r--includes/media/Bitmap.php488
-rw-r--r--includes/media/BitmapMetadataHandler.php58
-rw-r--r--includes/media/Bitmap_ClientOnly.php15
-rw-r--r--includes/media/DjVu.php140
-rw-r--r--includes/media/DjVuImage.php82
-rw-r--r--includes/media/Exif.php511
-rw-r--r--includes/media/ExifBitmap.php74
-rw-r--r--includes/media/FormatMetadata.php2061
-rw-r--r--includes/media/GIF.php48
-rw-r--r--includes/media/GIFMetadataExtractor.php51
-rw-r--r--includes/media/IPTC.php47
-rw-r--r--includes/media/ImageHandler.php72
-rw-r--r--includes/media/Jpeg.php94
-rw-r--r--includes/media/JpegMetadataExtractor.php24
-rw-r--r--includes/media/MediaHandler.php415
-rw-r--r--includes/media/MediaTransformOutput.php98
-rw-r--r--includes/media/PNG.php50
-rw-r--r--includes/media/PNGMetadataExtractor.php92
-rw-r--r--includes/media/SVG.php202
-rw-r--r--includes/media/SVGMetadataExtractor.php92
-rw-r--r--includes/media/Tiff.php24
-rw-r--r--includes/media/TransformationalImageHandler.php593
-rw-r--r--includes/media/XCF.php141
-rw-r--r--includes/media/XMP.php213
-rw-r--r--includes/media/XMPInfo.php992
-rw-r--r--includes/media/XMPValidate.php82
27 files changed, 4217 insertions, 2556 deletions
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index 99b7741a..d8b0ba64 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -28,9 +28,8 @@
* @ingroup Media
*/
class BmpHandler extends BitmapHandler {
-
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -40,9 +39,9 @@ class BmpHandler extends BitmapHandler {
/**
* Render files as PNG
*
- * @param $text
- * @param $mime
- * @param $params
+ * @param string $text
+ * @param string $mime
+ * @param array $params
* @return array
*/
function getThumbType( $text, $mime, $params = null ) {
@@ -52,8 +51,8 @@ class BmpHandler extends BitmapHandler {
/**
* Get width and height from the bmp header.
*
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
* @return array
*/
function getImageSize( $image, $filename ) {
@@ -75,6 +74,7 @@ class BmpHandler extends BitmapHandler {
} catch ( MWException $e ) {
return false;
}
+
return array( $w[1], $h[1] );
}
}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 79b0497d..e81b37de 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -26,204 +26,17 @@
*
* @ingroup Media
*/
-class BitmapHandler extends ImageHandler {
- /**
- * @param $image File
- * @param array $params Transform parameters. Entries with the keys 'width'
- * and 'height' are the respective screen width and height, while the keys
- * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
- * @return bool
- */
- function normaliseParams( $image, &$params ) {
- if ( !parent::normaliseParams( $image, $params ) ) {
- return false;
- }
-
- # Obtain the source, pre-rotation dimensions
- $srcWidth = $image->getWidth( $params['page'] );
- $srcHeight = $image->getHeight( $params['page'] );
-
- # Don't make an image bigger than the source
- if ( $params['physicalWidth'] >= $srcWidth ) {
- $params['physicalWidth'] = $srcWidth;
- $params['physicalHeight'] = $srcHeight;
-
- # Skip scaling limit checks if no scaling is required
- # due to requested size being bigger than source.
- if ( !$image->mustRender() ) {
- return true;
- }
- }
-
- # Check if the file is smaller than the maximum image area for thumbnailing
- $checkImageAreaHookResult = null;
- wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) );
- if ( is_null( $checkImageAreaHookResult ) ) {
- global $wgMaxImageArea;
-
- if ( $srcWidth * $srcHeight > $wgMaxImageArea &&
- !( $image->getMimeType() == 'image/jpeg' &&
- self::getScalerType( false, false ) == 'im' ) ) {
- # Only ImageMagick can efficiently downsize jpg images without loading
- # the entire file in memory
- return false;
- }
- } else {
- return $checkImageAreaHookResult;
- }
-
- return true;
- }
-
- /**
- * Extracts the width/height if the image will be scaled before rotating
- *
- * This will match the physical size/aspect ratio of the original image
- * prior to application of the rotation -- so for a portrait image that's
- * stored as raw landscape with 90-degress rotation, the resulting size
- * will be wider than it is tall.
- *
- * @param array $params Parameters as returned by normaliseParams
- * @param int $rotation The rotation angle that will be applied
- * @return array ($width, $height) array
- */
- public function extractPreRotationDimensions( $params, $rotation ) {
- if ( $rotation == 90 || $rotation == 270 ) {
- # We'll resize before rotation, so swap the dimensions again
- $width = $params['physicalHeight'];
- $height = $params['physicalWidth'];
- } else {
- $width = $params['physicalWidth'];
- $height = $params['physicalHeight'];
- }
- return array( $width, $height );
- }
-
- /**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
- * @param int $flags
- * @return MediaTransformError|ThumbnailImage|TransformParameterError
- */
- function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
- if ( !$this->normaliseParams( $image, $params ) ) {
- return new TransformParameterError( $params );
- }
- # Create a parameter array to pass to the scaler
- $scalerParams = array(
- # The size to which the image will be resized
- 'physicalWidth' => $params['physicalWidth'],
- 'physicalHeight' => $params['physicalHeight'],
- 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
- # The size of the image on the page
- 'clientWidth' => $params['width'],
- 'clientHeight' => $params['height'],
- # Comment as will be added to the Exif of the thumbnail
- 'comment' => isset( $params['descriptionUrl'] ) ?
- "File source: {$params['descriptionUrl']}" : '',
- # Properties of the original image
- 'srcWidth' => $image->getWidth(),
- 'srcHeight' => $image->getHeight(),
- 'mimeType' => $image->getMimeType(),
- 'dstPath' => $dstPath,
- 'dstUrl' => $dstUrl,
- );
-
- # Determine scaler type
- $scaler = self::getScalerType( $dstPath );
-
- wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" );
-
- if ( !$image->mustRender() &&
- $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
- && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) {
-
- # normaliseParams (or the user) wants us to return the unscaled image
- wfDebug( __METHOD__ . ": returning unscaled image\n" );
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- if ( $scaler == 'client' ) {
- # Client-side image scaling, use the source URL
- # Using the destination URL in a TRANSFORM_LATER request would be incorrect
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- if ( $flags & self::TRANSFORM_LATER ) {
- wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $dstUrl, false, $params );
- }
-
- # Try to make a target path for the thumbnail
- if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
- wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" );
- return $this->getClientScalingThumbnailImage( $image, $scalerParams );
- }
-
- # Transform functions and binaries need a FS source file
- $scalerParams['srcPath'] = $image->getLocalRefPath();
-
- # Try a hook
- $mto = null;
- wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
- if ( !is_null( $mto ) ) {
- wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
- $scaler = 'hookaborted';
- }
-
- switch ( $scaler ) {
- case 'hookaborted':
- # Handled by the hook above
- $err = $mto->isError() ? $mto : false;
- break;
- case 'im':
- $err = $this->transformImageMagick( $image, $scalerParams );
- break;
- case 'custom':
- $err = $this->transformCustom( $image, $scalerParams );
- break;
- case 'imext':
- $err = $this->transformImageMagickExt( $image, $scalerParams );
- break;
- case 'gd':
- default:
- $err = $this->transformGd( $image, $scalerParams );
- break;
- }
-
- # Remove the file if a zero-byte thumbnail was created, or if there was an error
- $removed = $this->removeBadFile( $dstPath, (bool)$err );
- if ( $err ) {
- # transform returned MediaTransforError
- return $err;
- } elseif ( $removed ) {
- # Thumbnail was zero-byte and had to be removed
- return new MediaTransformError( 'thumbnail_error',
- $scalerParams['clientWidth'], $scalerParams['clientHeight'] );
- } elseif ( $mto ) {
- return $mto;
- } else {
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
- }
- }
+class BitmapHandler extends TransformationalImageHandler {
/**
* Returns which scaler type should be used. Creates parent directories
* for $dstPath and returns 'client' on error
*
- * @return string client,im,custom,gd
+ * @param string $dstPath
+ * @param bool $checkDstPath
+ * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
*/
- protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ protected function getScalerType( $dstPath, $checkDstPath = true ) {
global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
if ( !$dstPath && $checkDstPath ) {
@@ -242,39 +55,21 @@ class BitmapHandler extends ImageHandler {
} else {
$scaler = 'client';
}
- return $scaler;
- }
- /**
- * Get a ThumbnailImage that respresents an image that will be scaled
- * client side
- *
- * @param $image File File associated with this thumbnail
- * @param array $scalerParams Array with scaler params
- * @return ThumbnailImage
- *
- * @todo fixme: no rotation support
- */
- protected function getClientScalingThumbnailImage( $image, $scalerParams ) {
- $params = array(
- 'width' => $scalerParams['clientWidth'],
- 'height' => $scalerParams['clientHeight']
- );
- return new ThumbnailImage( $image, $image->getURL(), null, $params );
+ return $scaler;
}
/**
* Transform an image using ImageMagick
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
*/
protected function transformImageMagick( $image, $params ) {
# use ImageMagick
- global $wgSharpenReductionThreshold, $wgSharpenParameter,
- $wgMaxAnimatedGifArea,
+ global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
$wgImageMagickTempDir, $wgImageMagickConvertCommand;
$quality = array();
@@ -284,18 +79,19 @@ class BitmapHandler extends ImageHandler {
$animation_post = array();
$decoderHint = array();
if ( $params['mimeType'] == 'image/jpeg' ) {
- $quality = array( '-quality', '80' ); // 80%
+ $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+ $quality = array( '-quality', $qualityVal ?: '80' ); // 80%
# Sharpening, see bug 6193
if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
- / ( $params['srcWidth'] + $params['srcHeight'] )
- < $wgSharpenReductionThreshold ) {
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold
+ ) {
$sharpen = array( '-sharpen', $wgSharpenParameter );
}
if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
// JPEG decoder hint to reduce memory, available since IM 6.5.6-2
$decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" );
}
-
} elseif ( $params['mimeType'] == 'image/png' ) {
$quality = array( '-quality', '95' ); // zlib 9, adaptive filtering
@@ -304,7 +100,6 @@ class BitmapHandler extends ImageHandler {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$scene = 0;
-
} elseif ( $this->isAnimatedImage( $image ) ) {
// Coalesce is needed to scale animated GIFs properly (bug 1017).
$animation_pre = array( '-coalesce' );
@@ -315,7 +110,30 @@ class BitmapHandler extends ImageHandler {
}
}
} elseif ( $params['mimeType'] == 'image/x-xcf' ) {
- $animation_post = array( '-layers', 'merge' );
+ // Before merging layers, we need to set the background
+ // to be transparent to preserve alpha, as -layers merge
+ // merges all layers on to a canvas filled with the
+ // background colour. After merging we reset the background
+ // to be white for the default background colour setting
+ // in the PNG image (which is used in old IE)
+ $animation_pre = array(
+ '-background', 'transparent',
+ '-layers', 'merge',
+ '-background', 'white',
+ );
+ wfSuppressWarnings();
+ $xcfMeta = unserialize( $image->getMetadata() );
+ wfRestoreWarnings();
+ if ( $xcfMeta
+ && isset( $xcfMeta['colorType'] )
+ && $xcfMeta['colorType'] === 'greyscale-alpha'
+ && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
+ ) {
+ // bug 66323 - Greyscale images not rendered properly.
+ // So only take the "red" channel.
+ $channelOnly = array( '-channel', 'R', '-separate' );
+ $animation_pre = array_merge( $animation_pre, $channelOnly );
+ }
}
// Use one thread only, to avoid deadlock bugs on OOM
@@ -358,7 +176,8 @@ class BitmapHandler extends ImageHandler {
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
- return $this->getMediaTransformError( $params, $err );
+
+ return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
}
return false; # No error
@@ -367,7 +186,7 @@ class BitmapHandler extends ImageHandler {
/**
* Transform an image using the Imagick PHP extension
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -382,13 +201,15 @@ class BitmapHandler extends ImageHandler {
if ( $params['mimeType'] == 'image/jpeg' ) {
// Sharpening, see bug 6193
if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
- / ( $params['srcWidth'] + $params['srcHeight'] )
- < $wgSharpenReductionThreshold ) {
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold
+ ) {
// Hack, since $wgSharpenParamater is written specifically for the command line convert
list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
$im->sharpenImage( $radius, $sigma );
}
- $im->setCompressionQuality( 80 );
+ $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+ $im->setCompressionQuality( $qualityVal ?: 80 );
} elseif ( $params['mimeType'] == 'image/png' ) {
$im->setCompressionQuality( 95 );
} elseif ( $params['mimeType'] == 'image/gif' ) {
@@ -432,19 +253,17 @@ class BitmapHandler extends ImageHandler {
return $this->getMediaTransformError( $params,
"Unable to write thumbnail to {$params['dstPath']}" );
}
-
} catch ( ImagickException $e ) {
return $this->getMediaTransformError( $params, $e->getMessage() );
}
return false;
-
}
/**
* Transform an image using a custom command
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -468,39 +287,17 @@ class BitmapHandler extends ImageHandler {
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return $this->getMediaTransformError( $params, $err );
}
- return false; # No error
- }
- /**
- * Log an error that occurred in an external process
- *
- * @param $retval int
- * @param $err int
- * @param $cmd string
- */
- protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
- }
- /**
- * Get a MediaTransformError with error 'thumbnail_error'
- *
- * @param array $params Parameter array as passed to the transform* functions
- * @param string $errMsg Error message
- * @return MediaTransformError
- */
- public function getMediaTransformError( $params, $errMsg ) {
- return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
- $params['clientHeight'], $errMsg );
+ return false; # No error
}
/**
* Transform an image using the built in GD library
*
- * @param $image File File associated with this thumbnail
+ * @param File $image File associated with this thumbnail
* @param array $params Array with scaler params
*
* @return MediaTransformError Error object if error occurred, false (=no error) otherwise
@@ -512,24 +309,28 @@ class BitmapHandler extends ImageHandler {
# input routine for this.
$typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
+ 'image/gif' => array( 'imagecreatefromgif', 'palette', false, 'imagegif' ),
+ 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', true,
+ array( __CLASS__, 'imageJpegWrapper' ) ),
+ 'image/png' => array( 'imagecreatefrompng', 'bits', false, 'imagepng' ),
+ 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ),
+ 'image/xbm' => array( 'imagecreatefromxbm', 'palette', false, 'imagexbm' ),
);
+
if ( !isset( $typemap[$params['mimeType']] ) ) {
$err = 'Image type not supported';
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_image-type' )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
- list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
+ list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
if ( !function_exists( $loader ) ) {
$err = "Incomplete GD library configuration: missing function $loader";
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
@@ -537,6 +338,7 @@ class BitmapHandler extends ImageHandler {
$err = "File seems to be missing: {$params['srcPath']}";
wfDebug( "$err\n" );
$errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
+
return $this->getMediaTransformError( $params, $errMsg );
}
@@ -574,7 +376,12 @@ class BitmapHandler extends ImageHandler {
imagesavealpha( $dst_image, true );
- call_user_func( $saveType, $dst_image, $params['dstPath'] );
+ $funcParams = array( $dst_image, $params['dstPath'] );
+ if ( $useQuality && isset( $params['quality'] ) ) {
+ $funcParams[] = $params['quality'];
+ }
+ call_user_func_array( $saveType, $funcParams );
+
imagedestroy( $dst_image );
imagedestroy( $src_image );
@@ -582,135 +389,21 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Escape a string for ImageMagick's property input (e.g. -set -comment)
- * See InterpretImageProperties() in magick/property.c
- * @return mixed|string
- */
- function escapeMagickProperty( $s ) {
- // Double the backslashes
- $s = str_replace( '\\', '\\\\', $s );
- // Double the percents
- $s = str_replace( '%', '%%', $s );
- // Escape initial - or @
- if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
- $s = '\\' . $s;
- }
- return $s;
- }
-
- /**
- * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
- * and GetPathComponent() in magick/utility.c.
- *
- * This won't work with an initial ~ or @, so input files should be prefixed
- * with the directory name.
- *
- * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
- * it's broken in a way that doesn't involve trying to convert every file
- * in a directory, so we're better off escaping and waiting for the bugfix
- * to filter down to users.
- *
- * @param string $path The file path
- * @param bool|string $scene The scene specification, or false if there is none
- * @throws MWException
- * @return string
- */
- function escapeMagickInput( $path, $scene = false ) {
- # Die on initial metacharacters (caller should prepend path)
- $firstChar = substr( $path, 0, 1 );
- if ( $firstChar === '~' || $firstChar === '@' ) {
- throw new MWException( __METHOD__ . ': cannot escape this path name' );
- }
-
- # Escape glob chars
- $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
-
- return $this->escapeMagickPath( $path, $scene );
- }
-
- /**
- * Escape a string for ImageMagick's output filename. See
- * InterpretImageFilename() in magick/image.c.
- * @return string
- */
- function escapeMagickOutput( $path, $scene = false ) {
- $path = str_replace( '%', '%%', $path );
- return $this->escapeMagickPath( $path, $scene );
- }
-
- /**
- * Armour a string against ImageMagick's GetPathComponent(). This is a
- * helper function for escapeMagickInput() and escapeMagickOutput().
- *
- * @param string $path The file path
- * @param bool|string $scene The scene specification, or false if there is none
- * @throws MWException
- * @return string
- */
- protected function escapeMagickPath( $path, $scene = false ) {
- # Die on format specifiers (other than drive letters). The regex is
- # meant to match all the formats you get from "convert -list format"
- if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
- if ( wfIsWindows() && is_dir( $m[0] ) ) {
- // OK, it's a drive letter
- // ImageMagick has a similar exception, see IsMagickConflict()
- } else {
- throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
- }
- }
-
- # If there are square brackets, add a do-nothing scene specification
- # to force a literal interpretation
- if ( $scene === false ) {
- if ( strpos( $path, '[' ) !== false ) {
- $path .= '[0--1]';
- }
- } else {
- $path .= "[$scene]";
- }
- return $path;
- }
-
- /**
- * Retrieve the version of the installed ImageMagick
- * You can use PHPs version_compare() to use this value
- * Value is cached for one hour.
- * @return String representing the IM version.
+ * Callback for transformGd when transforming jpeg images.
*/
- protected function getMagickVersion() {
- global $wgMemc;
-
- $cache = $wgMemc->get( "imagemagick-version" );
- if ( !$cache ) {
- global $wgImageMagickConvertCommand;
- $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
- wfDebug( __METHOD__ . ": Running convert -version\n" );
- $retval = '';
- $return = wfShellExec( $cmd, $retval );
- $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
- if ( $x != 1 ) {
- wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
- return null;
- }
- $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
- return $matches[1];
- }
- return $cache;
- }
-
- static function imageJpegWrapper( $dst_image, $thumbPath ) {
+ // FIXME: transformImageMagick() & transformImageMagickExt() uses JPEG quality 80, here it's 95?
+ static function imageJpegWrapper( $dst_image, $thumbPath, $quality = 95 ) {
imageinterlace( $dst_image );
- imagejpeg( $dst_image, $thumbPath, 95 );
+ imagejpeg( $dst_image, $thumbPath, $quality );
}
-
/**
* Returns whether the current scaler supports rotation (im and gd do)
*
* @return bool
*/
- public static function canRotate() {
- $scaler = self::getScalerType( null, false );
+ public function canRotate() {
+ $scaler = $this->getScalerType( null, false );
switch ( $scaler ) {
case 'im':
# ImageMagick supports autorotation
@@ -729,9 +422,24 @@ class BitmapHandler extends ImageHandler {
}
/**
- * @param $file File
+ * @see $wgEnableAutoRotation
+ * @return bool Whether auto rotation is enabled
+ */
+ public function autoRotateEnabled() {
+ global $wgEnableAutoRotation;
+
+ if ( $wgEnableAutoRotation === null ) {
+ // Only enable auto-rotation when we actually can
+ return $this->canRotate();
+ }
+
+ return $wgEnableAutoRotation;
+ }
+
+ /**
+ * @param File $file
* @param array $params Rotate parameters.
- * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
* @return bool
*/
@@ -741,7 +449,7 @@ class BitmapHandler extends ImageHandler {
$rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
$scene = false;
- $scaler = self::getScalerType( null, false );
+ $scaler = $this->getScalerType( null, false );
switch ( $scaler ) {
case 'im':
$cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
@@ -751,12 +459,14 @@ class BitmapHandler extends ImageHandler {
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExecWithStderr( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
}
+
return false;
case 'imext':
$im = new Imagick();
@@ -770,21 +480,11 @@ class BitmapHandler extends ImageHandler {
return new MediaTransformError( 'thumbnail_error', 0, 0,
"Unable to write image to {$params['dstPath']}" );
}
+
return false;
default:
return new MediaTransformError( 'thumbnail_error', 0, 0,
"$scaler rotation not implemented" );
}
}
-
- /**
- * Rerurns whether the file needs to be rendered. Returns true if the
- * file requires rotation and we are able to rotate it.
- *
- * @param $file File
- * @return bool
- */
- public function mustRender( $file ) {
- return self::canRotate() && $this->getRotation( $file ) != 0;
- }
}
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index 7c39c814..dd41c388 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -28,12 +28,14 @@
* This sort of acts as an intermediary between MediaHandler::getMetadata
* and the various metadata extractors.
*
- * @todo other image formats.
+ * @todo Other image formats.
* @ingroup Media
*/
class BitmapMetadataHandler {
-
+ /** @var array */
private $metadata = array();
+
+ /** @var array Metadata priority */
private $metaPriority = array(
20 => array( 'other' ),
40 => array( 'native' ),
@@ -44,6 +46,8 @@ class BitmapMetadataHandler {
100 => array( 'iptc-bad-hash' ),
120 => array( 'exif' ),
);
+
+ /** @var string */
private $iptcType = 'iptc-no-hash';
/**
@@ -76,8 +80,8 @@ class BitmapMetadataHandler {
*
* Parameters are passed to the Exif class.
*
- * @param $filename string
- * @param $byteOrder string
+ * @param string $filename
+ * @param string $byteOrder
*/
function getExif( $filename, $byteOrder ) {
global $wgShowEXIF;
@@ -89,11 +93,12 @@ class BitmapMetadataHandler {
}
}
}
+
/** Add misc metadata. Warning: atm if the metadata category
* doesn't have a priority, it will be silently discarded.
*
- * @param array $metaArray array of metadata values
- * @param string $type type. defaults to other. if two things have the same type they're merged
+ * @param array $metaArray Array of metadata values
+ * @param string $type Type. defaults to other. if two things have the same type they're merged
*/
function addMetadata( $metaArray, $type = 'other' ) {
if ( isset( $this->metadata[$type] ) ) {
@@ -111,12 +116,12 @@ class BitmapMetadataHandler {
*
* This function is generally called by the media handlers' getMetadata()
*
- * @return Array metadata array
+ * @return array Metadata array
*/
function getMetadataArray() {
// this seems a bit ugly... This is all so its merged in right order
// based on the MWG recomendation.
- $temp = Array();
+ $temp = array();
krsort( $this->metaPriority );
foreach ( $this->metaPriority as $pri ) {
foreach ( $pri as $type ) {
@@ -138,14 +143,15 @@ class BitmapMetadataHandler {
}
}
}
+
return $temp;
}
/** Main entry point for jpeg's.
*
- * @param string $filename filename (with full path)
- * @return array metadata result array.
- * @throws MWException on invalid file.
+ * @param string $filename Filename (with full path)
+ * @return array Metadata result array.
+ * @throws MWException On invalid file.
*/
static function Jpeg( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
@@ -153,7 +159,7 @@ class BitmapMetadataHandler {
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) {
- $meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
+ $meta->addMetadata( array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
}
if ( isset( $seg['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) {
foreach ( $seg['PSIR'] as $curPSIRValue ) {
@@ -168,7 +174,6 @@ class BitmapMetadataHandler {
* is not well tested and a bit fragile.
*/
$xmp->parseExtended( $xmpExt );
-
}
$res = $xmp->getResults();
foreach ( $res as $type => $array ) {
@@ -178,6 +183,7 @@ class BitmapMetadataHandler {
if ( isset( $seg['byteOrder'] ) ) {
$meta->getExif( $filename, $seg['byteOrder'] );
}
+
return $meta->getMetadataArray();
}
@@ -186,15 +192,17 @@ class BitmapMetadataHandler {
* merge the png various tEXt chunks to that
* are interesting, but for now it only does XMP
*
- * @param string $filename full path to file
- * @return Array Array for storage in img_metadata.
+ * @param string $filename Full path to file
+ * @return array Array for storage in img_metadata.
*/
public static function PNG( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$meta = new self();
$array = PNGMetadataExtractor::getMetadata( $filename );
- if ( isset( $array['text']['xmp']['x-default'] ) && $array['text']['xmp']['x-default'] !== '' && $showXMP ) {
+ if ( isset( $array['text']['xmp']['x-default'] )
+ && $array['text']['xmp']['x-default'] !== '' && $showXMP
+ ) {
$xmp = new XMPReader();
$xmp->parse( $array['text']['xmp']['x-default'] );
$xmpRes = $xmp->getResults();
@@ -207,6 +215,7 @@ class BitmapMetadataHandler {
unset( $array['text'] );
$array['metadata'] = $meta->getMetadataArray();
$array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION;
+
return $array;
}
@@ -215,8 +224,8 @@ class BitmapMetadataHandler {
* They don't really have native metadata, so just merges together
* XMP and image comment.
*
- * @param string $filename full path to file
- * @return Array metadata array
+ * @param string $filename Full path to file
+ * @return array Metadata array
*/
public static function GIF( $filename ) {
@@ -234,7 +243,6 @@ class BitmapMetadataHandler {
foreach ( $xmpRes as $type => $xmpSection ) {
$meta->addMetadata( $xmpSection, $type );
}
-
}
unset( $baseArray['comment'] );
@@ -242,6 +250,7 @@ class BitmapMetadataHandler {
$baseArray['metadata'] = $meta->getMetadataArray();
$baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION;
+
return $baseArray;
}
@@ -251,13 +260,12 @@ class BitmapMetadataHandler {
* but needs some further processing because PHP's exif support
* is stupid...)
*
- * @todo Add XMP support, so this function actually makes
- * sense to put here.
+ * @todo Add XMP support, so this function actually makes sense to put here.
*
* The various exceptions this throws are caught later.
- * @param $filename String
+ * @param string $filename
* @throws MWException
- * @return Array The metadata.
+ * @return array The metadata.
*/
public static function Tiff( $filename ) {
if ( file_exists( $filename ) ) {
@@ -269,6 +277,7 @@ class BitmapMetadataHandler {
$data = $exif->getFilteredData();
if ( $data ) {
$data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return $data;
} else {
throw new MWException( "Could not extract data from tiff file $filename" );
@@ -277,12 +286,13 @@ class BitmapMetadataHandler {
throw new MWException( "File doesn't exist - $filename" );
}
}
+
/**
* Read the first 2 bytes of a tiff file to figure out
* Little Endian or Big Endian. Needed for exif stuff.
*
* @param string $filename The filename
- * @return String 'BE' or 'LE' or false
+ * @return string 'BE' or 'LE' or false
*/
static function getTiffByteOrder( $filename ) {
$fh = fopen( $filename, 'rb' );
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 63af2552..b91fb8aa 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -29,11 +29,13 @@
*
* @ingroup Media
*/
+// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
class BitmapHandler_ClientOnly extends BitmapHandler {
+ // @codingStandardsIgnoreEnd
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -41,10 +43,10 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return ThumbnailImage|TransformParameterError
*/
@@ -52,6 +54,7 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
}
+
return new ThumbnailImage( $image, $image->getURL(), $image->getLocalRefPath(), $params );
}
}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 9b8116e9..daeb475f 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class DjVuHandler extends ImageHandler {
-
/**
* @return bool
*/
@@ -35,6 +34,7 @@ class DjVuHandler extends ImageHandler {
global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
+
return false;
} else {
return true;
@@ -42,7 +42,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -50,7 +50,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function isMultiPage( $file ) {
@@ -68,11 +68,16 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
* @return bool
*/
function validateParam( $name, $value ) {
+ if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
+ // Extra junk on the end of page, probably actually a caption
+ // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
+ return false;
+ }
if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
if ( $value <= 0 ) {
return false;
@@ -85,7 +90,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return bool|string
*/
function makeParamString( $params ) {
@@ -93,11 +98,12 @@ class DjVuHandler extends ImageHandler {
if ( !isset( $params['width'] ) ) {
return false;
}
+
return "page{$page}-{$params['width']}px";
}
/**
- * @param $str
+ * @param string $str
* @return array|bool
*/
function parseParamString( $str ) {
@@ -110,7 +116,7 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return array
*/
function getScriptParams( $params ) {
@@ -121,10 +127,10 @@ class DjVuHandler extends ImageHandler {
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return MediaTransformError|ThumbnailImage|TransformParameterError
*/
@@ -137,6 +143,7 @@ class DjVuHandler extends ImageHandler {
if ( !$xml ) {
$width = isset( $params['width'] ) ? $params['width'] : 0;
$height = isset( $params['height'] ) ? $params['height'] : 0;
+
return new MediaTransformError( 'thumbnail_error', $width, $height,
wfMessage( 'djvu_no_xml' )->text() );
}
@@ -162,6 +169,7 @@ class DjVuHandler extends ImageHandler {
'height' => $height,
'page' => $page
);
+
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
}
@@ -174,7 +182,33 @@ class DjVuHandler extends ImageHandler {
);
}
- $srcPath = $image->getLocalRefPath();
+ // Get local copy source for shell scripts
+ // Thumbnail extraction is very inefficient for large files.
+ // Provide a way to pool count limit the number of downloaders.
+ if ( $image->getSize() >= 1e7 ) { // 10MB
+ $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
+ array(
+ 'doWork' => function () use ( $image ) {
+ return $image->getLocalRefPath();
+ }
+ )
+ );
+ $srcPath = $work->execute();
+ } else {
+ $srcPath = $image->getLocalRefPath();
+ }
+
+ if ( $srcPath === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
# Use a subshell (brackets) to aggregate stderr from both pipeline commands
# before redirecting it to the overall stdout. This works in both Linux and Windows XP.
$cmd = '(' . wfEscapeShellArg(
@@ -195,9 +229,7 @@ class DjVuHandler extends ImageHandler {
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
} else {
$params = array(
@@ -205,6 +237,7 @@ class DjVuHandler extends ImageHandler {
'height' => $height,
'page' => $page
);
+
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
}
}
@@ -212,6 +245,8 @@ class DjVuHandler extends ImageHandler {
/**
* Cache an instance of DjVuImage in an Image object, return that instance
*
+ * @param File $image
+ * @param string $path
* @return DjVuImage
*/
function getDjVuImage( $image, $path ) {
@@ -222,23 +257,59 @@ class DjVuHandler extends ImageHandler {
} else {
$deja = $image->dejaImage;
}
+
return $deja;
}
/**
+ * Get metadata, unserializing it if neccessary.
+ *
+ * @param File $file The DjVu file in question
+ * @return string XML metadata as a string.
+ */
+ private function getUnserializedMetadata( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
+ // Old style. Not serialized but instead just a raw string of XML.
+ return $metadata;
+ }
+
+ wfSuppressWarnings();
+ $unser = unserialize( $metadata );
+ wfRestoreWarnings();
+ if ( is_array( $unser ) ) {
+ if ( isset( $unser['error'] ) ) {
+ return false;
+ } elseif ( isset( $unser['xml'] ) ) {
+ return $unser['xml'];
+ } else {
+ // Should never ever reach here.
+ throw new MWException( "Error unserializing DjVu metadata." );
+ }
+ }
+
+ // unserialize failed. Guess it wasn't really serialized after all,
+ return $metadata;
+ }
+
+ /**
* Cache a document tree for the DjVu XML metadata
- * @param $image File
- * @param $gettext Boolean: DOCUMENT (Default: false)
- * @return bool
+ * @param File $image
+ * @param bool $gettext DOCUMENT (Default: false)
+ * @return bool|SimpleXMLElement
*/
function getMetaTree( $image, $gettext = false ) {
- if ( isset( $image->dejaMetaTree ) ) {
+ if ( $gettext && isset( $image->djvuTextTree ) ) {
+ return $image->djvuTextTree;
+ }
+ if ( !$gettext && isset( $image->dejaMetaTree ) ) {
return $image->dejaMetaTree;
}
- $metadata = $image->getMetadata();
+ $metadata = $this->getUnserializedMetadata( $image );
if ( !$this->isMetadataValid( $image, $metadata ) ) {
wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+
return false;
}
wfProfileIn( __METHOD__ );
@@ -250,8 +321,11 @@ class DjVuHandler extends ImageHandler {
$image->djvuTextTree = false;
$tree = new SimpleXMLElement( $metadata );
if ( $tree->getName() == 'mw-djvu' ) {
+ /** @var SimpleXMLElement $b */
foreach ( $tree->children() as $b ) {
if ( $b->getName() == 'DjVuTxt' ) {
+ // @todo File::djvuTextTree and File::dejaMetaTree are declared
+ // dynamically. Add a public File::$data to facilitate this?
$image->djvuTextTree = $b;
} elseif ( $b->getName() == 'DjVuXML' ) {
$image->dejaMetaTree = $b;
@@ -272,6 +346,11 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param File $image
+ * @param string $path
+ * @return bool|array False on failure
+ */
function getImageSize( $image, $path ) {
return $this->getDjVuImage( $image, $path )->getImageSize();
}
@@ -283,12 +362,20 @@ class DjVuHandler extends ImageHandler {
$magic = MimeMagic::singleton();
$mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
}
+
return array( $wgDjvuOutputExtension, $mime );
}
function getMetadata( $image, $path ) {
wfDebug( "Getting DjVu metadata for $path\n" );
- return $this->getDjVuImage( $image, $path )->retrieveMetaData();
+
+ $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
+ if ( $xml === false ) {
+ // Special value so that we don't repetitively try and decode a broken file.
+ return serialize( array( 'error' => 'Error extracting metadata' ) );
+ } else {
+ return serialize( array( 'xml' => $xml ) );
+ }
}
function getMetadataType( $image ) {
@@ -304,6 +391,7 @@ class DjVuHandler extends ImageHandler {
if ( !$tree ) {
return false;
}
+
return count( $tree->xpath( '//OBJECT' ) );
}
@@ -324,6 +412,11 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param File $image
+ * @param int $page Page number to get information for
+ * @return bool|string Page text or false when no text found.
+ */
function getPageText( $image, $page ) {
$tree = $this->getMetaTree( $image, true );
if ( !$tree ) {
@@ -333,11 +426,10 @@ class DjVuHandler extends ImageHandler {
$o = $tree->BODY[0]->PAGE[$page - 1];
if ( $o ) {
$txt = $o['value'];
+
return $txt;
} else {
return false;
}
-
}
-
}
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
index 54efe7a8..6ff19c90 100644
--- a/includes/media/DjVuImage.php
+++ b/includes/media/DjVuImage.php
@@ -3,7 +3,7 @@
* DjVu image handler.
*
* Copyright © 2006 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * https://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,6 +35,11 @@
*/
class DjVuImage {
/**
+ * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
+ */
+ const DJVUTXT_MEMORY_LIMIT = 300000;
+
+ /**
* Constructor
*
* @param string $filename The DjVu file name.
@@ -44,22 +49,18 @@ class DjVuImage {
}
/**
- * @const DJVUTXT_MEMORY_LIMIT Memory limit for the DjVu description software
- */
- const DJVUTXT_MEMORY_LIMIT = 300000;
-
- /**
* Check if the given file is indeed a valid DjVu image file
* @return bool
*/
public function isValid() {
$info = $this->getInfo();
+
return $info !== false;
}
/**
* Return data in the style of getimagesize()
- * @return array or false on failure
+ * @return array|bool Array or false on failure
*/
public function getImageSize() {
$data = $this->getInfo();
@@ -71,6 +72,7 @@ class DjVuImage {
return array( $width, $height, 'DjVu',
"width=\"$width\" height=\"$height\"" );
}
+
return false;
}
@@ -82,8 +84,11 @@ class DjVuImage {
function dump() {
$file = fopen( $this->mFilename, 'rb' );
$header = fread( $file, 12 );
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
+ /** @var string $chunk
+ * @var string $chunkLength */
echo "$chunk $chunkLength\n";
$this->dumpForm( $file, $chunkLength, 1 );
fclose( $file );
@@ -98,8 +103,11 @@ class DjVuImage {
if ( $chunkHeader == '' ) {
break;
}
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
+ /** @var string $chunk
+ * @var string $chunkLength */
echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
if ( $chunk == 'FORM' ) {
@@ -120,6 +128,7 @@ class DjVuImage {
wfRestoreWarnings();
if ( $file === false ) {
wfDebug( __METHOD__ . ": missing or failed file read\n" );
+
return false;
}
@@ -129,9 +138,14 @@ class DjVuImage {
if ( strlen( $header ) < 16 ) {
wfDebug( __METHOD__ . ": too short file header\n" );
} else {
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
+ /** @var string $magic
+ * @var string $subtype
+ * @var string $formLength
+ * @var string $formType */
if ( $magic != 'AT&T' ) {
wfDebug( __METHOD__ . ": not a DjVu file\n" );
} elseif ( $subtype == 'DJVU' ) {
@@ -145,6 +159,7 @@ class DjVuImage {
}
}
fclose( $file );
+
return $info;
}
@@ -153,8 +168,12 @@ class DjVuImage {
if ( strlen( $header ) < 8 ) {
return array( false, 0 );
} else {
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack( 'a4chunk/Nlength', $header ) );
+
+ /** @var string $chunk
+ * @var string $length */
return array( $chunk, $length );
}
}
@@ -182,6 +201,7 @@ class DjVuImage {
$subtype = fread( $file, 4 );
if ( $subtype == 'DJVU' ) {
wfDebug( __METHOD__ . ": found first subpage\n" );
+
return $this->getPageInfo( $file, $length );
}
$this->skipChunk( $file, $length - 4 );
@@ -192,6 +212,7 @@ class DjVuImage {
} while ( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
+
return false;
}
@@ -199,20 +220,24 @@ class DjVuImage {
list( $chunk, $length ) = $this->readChunk( $file );
if ( $chunk != 'INFO' ) {
wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" );
+
return false;
}
if ( $length < 9 ) {
wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" );
+
return false;
}
$data = fread( $file, $length );
if ( strlen( $data ) < $length ) {
wfDebug( __METHOD__ . ": INFO chunk cut off\n" );
+
return false;
}
- // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with
+ // something that explicitly initializes local variables.
extract( unpack(
'nwidth/' .
'nheight/' .
@@ -220,8 +245,16 @@ class DjVuImage {
'Cmajor/' .
'vresolution/' .
'Cgamma', $data ) );
+
# Newer files have rotation info in byte 10, but we don't use it yet.
+ /** @var string $width
+ * @var string $height
+ * @var string $major
+ * @var string $minor
+ * @var string $resolution
+ * @var string $length
+ * @var string $gamma */
return array(
'width' => $width,
'height' => $height,
@@ -284,17 +317,21 @@ EOR;
}
}
wfProfileOut( __METHOD__ );
+
return $xml;
}
function pageTextCallback( $matches ) {
# Get rid of invalid UTF-8, strip control characters
- return '<PAGE value="' . htmlspecialchars( UtfNormal::cleanUp( $matches[1] ) ) . '" />';
+ $val = htmlspecialchars( UtfNormal::cleanUp( stripcslashes( $matches[1] ) ) );
+ $val = str_replace( array( "\n", '�' ), array( '&#10;', '' ), $val );
+ return '<PAGE value="' . $val . '" />';
}
/**
* Hack to temporarily work around djvutoxml bug
- * @return bool|string
+ * @param string $dump
+ * @return string
*/
function convertDumpToXML( $dump ) {
if ( strval( $dump ) == '' ) {
@@ -334,6 +371,7 @@ EOT;
if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
+
return false;
}
if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
@@ -352,6 +390,7 @@ EOT;
}
$xml .= "</BODY>\n</DjVuXML>\n";
+
return $xml;
}
@@ -367,8 +406,13 @@ EOT;
break;
}
- if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
- $xml .= Xml::tags( 'OBJECT',
+ if ( preg_match(
+ '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/',
+ $line,
+ $m
+ ) ) {
+ $xml .= Xml::tags(
+ 'OBJECT',
array(
#'data' => '',
#'type' => 'image/x.djvu',
@@ -377,13 +421,15 @@ EOT;
#'usemap' => '',
),
"\n" .
- Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
- Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
+ Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
+ Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
) . "\n";
+
return true;
}
$line = strtok( "\n" );
}
+
# Not found
return false;
}
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
index 9a2794a5..018b58c5 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -30,87 +30,82 @@
* @ingroup Media
*/
class Exif {
+ /** An 8-bit (1-byte) unsigned integer. */
+ const BYTE = 1;
- const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
- const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
- const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
- const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
- const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
- const SHORT_OR_LONG = 6; //!< A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer.
- const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
- const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
- const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
- const IGNORE = -1; // A fake value for things we don't want or don't support.
-
- //@{
- /* @var array
- * @private
+ /** An 8-bit byte containing one 7-bit ASCII code.
+ * The final byte is terminated with NULL.
*/
+ const ASCII = 2;
- /**
- * Exif tags grouped by category, the tagname itself is the key and the type
- * is the value, in the case of more than one possible value type they are
- * separated by commas.
- */
- var $mExifTags;
+ /** A 16-bit (2-byte) unsigned integer. */
+ const SHORT = 3;
- /**
- * The raw Exif data returned by exif_read_data()
- */
- var $mRawExifData;
+ /** A 32-bit (4-byte) unsigned integer. */
+ const LONG = 4;
- /**
- * A Filtered version of $mRawExifData that has been pruned of invalid
- * tags and tags that contain content they shouldn't contain according
- * to the Exif specification
+ /** Two LONGs. The first LONG is the numerator and the second LONG expresses
+ * the denominator
*/
- var $mFilteredExifData;
+ const RATIONAL = 5;
- /**
- * Filtered and formatted Exif data, see FormatMetadata::getFormattedData()
- */
- var $mFormattedExifData;
+ /** A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer. */
+ const SHORT_OR_LONG = 6;
- //@}
+ /** An 8-bit byte that can take any value depending on the field definition */
+ const UNDEFINED = 7;
- //@{
- /* @var string
- * @private
- */
+ /** A 32-bit (4-byte) signed integer (2's complement notation), */
+ const SLONG = 9;
- /**
- * The file being processed
+ /** Two SLONGs. The first SLONG is the numerator and the second SLONG is
+ * the denominator.
*/
- var $file;
+ const SRATIONAL = 10;
- /**
- * The basename of the file being processed
+ /** A fake value for things we don't want or don't support. */
+ const IGNORE = -1;
+
+ /** @var array Exif tags grouped by category, the tagname itself is the key
+ * and the type is the value, in the case of more than one possible value
+ * type they are separated by commas.
*/
- var $basename;
+ private $mExifTags;
- /**
- * The private log to log to, e.g. 'exif'
+ /** @var array The raw Exif data returned by exif_read_data() */
+ private $mRawExifData;
+
+ /** @var array A Filtered version of $mRawExifData that has been pruned
+ * of invalid tags and tags that contain content they shouldn't contain
+ * according to the Exif specification
*/
- var $log = false;
+ private $mFilteredExifData;
- /**
- * The byte order of the file. Needed because php's
- * extension doesn't fully process some obscure props.
+ /** @var string The file being processed */
+ private $file;
+
+ /** @var string The basename of the file being processed */
+ private $basename;
+
+ /** @var string The private log to log to, e.g. 'exif' */
+ private $log = false;
+
+ /** @var string The byte order of the file. Needed because php's extension
+ * doesn't fully process some obscure props.
*/
private $byteOrder;
- //@}
/**
* Constructor
*
- * @param string $file filename.
- * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+ * @param string $file Filename.
+ * @param string $byteOrder Type of byte ordering either 'BE' (Big Endian)
+ * or 'LE' (Little Endian). Default ''.
* @throws MWException
* @todo FIXME: The following are broke:
- * SubjectArea. Need to test the more obscure tags.
- *
- * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
- * possibly should treat 0/0 = 0. need to read exif spec on that.
+ * SubjectArea. Need to test the more obscure tags.
+ * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
+ * Possibly should treat 0/0 = 0. need to read exif spec on that.
*/
function __construct( $file, $byteOrder = '' ) {
/**
@@ -125,122 +120,123 @@ class Exif {
# TIFF Rev. 6.0 Attribute Information (p22)
'IFD0' => array(
# Tags relating to image structure
- 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
- 'ImageLength' => Exif::SHORT_OR_LONG, # Image height
- 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
+ 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
+ 'ImageLength' => Exif::SHORT_OR_LONG, # Image height
+ 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
# "When a primary image is JPEG compressed, this designation is not"
# "necessary and is omitted." (p23)
- 'Compression' => Exif::SHORT, # Compression scheme #p23
- 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
- 'Orientation' => Exif::SHORT, # Orientation of image #p24
- 'SamplesPerPixel' => Exif::SHORT, # Number of components
- 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
- 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24
- 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
- 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
- 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
- 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
+ 'Compression' => Exif::SHORT, # Compression scheme #p23
+ 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
+ 'Orientation' => Exif::SHORT, # Orientation of image #p24
+ 'SamplesPerPixel' => Exif::SHORT, # Number of components
+ 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
+ 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24
+ 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
+ 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
+ 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
+ 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
# Tags relating to recording offset
- 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
- 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
- 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
- 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
- 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
+ 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
+ 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
+ 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
+ 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
+ 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
# Tags relating to image data characteristics
- 'TransferFunction' => Exif::IGNORE, # Transfer function
- 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity
- 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities
- 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ), # Color space transformation matrix coefficients #p27
- 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values
+ 'TransferFunction' => Exif::IGNORE, # Transfer function
+ 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity
+ 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities
+ # Color space transformation matrix coefficients #p27
+ 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ),
+ 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values
# Other tags
- 'DateTime' => Exif::ASCII, # File change date and time
- 'ImageDescription' => Exif::ASCII, # Image title
- 'Make' => Exif::ASCII, # Image input equipment manufacturer
- 'Model' => Exif::ASCII, # Image input equipment model
- 'Software' => Exif::ASCII, # Software used
- 'Artist' => Exif::ASCII, # Person who created the image
- 'Copyright' => Exif::ASCII, # Copyright holder
+ 'DateTime' => Exif::ASCII, # File change date and time
+ 'ImageDescription' => Exif::ASCII, # Image title
+ 'Make' => Exif::ASCII, # Image input equipment manufacturer
+ 'Model' => Exif::ASCII, # Image input equipment model
+ 'Software' => Exif::ASCII, # Software used
+ 'Artist' => Exif::ASCII, # Person who created the image
+ 'Copyright' => Exif::ASCII, # Copyright holder
),
# Exif IFD Attribute Information (p30-31)
'EXIF' => array(
- # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
+ # @todo NOTE: Nonexistence of this field is taken to mean nonconformance
# to the Exif 2.1 AND 2.2 standards
- 'ExifVersion' => Exif::UNDEFINED, # Exif version
- 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
+ 'ExifVersion' => Exif::UNDEFINED, # Exif version
+ 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
# Tags relating to Image Data Characteristics
- 'ColorSpace' => Exif::SHORT, # Color space information #p32
+ 'ColorSpace' => Exif::SHORT, # Color space information #p32
# Tags relating to image configuration
- 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
- 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
- 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width
- 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height
+ 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
+ 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
+ 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width
+ 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height
# Tags relating to related user information
- 'MakerNote' => Exif::IGNORE, # Manufacturer notes
- 'UserComment' => Exif::UNDEFINED, # User comments #p34
+ 'MakerNote' => Exif::IGNORE, # Manufacturer notes
+ 'UserComment' => Exif::UNDEFINED, # User comments #p34
# Tags relating to related file information
- 'RelatedSoundFile' => Exif::ASCII, # Related audio file
+ 'RelatedSoundFile' => Exif::ASCII, # Related audio file
# Tags relating to date and time
- 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
- 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
- 'SubSecTime' => Exif::ASCII, # DateTime subseconds
- 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
- 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
+ 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
+ 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
+ 'SubSecTime' => Exif::ASCII, # DateTime subseconds
+ 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
+ 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
# Tags relating to picture-taking conditions (p31)
- 'ExposureTime' => Exif::RATIONAL, # Exposure time
- 'FNumber' => Exif::RATIONAL, # F Number
- 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
- 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
- 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
+ 'ExposureTime' => Exif::RATIONAL, # Exposure time
+ 'FNumber' => Exif::RATIONAL, # F Number
+ 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
+ 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
+ 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
'OECF' => Exif::IGNORE,
# Optoelectronic conversion factor. Note: We don't have support for this atm.
- 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
- 'ApertureValue' => Exif::RATIONAL, # Aperture
- 'BrightnessValue' => Exif::SRATIONAL, # Brightness
- 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
- 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
- 'SubjectDistance' => Exif::RATIONAL, # Subject distance
- 'MeteringMode' => Exif::SHORT, # Metering mode #p40
- 'LightSource' => Exif::SHORT, # Light source #p40-41
- 'Flash' => Exif::SHORT, # Flash #p41-42
- 'FocalLength' => Exif::RATIONAL, # Lens focal length
- 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
- 'FlashEnergy' => Exif::RATIONAL, # Flash energy
- 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
- 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
- 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
- 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
- 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location
- 'ExposureIndex' => Exif::RATIONAL, # Exposure index
- 'SensingMethod' => Exif::SHORT, # Sensing method #p46
- 'FileSource' => Exif::UNDEFINED, # File source #p47
- 'SceneType' => Exif::UNDEFINED, # Scene type #p47
- 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
- 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
- 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
- 'WhiteBalance' => Exif::SHORT, # White Balance #p49
- 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
- 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
- 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
- 'GainControl' => Exif::SHORT, # Scene control #p49-50
- 'Contrast' => Exif::SHORT, # Contrast #p50
- 'Saturation' => Exif::SHORT, # Saturation #p50
- 'Sharpness' => Exif::SHORT, # Sharpness #p50
+ 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
+ 'ApertureValue' => Exif::RATIONAL, # Aperture
+ 'BrightnessValue' => Exif::SRATIONAL, # Brightness
+ 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
+ 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
+ 'SubjectDistance' => Exif::RATIONAL, # Subject distance
+ 'MeteringMode' => Exif::SHORT, # Metering mode #p40
+ 'LightSource' => Exif::SHORT, # Light source #p40-41
+ 'Flash' => Exif::SHORT, # Flash #p41-42
+ 'FocalLength' => Exif::RATIONAL, # Lens focal length
+ 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
+ 'FlashEnergy' => Exif::RATIONAL, # Flash energy
+ 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
+ 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
+ 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
+ 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
+ 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location
+ 'ExposureIndex' => Exif::RATIONAL, # Exposure index
+ 'SensingMethod' => Exif::SHORT, # Sensing method #p46
+ 'FileSource' => Exif::UNDEFINED, # File source #p47
+ 'SceneType' => Exif::UNDEFINED, # Scene type #p47
+ 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
+ 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
+ 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
+ 'WhiteBalance' => Exif::SHORT, # White Balance #p49
+ 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
+ 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
+ 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
+ 'GainControl' => Exif::SHORT, # Scene control #p49-50
+ 'Contrast' => Exif::SHORT, # Contrast #p50
+ 'Saturation' => Exif::SHORT, # Saturation #p50
+ 'Sharpness' => Exif::SHORT, # Sharpness #p50
'DeviceSettingDescription' => Exif::IGNORE,
# Device settings description. This could maybe be supported. Need to find an
# example file that uses this to see if it has stuff of interest in it.
- 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
+ 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
- 'ImageUniqueID' => Exif::ASCII, # Unique image ID
+ 'ImageUniqueID' => Exif::ASCII, # Unique image ID
),
# GPS Attribute Information (p52)
@@ -248,38 +244,38 @@ class Exif {
'GPSVersion' => Exif::UNDEFINED,
# Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
# Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
- 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
- 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
- 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
- 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude
+ 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
+ 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
+ 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
+ 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude
'GPSAltitudeRef' => Exif::UNDEFINED,
# Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
# but php seems to disagree.
- 'GPSAltitude' => Exif::RATIONAL, # Altitude
- 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock)
- 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
- 'GPSStatus' => Exif::ASCII, # Receiver status #p54
- 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
- 'GPSDOP' => Exif::RATIONAL, # Measurement precision
- 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
- 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
- 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
- 'GPSTrack' => Exif::RATIONAL, # Direction of movement
- 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
- 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
- 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
- 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
- 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
- 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
- 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
- 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
- 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
- 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
- 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
- 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
- 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
- 'GPSDateStamp' => Exif::ASCII, # GPS date
- 'GPSDifferential' => Exif::SHORT, # GPS differential correction
+ 'GPSAltitude' => Exif::RATIONAL, # Altitude
+ 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock)
+ 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
+ 'GPSStatus' => Exif::ASCII, # Receiver status #p54
+ 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
+ 'GPSDOP' => Exif::RATIONAL, # Measurement precision
+ 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
+ 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
+ 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
+ 'GPSTrack' => Exif::RATIONAL, # Direction of movement
+ 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
+ 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
+ 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
+ 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
+ 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
+ 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
+ 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
+ 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
+ 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
+ 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
+ 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
+ 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
+ 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
+ 'GPSDateStamp' => Exif::ASCII, # GPS date
+ 'GPSDifferential' => Exif::SHORT, # GPS differential correction
),
);
@@ -302,14 +298,15 @@ class Exif {
$data = exif_read_data( $this->file, 0, true );
wfRestoreWarnings();
} else {
- throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
+ throw new MWException( "Internal error: exif_read_data not present. " .
+ "\$wgShowEXIF may be incorrectly set or not checked by an extension." );
}
/**
* exif_read_data() will return false on invalid input, such as
* when somebody uploads a file called something.jpeg
* containing random gibberish.
*/
- $this->mRawExifData = $data ? $data : array();
+ $this->mRawExifData = $data ?: array();
$this->makeFilteredData();
$this->collapseData();
$this->debugFile( __FUNCTION__, false );
@@ -319,16 +316,16 @@ class Exif {
* Make $this->mFilteredExifData
*/
function makeFilteredData() {
- $this->mFilteredExifData = Array();
+ $this->mFilteredExifData = array();
foreach ( array_keys( $this->mRawExifData ) as $section ) {
- if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
+ if ( !array_key_exists( $section, $this->mExifTags ) ) {
$this->debug( $section, __FUNCTION__, "'$section' is not a valid Exif section" );
continue;
}
foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
- if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
+ if ( !array_key_exists( $tag, $this->mExifTags[$section] ) ) {
$this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
continue;
}
@@ -371,15 +368,17 @@ class Exif {
$this->exifGPStoNumber( 'GPSLongitude' );
$this->exifGPStoNumber( 'GPSDestLongitude' );
- if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
-
- // We know altitude data is a <num>/<denom> from the validation functions ran earlier.
- // But multiplying such a string by -1 doesn't work well, so convert.
+ if ( isset( $this->mFilteredExifData['GPSAltitude'] )
+ && isset( $this->mFilteredExifData['GPSAltitudeRef'] )
+ ) {
+ // We know altitude data is a <num>/<denom> from the validation
+ // functions ran earlier. But multiplying such a string by -1
+ // doesn't work well, so convert.
list( $num, $denom ) = explode( '/', $this->mFilteredExifData['GPSAltitude'] );
$this->mFilteredExifData['GPSAltitude'] = $num / $denom;
if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
- $this->mFilteredExifData['GPSAltitude'] *= - 1;
+ $this->mFilteredExifData['GPSAltitude'] *= -1;
}
unset( $this->mFilteredExifData['GPSAltitudeRef'] );
}
@@ -397,7 +396,9 @@ class Exif {
if ( isset( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
$val = $this->mFilteredExifData['ComponentsConfiguration'];
$ccVals = array();
- for ( $i = 0; $i < strlen( $val ); $i++ ) {
+
+ $strLen = strlen( $val );
+ for ( $i = 0; $i < $strLen; $i++ ) {
$ccVals[$i] = ord( substr( $val, $i, 1 ) );
}
$ccVals['_type'] = 'ol'; //this is for formatting later.
@@ -414,12 +415,15 @@ class Exif {
if ( isset( $this->mFilteredExifData['GPSVersion'] ) ) {
$val = $this->mFilteredExifData['GPSVersion'];
$newVal = '';
- for ( $i = 0; $i < strlen( $val ); $i++ ) {
+
+ $strLen = strlen( $val );
+ for ( $i = 0; $i < $strLen; $i++ ) {
if ( $i !== 0 ) {
$newVal .= '.';
}
$newVal .= ord( substr( $val, $i, 1 ) );
}
+
if ( $this->byteOrder === 'LE' ) {
// Need to reverse the string
$newVal2 = '';
@@ -432,13 +436,13 @@ class Exif {
}
unset( $this->mFilteredExifData['GPSVersion'] );
}
-
}
+
/**
* Do userComment tags and similar. See pg. 34 of exif standard.
* basically first 8 bytes is charset, rest is value.
* This has not been tested on any shift-JIS strings.
- * @param string $prop prop name.
+ * @param string $prop Prop name
*/
private function charCodeString( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
@@ -448,6 +452,7 @@ class Exif {
$this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false );
unset( $this->mFilteredExifData[$prop] );
+
return;
}
$charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
@@ -465,8 +470,6 @@ class Exif {
$charset = "";
break;
}
- // This could possibly check to see if iconv is really installed
- // or if we're using the compatibility wrapper in globalFunctions.php
if ( $charset ) {
wfSuppressWarnings();
$val = iconv( $charset, 'UTF-8//IGNORE', $val );
@@ -488,6 +491,7 @@ class Exif {
//only whitespace.
$this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" );
unset( $this->mFilteredExifData[$prop] );
+
return;
}
@@ -495,28 +499,32 @@ class Exif {
$this->mFilteredExifData[$prop] = $val;
}
}
+
/**
* Convert an Exif::UNDEFINED from a raw binary string
* to its value. This is sometimes needed depending on
* the type of UNDEFINED field
- * @param string $prop name of property
+ * @param string $prop Name of property
*/
private function exifPropToOrd( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
$this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
}
}
+
/**
* Convert gps in exif form to a single floating point number
* for example 10 degress 20`40`` S -> -10.34444
- * @param string $prop a gps coordinate exif tag name (like GPSLongitude)
+ * @param string $prop A GPS coordinate exif tag name (like GPSLongitude)
*/
private function exifGPStoNumber( $prop ) {
$loc =& $this->mFilteredExifData[$prop];
$dir =& $this->mFilteredExifData[$prop . 'Ref'];
$res = false;
- if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
+ if ( isset( $loc ) && isset( $dir )
+ && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' )
+ ) {
list( $num, $denom ) = explode( '/', $loc[0] );
$res = $num / $denom;
list( $num, $denom ) = explode( '/', $loc[1] );
@@ -525,7 +533,7 @@ class Exif {
$res += ( $num / $denom ) * ( 1 / 3600 );
if ( $dir === 'S' || $dir === 'W' ) {
- $res *= - 1; // make negative
+ $res *= -1; // make negative
}
}
@@ -540,17 +548,6 @@ class Exif {
}
}
- /**
- * Use FormatMetadata to create formatted values for display to user
- * (is this ever used?)
- *
- * @deprecated since 1.18
- */
- function makeFormattedData() {
- wfDeprecated( __METHOD__, '1.18' );
- $this->mFormattedExifData = FormatMetadata::getFormattedData(
- $this->mFilteredExifData );
- }
/**#@-*/
/**#@+
@@ -566,26 +563,12 @@ class Exif {
/**
* Get $this->mFilteredExifData
+ * @return array
*/
function getFilteredData() {
return $this->mFilteredExifData;
}
- /**
- * Get $this->mFormattedExifData
- *
- * This returns the data for display to user.
- * Its unclear if this is ever used.
- *
- * @deprecated since 1.18
- */
- function getFormattedData() {
- wfDeprecated( __METHOD__, '1.18' );
- if ( !$this->mFormattedExifData ) {
- $this->makeFormattedData();
- }
- return $this->mFormattedExifData;
- }
/**#@-*/
/**
@@ -604,26 +587,26 @@ class Exif {
return 2; // We don't need no bloddy constants!
}
- /**#@+
+ /**
* Validates if a tag value is of the type it should be according to the Exif spec
*
- * @private
- *
- * @param $in Mixed: the input value to check
+ * @param mixed $in The input value to check
* @return bool
*/
private function isByte( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isASCII( $in ) {
@@ -633,11 +616,13 @@ class Exif {
if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
$this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
+
return false;
}
if ( preg_match( '/^\s*$/', $in ) ) {
$this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
+
return false;
}
@@ -645,93 +630,110 @@ class Exif {
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isShort( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isLong( $in ) {
if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isRational( $in ) {
$m = array();
- if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+
+ # Avoid division by zero
+ if ( !is_array( $in )
+ && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
+ ) {
return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isUndefined( $in ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isSlong( $in ) {
if ( $this->isLong( abs( $in ) ) ) {
$this->debug( $in, __FUNCTION__, true );
+
return true;
} else {
$this->debug( $in, __FUNCTION__, false );
+
return false;
}
}
/**
- * @param $in
+ * @param mixed $in The input value to check
* @return bool
*/
private function isSrational( $in ) {
$m = array();
- if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+
+ # Avoid division by zero
+ if ( !is_array( $in ) &&
+ preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
+ ) {
return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+
return false;
}
}
+
/**#@-*/
/**
* Validates if a tag has a legal value according to the Exif spec
*
- * @private
- * @param string $section section where tag is located.
- * @param string $tag the tag to check.
- * @param $val Mixed: the value of the tag.
- * @param $recursive Boolean: true if called recursively for array types.
+ * @param string $section Section where tag is located.
+ * @param string $tag The tag to check.
+ * @param mixed $val The value of the tag.
+ * @param bool $recursive True if called recursively for array types.
* @return bool
*/
private function validate( $section, $tag, $val, $recursive = false ) {
@@ -747,6 +749,7 @@ class Exif {
$count = count( $val );
if ( $ecount != $count ) {
$this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
+
return false;
}
if ( $count > 1 ) {
@@ -755,42 +758,54 @@ class Exif {
return false;
}
}
+
return true;
}
// Does not work if not typecast
switch ( (string)$etype ) {
case (string)Exif::BYTE:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isByte( $val );
case (string)Exif::ASCII:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isASCII( $val );
case (string)Exif::SHORT:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isShort( $val );
case (string)Exif::LONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isLong( $val );
case (string)Exif::RATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isRational( $val );
case (string)Exif::SHORT_OR_LONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isShort( $val ) || $this->isLong( $val );
case (string)Exif::UNDEFINED:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isUndefined( $val );
case (string)Exif::SLONG:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isSlong( $val );
case (string)Exif::SRATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
+
return $this->isSrational( $val );
case (string)Exif::IGNORE:
$this->debug( $val, __FUNCTION__, $debug );
+
return false;
default:
$this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
+
return false;
}
}
@@ -798,11 +813,9 @@ class Exif {
/**
* Convenience function for debugging output
*
- * @private
- *
- * @param $in Mixed:
- * @param $fname String:
- * @param $action Mixed: , default NULL.
+ * @param mixed $in Arrays will be processed with print_r().
+ * @param string $fname Function name to log.
+ * @param string|bool|null $action Default null.
*/
private function debug( $in, $fname, $action = null ) {
if ( !$this->log ) {
@@ -815,23 +828,21 @@ class Exif {
}
if ( $action === true ) {
- wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)" );
} elseif ( $action === false ) {
- wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)" );
} elseif ( $action === null ) {
- wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n" );
+ wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)" );
} else {
- wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n" );
+ wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')" );
}
}
/**
* Convenience function for debugging output
*
- * @private
- *
- * @param string $fname the name of the function calling this function
- * @param $io Boolean: Specify whether we're beginning or ending
+ * @param string $fname The name of the function calling this function
+ * @param bool $io Specify whether we're beginning or ending
*/
private function debugFile( $fname, $io ) {
if ( !$this->log ) {
@@ -839,9 +850,9 @@ class Exif {
}
$class = ucfirst( __CLASS__ );
if ( $io ) {
- wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
+ wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'" );
} else {
- wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
+ wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'" );
}
}
}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index d8d0bede..b7657cb3 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -28,7 +28,6 @@
* @ingroup Media
*/
class ExifBitmapHandler extends BitmapHandler {
-
const BROKEN_FILE = '-1'; // error extracting metadata
const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
@@ -61,22 +60,30 @@ class ExifBitmapHandler extends BitmapHandler {
. $metadata['Software'][0][1] . ')';
}
+ $formatter = new FormatMetadata;
+
// ContactInfo also has to be dealt with specially
if ( isset( $metadata['Contact'] ) ) {
$metadata['Contact'] =
- FormatMetadata::collapseContactInfo(
+ $formatter->collapseContactInfo(
$metadata['Contact'] );
}
foreach ( $metadata as &$val ) {
if ( is_array( $val ) ) {
- $val = FormatMetadata::flattenArray( $val, 'ul', $avoidHtml );
+ $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
}
}
$metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
+
return $metadata;
}
+ /**
+ * @param File $image
+ * @param array $metadata
+ * @return bool|int
+ */
function isMetadataValid( $image, $metadata ) {
global $wgShowEXIF;
if ( !$wgShowEXIF ) {
@@ -87,6 +94,7 @@ class ExifBitmapHandler extends BitmapHandler {
# Old special value indicating that there is no Exif data in the file.
# or that there was an error well extracting the metadata.
wfDebug( __METHOD__ . ": back-compat version\n" );
+
return self::METADATA_COMPATIBLE;
}
if ( $metadata === self::BROKEN_FILE ) {
@@ -95,47 +103,57 @@ class ExifBitmapHandler extends BitmapHandler {
wfSuppressWarnings();
$exif = unserialize( $metadata );
wfRestoreWarnings();
- if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
- $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
- {
- if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) &&
- $exif['MEDIAWIKI_EXIF_VERSION'] == 1 )
- {
+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+ || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
+ ) {
+ if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+ && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
+ ) {
//back-compatible but old
wfDebug( __METHOD__ . ": back-compat version\n" );
+
return self::METADATA_COMPATIBLE;
}
# Wrong (non-compatible) version
wfDebug( __METHOD__ . ": wrong version\n" );
+
return self::METADATA_BAD;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
- $metadata = $image->getMetadata();
- if ( $metadata === self::OLD_BROKEN_FILE ||
- $metadata === self::BROKEN_FILE ||
- $this->isMetadataValid( $image, $metadata ) === self::METADATA_BAD )
- {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ public function getCommonMetaArray( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( $metadata === self::OLD_BROKEN_FILE
+ || $metadata === self::BROKEN_FILE
+ || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
+ ) {
// So we don't try and display metadata from PagedTiffHandler
// for example when using InstantCommons.
- return false;
+ return array();
}
$exif = unserialize( $metadata );
if ( !$exif ) {
- return false;
+ return array();
}
unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
- if ( count( $exif ) == 0 ) {
- return false;
- }
- return $this->formatMetadataHelper( $exif );
+
+ return $exif;
}
function getMetadataType( $image ) {
@@ -151,12 +169,11 @@ class ExifBitmapHandler extends BitmapHandler {
* @return array
*/
function getImageSize( $image, $path ) {
- global $wgEnableAutoRotation;
$gis = parent::getImageSize( $image, $path );
// Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
// This may mean we read EXIF data twice on initial upload.
- if ( $wgEnableAutoRotation ) {
+ if ( $this->autoRotateEnabled() ) {
$meta = $this->getMetadata( $image, $path );
$rotation = $this->getRotationForExif( $meta );
} else {
@@ -168,6 +185,7 @@ class ExifBitmapHandler extends BitmapHandler {
$gis[0] = $gis[1];
$gis[1] = $width;
}
+
return $gis;
}
@@ -180,16 +198,16 @@ class ExifBitmapHandler extends BitmapHandler {
* the width and height we normally work with is logical, and will match
* any produced output views.
*
- * @param $file File
+ * @param File $file
* @return int 0, 90, 180 or 270
*/
public function getRotation( $file ) {
- global $wgEnableAutoRotation;
- if ( !$wgEnableAutoRotation ) {
+ if ( !$this->autoRotateEnabled() ) {
return 0;
}
$data = $file->getMetadata();
+
return $this->getRotationForExif( $data );
}
@@ -199,8 +217,7 @@ class ExifBitmapHandler extends BitmapHandler {
*
* @param string $data
* @return int 0, 90, 180 or 270
- * @todo FIXME orientation can include flipping as well; see if this is an
- * issue!
+ * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
*/
protected function getRotationForExif( $data ) {
if ( !$data ) {
@@ -222,6 +239,7 @@ class ExifBitmapHandler extends BitmapHandler {
return 0;
}
}
+
return 0;
}
}
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 1c5136f5..43569539 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -43,8 +43,26 @@
* is already a large number of messages using the 'exif' prefix.
*
* @ingroup Media
+ * @since 1.23 the class extends ContextSource and various formerly-public
+ * internal methods are private
*/
-class FormatMetadata {
+class FormatMetadata extends ContextSource {
+ /**
+ * Only output a single language for multi-language fields
+ * @var bool
+ * @since 1.23
+ */
+ protected $singleLang = false;
+
+ /**
+ * Trigger only outputting single language for multilanguage fields
+ *
+ * @param bool $val
+ * @since 1.23
+ */
+ public function setSingleLanguage( $val ) {
+ $this->singleLang = $val;
+ }
/**
* Numbers given by Exif user agents are often magical, that is they
@@ -52,13 +70,34 @@ class FormatMetadata {
* value which most of the time are plain integers. This function
* formats Exif (and other metadata) values into human readable form.
*
- * @param array $tags the Exif data to format ( as returned by
- * Exif::getFilteredData() or BitmapMetadataHandler )
+ * This is the usual entry point for this class.
+ *
+ * @param array $tags The Exif data to format ( as returned by
+ * Exif::getFilteredData() or BitmapMetadataHandler )
+ * @param bool|IContextSource $context Context to use (optional)
* @return array
*/
- public static function getFormattedData( $tags ) {
- global $wgLang;
+ public static function getFormattedData( $tags, $context = false ) {
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+ return $obj->makeFormattedData( $tags );
+ }
+
+ /**
+ * Numbers given by Exif user agents are often magical, that is they
+ * should be replaced by a detailed explanation depending on their
+ * value which most of the time are plain integers. This function
+ * formats Exif (and other metadata) values into human readable form.
+ *
+ * @param array $tags The Exif data to format ( as returned by
+ * Exif::getFilteredData() or BitmapMetadataHandler )
+ * @return array
+ * @since 1.23
+ */
+ public function makeFormattedData( $tags ) {
$resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
unset( $tags['ResolutionUnit'] );
@@ -67,7 +106,7 @@ class FormatMetadata {
// This seems ugly to wrap non-array's in an array just to unwrap again,
// especially when most of the time it is not an array
if ( !is_array( $tags[$tag] ) ) {
- $vals = Array( $vals );
+ $vals = array( $vals );
}
// _type is a special value to say what array type
@@ -107,7 +146,7 @@ class FormatMetadata {
$time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
// the 1971:01:01 is just a placeholder, and not shown to user.
if ( $time && intval( $time ) > 0 ) {
- $tags[$tag] = $wgLang->time( $time );
+ $tags[$tag] = $this->getLanguage()->time( $time );
}
} catch ( TimestampException $e ) {
// This shouldn't happen, but we've seen bad formats
@@ -121,727 +160,892 @@ class FormatMetadata {
// instead of the other props which are single
// valued (mostly) so handle as a special case.
if ( $tag === 'Contact' ) {
- $vals = self::collapseContactInfo( $vals );
+ $vals = $this->collapseContactInfo( $vals );
continue;
}
foreach ( $vals as &$val ) {
switch ( $tag ) {
- case 'Compression':
- switch ( $val ) {
- case 1: case 2: case 3: case 4:
- case 5: case 6: case 7: case 8:
- case 32773: case 32946: case 34712:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'Compression':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 32773:
+ case 32946:
+ case 34712:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'PhotometricInterpretation':
- switch ( $val ) {
- case 2: case 6:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'PhotometricInterpretation':
+ switch ( $val ) {
+ case 2:
+ case 6:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Orientation':
- switch ( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'Orientation':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'PlanarConfiguration':
- switch ( $val ) {
- case 1: case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
-
- // TODO: YCbCrSubSampling
- case 'YCbCrPositioning':
- switch ( $val ) {
- case 1:
- case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'PlanarConfiguration':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- case 'XResolution':
- case 'YResolution':
- switch ( $resolutionunit ) {
- case 2:
- $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
- break;
- case 3:
- $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
- break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
-
- // TODO: YCbCrCoefficients #p27 (see annex E)
- case 'ExifVersion': case 'FlashpixVersion':
- $val = "$val" / 100;
- break;
- case 'ColorSpace':
- switch ( $val ) {
- case 1: case 65535:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ // TODO: YCbCrSubSampling
+ case 'YCbCrPositioning':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'ComponentsConfiguration':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6:
- $val = self::msg( $tag, $val );
+ case 'XResolution':
+ case 'YResolution':
+ switch ( $resolutionunit ) {
+ case 2:
+ $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) );
+ break;
+ case 3:
+ $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ // TODO: YCbCrCoefficients #p27 (see annex E)
+ case 'ExifVersion':
+ case 'FlashpixVersion':
+ $val = "$val" / 100;
break;
- }
- break;
-
- case 'DateTime':
- case 'DateTimeOriginal':
- case 'DateTimeDigitized':
- case 'DateTimeReleased':
- case 'DateTimeExpires':
- case 'GPSDateStamp':
- case 'dc-date':
- case 'DateTimeMetadata':
- if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
- $val = wfMessage( 'exif-unknowndate' )->text();
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
- // Full date.
- $time = wfTimestamp( TS_MW, $val );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->timeanddate( $time );
- }
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
- // No second field. Still format the same
- // since timeanddate doesn't include seconds anyways,
- // but second still available in api
- $time = wfTimestamp( TS_MW, $val . ':00' );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->timeanddate( $time );
- }
- } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
- // If only the date but not the time is filled in.
- $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
- . substr( $val, 5, 2 )
- . substr( $val, 8, 2 )
- . '000000' );
- if ( $time && intval( $time ) > 0 ) {
- $val = $wgLang->date( $time );
- }
- }
- // else it will just output $val without formatting it.
- break;
- case 'ExposureProgram':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $val = self::msg( $tag, $val );
+ case 'ColorSpace':
+ switch ( $val ) {
+ case 1:
+ case 65535:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'ComponentsConfiguration':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SubjectDistance':
- $val = self::msg( $tag, '', self::formatNum( $val ) );
- break;
+ case 'DateTime':
+ case 'DateTimeOriginal':
+ case 'DateTimeDigitized':
+ case 'DateTimeReleased':
+ case 'DateTimeExpires':
+ case 'GPSDateStamp':
+ case 'dc-date':
+ case 'DateTimeMetadata':
+ if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
+ $val = $this->msg( 'exif-unknowndate' )->text();
+ } elseif ( preg_match(
+ '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
+ $val
+ ) ) {
+ // Full date.
+ $time = wfTimestamp( TS_MW, $val );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->timeanddate( $time );
+ }
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // No second field. Still format the same
+ // since timeanddate doesn't include seconds anyways,
+ // but second still available in api
+ $time = wfTimestamp( TS_MW, $val . ':00' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->timeanddate( $time );
+ }
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // If only the date but not the time is filled in.
+ $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
+ . substr( $val, 5, 2 )
+ . substr( $val, 8, 2 )
+ . '000000' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $this->getLanguage()->date( $time );
+ }
+ }
+ // else it will just output $val without formatting it.
+ break;
- case 'MeteringMode':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
- $val = self::msg( $tag, $val );
+ case 'ExposureProgram':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'SubjectDistance':
+ $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
break;
- }
- break;
-
- case 'LightSource':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
- case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
- case 21: case 22: case 23: case 24: case 255:
- $val = self::msg( $tag, $val );
+
+ case 'MeteringMode':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 255:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'LightSource':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 255:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- case 'Flash':
- $flashDecode = array(
- 'fired' => $val & bindec( '00000001' ),
- 'return' => ( $val & bindec( '00000110' ) ) >> 1,
- 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
- 'function' => ( $val & bindec( '00100000' ) ) >> 5,
- 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
+
+ case 'Flash':
+ $flashDecode = array(
+ 'fired' => $val & bindec( '00000001' ),
+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
+ 'function' => ( $val & bindec( '00100000' ) ) >> 5,
+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
// 'reserved' => ($val & bindec( '10000000' )) >> 7,
- );
- $flashMsgs = array();
- # We do not need to handle unknown values since all are used.
- foreach ( $flashDecode as $subTag => $subValue ) {
- # We do not need any message for zeroed values.
- if ( $subTag != 'fired' && $subValue == 0 ) {
- continue;
+ );
+ $flashMsgs = array();
+ # We do not need to handle unknown values since all are used.
+ foreach ( $flashDecode as $subTag => $subValue ) {
+ # We do not need any message for zeroed values.
+ if ( $subTag != 'fired' && $subValue == 0 ) {
+ continue;
+ }
+ $fullTag = $tag . '-' . $subTag;
+ $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
}
- $fullTag = $tag . '-' . $subTag;
- $flashMsgs[] = self::msg( $fullTag, $subValue );
- }
- $val = $wgLang->commaList( $flashMsgs );
- break;
-
- case 'FocalPlaneResolutionUnit':
- switch ( $val ) {
- case 2:
- $val = self::msg( $tag, $val );
+ $val = $this->getLanguage()->commaList( $flashMsgs );
break;
- default:
- /* If not recognized, display as is. */
- break;
- }
- break;
- case 'SensingMethod':
- switch ( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 7: case 8:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'FocalPlaneResolutionUnit':
+ switch ( $val ) {
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'FileSource':
- switch ( $val ) {
- case 3:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'SensingMethod':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 7:
+ case 8:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SceneType':
- switch ( $val ) {
- case 1:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'FileSource':
+ switch ( $val ) {
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'CustomRendered':
- switch ( $val ) {
- case 0: case 1:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'SceneType':
+ switch ( $val ) {
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'ExposureMode':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'CustomRendered':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'WhiteBalance':
- switch ( $val ) {
- case 0: case 1:
- $val = self::msg( $tag, $val );
+ case 'ExposureMode':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'WhiteBalance':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SceneCaptureType':
- switch ( $val ) {
- case 0: case 1: case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'SceneCaptureType':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GainControl':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'GainControl':
- switch ( $val ) {
- case 0: case 1: case 2: case 3: case 4:
- $val = self::msg( $tag, $val );
+ case 'Contrast':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'Saturation':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Contrast':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ case 'Sharpness':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'SubjectDistanceRange':
+ switch ( $val ) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Saturation':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ //The GPS...Ref values are kept for compatibility, probably won't be reached.
+ case 'GPSLatitudeRef':
+ case 'GPSDestLatitudeRef':
+ switch ( $val ) {
+ case 'N':
+ case 'S':
+ $val = $this->exifMsg( 'GPSLatitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSLongitudeRef':
+ case 'GPSDestLongitudeRef':
+ switch ( $val ) {
+ case 'E':
+ case 'W':
+ $val = $this->exifMsg( 'GPSLongitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'Sharpness':
- switch ( $val ) {
- case 0: case 1: case 2:
- $val = self::msg( $tag, $val );
+ case 'GPSAltitude':
+ if ( $val < 0 ) {
+ $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
+ } else {
+ $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSStatus':
+ switch ( $val ) {
+ case 'A':
+ case 'V':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'SubjectDistanceRange':
- switch ( $val ) {
- case 0: case 1: case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'GPSMeasureMode':
+ switch ( $val ) {
+ case 2:
+ case 3:
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'GPSTrackRef':
+ case 'GPSImgDirectionRef':
+ case 'GPSDestBearingRef':
+ switch ( $val ) {
+ case 'T':
+ case 'M':
+ $val = $this->exifMsg( 'GPSDirection', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
-
- //The GPS...Ref values are kept for compatibility, probably won't be reached.
- case 'GPSLatitudeRef':
- case 'GPSDestLatitudeRef':
- switch ( $val ) {
- case 'N': case 'S':
- $val = self::msg( 'GPSLatitude', $val );
+
+ case 'GPSLatitude':
+ case 'GPSDestLatitude':
+ $val = $this->formatCoords( $val, 'latitude' );
break;
- default:
- /* If not recognized, display as is. */
+ case 'GPSLongitude':
+ case 'GPSDestLongitude':
+ $val = $this->formatCoords( $val, 'longitude' );
break;
- }
- break;
- case 'GPSLongitudeRef':
- case 'GPSDestLongitudeRef':
- switch ( $val ) {
- case 'E': case 'W':
- $val = self::msg( 'GPSLongitude', $val );
- break;
- default:
- /* If not recognized, display as is. */
+ case 'GPSSpeedRef':
+ switch ( $val ) {
+ case 'K':
+ case 'M':
+ case 'N':
+ $val = $this->exifMsg( 'GPSSpeed', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
break;
- }
- break;
- case 'GPSAltitude':
- if ( $val < 0 ) {
- $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
- } else {
- $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
- }
- break;
+ case 'GPSDestDistanceRef':
+ switch ( $val ) {
+ case 'K':
+ case 'M':
+ case 'N':
+ $val = $this->exifMsg( 'GPSDestDistance', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
- case 'GPSStatus':
- switch ( $val ) {
- case 'A': case 'V':
- $val = self::msg( $tag, $val );
+ case 'GPSDOP':
+ // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
+ if ( $val <= 2 ) {
+ $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
+ } elseif ( $val <= 5 ) {
+ $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
+ } elseif ( $val <= 10 ) {
+ $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
+ } elseif ( $val <= 20 ) {
+ $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
+ } else {
+ $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ // This is not in the Exif standard, just a special
+ // case for our purposes which enables wikis to wikify
+ // the make, model and software name to link to their articles.
+ case 'Make':
+ case 'Model':
+ $val = $this->exifMsg( $tag, '', $val );
break;
- }
- break;
- case 'GPSMeasureMode':
- switch ( $val ) {
- case 2: case 3:
- $val = self::msg( $tag, $val );
+ case 'Software':
+ if ( is_array( $val ) ) {
+ //if its a software, version array.
+ $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
+ } else {
+ $val = $this->exifMsg( $tag, '', $val );
+ }
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'ExposureTime':
+ // Show the pretty fraction as well as decimal version
+ $val = $this->msg( 'exif-exposuretime-format',
+ $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
break;
- }
- break;
-
- case 'GPSTrackRef':
- case 'GPSImgDirectionRef':
- case 'GPSDestBearingRef':
- switch ( $val ) {
- case 'T': case 'M':
- $val = self::msg( 'GPSDirection', $val );
+ case 'ISOSpeedRatings':
+ // If its = 65535 that means its at the
+ // limit of the size of Exif::short and
+ // is really higher.
+ if ( $val == '65535' ) {
+ $val = $this->exifMsg( $tag, 'overflow' );
+ } else {
+ $val = $this->formatNum( $val );
+ }
break;
- default:
- /* If not recognized, display as is. */
+ case 'FNumber':
+ $val = $this->msg( 'exif-fnumber-format',
+ $this->formatNum( $val ) )->text();
break;
- }
- break;
-
- case 'GPSLatitude':
- case 'GPSDestLatitude':
- $val = self::formatCoords( $val, 'latitude' );
- break;
- case 'GPSLongitude':
- case 'GPSDestLongitude':
- $val = self::formatCoords( $val, 'longitude' );
- break;
-
- case 'GPSSpeedRef':
- switch ( $val ) {
- case 'K': case 'M': case 'N':
- $val = self::msg( 'GPSSpeed', $val );
+
+ case 'FocalLength':
+ case 'FocalLengthIn35mmFilm':
+ $val = $this->msg( 'exif-focallength-format',
+ $this->formatNum( $val ) )->text();
break;
- default:
- /* If not recognized, display as is. */
+
+ case 'MaxApertureValue':
+ if ( strpos( $val, '/' ) !== false ) {
+ // need to expand this earlier to calculate fNumber
+ list( $n, $d ) = explode( '/', $val );
+ if ( is_numeric( $n ) && is_numeric( $d ) ) {
+ $val = $n / $d;
+ }
+ }
+ if ( is_numeric( $val ) ) {
+ $fNumber = pow( 2, $val / 2 );
+ if ( $fNumber !== false ) {
+ $val = $this->msg( 'exif-maxaperturevalue-value',
+ $this->formatNum( $val ),
+ $this->formatNum( $fNumber, 2 )
+ )->text();
+ }
+ }
break;
- }
- break;
- case 'GPSDestDistanceRef':
- switch ( $val ) {
- case 'K': case 'M': case 'N':
- $val = self::msg( 'GPSDestDistance', $val );
+ case 'iimCategory':
+ switch ( strtolower( $val ) ) {
+ // See pg 29 of IPTC photo
+ // metadata standard.
+ case 'ace':
+ case 'clj':
+ case 'dis':
+ case 'fin':
+ case 'edu':
+ case 'evn':
+ case 'hth':
+ case 'hum':
+ case 'lab':
+ case 'lif':
+ case 'pol':
+ case 'rel':
+ case 'sci':
+ case 'soi':
+ case 'spo':
+ case 'war':
+ case 'wea':
+ $val = $this->exifMsg(
+ 'iimcategory',
+ $val
+ );
+ }
break;
- default:
- /* If not recognized, display as is. */
+ case 'SubjectNewsCode':
+ // Essentially like iimCategory.
+ // 8 (numeric) digit hierarchical
+ // classification. We decode the
+ // first 2 digits, which provide
+ // a broad category.
+ $val = $this->convertNewsCode( $val );
break;
- }
- break;
-
- case 'GPSDOP':
- // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
- if ( $val <= 2 ) {
- $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
- } elseif ( $val <= 5 ) {
- $val = self::msg( $tag, 'good', self::formatNum( $val ) );
- } elseif ( $val <= 10 ) {
- $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
- } elseif ( $val <= 20 ) {
- $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
- } else {
- $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
- }
- break;
-
- // This is not in the Exif standard, just a special
- // case for our purposes which enables wikis to wikify
- // the make, model and software name to link to their articles.
- case 'Make':
- case 'Model':
- $val = self::msg( $tag, '', $val );
- break;
-
- case 'Software':
- if ( is_array( $val ) ) {
- //if its a software, version array.
- $val = wfMessage( 'exif-software-version-value', $val[0], $val[1] )->text();
- } else {
- $val = self::msg( $tag, '', $val );
- }
- break;
-
- case 'ExposureTime':
- // Show the pretty fraction as well as decimal version
- $val = wfMessage( 'exif-exposuretime-format',
- self::formatFraction( $val ), self::formatNum( $val ) )->text();
- break;
- case 'ISOSpeedRatings':
- // If its = 65535 that means its at the
- // limit of the size of Exif::short and
- // is really higher.
- if ( $val == '65535' ) {
- $val = self::msg( $tag, 'overflow' );
- } else {
- $val = self::formatNum( $val );
- }
- break;
- case 'FNumber':
- $val = wfMessage( 'exif-fnumber-format',
- self::formatNum( $val ) )->text();
- break;
-
- case 'FocalLength': case 'FocalLengthIn35mmFilm':
- $val = wfMessage( 'exif-focallength-format',
- self::formatNum( $val ) )->text();
- break;
-
- case 'MaxApertureValue':
- if ( strpos( $val, '/' ) !== false ) {
- // need to expand this earlier to calculate fNumber
- list( $n, $d ) = explode( '/', $val );
- if ( is_numeric( $n ) && is_numeric( $d ) ) {
- $val = $n / $d;
- }
- }
- if ( is_numeric( $val ) ) {
- $fNumber = pow( 2, $val / 2 );
- if ( $fNumber !== false ) {
- $val = wfMessage( 'exif-maxaperturevalue-value',
- self::formatNum( $val ),
- self::formatNum( $fNumber, 2 )
- )->text();
+ case 'Urgency':
+ // 1-8 with 1 being highest, 5 normal
+ // 0 is reserved, and 9 is 'user-defined'.
+ $urgency = '';
+ if ( $val == 0 || $val == 9 ) {
+ $urgency = 'other';
+ } elseif ( $val < 5 && $val > 1 ) {
+ $urgency = 'high';
+ } elseif ( $val == 5 ) {
+ $urgency = 'normal';
+ } elseif ( $val <= 8 && $val > 5 ) {
+ $urgency = 'low';
}
- }
- break;
-
- case 'iimCategory':
- switch ( strtolower( $val ) ) {
- // See pg 29 of IPTC photo
- // metadata standard.
- case 'ace': case 'clj':
- case 'dis': case 'fin':
- case 'edu': case 'evn':
- case 'hth': case 'hum':
- case 'lab': case 'lif':
- case 'pol': case 'rel':
- case 'sci': case 'soi':
- case 'spo': case 'war':
- case 'wea':
- $val = self::msg(
- 'iimcategory',
- $val
+
+ if ( $urgency !== '' ) {
+ $val = $this->exifMsg( 'urgency',
+ $urgency, $val
);
- }
- break;
- case 'SubjectNewsCode':
- // Essentially like iimCategory.
- // 8 (numeric) digit hierarchical
- // classification. We decode the
- // first 2 digits, which provide
- // a broad category.
- $val = self::convertNewsCode( $val );
- break;
- case 'Urgency':
- // 1-8 with 1 being highest, 5 normal
- // 0 is reserved, and 9 is 'user-defined'.
- $urgency = '';
- if ( $val == 0 || $val == 9 ) {
- $urgency = 'other';
- } elseif ( $val < 5 && $val > 1 ) {
- $urgency = 'high';
- } elseif ( $val == 5 ) {
- $urgency = 'normal';
- } elseif ( $val <= 8 && $val > 5 ) {
- $urgency = 'low';
- }
+ }
+ break;
- if ( $urgency !== '' ) {
- $val = self::msg( 'urgency',
- $urgency, $val
- );
- }
- break;
-
- // Things that have a unit of pixels.
- case 'OriginalImageHeight':
- case 'OriginalImageWidth':
- case 'PixelXDimension':
- case 'PixelYDimension':
- case 'ImageWidth':
- case 'ImageLength':
- $val = self::formatNum( $val ) . ' ' . wfMessage( 'unit-pixel' )->text();
- break;
-
- // Do not transform fields with pure text.
- // For some languages the formatNum()
- // conversion results to wrong output like
- // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
- // Also some 'numeric' things like Scene codes
- // are included here as we really don't want
- // commas inserted.
- case 'ImageDescription':
- case 'Artist':
- case 'Copyright':
- case 'RelatedSoundFile':
- case 'ImageUniqueID':
- case 'SpectralSensitivity':
- case 'GPSSatellites':
- case 'GPSVersionID':
- case 'GPSMapDatum':
- case 'Keywords':
- case 'WorldRegionDest':
- case 'CountryDest':
- case 'CountryCodeDest':
- case 'ProvinceOrStateDest':
- case 'CityDest':
- case 'SublocationDest':
- case 'WorldRegionCreated':
- case 'CountryCreated':
- case 'CountryCodeCreated':
- case 'ProvinceOrStateCreated':
- case 'CityCreated':
- case 'SublocationCreated':
- case 'ObjectName':
- case 'SpecialInstructions':
- case 'Headline':
- case 'Credit':
- case 'Source':
- case 'EditStatus':
- case 'FixtureIdentifier':
- case 'LocationDest':
- case 'LocationDestCode':
- case 'Writer':
- case 'JPEGFileComment':
- case 'iimSupplementalCategory':
- case 'OriginalTransmissionRef':
- case 'Identifier':
- case 'dc-contributor':
- case 'dc-coverage':
- case 'dc-publisher':
- case 'dc-relation':
- case 'dc-rights':
- case 'dc-source':
- case 'dc-type':
- case 'Lens':
- case 'SerialNumber':
- case 'CameraOwnerName':
- case 'Label':
- case 'Nickname':
- case 'RightsCertificate':
- case 'CopyrightOwner':
- case 'UsageTerms':
- case 'WebStatement':
- case 'OriginalDocumentID':
- case 'LicenseUrl':
- case 'MorePermissionsUrl':
- case 'AttributionUrl':
- case 'PreferredAttributionName':
- case 'PNGFileComment':
- case 'Disclaimer':
- case 'ContentWarning':
- case 'GIFFileComment':
- case 'SceneCode':
- case 'IntellectualGenre':
- case 'Event':
- case 'OrginisationInImage':
- case 'PersonInImage':
-
- $val = htmlspecialchars( $val );
- break;
-
- case 'ObjectCycle':
- switch ( $val ) {
- case 'a': case 'p': case 'b':
- $val = self::msg( $tag, $val );
+ // Things that have a unit of pixels.
+ case 'OriginalImageHeight':
+ case 'OriginalImageWidth':
+ case 'PixelXDimension':
+ case 'PixelYDimension':
+ case 'ImageWidth':
+ case 'ImageLength':
+ $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
break;
- default:
+
+ // Do not transform fields with pure text.
+ // For some languages the formatNum()
+ // conversion results to wrong output like
+ // foo,bar@example,com or fooÙ«bar@exampleÙ«com.
+ // Also some 'numeric' things like Scene codes
+ // are included here as we really don't want
+ // commas inserted.
+ case 'ImageDescription':
+ case 'Artist':
+ case 'Copyright':
+ case 'RelatedSoundFile':
+ case 'ImageUniqueID':
+ case 'SpectralSensitivity':
+ case 'GPSSatellites':
+ case 'GPSVersionID':
+ case 'GPSMapDatum':
+ case 'Keywords':
+ case 'WorldRegionDest':
+ case 'CountryDest':
+ case 'CountryCodeDest':
+ case 'ProvinceOrStateDest':
+ case 'CityDest':
+ case 'SublocationDest':
+ case 'WorldRegionCreated':
+ case 'CountryCreated':
+ case 'CountryCodeCreated':
+ case 'ProvinceOrStateCreated':
+ case 'CityCreated':
+ case 'SublocationCreated':
+ case 'ObjectName':
+ case 'SpecialInstructions':
+ case 'Headline':
+ case 'Credit':
+ case 'Source':
+ case 'EditStatus':
+ case 'FixtureIdentifier':
+ case 'LocationDest':
+ case 'LocationDestCode':
+ case 'Writer':
+ case 'JPEGFileComment':
+ case 'iimSupplementalCategory':
+ case 'OriginalTransmissionRef':
+ case 'Identifier':
+ case 'dc-contributor':
+ case 'dc-coverage':
+ case 'dc-publisher':
+ case 'dc-relation':
+ case 'dc-rights':
+ case 'dc-source':
+ case 'dc-type':
+ case 'Lens':
+ case 'SerialNumber':
+ case 'CameraOwnerName':
+ case 'Label':
+ case 'Nickname':
+ case 'RightsCertificate':
+ case 'CopyrightOwner':
+ case 'UsageTerms':
+ case 'WebStatement':
+ case 'OriginalDocumentID':
+ case 'LicenseUrl':
+ case 'MorePermissionsUrl':
+ case 'AttributionUrl':
+ case 'PreferredAttributionName':
+ case 'PNGFileComment':
+ case 'Disclaimer':
+ case 'ContentWarning':
+ case 'GIFFileComment':
+ case 'SceneCode':
+ case 'IntellectualGenre':
+ case 'Event':
+ case 'OrginisationInImage':
+ case 'PersonInImage':
+
$val = htmlspecialchars( $val );
break;
- }
- break;
- case 'Copyrighted':
- switch ( $val ) {
- case 'True': case 'False':
- $val = self::msg( $tag, $val );
+
+ case 'ObjectCycle':
+ switch ( $val ) {
+ case 'a':
+ case 'p':
+ case 'b':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ default:
+ $val = htmlspecialchars( $val );
+ break;
+ }
+ break;
+ case 'Copyrighted':
+ switch ( $val ) {
+ case 'True':
+ case 'False':
+ $val = $this->exifMsg( $tag, $val );
+ break;
+ }
+ break;
+ case 'Rating':
+ if ( $val == '-1' ) {
+ $val = $this->exifMsg( $tag, 'rejected' );
+ } else {
+ $val = $this->formatNum( $val );
+ }
break;
- }
- break;
- case 'Rating':
- if ( $val == '-1' ) {
- $val = self::msg( $tag, 'rejected' );
- } else {
- $val = self::formatNum( $val );
- }
- break;
- case 'LanguageCode':
- $lang = Language::fetchLanguageName( strtolower( $val ), $wgLang->getCode() );
- if ( $lang ) {
- $val = htmlspecialchars( $lang );
- } else {
- $val = htmlspecialchars( $val );
- }
- break;
+ case 'LanguageCode':
+ $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
+ if ( $lang ) {
+ $val = htmlspecialchars( $lang );
+ } else {
+ $val = htmlspecialchars( $val );
+ }
+ break;
- default:
- $val = self::formatNum( $val );
- break;
+ default:
+ $val = $this->formatNum( $val );
+ break;
}
}
// End formatting values, start flattening arrays.
- $vals = self::flattenArray( $vals, $type );
-
+ $vals = $this->flattenArrayReal( $vals, $type );
}
+
return $tags;
}
/**
+ * Flatten an array, using the content language for any messages.
+ *
+ * @param array $vals Array of values
+ * @param string $type Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old MediaWiki).
+ * @param bool|IContextSource $context
+ * @return string Single value (in wiki-syntax).
+ * @since 1.23
+ */
+ public static function flattenArrayContentLang( $vals, $type = 'ul',
+ $noHtml = false, $context = false
+ ) {
+ global $wgContLang;
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+ $context = new DerivativeContext( $obj->getContext() );
+ $context->setLanguage( $wgContLang );
+ $obj->setContext( $context );
+
+ return $obj->flattenArrayReal( $vals, $type, $noHtml );
+ }
+
+ /**
+ * Flatten an array, using the user language for any messages.
+ *
+ * @param array $vals Array of values
+ * @param string $type Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old MediaWiki).
+ * @param bool|IContextSource $context
+ * @return string Single value (in wiki-syntax).
+ */
+ public static function flattenArray( $vals, $type = 'ul', $noHtml = false, $context = false ) {
+ $obj = new FormatMetadata;
+ if ( $context ) {
+ $obj->setContext( $context );
+ }
+
+ return $obj->flattenArrayReal( $vals, $type, $noHtml );
+ }
+
+ /**
* A function to collapse multivalued tags into a single value.
* This turns an array of (for example) authors into a bulleted list.
*
* This is public on the basis it might be useful outside of this class.
*
- * @param array $vals array of values
+ * @param array $vals Array of values
* @param string $type Type of array (either lang, ul, ol).
- * lang = language assoc array with keys being the lang code
- * ul = unordered list, ol = ordered list
- * type can also come from the '_type' member of $vals.
- * @param $noHtml Boolean If to avoid returning anything resembling
- * html. (Ugly hack for backwards compatibility with old mediawiki).
- * @return String single value (in wiki-syntax).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param bool $noHtml If to avoid returning anything resembling HTML.
+ * (Ugly hack for backwards compatibility with old mediawiki).
+ * @return string Single value (in wiki-syntax).
+ * @since 1.23
*/
- public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
+ public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
+ if ( !is_array( $vals ) ) {
+ return $vals; // do nothing if not an array;
+ }
+
if ( isset( $vals['_type'] ) ) {
$type = $vals['_type'];
unset( $vals['_type'] );
@@ -849,105 +1053,118 @@ class FormatMetadata {
if ( !is_array( $vals ) ) {
return $vals; // do nothing if not an array;
- }
- elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
+ } elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
return $vals[0];
- }
- elseif ( count( $vals ) === 0 ) {
+ } elseif ( count( $vals ) === 0 ) {
wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
+
return ""; // paranoia. This should never happen
- }
- /* @todo FIXME: This should hide some of the list entries if there are
- * say more than four. Especially if a field is translated into 20
- * languages, we don't want to show them all by default
- */
- else {
- global $wgContLang;
+ } else {
+ /* @todo FIXME: This should hide some of the list entries if there are
+ * say more than four. Especially if a field is translated into 20
+ * languages, we don't want to show them all by default
+ */
switch ( $type ) {
- case 'lang':
- // Display default, followed by ContLang,
- // followed by the rest in no particular
- // order.
-
- // Todo: hide some items if really long list.
-
- $content = '';
-
- $cLang = $wgContLang->getCode();
- $defaultItem = false;
- $defaultLang = false;
-
- // If default is set, save it for later,
- // as we don't know if it's equal to
- // one of the lang codes. (In xmp
- // you specify the language for a
- // default property by having both
- // a default prop, and one in the language
- // that are identical)
- if ( isset( $vals['x-default'] ) ) {
- $defaultItem = $vals['x-default'];
- unset( $vals['x-default'] );
- }
- // Do contentLanguage.
- if ( isset( $vals[$cLang] ) ) {
- $isDefault = false;
- if ( $vals[$cLang] === $defaultItem ) {
- $defaultItem = false;
- $isDefault = true;
+ case 'lang':
+ // Display default, followed by ContLang,
+ // followed by the rest in no particular
+ // order.
+
+ // Todo: hide some items if really long list.
+
+ $content = '';
+
+ $priorityLanguages = $this->getPriorityLanguages();
+ $defaultItem = false;
+ $defaultLang = false;
+
+ // If default is set, save it for later,
+ // as we don't know if it's equal to
+ // one of the lang codes. (In xmp
+ // you specify the language for a
+ // default property by having both
+ // a default prop, and one in the language
+ // that are identical)
+ if ( isset( $vals['x-default'] ) ) {
+ $defaultItem = $vals['x-default'];
+ unset( $vals['x-default'] );
+ }
+ foreach ( $priorityLanguages as $pLang ) {
+ if ( isset( $vals[$pLang] ) ) {
+ $isDefault = false;
+ if ( $vals[$pLang] === $defaultItem ) {
+ $defaultItem = false;
+ $isDefault = true;
+ }
+ $content .= $this->langItem(
+ $vals[$pLang], $pLang,
+ $isDefault, $noHtml );
+
+ unset( $vals[$pLang] );
+
+ if ( $this->singleLang ) {
+ return Html::rawElement( 'span',
+ array( 'lang' => $pLang ), $vals[$pLang] );
+ }
+ }
}
- $content .= self::langItem(
- $vals[$cLang], $cLang,
- $isDefault, $noHtml );
-
- unset( $vals[$cLang] );
- }
- // Now do the rest.
- foreach ( $vals as $lang => $item ) {
- if ( $item === $defaultItem ) {
- $defaultLang = $lang;
- continue;
+ // Now do the rest.
+ foreach ( $vals as $lang => $item ) {
+ if ( $item === $defaultItem ) {
+ $defaultLang = $lang;
+ continue;
+ }
+ $content .= $this->langItem( $item,
+ $lang, false, $noHtml );
+ if ( $this->singleLang ) {
+ return Html::rawElement( 'span',
+ array( 'lang' => $lang ), $item );
+ }
}
- $content .= self::langItem( $item,
- $lang, false, $noHtml );
- }
- if ( $defaultItem !== false ) {
- $content = self::langItem( $defaultItem,
- $defaultLang, true, $noHtml ) .
- $content;
- }
- if ( $noHtml ) {
- return $content;
- }
- return '<ul class="metadata-langlist">' .
+ if ( $defaultItem !== false ) {
+ $content = $this->langItem( $defaultItem,
+ $defaultLang, true, $noHtml ) .
+ $content;
+ if ( $this->singleLang ) {
+ return $defaultItem;
+ }
+ }
+ if ( $noHtml ) {
+ return $content;
+ }
+
+ return '<ul class="metadata-langlist">' .
$content .
'</ul>';
- case 'ol':
- if ( $noHtml ) {
- return "\n#" . implode( "\n#", $vals );
- }
- return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
- case 'ul':
- default:
- if ( $noHtml ) {
- return "\n*" . implode( "\n*", $vals );
- }
- return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
+ case 'ol':
+ if ( $noHtml ) {
+ return "\n#" . implode( "\n#", $vals );
+ }
+
+ return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
+ case 'ul':
+ default:
+ if ( $noHtml ) {
+ return "\n*" . implode( "\n*", $vals );
+ }
+
+ return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
}
}
}
/** Helper function for creating lists of translations.
*
- * @param string $value value (this is not escaped)
- * @param string $lang lang code of item or false
- * @param $default Boolean if it is default value.
- * @param $noHtml Boolean If to avoid html (for back-compat)
+ * @param string $value Value (this is not escaped)
+ * @param string $lang Lang code of item or false
+ * @param bool $default If it is default value.
+ * @param bool $noHtml If to avoid html (for back-compat)
* @throws MWException
- * @return string language item (Note: despite how this looks,
- * this is treated as wikitext not html).
+ * @return string Language item (Note: despite how this looks, this is
+ * treated as wikitext, not as HTML).
*/
- private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
+ private function langItem( $value, $lang, $default = false, $noHtml = false ) {
if ( $lang === false && $default === false ) {
throw new MWException( '$lang and $default cannot both '
. 'be false.' );
@@ -961,13 +1178,13 @@ class FormatMetadata {
}
if ( $lang === false ) {
+ $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
if ( $noHtml ) {
- return wfMessage( 'metadata-langitem-default',
- $wrappedValue )->text() . "\n\n";
+ return $msg->text() . "\n\n";
} /* else */
+
return '<li class="mw-metadata-lang-default">'
- . wfMessage( 'metadata-langitem-default',
- $wrappedValue )->text()
+ . $msg->text()
. "</li>\n";
}
@@ -984,9 +1201,9 @@ class FormatMetadata {
}
// else we have a language specified
+ $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
if ( $noHtml ) {
- return '*' . wfMessage( 'metadata-langitem',
- $wrappedValue, $langName, $lang )->text();
+ return '*' . $msg->text();
} /* else: */
$item = '<li class="mw-metadata-lang-code-'
@@ -995,49 +1212,48 @@ class FormatMetadata {
$item .= ' mw-metadata-lang-default';
}
$item .= '" lang="' . $lang . '">';
- $item .= wfMessage( 'metadata-langitem',
- $wrappedValue, $langName, $lang )->text();
+ $item .= $msg->text();
$item .= "</li>\n";
+
return $item;
}
/**
* Convenience function for getFormattedData()
*
- * @private
- *
- * @param string $tag the tag name to pass on
- * @param string $val the value of the tag
- * @param string $arg an argument to pass ($1)
- * @param string $arg2 a 2nd argument to pass ($2)
- * @return string A wfMessage of "exif-$tag-$val" in lower case
+ * @param string $tag The tag name to pass on
+ * @param string $val The value of the tag
+ * @param string $arg An argument to pass ($1)
+ * @param string $arg2 A 2nd argument to pass ($2)
+ * @return string The text content of "exif-$tag-$val" message in lower case
*/
- static function msg( $tag, $val, $arg = null, $arg2 = null ) {
+ private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
global $wgContLang;
if ( $val === '' ) {
$val = 'value';
}
- return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
+
+ return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
}
/**
* Format a number, convert numbers from fractions into floating point
* numbers, joins arrays of numbers with commas.
*
- * @param $num Mixed: the value to format
- * @param $round float|int|bool digits to round to or false.
+ * @param mixed $num The value to format
+ * @param float|int|bool $round Digits to round to or false.
* @return mixed A floating point number or whatever we were fed
*/
- static function formatNum( $num, $round = false ) {
- global $wgLang;
+ private function formatNum( $num, $round = false ) {
$m = array();
if ( is_array( $num ) ) {
$out = array();
foreach ( $num as $number ) {
- $out[] = self::formatNum( $number );
+ $out[] = $this->formatNum( $number );
}
- return $wgLang->commaList( $out );
+
+ return $this->getLanguage()->commaList( $out );
}
if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
if ( $m[2] != 0 ) {
@@ -1049,46 +1265,45 @@ class FormatMetadata {
$newNum = $num;
}
- return $wgLang->formatNum( $newNum );
+ return $this->getLanguage()->formatNum( $newNum );
} else {
if ( is_numeric( $num ) && $round !== false ) {
$num = round( $num, $round );
}
- return $wgLang->formatNum( $num );
+
+ return $this->getLanguage()->formatNum( $num );
}
}
/**
* Format a rational number, reducing fractions
*
- * @private
- *
- * @param $num Mixed: the value to format
+ * @param mixed $num The value to format
* @return mixed A floating point number or whatever we were fed
*/
- static function formatFraction( $num ) {
+ private function formatFraction( $num ) {
$m = array();
if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
$numerator = intval( $m[1] );
$denominator = intval( $m[2] );
- $gcd = self::gcd( abs( $numerator ), $denominator );
+ $gcd = $this->gcd( abs( $numerator ), $denominator );
if ( $gcd != 0 ) {
// 0 shouldn't happen! ;)
- return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
+ return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
}
}
- return self::formatNum( $num );
+
+ return $this->formatNum( $num );
}
/**
* Calculate the greatest common divisor of two integers.
*
- * @param $a Integer: Numerator
- * @param $b Integer: Denominator
+ * @param int $a Numerator
+ * @param int $b Denominator
* @return int
- * @private
*/
- static function gcd( $a, $b ) {
+ private function gcd( $a, $b ) {
/*
// http://en.wikipedia.org/wiki/Euclidean_algorithm
// Recursive form would be:
@@ -1104,6 +1319,7 @@ class FormatMetadata {
$a = $b;
$b = $remainder;
}
+
return $a;
}
@@ -1119,7 +1335,7 @@ class FormatMetadata {
* @param string $val The 8 digit news code.
* @return string The human readable form
*/
- private static function convertNewsCode( $val ) {
+ private function convertNewsCode( $val ) {
if ( !preg_match( '/^\d{8}$/D', $val ) ) {
// Not a valid news code.
return $val;
@@ -1179,9 +1395,10 @@ class FormatMetadata {
break;
}
if ( $cat !== '' ) {
- $catMsg = self::msg( 'iimcategory', $cat );
- $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
+ $catMsg = $this->exifMsg( 'iimcategory', $cat );
+ $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
}
+
return $val;
}
@@ -1189,11 +1406,11 @@ class FormatMetadata {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param int $coord degrees, minutes and seconds
- * @param string $type latitude or longitude (for if its a NWS or E)
+ * @param int $coord Degrees, minutes and seconds
+ * @param string $type Latitude or longitude (for if its a NWS or E)
* @return mixed A floating point number or whatever we were fed
*/
- static function formatCoords( $coord, $type ) {
+ private function formatCoords( $coord, $type ) {
$ref = '';
if ( $coord < 0 ) {
$nCoord = -$coord;
@@ -1215,28 +1432,28 @@ class FormatMetadata {
$min = floor( ( $nCoord - $deg ) * 60.0 );
$sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
- $deg = self::formatNum( $deg );
- $min = self::formatNum( $min );
- $sec = self::formatNum( $sec );
+ $deg = $this->formatNum( $deg );
+ $min = $this->formatNum( $min );
+ $sec = $this->formatNum( $sec );
- return wfMessage( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
+ return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
}
/**
* Format the contact info field into a single value.
*
- * @param array $vals array with fields of the ContactInfo
- * struct defined in the IPTC4XMP spec. Or potentially
- * an array with one element that is a free form text
- * value from the older iptc iim 1:118 prop.
- *
* This function might be called from
* JpegHandler::convertMetadataVersion which is why it is
* public.
*
- * @return String of html-ish looking wikitext
+ * @param array $vals Array with fields of the ContactInfo
+ * struct defined in the IPTC4XMP spec. Or potentially
+ * an array with one element that is a free form text
+ * value from the older iptc iim 1:118 prop.
+ * @return string HTML-ish looking wikitext
+ * @since 1.23 no longer static
*/
- public static function collapseContactInfo( $vals ) {
+ public function collapseContactInfo( $vals ) {
if ( !( isset( $vals['CiAdrExtadr'] )
|| isset( $vals['CiAdrCity'] )
|| isset( $vals['CiAdrCtry'] )
@@ -1258,7 +1475,8 @@ class FormatMetadata {
foreach ( $vals as &$val ) {
$val = htmlspecialchars( $val );
}
- return self::flattenArray( $vals );
+
+ return $this->flattenArrayReal( $vals );
} else {
// We have a real ContactInfo field.
// Its unclear if all these fields have to be
@@ -1308,10 +1526,10 @@ class FormatMetadata {
$emails[] = $finalEmail;
} else {
$emails[] = '[mailto:'
- . $finalEmail
- . ' <span class="email">'
- . $finalEmail
- . '</span>]';
+ . $finalEmail
+ . ' <span class="email">'
+ . $finalEmail
+ . '</span>]';
}
}
}
@@ -1340,34 +1558,315 @@ class FormatMetadata {
. htmlspecialchars( $vals['CiUrlWork'] )
. '</span>';
}
- return wfMessage( 'exif-contact-value', $email, $url,
+
+ return $this->msg( 'exif-contact-value', $email, $url,
$street, $city, $region, $postal, $country,
$tel )->text();
}
}
-}
-/** For compatability with old FormatExif class
- * which some extensions use.
- *
- * @deprecated since 1.18
- *
- */
-class FormatExif {
- var $meta;
+ /**
+ * Get a list of fields that are visible by default.
+ *
+ * @return array
+ * @since 1.23
+ */
+ public static function getVisibleFields() {
+ $fields = array();
+ $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
+ foreach ( $lines as $line ) {
+ $matches = array();
+ if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+ $fields[] = $matches[1];
+ }
+ }
+ $fields = array_map( 'strtolower', $fields );
+
+ return $fields;
+ }
+
+ /**
+ * Get an array of extended metadata. (See the imageinfo API for format.)
+ *
+ * @param File $file File to use
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ public function fetchExtendedMetadata( File $file ) {
+ global $wgMemc;
+
+ wfProfileIn( __METHOD__ );
+
+ // If revision deleted, exit immediately
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
+ wfProfileOut( __METHOD__ );
+
+ return array();
+ }
+
+ $cacheKey = wfMemcKey(
+ 'getExtendedMetadata',
+ $this->getLanguage()->getCode(),
+ (int)$this->singleLang,
+ $file->getSha1()
+ );
+
+ $cachedValue = $wgMemc->get( $cacheKey );
+ if (
+ $cachedValue
+ && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
+ ) {
+ $extendedMetadata = $cachedValue['data'];
+ } else {
+ $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
+ $fileMetadata = $this->getExtendedMetadataFromFile( $file );
+ $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
+ if ( $this->singleLang ) {
+ $this->resolveMultilangMetadata( $extendedMetadata );
+ }
+ // Make sure the metadata won't break the API when an XML format is used.
+ // This is an API-specific function so it would be cleaner to call it from
+ // outside fetchExtendedMetadata, but this way we don't need to redo the
+ // computation on a cache hit.
+ $this->sanitizeArrayForXml( $extendedMetadata );
+ $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() );
+ $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime );
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $extendedMetadata;
+ }
+
+ /**
+ * Get file-based metadata in standardized format.
+ *
+ * Note that for a remote file, this might return metadata supplied by extensions.
+ *
+ * @param File $file File to use
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ protected function getExtendedMetadataFromFile( File $file ) {
+ // If this is a remote file accessed via an API request, we already
+ // have remote metadata so we just ignore any local one
+ if ( $file instanceof ForeignAPIFile ) {
+ // In case of error we pretend no metadata - this will get cached.
+ // Might or might not be a good idea.
+ return $file->getExtendedMetadata() ?: array();
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
+
+ $fileMetadata = array(
+ // This is modification time, which is close to "upload" time.
+ 'DateTime' => array(
+ 'value' => $uploadDate,
+ 'source' => 'mediawiki-metadata',
+ ),
+ );
+
+ $title = $file->getTitle();
+ if ( $title ) {
+ $text = $title->getText();
+ $pos = strrpos( $text, '.' );
+
+ if ( $pos ) {
+ $name = substr( $text, 0, $pos );
+ } else {
+ $name = $text;
+ }
+
+ $fileMetadata['ObjectName'] = array(
+ 'value' => $name,
+ 'source' => 'mediawiki-metadata',
+ );
+ }
+
+ $common = $file->getCommonMetaArray();
+
+ if ( $common !== false ) {
+ foreach ( $common as $key => $value ) {
+ $fileMetadata[$key] = array(
+ 'value' => $value,
+ 'source' => 'file-metadata',
+ );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $fileMetadata;
+ }
+
+ /**
+ * Get additional metadata from hooks in standardized format.
+ *
+ * @param File $file File to use
+ * @param array $extendedMetadata
+ * @param int $maxCacheTime Hook handlers might use this parameter to override cache time
+ *
+ * @return array [<property name> => ['value' => <value>]], or [] on error
+ * @since 1.23
+ */
+ protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
+ &$maxCacheTime
+ ) {
+ wfProfileIn( __METHOD__ );
+
+ wfRunHooks( 'GetExtendedMetadata', array(
+ &$extendedMetadata,
+ $file,
+ $this->getContext(),
+ $this->singleLang,
+ &$maxCacheTime
+ ) );
+
+ $visible = array_flip( self::getVisibleFields() );
+ foreach ( $extendedMetadata as $key => $value ) {
+ if ( !isset( $visible[strtolower( $key )] ) ) {
+ $extendedMetadata[$key]['hidden'] = '';
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $extendedMetadata;
+ }
/**
- * @param $meta array
+ * Turns an XMP-style multilang array into a single value.
+ * If the value is not a multilang array, it is returned unchanged.
+ * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+ * @param mixed $value
+ * @return mixed Value in best language, null if there were no languages at all
+ * @since 1.23
*/
- function FormatExif( $meta ) {
- wfDeprecated( __METHOD__, '1.18' );
- $this->meta = $meta;
+ protected function resolveMultilangValue( $value ) {
+ if (
+ !is_array( $value )
+ || !isset( $value['_type'] )
+ || $value['_type'] != 'lang'
+ ) {
+ return $value; // do nothing if not a multilang array
+ }
+
+ // choose the language best matching user or site settings
+ $priorityLanguages = $this->getPriorityLanguages();
+ foreach ( $priorityLanguages as $lang ) {
+ if ( isset( $value[$lang] ) ) {
+ return $value[$lang];
+ }
+ }
+
+ // otherwise go with the default language, if set
+ if ( isset( $value['x-default'] ) ) {
+ return $value['x-default'];
+ }
+
+ // otherwise just return any one language
+ unset( $value['_type'] );
+ if ( !empty( $value ) ) {
+ return reset( $value );
+ }
+
+ // this should not happen; signal error
+ return null;
+ }
+
+ /**
+ * Takes an array returned by the getExtendedMetadata* functions,
+ * and resolves multi-language values in it.
+ * @param array $metadata
+ * @since 1.23
+ */
+ protected function resolveMultilangMetadata( &$metadata ) {
+ if ( !is_array( $metadata ) ) {
+ return;
+ }
+ foreach ( $metadata as &$field ) {
+ if ( isset( $field['value'] ) ) {
+ $field['value'] = $this->resolveMultilangValue( $field['value'] );
+ }
+ }
+ }
+
+ /**
+ * Makes sure the given array is a valid API response fragment
+ * (can be transformed into XML)
+ * @param array $arr
+ */
+ protected function sanitizeArrayForXml( &$arr ) {
+ if ( !is_array( $arr ) ) {
+ return;
+ }
+
+ $counter = 1;
+ foreach ( $arr as $key => &$value ) {
+ $sanitizedKey = $this->sanitizeKeyForXml( $key );
+ if ( $sanitizedKey !== $key ) {
+ if ( isset( $arr[$sanitizedKey] ) ) {
+ // Make the sanitized keys hopefully unique.
+ // To make it definitely unique would be too much effort, given that
+ // sanitizing is only needed for misformatted metadata anyway, but
+ // this at least covers the case when $arr is numeric.
+ $sanitizedKey .= $counter;
+ ++$counter;
+ }
+ $arr[$sanitizedKey] = $arr[$key];
+ unset( $arr[$key] );
+ }
+ if ( is_array( $value ) ) {
+ $this->sanitizeArrayForXml( $value );
+ }
+ }
+ }
+
+ /**
+ * Turns a string into a valid XML identifier.
+ * Used to ensure that keys of an associative array in the
+ * API response do not break the XML formatter.
+ * @param string $key
+ * @return string
+ * @since 1.23
+ */
+ protected function sanitizeKeyForXml( $key ) {
+ // drop all characters which are not valid in an XML tag name
+ // a bunch of non-ASCII letters would be valid but probably won't
+ // be used so we take the easy way
+ $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
+ // drop characters which are invalid at the first position
+ $key = preg_replace( '/^[\d-.]+/', '', $key );
+
+ if ( $key == '' ) {
+ $key = '_';
+ }
+
+ // special case for an internal keyword
+ if ( $key == '_element' ) {
+ $key = 'element';
+ }
+
+ return $key;
}
/**
+ * Returns a list of languages (first is best) to use when formatting multilang fields,
+ * based on user and site preferences.
* @return array
+ * @since 1.23
*/
- function getFormattedData() {
- return FormatMetadata::getFormattedData( $this->meta );
+ protected function getPriorityLanguages() {
+ $priorityLanguages =
+ Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
+ $priorityLanguages = array_merge(
+ (array)$this->getLanguage()->getCode(),
+ $priorityLanguages[0],
+ $priorityLanguages[1]
+ );
+
+ return $priorityLanguages;
}
}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 608fb257..5992be11 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class GIFHandler extends BitmapHandler {
-
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
function getMetadata( $image, $filename ) {
@@ -36,6 +35,7 @@ class GIFHandler extends BitmapHandler {
} catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return self::BROKEN_FILE;
}
@@ -43,35 +43,49 @@ class GIFHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ /**
+ * Return the standard metadata elements for #filemetadata parser func.
+ * @param File $image
+ * @return array|bool
+ */
+ public function getCommonMetaArray( File $image ) {
$meta = $image->getMetadata();
if ( !$meta ) {
- return false;
+ return array();
}
$meta = unserialize( $meta );
- if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
- return false;
+ if ( !isset( $meta['metadata'] ) ) {
+ return array();
}
+ unset( $meta['metadata']['_MW_GIF_VERSION'] );
- if ( isset( $meta['metadata']['_MW_GIF_VERSION'] ) ) {
- unset( $meta['metadata']['_MW_GIF_VERSION'] );
- }
- return $this->formatMetadataHelper( $meta['metadata'] );
+ return $meta['metadata'];
}
/**
- * @param $image File
- * @todo unittests
+ * @todo Add unit tests
+ *
+ * @param File $image
* @return bool
*/
function getImageArea( $image ) {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
+
return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
} else {
return $image->getWidth() * $image->getHeight();
@@ -79,7 +93,7 @@ class GIFHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return bool
*/
function isAnimatedImage( $image ) {
@@ -90,6 +104,7 @@ class GIFHandler extends BitmapHandler {
return true;
}
}
+
return false;
}
@@ -101,6 +116,7 @@ class GIFHandler extends BitmapHandler {
function canAnimateThumbnail( $file ) {
global $wgMaxAnimatedGifArea;
$answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
+
return $answer;
}
@@ -120,19 +136,23 @@ class GIFHandler extends BitmapHandler {
if ( !$data || !is_array( $data ) ) {
wfDebug( __METHOD__ . " invalid GIF metadata\n" );
+
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
- || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION ) {
+ || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
+ ) {
wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
+
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return string
*/
function getLongDesc( $image ) {
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index 887afa3f..178b0bf7 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -32,9 +32,14 @@
* @ingroup Media
*/
class GIFMetadataExtractor {
- static $gif_frame_sep;
- static $gif_extension_sep;
- static $gif_term;
+ /** @var string */
+ private static $gifFrameSep;
+
+ /** @var string */
+ private static $gifExtensionSep;
+
+ /** @var string */
+ private static $gifTerm;
const VERSION = 1;
@@ -45,13 +50,13 @@ class GIFMetadataExtractor {
/**
* @throws Exception
- * @param $filename string
+ * @param string $filename
* @return array
*/
static function getMetadata( $filename ) {
- self::$gif_frame_sep = pack( "C", ord( "," ) );
- self::$gif_extension_sep = pack( "C", ord( "!" ) );
- self::$gif_term = pack( "C", ord( ";" ) );
+ self::$gifFrameSep = pack( "C", ord( "," ) );
+ self::$gifExtensionSep = pack( "C", ord( "!" ) );
+ self::$gifTerm = pack( "C", ord( ";" ) );
$frameCount = 0;
$duration = 0.0;
@@ -93,7 +98,7 @@ class GIFMetadataExtractor {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
- if ( $buf == self::$gif_frame_sep ) {
+ if ( $buf == self::$gifFrameSep ) {
// Found a frame
$frameCount++;
@@ -108,7 +113,7 @@ class GIFMetadataExtractor {
self::readGCT( $fh, $bpp );
fread( $fh, 1 );
self::skipBlock( $fh );
- } elseif ( $buf == self::$gif_extension_sep ) {
+ } elseif ( $buf == self::$gifExtensionSep ) {
$buf = fread( $fh, 1 );
if ( strlen( $buf ) < 1 ) {
throw new Exception( "Ran out of input" );
@@ -163,8 +168,8 @@ class GIFMetadataExtractor {
$commentCount = count( $comment );
if ( $commentCount === 0
- || $comment[$commentCount - 1] !== $data )
- {
+ || $comment[$commentCount - 1] !== $data
+ ) {
// Some applications repeat the same comment on each
// frame of an animated GIF image, so if this comment
// is identical to the last, only extract once.
@@ -217,15 +222,14 @@ class GIFMetadataExtractor {
$xmp = self::readBlock( $fh, true );
if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
- || substr( $xmp, -4 ) !== "\x03\x02\x01\x00" )
- {
+ || substr( $xmp, -4 ) !== "\x03\x02\x01\x00"
+ ) {
// this is just a sanity check.
throw new Exception( "XMP does not have magic trailer!" );
}
// strip out trailer.
$xmp = substr( $xmp, 0, -257 );
-
} else {
// unrecognized extension block
fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
@@ -235,7 +239,7 @@ class GIFMetadataExtractor {
} else {
self::skipBlock( $fh );
}
- } elseif ( $buf == self::$gif_term ) {
+ } elseif ( $buf == self::$gifTerm ) {
break;
} else {
if ( strlen( $buf ) < 1 ) {
@@ -257,20 +261,21 @@ class GIFMetadataExtractor {
}
/**
- * @param $fh
- * @param $bpp
+ * @param resource $fh
+ * @param int $bpp
* @return void
*/
static function readGCT( $fh, $bpp ) {
if ( $bpp > 0 ) {
- for ( $i = 1; $i <= pow( 2, $bpp ); ++$i ) {
+ $max = pow( 2, $bpp );
+ for ( $i = 1; $i <= $max; ++$i ) {
fread( $fh, 3 );
}
}
}
/**
- * @param $data
+ * @param string $data
* @throws Exception
* @return int
*/
@@ -289,7 +294,7 @@ class GIFMetadataExtractor {
}
/**
- * @param $fh
+ * @param resource $fh
* @throws Exception
*/
static function skipBlock( $fh ) {
@@ -313,8 +318,8 @@ class GIFMetadataExtractor {
* saying how long the sub-block is, followed by the sub-block.
* The entire block is terminated by a sub-block of length
* 0.
- * @param $fh FileHandle
- * @param $includeLengths Boolean Include the length bytes of the
+ * @param resource $fh File handle
+ * @param bool $includeLengths Include the length bytes of the
* sub-blocks in the returned value. Normally this is false,
* except XMP is weird and does a hack where you need to keep
* these length bytes.
@@ -341,7 +346,7 @@ class GIFMetadataExtractor {
$data .= fread( $fh, ord( $subLength ) );
$subLength = fread( $fh, 1 );
}
+
return $data;
}
-
}
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
index 544dd211..478249fe 100644
--- a/includes/media/IPTC.php
+++ b/includes/media/IPTC.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class IPTC {
-
/**
* This takes the results of iptcparse() and puts it into a
* form that can be handled by mediawiki. Generally called from
@@ -35,14 +34,14 @@ class IPTC {
*
* @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
*
- * @param string $rawData app13 block from jpeg containing iptc/iim data
- * @return Array iptc metadata array
+ * @param string $rawData The app13 block from jpeg containing iptc/iim data
+ * @return array IPTC metadata array
*/
static function parse( $rawData ) {
$parsed = iptcparse( $rawData );
- $data = Array();
+ $data = array();
if ( !is_array( $parsed ) ) {
- return $data;
+ return $data;
}
$c = '';
@@ -85,7 +84,8 @@ class IPTC {
$titles = array();
}
- for ( $i = 0; $i < count( $titles ); $i++ ) {
+ $titleCount = count( $titles );
+ for ( $i = 0; $i < $titleCount; $i++ ) {
if ( isset( $bylines[$i] ) ) {
// theoretically this should always be set
// but doesn't hurt to be careful.
@@ -225,7 +225,7 @@ class IPTC {
if ( isset( $parsed['2#060'] ) ) {
$time = $parsed['2#060'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -239,7 +239,7 @@ class IPTC {
if ( isset( $parsed['2#063'] ) ) {
$time = $parsed['2#063'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -252,7 +252,7 @@ class IPTC {
if ( isset( $parsed['2#035'] ) ) {
$time = $parsed['2#035'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -265,7 +265,7 @@ class IPTC {
if ( isset( $parsed['2#038'] ) ) {
$time = $parsed['2#038'];
} else {
- $time = Array();
+ $time = array();
}
$timestamp = self::timeHelper( $val, $time, $c );
if ( $timestamp ) {
@@ -300,7 +300,7 @@ class IPTC {
wfDebugLog( 'iptc', 'IPTC: '
. '2:04 too short. '
. 'Ignoring.' );
- break;
+ break;
}
$extracted = substr( $con[0], 4 );
$data['IntellectualGenre'] = $extracted;
@@ -315,9 +315,7 @@ class IPTC {
foreach ( $codes as $ic ) {
$fields = explode( ':', $ic, 3 );
- if ( count( $fields ) < 2 ||
- $fields[0] !== 'IPTC' )
- {
+ if ( count( $fields ) < 2 || $fields[0] !== 'IPTC' ) {
wfDebugLog( 'IPTC', 'IPTC: '
. 'Invalid 2:12 - ' . $ic );
break;
@@ -341,11 +339,11 @@ class IPTC {
break;
default:
- wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ));
+ wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ) );
break;
}
-
}
+
return $data;
}
@@ -355,8 +353,8 @@ class IPTC {
* @todo Potentially this should also capture the timezone offset.
* @param array $date The date tag
* @param array $time The time tag
- * @param $c
- * @return String Date in exif format.
+ * @param string $c The charset
+ * @return string Date in EXIF format.
*/
private static function timeHelper( $date, $time, $c ) {
if ( count( $date ) === 1 ) {
@@ -387,12 +385,14 @@ class IPTC {
// April, but the year and day is unknown. We don't process these
// types of incomplete dates atm.
wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )" );
+
return null;
}
- $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ));
+ $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ) );
if ( $unixTS === false ) {
wfDebugLog( 'iptc', "IPTC: can't convert date to TS_UNIX: $date $time." );
+
return null;
}
@@ -400,12 +400,13 @@ class IPTC {
+ ( intval( substr( $time, 9, 2 ) ) * 60 );
if ( substr( $time, 6, 1 ) === '-' ) {
- $tz = - $tz;
+ $tz = -$tz;
}
$finalTimestamp = wfTimestamp( TS_EXIF, $unixTS + $tz );
if ( $finalTimestamp === false ) {
wfDebugLog( 'iptc', "IPTC: can't make final timestamp. Date: " . ( $unixTS + $tz ) );
+
return null;
}
if ( $dateOnly ) {
@@ -434,9 +435,10 @@ class IPTC {
return $data;
}
+
/**
* Helper function of a helper function to convert charset for iptc values.
- * @param $data Mixed String or Array: The iptc string
+ * @param string|array $data The IPTC string
* @param string $charset The charset
*
* @return string
@@ -461,13 +463,14 @@ class IPTC {
return self::convIPTCHelper( $oldData, 'Windows-1252' );
}
}
+
return trim( $data );
}
/**
* take the value of 1:90 tag and returns a charset
* @param string $tag 1:90 tag.
- * @return string charset name or "?"
+ * @return string Charset name or "?"
* Warning, this function does not (and is not intended to) detect
* all iso 2022 escape codes. In practise, the code for utf-8 is the
* only code that seems to have wide use. It does detect that code.
diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php
index 6794e4bf..6dd0453e 100644
--- a/includes/media/ImageHandler.php
+++ b/includes/media/ImageHandler.php
@@ -27,9 +27,8 @@
* @ingroup Media
*/
abstract class ImageHandler extends MediaHandler {
-
/**
- * @param $file File
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
@@ -60,6 +59,7 @@ abstract class ImageHandler extends MediaHandler {
} else {
throw new MWException( 'No width specified to ' . __METHOD__ );
}
+
# Removed for ProofreadPage
#$width = intval( $width );
return "{$width}px";
@@ -79,8 +79,8 @@ abstract class ImageHandler extends MediaHandler {
}
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -141,20 +141,22 @@ abstract class ImageHandler extends MediaHandler {
}
if ( !$this->validateThumbParams( $params['physicalWidth'],
- $params['physicalHeight'], $srcWidth, $srcHeight, $mimeType ) ) {
+ $params['physicalHeight'], $srcWidth, $srcHeight, $mimeType )
+ ) {
return false;
}
+
return true;
}
/**
* Validate thumbnail parameters and fill in the correct height
*
- * @param $width Integer: specified width (input/output)
- * @param $height Integer: height (output only)
- * @param $srcWidth Integer: width of the source image
- * @param $srcHeight Integer: height of the source image
- * @param $mimeType
+ * @param int $width Specified width (input/output)
+ * @param int $height Height (output only)
+ * @param int $srcWidth Width of the source image
+ * @param int $srcHeight Height of the source image
+ * @param string $mimeType Unused
* @return bool False to indicate that an error should be returned to the user.
*/
function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
@@ -163,10 +165,12 @@ abstract class ImageHandler extends MediaHandler {
# Sanity check $width
if ( $width <= 0 ) {
wfDebug( __METHOD__ . ": Invalid destination width: $width\n" );
+
return false;
}
if ( $srcWidth <= 0 ) {
wfDebug( __METHOD__ . ": Invalid source width: $srcWidth\n" );
+
return false;
}
@@ -175,14 +179,15 @@ abstract class ImageHandler extends MediaHandler {
# Force height to be at least 1 pixel
$height = 1;
}
+
return true;
}
/**
- * @param $image File
- * @param $script
- * @param $params
- * @return bool|ThumbnailImage
+ * @param File $image
+ * @param string $script
+ * @param array $params
+ * @return bool|MediaTransformOutput
*/
function getScriptedTransform( $image, $script, $params ) {
if ( !$this->normaliseParams( $image, $params ) ) {
@@ -199,8 +204,10 @@ abstract class ImageHandler extends MediaHandler {
wfSuppressWarnings();
$gis = getimagesize( $path );
wfRestoreWarnings();
+
return $gis;
}
+
/**
* Function that returns the number of pixels to be thumbnailed.
* Intended for animated GIFs to multiply by the number of frames.
@@ -214,21 +221,21 @@ abstract class ImageHandler extends MediaHandler {
return $image->getWidth() * $image->getHeight();
}
-
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getShortDesc( $file ) {
global $wgLang;
$nbytes = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
- $widthheight = wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->escaped();
+ $widthheight = wfMessage( 'widthheight' )
+ ->numParams( $file->getWidth(), $file->getHeight() )->escaped();
return "$widthheight ($nbytes)";
}
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
@@ -238,25 +245,44 @@ abstract class ImageHandler extends MediaHandler {
if ( $pages === false || $pages <= 1 ) {
$msg = wfMessage( 'file-info-size' )->numParams( $file->getWidth(),
$file->getHeight() )->params( $size,
- $file->getMimeType() )->parse();
+ $file->getMimeType() )->parse();
} else {
$msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(),
$file->getHeight() )->params( $size,
- $file->getMimeType() )->numParams( $pages )->parse();
+ $file->getMimeType() )->numParams( $pages )->parse();
}
+
return $msg;
}
/**
- * @param $file File
+ * @param File $file
* @return string
*/
function getDimensionsString( $file ) {
$pages = $file->pageCount();
if ( $pages > 1 ) {
- return wfMessage( 'widthheightpage' )->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
+ return wfMessage( 'widthheightpage' )
+ ->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
} else {
- return wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->text();
+ return wfMessage( 'widthheight' )
+ ->numParams( $file->getWidth(), $file->getHeight() )->text();
}
}
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // We unset the height parameters in order to let normaliseParams recalculate them
+ // Otherwise there might be a height discrepancy
+ if ( isset( $params['height'] ) ) {
+ unset( $params['height'] );
+ }
+
+ if ( isset( $params['physicalHeight'] ) ) {
+ unset( $params['physicalHeight'] );
+ }
+
+ return $params;
+ }
}
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
index fa763668..fbdbdfe3 100644
--- a/includes/media/Jpeg.php
+++ b/includes/media/Jpeg.php
@@ -32,6 +32,70 @@
*/
class JpegHandler extends ExifBitmapHandler {
+ function normaliseParams( $image, &$params ) {
+ if ( !parent::normaliseParams( $image, $params ) ) {
+ return false;
+ }
+ if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ function validateParam( $name, $value ) {
+ if ( $name === 'quality' ) {
+ return self::validateQuality( $value );
+ } else {
+ return parent::validateParam( $name, $value );
+ }
+ }
+
+ /** Validate and normalize quality value to be between 1 and 100 (inclusive).
+ * @param int $value Quality value, will be converted to integer or 0 if invalid
+ * @return bool True if the value is valid
+ */
+ private static function validateQuality( $value ) {
+ return $value === 'low';
+ }
+
+ function makeParamString( $params ) {
+ // Prepend quality as "qValue-". This has to match parseParamString() below
+ $res = parent::makeParamString( $params );
+ if ( $res && isset( $params['quality'] ) ) {
+ $res = "q{$params['quality']}-$res";
+ }
+ return $res;
+ }
+
+ function parseParamString( $str ) {
+ // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
+ // first - check if the string begins with "qlow-", and if so, treat it as quality.
+ // Pass the first portion, or the whole string if "qlow-" not found, to the parent
+ // The parsing must match the makeParamString() above
+ $res = false;
+ $m = false;
+ if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
+ $v = $m[1];
+ if ( self::validateQuality( $v ) ) {
+ $res = parent::parseParamString( $m[2] );
+ if ( $res ) {
+ $res['quality'] = $v;
+ }
+ }
+ } else {
+ $res = parent::parseParamString( $str );
+ }
+ return $res;
+ }
+
+ function getScriptParams( $params ) {
+ $res = parent::getScriptParams( $params );
+ if ( isset( $params['quality'] ) ) {
+ $res['quality'] = $params['quality'];
+ }
+ return $res;
+ }
+
function getMetadata( $image, $filename ) {
try {
$meta = BitmapMetadataHandler::Jpeg( $filename );
@@ -40,10 +104,11 @@ class JpegHandler extends ExifBitmapHandler {
throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return serialize( $meta );
- }
- catch ( MWException $e ) {
- // BitmapMetadataHandler throws an exception in certain exceptional cases like if file does not exist.
+ } catch ( MWException $e ) {
+ // BitmapMetadataHandler throws an exception in certain exceptional
+ // cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
/* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
@@ -55,14 +120,15 @@ class JpegHandler extends ExifBitmapHandler {
* Thus switch to using -1 to denote only a broken file, and use an array with only
* MEDIAWIKI_EXIF_VERSION to denote no props.
*/
+
return ExifBitmapHandler::BROKEN_FILE;
}
}
/**
- * @param $file File
+ * @param File $file
* @param array $params Rotate parameters.
- * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
* @return bool
*/
@@ -79,16 +145,32 @@ class JpegHandler extends ExifBitmapHandler {
wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
wfProfileIn( 'jpegtran' );
$retval = 0;
- $err = wfShellExecWithStderr( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'jpegtran' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
+
return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
}
+
return false;
} else {
return parent::rotate( $file, $params );
}
}
+ public function supportsBucketing() {
+ return true;
+ }
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // Quality needs to be cleared for bucketing. Buckets need to be default quality
+ if ( isset( $params['quality'] ) ) {
+ unset( $params['quality'] );
+ }
+
+ return $params;
+ }
}
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index c7030eba..8c5b46bb 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -30,8 +30,8 @@
* @ingroup Media
*/
class JpegMetadataExtractor {
-
const MAX_JPEG_SEGMENTS = 200;
+
// the max segment is a sanity check.
// A jpeg file should never even remotely have
// that many segments. Your average file has about 10.
@@ -43,9 +43,9 @@ class JpegMetadataExtractor {
* but gis doesn't support having multiple app1 segments
* and those can't extract xmp on files containing both exif and xmp data
*
- * @param string $filename name of jpeg file
- * @return Array of interesting segments.
- * @throws MWException if given invalid file.
+ * @param string $filename Name of jpeg file
+ * @return array Array of interesting segments.
+ * @throws MWException If given invalid file.
*/
static function segmentSplitter( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
@@ -83,7 +83,8 @@ class JpegMetadataExtractor {
throw new MWException( 'Too many jpeg segments. Aborting' );
}
if ( $buffer !== "\xFF" ) {
- throw new MWException( "Error reading jpeg file marker. Expected 0xFF but got " . bin2hex( $buffer ) );
+ throw new MWException( "Error reading jpeg file marker. " .
+ "Expected 0xFF but got " . bin2hex( $buffer ) );
}
$buffer = fread( $fh, 1 );
@@ -113,7 +114,6 @@ class JpegMetadataExtractor {
} else {
wfDebug( __METHOD__ . " Ignoring JPEG comment as is garbage.\n" );
}
-
} elseif ( $buffer === "\xE1" ) {
// APP1 section (Exif, XMP, and XMP extended)
// only extract if XMP is enabled.
@@ -160,7 +160,6 @@ class JpegMetadataExtractor {
}
fseek( $fh, $size['int'] - 2, SEEK_CUR );
}
-
}
// shouldn't get here.
throw new MWException( "Reached end of jpeg file unexpectedly" );
@@ -168,9 +167,9 @@ class JpegMetadataExtractor {
/**
* Helper function for jpegSegmentSplitter
- * @param &$fh FileHandle for jpeg file
+ * @param resource &$fh File handle for JPEG file
* @throws MWException
- * @return string data content of segment.
+ * @return string Data content of segment.
*/
private static function jpegExtractMarker( &$fh ) {
$size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
@@ -181,6 +180,7 @@ class JpegMetadataExtractor {
if ( strlen( $segment ) !== $size['int'] - 2 ) {
throw new MWException( "Segment shorter than expected" );
}
+
return $segment;
}
@@ -193,9 +193,10 @@ class JpegMetadataExtractor {
*
* This should generally be called by BitmapMetadataHandler::doApp13()
*
- * @param string $app13 photoshop psir app13 block from jpg.
+ * @param string $app13 Photoshop psir app13 block from jpg.
* @throws MWException (It gets caught next level up though)
- * @return String if the iptc hash is good or not.
+ * @return string If the iptc hash is good or not. One of 'iptc-no-hash',
+ * 'iptc-good-hash', 'iptc-bad-hash'.
*/
public static function doPSIR( $app13 ) {
if ( !$app13 ) {
@@ -275,7 +276,6 @@ class JpegMetadataExtractor {
$lenData['len']++;
}
$offset += $lenData['len'];
-
}
if ( !$realHash || !$recordedHash ) {
diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php
index 779e23c9..64ca0115 100644
--- a/includes/media/MediaHandler.php
+++ b/includes/media/MediaHandler.php
@@ -32,34 +32,46 @@ abstract class MediaHandler {
const METADATA_BAD = false;
const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
/**
- * Instance cache
+ * Max length of error logged by logErrorForExternalProcess()
*/
- static $handlers = array();
+ const MAX_ERR_LOG_SIZE = 65535;
+
+ /** @var MediaHandler[] Instance cache with array of MediaHandler */
+ protected static $handlers = array();
/**
* Get a MediaHandler for a given MIME type from the instance cache
*
- * @param $type string
- *
+ * @param string $type
* @return MediaHandler
*/
static function getHandler( $type ) {
global $wgMediaHandlers;
if ( !isset( $wgMediaHandlers[$type] ) ) {
wfDebug( __METHOD__ . ": no handler found for $type.\n" );
+
return false;
}
$class = $wgMediaHandlers[$type];
if ( !isset( self::$handlers[$class] ) ) {
self::$handlers[$class] = new $class;
if ( !self::$handlers[$class]->isEnabled() ) {
+ wfDebug( __METHOD__ . ": $class is not enabled\n" );
self::$handlers[$class] = false;
}
}
+
return self::$handlers[$class];
}
/**
+ * Resets all static caches
+ */
+ public static function resetCache() {
+ self::$handlers = array();
+ }
+
+ /**
* Get an associative array mapping magic word IDs to parameter names.
* Will be used by the parser to identify parameters.
*/
@@ -70,24 +82,24 @@ abstract class MediaHandler {
* Return true to accept the parameter, and false to reject it.
* If you return false, the parser will do something quiet and forgiving.
*
- * @param $name
- * @param $value
+ * @param string $name
+ * @param mixed $value
*/
abstract function validateParam( $name, $value );
/**
* Merge a parameter array into a string appropriate for inclusion in filenames
*
- * @param $params array Array of parameters that have been through normaliseParams.
- * @return String
+ * @param array $params Array of parameters that have been through normaliseParams.
+ * @return string
*/
abstract function makeParamString( $params );
/**
* Parse a param string made with makeParamString back into an array
*
- * @param $str string The parameter string without file name (e.g. 122px)
- * @return Array|Boolean Array of parameters or false on failure.
+ * @param string $str The parameter string without file name (e.g. 122px)
+ * @return array|bool Array of parameters or false on failure.
*/
abstract function parseParamString( $str );
@@ -95,8 +107,8 @@ abstract class MediaHandler {
* Changes the parameter array as necessary, ready for transformation.
* Should be idempotent.
* Returns false if the parameters are unacceptable and the transform should fail
- * @param $image
- * @param $params
+ * @param File $image
+ * @param array $params
*/
abstract function normaliseParams( $image, &$params );
@@ -104,19 +116,30 @@ abstract class MediaHandler {
* Get an image size array like that returned by getimagesize(), or false if it
* can't be determined.
*
- * @param $image File: the image object, or false if there isn't one
- * @param string $path the filename
- * @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
+ * This function is used for determining the width, height and bitdepth directly
+ * from an image. The results are stored in the database in the img_width,
+ * img_height, img_bits fields.
+ *
+ * @note If this is a multipage file, return the width and height of the
+ * first page.
+ *
+ * @param File $image The image object, or false if there isn't one
+ * @param string $path The filename
+ * @return array Follow the format of PHP getimagesize() internal function.
+ * See http://www.php.net/getimagesize. MediaWiki will only ever use the
+ * first two array keys (the width and height), and the 'bits' associative
+ * key. All other array keys are ignored. Returning a 'bits' key is optional
+ * as not all formats have a notion of "bitdepth".
*/
abstract function getImageSize( $image, $path );
/**
* Get handler-specific metadata which will be saved in the img_metadata field.
*
- * @param $image File: the image object, or false if there isn't one.
+ * @param File $image The image object, or false if there isn't one.
* Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
- * @param string $path the filename
- * @return String
+ * @param string $path The filename
+ * @return string A string of metadata in php serialized form (Run through serialize())
*/
function getMetadata( $image, $path ) {
return '';
@@ -127,7 +150,7 @@ abstract class MediaHandler {
*
* This is not used for validating metadata, this is used for the api when returning
* metadata, since api content formats should stay the same over time, and so things
- * using ForiegnApiRepo can keep backwards compatibility
+ * using ForeignApiRepo can keep backwards compatibility
*
* All core media handlers share a common version number, and extensions can
* use the GetMetadataVersion hook to append to the array (they should append a unique
@@ -135,11 +158,12 @@ abstract class MediaHandler {
* version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
* version is 2, the end version string would look like '2;foo=3'.
*
- * @return string version string
+ * @return string Version string
*/
static function getMetadataVersion() {
- $version = Array( '2' ); // core metadata version
- wfRunHooks( 'GetMetadataVersion', Array( &$version ) );
+ $version = array( '2' ); // core metadata version
+ wfRunHooks( 'GetMetadataVersion', array( &$version ) );
+
return implode( ';', $version );
}
@@ -149,9 +173,9 @@ abstract class MediaHandler {
* By default just returns $metadata, but can be used to allow
* media handlers to convert between metadata versions.
*
- * @param $metadata Mixed String or Array metadata array (serialized if string)
- * @param $version Integer target version
- * @return Array serialized metadata in specified version, or $metadata on fail.
+ * @param string|array $metadata Metadata array (serialized if string)
+ * @param int $version Target version
+ * @return array Serialized metadata in specified version, or $metadata on fail.
*/
function convertMetadataVersion( $metadata, $version = 1 ) {
if ( !is_array( $metadata ) ) {
@@ -160,14 +184,18 @@ abstract class MediaHandler {
wfSuppressWarnings();
$ret = unserialize( $metadata );
wfRestoreWarnings();
+
return $ret;
}
+
return $metadata;
}
/**
* Get a string describing the type of metadata, for display purposes.
*
+ * @note This method is currently unused.
+ * @param File $image
* @return string
*/
function getMetadataType( $image ) {
@@ -179,8 +207,15 @@ abstract class MediaHandler {
* If it returns MediaHandler::METADATA_BAD (or false), Image
* will reload the metadata from the file and update the database.
* MediaHandler::METADATA_GOOD for if the metadata is a-ok,
- * MediaHanlder::METADATA_COMPATIBLE if metadata is old but backwards
+ * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards
* compatible (which may or may not trigger a metadata reload).
+ *
+ * @note Returning self::METADATA_BAD will trigger a metadata reload from
+ * file on page view. Always returning this from a broken file, or suddenly
+ * triggering as bad metadata for a large number of files can cause
+ * performance problems.
+ * @param File $image
+ * @param string $metadata The metadata in serialized form
* @return bool
*/
function isMetadataValid( $image, $metadata ) {
@@ -188,13 +223,52 @@ abstract class MediaHandler {
}
/**
+ * Get an array of standard (FormatMetadata type) metadata values.
+ *
+ * The returned data is largely the same as that from getMetadata(),
+ * but formatted in a standard, stable, handler-independent way.
+ * The idea being that some values like ImageDescription or Artist
+ * are universal and should be retrievable in a handler generic way.
+ *
+ * The specific properties are the type of properties that can be
+ * handled by the FormatMetadata class. These values are exposed to the
+ * user via the filemetadata parser function.
+ *
+ * Details of the response format of this function can be found at
+ * https://www.mediawiki.org/wiki/Manual:File_metadata_handling
+ * tl/dr: the response is an associative array of
+ * properties keyed by name, but the value can be complex. You probably
+ * want to call one of the FormatMetadata::flatten* functions on the
+ * property values before using them, or call
+ * FormatMetadata::getFormattedData() on the full response array, which
+ * transforms all values into prettified, human-readable text.
+ *
+ * Subclasses overriding this function must return a value which is a
+ * valid API response fragment (all associative array keys are valid
+ * XML tagnames).
+ *
+ * Note, if the file simply has no metadata, but the handler supports
+ * this interface, it should return an empty array, not false.
+ *
+ * @param File $file
+ * @return array|bool False if interface not supported
+ * @since 1.23
+ */
+ public function getCommonMetaArray( File $file ) {
+ return false;
+ }
+
+ /**
* Get a MediaTransformOutput object representing an alternate of the transformed
* output which will call an intermediary thumbnail assist script.
*
* Used when the repository has a thumbnailScriptUrl option configured.
*
* Return false to fall back to the regular getTransform().
- * @return bool
+ * @param File $image
+ * @param string $script
+ * @param array $params
+ * @return bool|ThumbnailImage
*/
function getScriptedTransform( $image, $script, $params ) {
return false;
@@ -204,8 +278,8 @@ abstract class MediaHandler {
* Get a MediaTransformOutput object representing the transformed output. Does not
* actually do the transform.
*
- * @param $image File: the image object
- * @param string $dstPath filesystem destination path
+ * @param File $image The image object
+ * @param string $dstPath Filesystem destination path
* @param string $dstUrl Destination URL to use in output HTML
* @param array $params Arbitrary set of parameters validated by $this->validateParam()
* @return MediaTransformOutput
@@ -218,13 +292,12 @@ abstract class MediaHandler {
* Get a MediaTransformOutput object representing the transformed output. Does the
* transform unless $flags contains self::TRANSFORM_LATER.
*
- * @param $image File: the image object
- * @param string $dstPath filesystem destination path
- * @param string $dstUrl destination URL to use in output HTML
- * @param array $params arbitrary set of parameters validated by $this->validateParam()
+ * @param File $image The image object
+ * @param string $dstPath Filesystem destination path
+ * @param string $dstUrl Destination URL to use in output HTML
+ * @param array $params Arbitrary set of parameters validated by $this->validateParam()
* Note: These parameters have *not* gone through $this->normaliseParams()
- * @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
- *
+ * @param int $flags A bitfield, may contain self::TRANSFORM_LATER
* @return MediaTransformOutput
*/
abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
@@ -232,31 +305,32 @@ abstract class MediaHandler {
/**
* Get the thumbnail extension and MIME type for a given source MIME type
*
- * @param String $ext Extension of original file
- * @param String $mime Mime type of original file
- * @param Array $params Handler specific rendering parameters
- * @return array thumbnail extension and MIME type
+ * @param string $ext Extension of original file
+ * @param string $mime MIME type of original file
+ * @param array $params Handler specific rendering parameters
+ * @return array Thumbnail extension and MIME type
*/
function getThumbType( $ext, $mime, $params = null ) {
$magic = MimeMagic::singleton();
if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
- // The extension is not valid for this mime type and we do
- // recognize the mime type
+ // The extension is not valid for this MIME type and we do
+ // recognize the MIME type
$extensions = $magic->getExtensionsForType( $mime );
if ( $extensions ) {
return array( strtok( $extensions, ' ' ), $mime );
}
}
- // The extension is correct (true) or the mime type is unknown to
+ // The extension is correct (true) or the MIME type is unknown to
// MediaWiki (null)
return array( $ext, $mime );
}
/**
* Get useful response headers for GET/HEAD requests for a file with the given metadata
- * @param $metadata mixed Result of the getMetadata() function of this handler for a file
- * @return Array
+ *
+ * @param mixed $metadata Result of the getMetadata() function of this handler for a file
+ * @return array
*/
public function getStreamHeaders( $metadata ) {
return array();
@@ -264,6 +338,8 @@ abstract class MediaHandler {
/**
* True if the handled types can be transformed
+ *
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
@@ -273,6 +349,8 @@ abstract class MediaHandler {
/**
* True if handled types cannot be displayed directly in a browser
* but can be rendered
+ *
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -281,6 +359,8 @@ abstract class MediaHandler {
/**
* True if the type has multi-page capabilities
+ *
+ * @param File $file
* @return bool
*/
function isMultiPage( $file ) {
@@ -289,6 +369,8 @@ abstract class MediaHandler {
/**
* Page count for a multi-page document, false if unsupported or unknown
+ *
+ * @param File $file
* @return bool
*/
function pageCount( $file ) {
@@ -297,6 +379,8 @@ abstract class MediaHandler {
/**
* The material is vectorized and thus scaling is lossless
+ *
+ * @param File $file
* @return bool
*/
function isVectorized( $file ) {
@@ -307,6 +391,8 @@ abstract class MediaHandler {
* The material is an image, and is animated.
* In particular, video material need not return true.
* @note Before 1.20, this was a method of ImageHandler only
+ *
+ * @param File $file
* @return bool
*/
function isAnimatedImage( $file ) {
@@ -316,6 +402,8 @@ abstract class MediaHandler {
/**
* If the material is animated, we can animate the thumbnail
* @since 1.20
+ *
+ * @param File $file
* @return bool If material is not animated, handler may return any value.
*/
function canAnimateThumbnail( $file ) {
@@ -342,8 +430,8 @@ abstract class MediaHandler {
*
* @note For non-paged media, use getImageSize.
*
- * @param $image File
- * @param $page What page to get dimensions of
+ * @param File $image
+ * @param int $page What page to get dimensions of
* @return array|bool
*/
function getPageDimensions( $image, $page ) {
@@ -361,13 +449,40 @@ abstract class MediaHandler {
/**
* Generic getter for text layer.
* Currently overloaded by PDF and DjVu handlers
- * @return bool
+ * @param File $image
+ * @param int $page Page number to get information for
+ * @return bool|string Page text or false when no text found or if
+ * unsupported.
*/
function getPageText( $image, $page ) {
return false;
}
/**
+ * Get the text of the entire document.
+ * @param File $file
+ * @return bool|string The text of the document or false if unsupported.
+ */
+ public function getEntireText( File $file ) {
+ $numPages = $file->pageCount();
+ if ( !$numPages ) {
+ // Not a multipage document
+ return $this->getPageText( $file, 1 );
+ }
+ $document = '';
+ for ( $i = 1; $i <= $numPages; $i++ ) {
+ $curPage = $this->getPageText( $file, $i );
+ if ( is_string( $curPage ) ) {
+ $document .= $curPage . "\n";
+ }
+ }
+ if ( $document !== '' ) {
+ return $document;
+ }
+ return false;
+ }
+
+ /**
* Get an array structure that looks like this:
*
* array(
@@ -387,12 +502,12 @@ abstract class MediaHandler {
*/
/**
- * @todo FIXME: I don't really like this interface, it's not very flexible
- * I think the media handler should generate HTML instead. It can do
- * all the formatting according to some standard. That makes it possible
- * to do things like visual indication of grouped and chained streams
- * in ogg container files.
- * @return bool
+ * @todo FIXME: This interface is not very flexible. The media handler
+ * should generate HTML instead. It can do all the formatting according
+ * to some standard. That makes it possible to do things like visual
+ * indication of grouped and chained streams in ogg container files.
+ * @param File $image
+ * @return array|bool
*/
function formatMetadata( $image ) {
return false;
@@ -404,8 +519,8 @@ abstract class MediaHandler {
*
* This is used by the media handlers that use the FormatMetadata class
*
- * @param array $metadataArray metadata array
- * @return array for use displaying metadata.
+ * @param array $metadataArray Metadata array
+ * @return array Array for use displaying metadata.
*/
function formatMetadataHelper( $metadataArray ) {
$result = array(
@@ -425,6 +540,7 @@ abstract class MediaHandler {
$value
);
}
+
return $result;
}
@@ -432,20 +548,10 @@ abstract class MediaHandler {
* Get a list of metadata items which should be displayed when
* the metadata table is collapsed.
*
- * @return array of strings
- * @access protected
+ * @return array Array of strings
*/
- function visibleMetadataFields() {
- $fields = array();
- $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
- foreach ( $lines as $line ) {
- $matches = array();
- if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
- $fields[] = $matches[1];
- }
- }
- $fields = array_map( 'strtolower', $fields );
- return $fields;
+ protected function visibleMetadataFields() {
+ return FormatMetadata::getVisibleFields();
}
/**
@@ -453,21 +559,21 @@ abstract class MediaHandler {
* That array is then used to generate the table of metadata values
* on the image page
*
- * @param &$array Array An array containing elements for each type of visibility
- * and each of those elements being an array of metadata items. This function adds
- * a value to that array.
+ * @param array &$array An array containing elements for each type of visibility
+ * and each of those elements being an array of metadata items. This function adds
+ * a value to that array.
* @param string $visibility ('visible' or 'collapsed') if this value is hidden
- * by default.
- * @param string $type type of metadata tag (currently always 'exif')
- * @param string $id the name of the metadata tag (like 'artist' for example).
- * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
- * @param string $value thingy goes into a wikitext table; it used to be escaped but
- * that was incompatible with previous practise of customized display
- * with wikitext formatting via messages such as 'exif-model-value'.
- * So the escaping is taken back out, but generally this seems a confusing
- * interface.
- * @param string $param value to pass to the message for the name of the field
- * as $1. Currently this parameter doesn't seem to ever be used.
+ * by default.
+ * @param string $type Type of metadata tag (currently always 'exif')
+ * @param string $id The name of the metadata tag (like 'artist' for example).
+ * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
+ * @param string $value Thingy goes into a wikitext table; it used to be escaped but
+ * that was incompatible with previous practise of customized display
+ * with wikitext formatting via messages such as 'exif-model-value'.
+ * So the escaping is taken back out, but generally this seems a confusing
+ * interface.
+ * @param bool|string $param Value to pass to the message for the name of the field
+ * as $1. Currently this parameter doesn't seem to ever be used.
*
* Note, everything here is passed through the parser later on (!)
*/
@@ -492,58 +598,55 @@ abstract class MediaHandler {
}
/**
- * Used instead of getLongDesc if there is no handler registered for file.
+ * Short description. Shown on Special:Search results.
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getShortDesc( $file ) {
- global $wgLang;
- return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
+ return self::getGeneralShortDesc( $file );
}
/**
- * Short description. Shown on Special:Search results.
+ * Long description. Shown under image on image description page surounded by ().
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
- global $wgLang;
- return wfMessage( 'file-info', htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ),
- $file->getMimeType() )->parse();
+ return self::getGeneralLongDesc( $file );
}
/**
- * Long description. Shown under image on image description page surounded by ().
+ * Used instead of getShortDesc if there is no handler registered for file.
*
- * @param $file File
+ * @param File $file
* @return string
*/
static function getGeneralShortDesc( $file ) {
global $wgLang;
- return $wgLang->formatSize( $file->getSize() );
+
+ return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
}
/**
- * Used instead of getShortDesc if there is no handler registered for file.
+ * Used instead of getLongDesc if there is no handler registered for file.
*
- * @param $file File
+ * @param File $file
* @return string
*/
static function getGeneralLongDesc( $file ) {
- global $wgLang;
- return wfMessage( 'file-info', $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() )->parse();
+ return wfMessage( 'file-info' )->sizeParams( $file->getSize() )
+ ->params( $file->getMimeType() )->parse();
}
/**
* Calculate the largest thumbnail width for a given original file size
* such that the thumbnail's height is at most $maxHeight.
- * @param $boxWidth Integer Width of the thumbnail box.
- * @param $boxHeight Integer Height of the thumbnail box.
- * @param $maxHeight Integer Maximum height expected for the thumbnail.
- * @return Integer.
+ * @param int $boxWidth Width of the thumbnail box.
+ * @param int $boxHeight Height of the thumbnail box.
+ * @param int $maxHeight Maximum height expected for the thumbnail.
+ * @return int
*/
public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
$idealWidth = $boxWidth * $maxHeight / $boxHeight;
@@ -559,7 +662,7 @@ abstract class MediaHandler {
* Shown in file history box on image description page.
*
* @param File $file
- * @return String Dimensions
+ * @return string Dimensions
*/
function getDimensionsString( $file ) {
return '';
@@ -575,7 +678,8 @@ abstract class MediaHandler {
* @param Parser $parser
* @param File $file
*/
- function parserTransformHook( $parser, $file ) {}
+ function parserTransformHook( $parser, $file ) {
+ }
/**
* File validation hook called on upload.
@@ -585,7 +689,7 @@ abstract class MediaHandler {
* relevant errors.
*
* @param string $fileName The local path to the file.
- * @return Status object
+ * @return Status
*/
function verifyUpload( $fileName ) {
return Status::newGood();
@@ -614,9 +718,11 @@ abstract class MediaHandler {
sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
$thumbstat['size'], $dstPath ) );
}
+
return true;
}
}
+
return false;
}
@@ -637,12 +743,12 @@ abstract class MediaHandler {
// Do nothing
}
- /*
+ /**
* True if the handler can rotate the media
- * @since 1.21
+ * @since 1.24 non-static. From 1.21-1.23 was static
* @return bool
*/
- public static function canRotate() {
+ public function canRotate() {
return false;
}
@@ -657,11 +763,100 @@ abstract class MediaHandler {
*
* For files we don't know, we return 0.
*
- * @param $file File
+ * @param File $file
* @return int 0, 90, 180 or 270
*/
public function getRotation( $file ) {
return 0;
}
+ /**
+ * Log an error that occurred in an external process
+ *
+ * Moved from BitmapHandler to MediaHandler with MediaWiki 1.23
+ *
+ * @since 1.23
+ * @param int $retval
+ * @param string $err Error reported by command. Anything longer than
+ * MediaHandler::MAX_ERR_LOG_SIZE is stripped off.
+ * @param string $cmd
+ */
+ protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
+ # Keep error output limited (bug 57985)
+ $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) );
+
+ wfDebugLog( 'thumbnail',
+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+ wfHostname(), $retval, $errMessage, $cmd ) );
+ }
+
+ /**
+ * Get list of languages file can be viewed in.
+ *
+ * @param File $file
+ * @return string[] Array of language codes, or empty array if unsupported.
+ * @since 1.23
+ */
+ public function getAvailableLanguages( File $file ) {
+ return array();
+ }
+
+ /**
+ * On file types that support renderings in multiple languages,
+ * which language is used by default if unspecified.
+ *
+ * If getAvailableLanguages returns a non-empty array, this must return
+ * a valid language code. Otherwise can return null if files of this
+ * type do not support alternative language renderings.
+ *
+ * @param File $file
+ * @return string|null Language code or null if multi-language not supported for filetype.
+ * @since 1.23
+ */
+ public function getDefaultRenderLanguage( File $file ) {
+ return null;
+ }
+
+ /**
+ * If its an audio file, return the length of the file. Otherwise 0.
+ *
+ * File::getLength() existed for a long time, but was calling a method
+ * that only existed in some subclasses of this class (The TMH ones).
+ *
+ * @param File $file
+ * @return float Length in seconds
+ * @since 1.23
+ */
+ public function getLength( $file ) {
+ return 0.0;
+ }
+
+ /**
+ * True if creating thumbnails from the file is large or otherwise resource-intensive.
+ * @param File $file
+ * @return bool
+ */
+ public function isExpensiveToThumbnail( $file ) {
+ return false;
+ }
+
+ /**
+ * Returns whether or not this handler supports the chained generation of thumbnails according
+ * to buckets
+ * @return bool
+ * @since 1.24
+ */
+ public function supportsBucketing() {
+ return false;
+ }
+
+ /**
+ * Returns a normalised params array for which parameters have been cleaned up for bucketing
+ * purposes
+ * @param array $params
+ * @return array
+ */
+ public function sanitizeParamsForBucketing( $params ) {
+ return $params;
+ }
}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index c49d3f20..bc9e9173 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -27,46 +27,67 @@
* @ingroup Media
*/
abstract class MediaTransformOutput {
- /**
- * @var File
+ /** @var array Associative array mapping optional supplementary image files
+ * from pixel density (eg 1.5 or 2) to additional URLs.
*/
- var $file;
+ public $responsiveUrls = array();
- var $width, $height, $url, $page, $path, $lang;
+ /** @var File */
+ protected $file;
- /**
- * @var array Associative array mapping optional supplementary image files
- * from pixel density (eg 1.5 or 2) to additional URLs.
- */
- public $responsiveUrls = array();
+ /** @var int Image width */
+ protected $width;
+
+ /** @var int Image height */
+ protected $height;
+
+ /** @var string URL path to the thumb */
+ protected $url;
+ /** @var bool|string */
+ protected $page;
+
+ /** @var bool|string Filesystem path to the thumb */
+ protected $path;
+
+ /** @var bool|string Language code, false if not set */
+ protected $lang;
+
+ /** @var bool|string Permanent storage path */
protected $storagePath = false;
/**
- * @return integer Width of the output box
+ * @return int Width of the output box
*/
public function getWidth() {
return $this->width;
}
/**
- * @return integer Height of the output box
+ * @return int Height of the output box
*/
public function getHeight() {
return $this->height;
}
/**
+ * @return File
+ */
+ public function getFile() {
+ return $this->file;
+ }
+
+ /**
* Get the final extension of the thumbnail.
* Returns false for scripted transformations.
- * @return string|false
+ * @return string|bool
*/
public function getExtension() {
return $this->path ? FileBackend::extensionFromPath( $this->path ) : false;
}
/**
- * @return string|false The thumbnail URL
+ * @return string|bool The thumbnail URL
*/
public function getUrl() {
return $this->url;
@@ -85,6 +106,9 @@ abstract class MediaTransformOutput {
*/
public function setStoragePath( $storagePath ) {
$this->storagePath = $storagePath;
+ if ( $this->path === false ) {
+ $this->path = $storagePath;
+ }
}
/**
@@ -119,11 +143,14 @@ abstract class MediaTransformOutput {
/**
* Check if an output thumbnail file actually exists.
+ *
* This will return false if there was an error, the
* thumbnail is to be handled client-side only, or if
* transformation was deferred via TRANSFORM_LATER.
+ * This file may exist as a new file in /tmp, a file
+ * in permanent storage, or even refer to the original.
*
- * @return Bool
+ * @return bool
*/
public function hasFile() {
// If TRANSFORM_LATER, $this->path will be false.
@@ -135,7 +162,7 @@ abstract class MediaTransformOutput {
* Check if the output thumbnail is the same as the source.
* This can occur if the requested width was bigger than the source.
*
- * @return Bool
+ * @return bool
*/
public function fileIsSource() {
return ( !$this->isError() && $this->path === null );
@@ -156,6 +183,7 @@ abstract class MediaTransformOutput {
$be = $this->file->getRepo()->getBackend();
// The temp file will be process cached by FileBackend
$fsFile = $be->getLocalReference( array( 'src' => $this->path ) );
+
return $fsFile ? $fsFile->getPath() : false;
} else {
return $this->path; // may return false
@@ -166,13 +194,14 @@ abstract class MediaTransformOutput {
* Stream the file if there were no errors
*
* @param array $headers Additional HTTP headers to send on success
- * @return Bool success
+ * @return bool Success
*/
public function streamFile( $headers = array() ) {
if ( !$this->path ) {
return false;
} elseif ( FileBackend::isStoragePath( $this->path ) ) {
$be = $this->file->getRepo()->getBackend();
+
return $be->streamFile( array( 'src' => $this->path, 'headers' => $headers ) )->isOK();
} else { // FS-file
return StreamFile::stream( $this->getLocalCopyPath(), $headers );
@@ -182,9 +211,8 @@ abstract class MediaTransformOutput {
/**
* Wrap some XHTML text in an anchor tag with the given attributes
*
- * @param $linkAttribs array
- * @param $contents string
- *
+ * @param array $linkAttribs
+ * @param string $contents
* @return string
*/
protected function linkWrap( $linkAttribs, $contents ) {
@@ -196,8 +224,8 @@ abstract class MediaTransformOutput {
}
/**
- * @param $title string
- * @param $params string|array Query parameters to add
+ * @param string $title
+ * @param string|array $params Query parameters to add
* @return array
*/
public function getDescLinkAttribs( $title = null, $params = array() ) {
@@ -224,6 +252,7 @@ abstract class MediaTransformOutput {
if ( $title ) {
$attribs['title'] = $title;
}
+
return $attribs;
}
}
@@ -241,11 +270,10 @@ class ThumbnailImage extends MediaTransformOutput {
* $parameters should include, as a minimum, (file) 'width' and 'height'.
* It may also include a 'page' parameter for multipage files.
*
- * @param $file File object
+ * @param File $file
* @param string $url URL path to the thumb
- * @param $path String|bool|null: filesystem path to the thumb
+ * @param string|bool $path Filesystem path to the thumb
* @param array $parameters Associative array of parameters
- * @private
*/
function __construct( $file, $url, $path = false, $parameters = array() ) {
# Previous parameters:
@@ -300,6 +328,8 @@ class ThumbnailImage extends MediaTransformOutput {
* desc-query String, description link query params
* override-width Override width attribute. Should generally not set
* override-height Override height attribute. Should generally not set
+ * no-dimensions Boolean, skip width and height attributes (useful if
+ * set in CSS)
* custom-url-link Custom URL to link to
* custom-title-link Custom Title object to link to
* custom target-link Value of the target attribute, for custom-target-link
@@ -336,13 +366,17 @@ class ThumbnailImage extends MediaTransformOutput {
$linkAttribs['rel'] = $options['parser-extlink-rel'];
}
} elseif ( !empty( $options['custom-title-link'] ) ) {
+ /** @var Title $title */
$title = $options['custom-title-link'];
$linkAttribs = array(
'href' => $title->getLinkURL(),
'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
);
} elseif ( !empty( $options['desc-link'] ) ) {
- $linkAttribs = $this->getDescLinkAttribs( empty( $options['title'] ) ? null : $options['title'], $query );
+ $linkAttribs = $this->getDescLinkAttribs(
+ empty( $options['title'] ) ? null : $options['title'],
+ $query
+ );
} elseif ( !empty( $options['file-link'] ) ) {
$linkAttribs = array( 'href' => $this->file->getURL() );
} else {
@@ -352,9 +386,12 @@ class ThumbnailImage extends MediaTransformOutput {
$attribs = array(
'alt' => $alt,
'src' => $this->url,
- 'width' => $this->width,
- 'height' => $this->height
);
+
+ if ( empty( $options['no-dimensions'] ) ) {
+ $attribs['width'] = $this->width;
+ $attribs['height'] = $this->height;
+ }
if ( !empty( $options['valign'] ) ) {
$attribs['style'] = "vertical-align: {$options['valign']}";
}
@@ -377,7 +414,6 @@ class ThumbnailImage extends MediaTransformOutput {
return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
}
-
}
/**
@@ -386,7 +422,11 @@ class ThumbnailImage extends MediaTransformOutput {
* @ingroup Media
*/
class MediaTransformError extends MediaTransformOutput {
- var $htmlMsg, $textMsg, $width, $height, $url, $path;
+ /** @var string HTML formatted version of the error */
+ private $htmlMsg;
+
+ /** @var string Plain text formatted version of the error */
+ private $textMsg;
function __construct( $msg, $width, $height /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index 98f13861..7b3ddb51 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -27,7 +27,6 @@
* @ingroup Media
*/
class PNGHandler extends BitmapHandler {
-
const BROKEN_FILE = '0';
/**
@@ -41,6 +40,7 @@ class PNGHandler extends BitmapHandler {
} catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return self::BROKEN_FILE;
}
@@ -48,28 +48,41 @@ class PNGHandler extends BitmapHandler {
}
/**
- * @param $image File
+ * @param File $image
* @return array|bool
*/
function formatMetadata( $image ) {
+ $meta = $this->getCommonMetaArray( $image );
+ if ( count( $meta ) === 0 ) {
+ return false;
+ }
+
+ return $this->formatMetadataHelper( $meta );
+ }
+
+ /**
+ * Get a file type independent array of metadata.
+ *
+ * @param File $image
+ * @return array The metadata array
+ */
+ public function getCommonMetaArray( File $image ) {
$meta = $image->getMetadata();
if ( !$meta ) {
- return false;
+ return array();
}
$meta = unserialize( $meta );
- if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
- return false;
+ if ( !isset( $meta['metadata'] ) ) {
+ return array();
}
+ unset( $meta['metadata']['_MW_PNG_VERSION'] );
- if ( isset( $meta['metadata']['_MW_PNG_VERSION'] ) ) {
- unset( $meta['metadata']['_MW_PNG_VERSION'] );
- }
- return $this->formatMetadataHelper( $meta['metadata'] );
+ return $meta['metadata'];
}
/**
- * @param $image File
+ * @param File $image
* @return bool
*/
function isAnimatedImage( $image ) {
@@ -80,12 +93,14 @@ class PNGHandler extends BitmapHandler {
return true;
}
}
+
return false;
}
+
/**
* We do not support making APNG thumbnails, so always false
- * @param $image File
- * @return bool false
+ * @param File $image
+ * @return bool False
*/
function canAnimateThumbnail( $image ) {
return false;
@@ -108,19 +123,23 @@ class PNGHandler extends BitmapHandler {
if ( !$data || !is_array( $data ) ) {
wfDebug( __METHOD__ . " invalid png metadata\n" );
+
return self::METADATA_BAD;
}
if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
- || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION ) {
+ || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
+ ) {
wfDebug( __METHOD__ . " old but compatible png metadata\n" );
+
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
/**
- * @param $image File
+ * @param File $image
* @return string
*/
function getLongDesc( $image ) {
@@ -155,4 +174,7 @@ class PNGHandler extends BitmapHandler {
return $wgLang->commaList( $info );
}
+ public function supportsBucketing() {
+ return true;
+ }
}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
index 845d212a..bccd36c1 100644
--- a/includes/media/PNGMetadataExtractor.php
+++ b/includes/media/PNGMetadataExtractor.php
@@ -31,40 +31,45 @@
* @ingroup Media
*/
class PNGMetadataExtractor {
- static $png_sig;
- static $CRC_size;
- static $text_chunks;
+ /** @var string */
+ private static $pngSig;
+
+ /** @var int */
+ private static $crcSize;
+
+ /** @var array */
+ private static $textChunks;
const VERSION = 1;
const MAX_CHUNK_SIZE = 3145728; // 3 megabytes
static function getMetadata( $filename ) {
- self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
- self::$CRC_size = 4;
+ self::$pngSig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
+ self::$crcSize = 4;
/* based on list at http://owl.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
* and http://www.w3.org/TR/PNG/#11keywords
*/
- self::$text_chunks = array(
+ self::$textChunks = array(
'xml:com.adobe.xmp' => 'xmp',
# Artist is unofficial. Author is the recommended
# keyword in the PNG spec. However some people output
# Artist so support both.
- 'artist' => 'Artist',
- 'model' => 'Model',
- 'make' => 'Make',
- 'author' => 'Artist',
- 'comment' => 'PNGFileComment',
+ 'artist' => 'Artist',
+ 'model' => 'Model',
+ 'make' => 'Make',
+ 'author' => 'Artist',
+ 'comment' => 'PNGFileComment',
'description' => 'ImageDescription',
- 'title' => 'ObjectName',
- 'copyright' => 'Copyright',
+ 'title' => 'ObjectName',
+ 'copyright' => 'Copyright',
# Source as in original device used to make image
# not as in who gave you the image
- 'source' => 'Model',
- 'software' => 'Software',
- 'disclaimer' => 'Disclaimer',
- 'warning' => 'ContentWarning',
- 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
- 'label' => 'Label',
+ 'source' => 'Model',
+ 'software' => 'Software',
+ 'disclaimer' => 'Disclaimer',
+ 'warning' => 'ContentWarning',
+ 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
+ 'label' => 'Label',
'creation time' => 'DateTimeDigitized',
/* Other potentially useful things - Document */
);
@@ -90,7 +95,7 @@ class PNGMetadataExtractor {
// Check for the PNG header
$buf = fread( $fh, 8 );
- if ( $buf != self::$png_sig ) {
+ if ( $buf != self::$pngSig ) {
throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
}
@@ -181,9 +186,9 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$items[1] = strtolower( $items[1] );
- if ( !isset( self::$text_chunks[$items[1]] ) ) {
+ if ( !isset( self::$textChunks[$items[1]] ) ) {
// Only extract textual chunks on our list.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -203,26 +208,23 @@ class PNGMetadataExtractor {
if ( $items[5] === false ) {
// decompression failed
wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] . "\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
-
} else {
wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
. " or potentially invalid compression method\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
}
- $finalKeyword = self::$text_chunks[$items[1]];
+ $finalKeyword = self::$textChunks[$items[1]];
$text[$finalKeyword][$items[3]] = $items[5];
$text[$finalKeyword]['_type'] = 'lang';
-
} else {
// Error reading iTXt chunk
throw new Exception( __METHOD__ . ": Read error on iTXt chunk" );
}
-
} elseif ( $chunk_type == 'tEXt' ) {
$buf = self::read( $fh, $chunk_size );
@@ -238,9 +240,9 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$keyword = strtolower( $keyword );
- if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ if ( !isset( self::$textChunks[$keyword] ) ) {
// Don't recognize chunk, so skip.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
wfSuppressWarnings();
@@ -251,10 +253,9 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[$keyword];
+ $finalKeyword = self::$textChunks[$keyword];
$text[$finalKeyword]['x-default'] = $content;
$text[$finalKeyword]['_type'] = 'lang';
-
} elseif ( $chunk_type == 'zTXt' ) {
if ( function_exists( 'gzuncompress' ) ) {
$buf = self::read( $fh, $chunk_size );
@@ -271,16 +272,16 @@ class PNGMetadataExtractor {
// Theoretically should be case-sensitive, but in practise...
$keyword = strtolower( $keyword );
- if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ if ( !isset( self::$textChunks[$keyword] ) ) {
// Don't recognize chunk, so skip.
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
$compression = substr( $postKeyword, 0, 1 );
$content = substr( $postKeyword, 1 );
if ( $compression !== "\x00" ) {
wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping.\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -291,7 +292,7 @@ class PNGMetadataExtractor {
if ( $content === false ) {
// decompression failed
wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword . "\n" );
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
continue;
}
@@ -303,10 +304,9 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[$keyword];
+ $finalKeyword = self::$textChunks[$keyword];
$text[$finalKeyword]['x-default'] = $content;
$text[$finalKeyword]['_type'] = 'lang';
-
} else {
wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping.\n" );
fseek( $fh, $chunk_size, SEEK_CUR );
@@ -332,7 +332,6 @@ class PNGMetadataExtractor {
if ( $exifTime ) {
$text['DateTime'] = $exifTime;
}
-
} elseif ( $chunk_type == 'pHYs' ) {
// how big pixels are (dots per meter).
if ( $chunk_size !== 9 ) {
@@ -359,13 +358,12 @@ class PNGMetadataExtractor {
// 3 = dots per cm (from Exif).
}
}
-
} elseif ( $chunk_type == "IEND" ) {
break;
} else {
fseek( $fh, $chunk_size, SEEK_CUR );
}
- fseek( $fh, self::$CRC_size, SEEK_CUR );
+ fseek( $fh, self::$crcSize, SEEK_CUR );
}
fclose( $fh );
@@ -399,6 +397,7 @@ class PNGMetadataExtractor {
}
}
}
+
return array(
'frameCount' => $frameCount,
'loopCount' => $loopCount,
@@ -407,21 +406,22 @@ class PNGMetadataExtractor {
'bitDepth' => $bitDepth,
'colorType' => $colorType,
);
-
}
+
/**
* Read a chunk, checking to make sure its not too big.
*
- * @param $fh resource The file handle
- * @param $size Integer size in bytes.
- * @throws Exception if too big.
- * @return String The chunk.
+ * @param resource $fh The file handle
+ * @param int $size Size in bytes.
+ * @throws Exception If too big
+ * @return string The chunk.
*/
private static function read( $fh, $size ) {
if ( $size > self::MAX_CHUNK_SIZE ) {
throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
}
+
return fread( $fh, $size );
}
}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index 72a9696c..74e5e048 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -29,10 +29,22 @@
class SvgHandler extends ImageHandler {
const SVG_METADATA_VERSION = 2;
+ /** @var array A list of metadata tags that can be converted
+ * to the commonly used exif tags. This allows messages
+ * to be reused, and consistent tag names for {{#formatmetadata:..}}
+ */
+ private static $metaConversion = array(
+ 'originalwidth' => 'ImageWidth',
+ 'originalheight' => 'ImageLength',
+ 'description' => 'ImageDescription',
+ 'title' => 'ObjectName',
+ );
+
function isEnabled() {
global $wgSVGConverters, $wgSVGConverter;
if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
+
return false;
} else {
return true;
@@ -48,11 +60,11 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $file File
+ * @param File $file
* @return bool
*/
function isAnimatedImage( $file ) {
- # TODO: detect animated SVGs
+ # @todo Detect animated SVGs
$metadata = $file->getMetadata();
if ( $metadata ) {
$metadata = $this->unpackMetadata( $metadata );
@@ -60,19 +72,60 @@ class SvgHandler extends ImageHandler {
return $metadata['animated'];
}
}
+
return false;
}
/**
+ * Which languages (systemLanguage attribute) is supported.
+ *
+ * @note This list is not guaranteed to be exhaustive.
+ * To avoid OOM errors, we only look at first bit of a file.
+ * Thus all languages on this list are present in the file,
+ * but its possible for the file to have a language not on
+ * this list.
+ *
+ * @param File $file
+ * @return array Array of language codes, or empty if no language switching supported.
+ */
+ public function getAvailableLanguages( File $file ) {
+ $metadata = $file->getMetadata();
+ $langList = array();
+ if ( $metadata ) {
+ $metadata = $this->unpackMetadata( $metadata );
+ if ( isset( $metadata['translations'] ) ) {
+ foreach ( $metadata['translations'] as $lang => $langType ) {
+ if ( $langType === SvgReader::LANG_FULL_MATCH ) {
+ $langList[] = $lang;
+ }
+ }
+ }
+ }
+ return $langList;
+ }
+
+ /**
+ * What language to render file in if none selected.
+ *
+ * @param File $file
+ * @return string Language code.
+ */
+ public function getDefaultRenderLanguage( File $file ) {
+ return 'en';
+ }
+
+ /**
* We do not support making animated svg thumbnails
+ * @param File $file
+ * @return bool
*/
- function canAnimateThumb( $file ) {
+ function canAnimateThumbnail( $file ) {
return false;
}
/**
- * @param $image File
- * @param $params
+ * @param File $image
+ * @param array $params
* @return bool
*/
function normaliseParams( $image, &$params ) {
@@ -96,14 +149,15 @@ class SvgHandler extends ImageHandler {
$params['physicalHeight'] = $wgSVGMaxSize;
}
}
+
return true;
}
/**
- * @param $image File
- * @param $dstPath
- * @param $dstUrl
- * @param $params
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
* @param int $flags
* @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
*/
@@ -115,7 +169,7 @@ class SvgHandler extends ImageHandler {
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
- $lang = isset( $params['lang'] ) ? $params['lang'] : 'en';
+ $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
@@ -124,6 +178,7 @@ class SvgHandler extends ImageHandler {
$metadata = $this->unpackMetadata( $image->getMetadata() );
if ( isset( $metadata['error'] ) ) { // sanity check
$err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
}
@@ -133,7 +188,40 @@ class SvgHandler extends ImageHandler {
}
$srcPath = $image->getLocalRefPath();
- $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
+ if ( $srcPath === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
+ // Make a temp dir with a symlink to the local copy in it.
+ // This plays well with rsvg-convert policy for external entities.
+ // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
+ $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
+ $lnPath = "$tmpDir/" . basename( $srcPath );
+ $ok = mkdir( $tmpDir, 0771 ) && symlink( $srcPath, $lnPath );
+ $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
+ wfSuppressWarnings();
+ unlink( $lnPath );
+ rmdir( $tmpDir );
+ wfRestoreWarnings();
+ } );
+ if ( !$ok ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not link %s to %s',
+ wfHostname(), $lnPath, $srcPath ) );
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], $params['height'],
+ wfMessage( 'thumbnail-temp-create' )->text()
+ );
+ }
+
+ $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
if ( $status === true ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
} else {
@@ -148,7 +236,7 @@ class SvgHandler extends ImageHandler {
* @param string $dstPath
* @param string $width
* @param string $height
- * @param string $lang Language code of the language to render the SVG in
+ * @param bool|string $lang Language code of the language to render the SVG in
* @throws MWException
* @return bool|MediaTransformError
*/
@@ -192,10 +280,10 @@ class SvgHandler extends ImageHandler {
}
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim( $err ), $cmd ) );
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
}
+
return true;
}
@@ -214,9 +302,9 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $file File
- * @param $path
- * @param bool $metadata
+ * @param File $file
+ * @param string $path Unused
+ * @param bool|array $metadata
* @return array
*/
function getImageSize( $file, $path, $metadata = false ) {
@@ -227,7 +315,7 @@ class SvgHandler extends ImageHandler {
if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
return array( $metadata['width'], $metadata['height'], 'SVG',
- "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
+ "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
} else { // error
return array( 0, 0, 'SVG', "width=\"0\" height=\"0\"" );
}
@@ -243,7 +331,7 @@ class SvgHandler extends ImageHandler {
* a "nominal" resolution, and not a fixed one,
* as well as so animation can be denoted.
*
- * @param $file File
+ * @param File $file
* @return string
*/
function getLongDesc( $file ) {
@@ -267,6 +355,11 @@ class SvgHandler extends ImageHandler {
return $msg->parse();
}
+ /**
+ * @param File $file
+ * @param string $filename
+ * @return string Serialised metadata
+ */
function getMetadata( $file, $filename ) {
$metadata = array( 'version' => self::SVG_METADATA_VERSION );
try {
@@ -279,6 +372,7 @@ class SvgHandler extends ImageHandler {
);
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
}
+
return serialize( $metadata );
}
@@ -306,16 +400,18 @@ class SvgHandler extends ImageHandler {
// Old but compatible
return self::METADATA_COMPATIBLE;
}
+
return self::METADATA_GOOD;
}
- function visibleMetadataFields() {
+ protected function visibleMetadataFields() {
$fields = array( 'objectname', 'imagedescription' );
+
return $fields;
}
/**
- * @param $file File
+ * @param File $file
* @return array|bool
*/
function formatMetadata( $file ) {
@@ -332,7 +428,7 @@ class SvgHandler extends ImageHandler {
return false;
}
- /* TODO: add a formatter
+ /* @todo Add a formatter
$format = new FormatSVG( $metadata );
$formatted = $format->getFormattedData();
*/
@@ -340,19 +436,11 @@ class SvgHandler extends ImageHandler {
// Sort fields into visible and collapsed
$visibleFields = $this->visibleMetadataFields();
- // Rename fields to be compatible with exif, so that
- // the labels for these fields work and reuse existing messages.
- $conversion = array(
- 'originalwidth' => 'imagewidth',
- 'originalheight' => 'imagelength',
- 'description' => 'imagedescription',
- 'title' => 'objectname',
- );
$showMeta = false;
foreach ( $metadata as $name => $value ) {
$tag = strtolower( $name );
- if ( isset( $conversion[$tag] ) ) {
- $tag = $conversion[$tag];
+ if ( isset( self::$metaConversion[$tag] ) ) {
+ $tag = strtolower( self::$metaConversion[$tag] );
} else {
// Do not output other metadata not in list
continue;
@@ -365,13 +453,13 @@ class SvgHandler extends ImageHandler {
$value
);
}
+
return $showMeta ? $result : false;
}
-
/**
* @param string $name Parameter name
- * @param $string $value Parameter value
+ * @param mixed $value Parameter value
* @return bool Validity
*/
function validateParam( $name, $value ) {
@@ -380,18 +468,21 @@ class SvgHandler extends ImageHandler {
return ( $value > 0 );
} elseif ( $name == 'lang' ) {
// Validate $code
- if ( !Language::isValidBuiltinCode( $value ) ) {
+ if ( $value === '' || !Language::isValidBuiltinCode( $value ) ) {
wfDebug( "Invalid user language code\n" );
+
return false;
}
+
return true;
}
+
// Only lang, width and height are acceptable keys
return false;
}
/**
- * @param array $params name=>value pairs of parameters
+ * @param array $params Name=>value pairs of parameters
* @return string Filename to use
*/
function makeParamString( $params ) {
@@ -403,6 +494,7 @@ class SvgHandler extends ImageHandler {
if ( !isset( $params['width'] ) ) {
return false;
}
+
return "$lang{$params['width']}px";
}
@@ -422,13 +514,41 @@ class SvgHandler extends ImageHandler {
}
/**
- * @param $params
+ * @param array $params
* @return array
*/
function getScriptParams( $params ) {
- return array(
- 'width' => $params['width'],
- 'lang' => $params['lang'],
- );
+ $scriptParams = array( 'width' => $params['width'] );
+ if ( isset( $params['lang'] ) ) {
+ $scriptParams['lang'] = $params['lang'];
+ }
+
+ return $scriptParams;
+ }
+
+ public function getCommonMetaArray( File $file ) {
+ $metadata = $file->getMetadata();
+ if ( !$metadata ) {
+ return array();
+ }
+ $metadata = $this->unpackMetadata( $metadata );
+ if ( !$metadata || isset( $metadata['error'] ) ) {
+ return array();
+ }
+ $stdMetadata = array();
+ foreach ( $metadata as $name => $value ) {
+ $tag = strtolower( $name );
+ if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
+ // Skip these. In the exif metadata stuff, it is assumed these
+ // are measured in px, which is not the case here.
+ continue;
+ }
+ if ( isset( self::$metaConversion[$tag] ) ) {
+ $tag = self::$metaConversion[$tag];
+ $stdMetadata[$tag] = $value;
+ }
+ }
+
+ return $stdMetadata;
}
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 2e33bb98..2a1091d8 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -31,6 +31,7 @@
class SVGMetadataExtractor {
static function getMetadata( $filename ) {
$svg = new SVGReader( $filename );
+
return $svg->getMetadata();
}
}
@@ -42,10 +43,19 @@ class SVGReader {
const DEFAULT_WIDTH = 512;
const DEFAULT_HEIGHT = 512;
const NS_SVG = 'http://www.w3.org/2000/svg';
+ const LANG_PREFIX_MATCH = 1;
+ const LANG_FULL_MATCH = 2;
+ /** @var null|XMLReader */
private $reader = null;
+
+ /** @var bool */
private $mDebug = false;
- private $metadata = Array();
+
+ /** @var array */
+ private $metadata = array();
+ private $languages = array();
+ private $languagePrefixes = array();
/**
* Constructor
@@ -113,7 +123,7 @@ class SVGReader {
}
/**
- * @return Array with the known metadata
+ * @return array Array with the known metadata
*/
public function getMetadata() {
return $this->metadata;
@@ -148,7 +158,9 @@ class SVGReader {
$this->debug( "$tag" );
- if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT && $this->reader->depth <= $exitDepth ) {
+ if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT
+ && $this->reader->depth <= $exitDepth
+ ) {
break;
} elseif ( $isSVG && $tag == 'title' ) {
$this->readField( $tag, 'title' );
@@ -164,10 +176,8 @@ class SVGReader {
} elseif ( $tag !== '#text' ) {
$this->debug( "Unhandled top-level XML tag $tag" );
- if ( !isset( $this->metadata['animated'] ) ) {
- // Recurse into children of current tag, looking for animation.
- $this->animateFilter( $tag );
- }
+ // Recurse into children of current tag, looking for animation and languages.
+ $this->animateFilterAndLang( $tag );
}
// Goto next element, which is sibling of current (Skip children).
@@ -176,14 +186,16 @@ class SVGReader {
$this->reader->close();
+ $this->metadata['translations'] = $this->languages + $this->languagePrefixes;
+
return true;
}
/**
* Read a textelement from an element
*
- * @param string $name of the element that we are reading from
- * @param string $metafield that we will fill with the result
+ * @param string $name Name of the element that we are reading from
+ * @param string $metafield Field that we will fill with the result
*/
private function readField( $name, $metafield = null ) {
$this->debug( "Read field $metafield" );
@@ -192,7 +204,10 @@ class SVGReader {
}
$keepReading = $this->reader->read();
while ( $keepReading ) {
- if ( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ if ( $this->reader->localName == $name
+ && $this->reader->namespaceURI == self::NS_SVG
+ && $this->reader->nodeType == XmlReader::END_ELEMENT
+ ) {
break;
} elseif ( $this->reader->nodeType == XmlReader::TEXT ) {
$this->metadata[$metafield] = trim( $this->reader->value );
@@ -204,7 +219,7 @@ class SVGReader {
/**
* Read an XML snippet from an element
*
- * @param string $metafield that we will fill with the result
+ * @param string $metafield Field that we will fill with the result
* @throws MWException
*/
private function readXml( $metafield = null ) {
@@ -212,21 +227,24 @@ class SVGReader {
if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
- // TODO: find and store type of xml snippet. metadata['metadataType'] = "rdf"
+ // @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf"
if ( method_exists( $this->reader, 'readInnerXML' ) ) {
$this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
} else {
- throw new MWException( "The PHP XMLReader extension does not come with readInnerXML() method. Your libxml is probably out of date (need 2.6.20 or later)." );
+ throw new MWException( "The PHP XMLReader extension does not come " .
+ "with readInnerXML() method. Your libxml is probably out of " .
+ "date (need 2.6.20 or later)." );
}
$this->reader->next();
}
/**
- * Filter all children, looking for animate elements
+ * Filter all children, looking for animated elements.
+ * Also get a list of languages that can be targeted.
*
- * @param string $name of the element that we are reading from
+ * @param string $name Name of the element that we are reading from
*/
- private function animateFilter( $name ) {
+ private function animateFilterAndLang( $name ) {
$this->debug( "animate filter for tag $name" );
if ( $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
@@ -238,9 +256,38 @@ class SVGReader {
$keepReading = $this->reader->read();
while ( $keepReading ) {
if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
- && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ && $this->reader->nodeType == XmlReader::END_ELEMENT
+ ) {
break;
- } elseif ( $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::ELEMENT ) {
+ } elseif ( $this->reader->namespaceURI == self::NS_SVG
+ && $this->reader->nodeType == XmlReader::ELEMENT
+ ) {
+
+ $sysLang = $this->reader->getAttribute( 'systemLanguage' );
+ if ( !is_null( $sysLang ) && $sysLang !== '' ) {
+ // See http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
+ $langList = explode( ',', $sysLang );
+ foreach ( $langList as $langItem ) {
+ $langItem = trim( $langItem );
+ if ( Language::isWellFormedLanguageTag( $langItem ) ) {
+ $this->languages[$langItem] = self::LANG_FULL_MATCH;
+ }
+ // Note, the standard says that any prefix should work,
+ // here we do only the initial prefix, since that will catch
+ // 99% of cases, and we are going to compare against fallbacks.
+ // This differs mildly from how the spec says languages should be
+ // handled, however it matches better how the MediaWiki language
+ // preference is generally handled.
+ $dash = strpos( $langItem, '-' );
+ // Intentionally checking both !false and > 0 at the same time.
+ if ( $dash ) {
+ $itemPrefix = substr( $langItem, 0, $dash );
+ if ( Language::isWellFormedLanguageTag( $itemPrefix ) ) {
+ $this->languagePrefixes[$itemPrefix] = self::LANG_PREFIX_MATCH;
+ }
+ }
+ }
+ }
switch ( $this->reader->localName ) {
case 'script':
// Normally we disallow files with
@@ -261,6 +308,7 @@ class SVGReader {
}
}
+ // @todo FIXME: Unused, remove?
private function throwXmlError( $err ) {
$this->debug( "FAILURE: $err" );
wfDebug( "SVGReader XML error: $err\n" );
@@ -272,10 +320,12 @@ class SVGReader {
}
}
+ // @todo FIXME: Unused, remove?
private function warn( $data ) {
wfDebug( "SVGReader: $data\n" );
}
+ // @todo FIXME: Unused, remove?
private function notice( $data ) {
wfDebug( "SVGReader WARN: $data\n" );
}
@@ -333,8 +383,8 @@ class SVGReader {
* http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
*
* @param string $length CSS/SVG length.
- * @param $viewportSize: Float optional scale for percentage units...
- * @return float: length in pixels
+ * @param float|int $viewportSize Optional scale for percentage units...
+ * @return float Length in pixels
*/
static function scaleSVGUnit( $length, $viewportSize = 512 ) {
static $unitLength = array(
@@ -347,7 +397,7 @@ class SVGReader {
'em' => 16.0, // fake it?
'ex' => 12.0, // fake it?
'' => 1.0, // "User units" pixels by default
- );
+ );
$matches = array();
if ( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
$length = floatval( $matches[1] );
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index 55acb120..bea6cab3 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -27,6 +27,7 @@
* @ingroup Media
*/
class TiffHandler extends ExifBitmapHandler {
+ const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
/**
* Conversion to PNG for inline display can be disabled here...
@@ -36,12 +37,12 @@ class TiffHandler extends ExifBitmapHandler {
* InstantCommons will have thumbnails managed from the remote instance,
* so we can skip this check.
*
- * @param $file
- *
+ * @param File $file
* @return bool
*/
function canRender( $file ) {
global $wgTiffThumbnailType;
+
return (bool)$wgTiffThumbnailType
|| $file->getRepo() instanceof ForeignAPIRepo;
}
@@ -50,8 +51,7 @@ class TiffHandler extends ExifBitmapHandler {
* Browsers don't support TIFF inline generally...
* For inline display, we need to convert to PNG.
*
- * @param $file
- *
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -59,13 +59,14 @@ class TiffHandler extends ExifBitmapHandler {
}
/**
- * @param $ext
- * @param $mime
- * @param $params
+ * @param string $ext
+ * @param string $mime
+ * @param array $params
* @return bool
*/
function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
+
return $wgTiffThumbnailType;
}
@@ -85,16 +86,21 @@ class TiffHandler extends ExifBitmapHandler {
throw new MWException( 'Metadata array is not an array' );
}
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
return serialize( $meta );
- }
- catch ( MWException $e ) {
+ } catch ( MWException $e ) {
// BitmapMetadataHandler throws an exception in certain exceptional
// cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
return ExifBitmapHandler::BROKEN_FILE;
}
} else {
return '';
}
}
+
+ public function isExpensiveToThumbnail( $file ) {
+ return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+ }
}
diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php
new file mode 100644
index 00000000..3e3be3d1
--- /dev/null
+++ b/includes/media/TransformationalImageHandler.php
@@ -0,0 +1,593 @@
+<?php
+/**
+ * Base class for handlers which require transforming images in a
+ * similar way as BitmapHandler does.
+ *
+ * This was split from BitmapHandler on the basis that some extensions
+ * might want to work in a similar way to BitmapHandler, but for
+ * different formats.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for images that need to be transformed
+ *
+ * @since 1.24
+ * @ingroup Media
+ */
+abstract class TransformationalImageHandler extends ImageHandler {
+ /**
+ * @param File $image
+ * @param array $params Transform parameters. Entries with the keys 'width'
+ * and 'height' are the respective screen width and height, while the keys
+ * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
+ * @return bool
+ */
+ function normaliseParams( $image, &$params ) {
+ if ( !parent::normaliseParams( $image, $params ) ) {
+ return false;
+ }
+
+ # Obtain the source, pre-rotation dimensions
+ $srcWidth = $image->getWidth( $params['page'] );
+ $srcHeight = $image->getHeight( $params['page'] );
+
+ # Don't make an image bigger than the source
+ if ( $params['physicalWidth'] >= $srcWidth ) {
+ $params['physicalWidth'] = $srcWidth;
+ $params['physicalHeight'] = $srcHeight;
+
+ # Skip scaling limit checks if no scaling is required
+ # due to requested size being bigger than source.
+ if ( !$image->mustRender() ) {
+ return true;
+ }
+ }
+
+ # Check if the file is smaller than the maximum image area for thumbnailing
+ # For historical reasons, hook starts with BitmapHandler
+ $checkImageAreaHookResult = null;
+ wfRunHooks(
+ 'BitmapHandlerCheckImageArea',
+ array( $image, &$params, &$checkImageAreaHookResult )
+ );
+
+ if ( is_null( $checkImageAreaHookResult ) ) {
+ global $wgMaxImageArea;
+
+ if ( $srcWidth * $srcHeight > $wgMaxImageArea
+ && !( $image->getMimeType() == 'image/jpeg'
+ && $this->getScalerType( false, false ) == 'im' )
+ ) {
+ # Only ImageMagick can efficiently downsize jpg images without loading
+ # the entire file in memory
+ return false;
+ }
+ } else {
+ return $checkImageAreaHookResult;
+ }
+
+ return true;
+ }
+
+ /**
+ * Extracts the width/height if the image will be scaled before rotating
+ *
+ * This will match the physical size/aspect ratio of the original image
+ * prior to application of the rotation -- so for a portrait image that's
+ * stored as raw landscape with 90-degress rotation, the resulting size
+ * will be wider than it is tall.
+ *
+ * @param array $params Parameters as returned by normaliseParams
+ * @param int $rotation The rotation angle that will be applied
+ * @return array ($width, $height) array
+ */
+ public function extractPreRotationDimensions( $params, $rotation ) {
+ if ( $rotation == 90 || $rotation == 270 ) {
+ # We'll resize before rotation, so swap the dimensions again
+ $width = $params['physicalHeight'];
+ $height = $params['physicalWidth'];
+ } else {
+ $width = $params['physicalWidth'];
+ $height = $params['physicalHeight'];
+ }
+
+ return array( $width, $height );
+ }
+
+ /**
+ * Create a thumbnail.
+ *
+ * This sets up various parameters, and then calls a helper method
+ * based on $this->getScalerType in order to scale the image.
+ *
+ * @param File $image
+ * @param string $dstPath
+ * @param string $dstUrl
+ * @param array $params
+ * @param int $flags
+ * @return MediaTransformError|ThumbnailImage|TransformParameterError
+ */
+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return new TransformParameterError( $params );
+ }
+
+ # Create a parameter array to pass to the scaler
+ $scalerParams = array(
+ # The size to which the image will be resized
+ 'physicalWidth' => $params['physicalWidth'],
+ 'physicalHeight' => $params['physicalHeight'],
+ 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
+ # The size of the image on the page
+ 'clientWidth' => $params['width'],
+ 'clientHeight' => $params['height'],
+ # Comment as will be added to the Exif of the thumbnail
+ 'comment' => isset( $params['descriptionUrl'] )
+ ? "File source: {$params['descriptionUrl']}"
+ : '',
+ # Properties of the original image
+ 'srcWidth' => $image->getWidth(),
+ 'srcHeight' => $image->getHeight(),
+ 'mimeType' => $image->getMimeType(),
+ 'dstPath' => $dstPath,
+ 'dstUrl' => $dstUrl,
+ );
+
+ if ( isset( $params['quality'] ) && $params['quality'] === 'low' ) {
+ $scalerParams['quality'] = 30;
+ }
+
+ // For subclasses that might be paged.
+ if ( $image->isMultipage() && isset( $params['page'] ) ) {
+ $scalerParams['page'] = intval( $params['page'] );
+ }
+
+ # Determine scaler type
+ $scaler = $this->getScalerType( $dstPath );
+
+ if ( is_array( $scaler ) ) {
+ $scalerName = get_class( $scaler[0] );
+ } else {
+ $scalerName = $scaler;
+ }
+
+ wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " .
+ "thumbnail at $dstPath using scaler $scalerName\n" );
+
+ if ( !$image->mustRender() &&
+ $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
+ && $scalerParams['physicalHeight'] == $scalerParams['srcHeight']
+ && !isset( $scalerParams['quality'] )
+ ) {
+
+ # normaliseParams (or the user) wants us to return the unscaled image
+ wfDebug( __METHOD__ . ": returning unscaled image\n" );
+
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ if ( $scaler == 'client' ) {
+ # Client-side image scaling, use the source URL
+ # Using the destination URL in a TRANSFORM_LATER request would be incorrect
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ if ( $flags & self::TRANSFORM_LATER ) {
+ wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
+ $newParams = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+ if ( isset( $params['quality'] ) ) {
+ $newParams['quality'] = $params['quality'];
+ }
+ if ( isset( $params['page'] ) && $params['page'] ) {
+ $newParams['page'] = $params['page'];
+ }
+ return new ThumbnailImage( $image, $dstUrl, false, $newParams );
+ }
+
+ # Try to make a target path for the thumbnail
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+ wfDebug( __METHOD__ . ": Unable to create thumbnail destination " .
+ "directory, falling back to client scaling\n" );
+
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ # Transform functions and binaries need a FS source file
+ $thumbnailSource = $this->getThumbnailSource( $image, $params );
+
+ $scalerParams['srcPath'] = $thumbnailSource['path'];
+ $scalerParams['srcWidth'] = $thumbnailSource['width'];
+ $scalerParams['srcHeight'] = $thumbnailSource['height'];
+
+ if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+ wfHostname(), $image->getName() ) );
+
+ return new MediaTransformError( 'thumbnail_error',
+ $scalerParams['clientWidth'], $scalerParams['clientHeight'],
+ wfMessage( 'filemissing' )->text()
+ );
+ }
+
+ # Try a hook. Called "Bitmap" for historical reasons.
+ /** @var $mto MediaTransformOutput */
+ $mto = null;
+ wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
+ if ( !is_null( $mto ) ) {
+ wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
+ $scaler = 'hookaborted';
+ }
+
+ // $scaler will return a MediaTransformError on failure, or false on success.
+ // If the scaler is succesful, it will have created a thumbnail at the destination
+ // path.
+ if ( is_array( $scaler ) && is_callable( $scaler ) ) {
+ // Allow subclasses to specify their own rendering methods.
+ $err = call_user_func( $scaler, $image, $scalerParams );
+ } else {
+ switch ( $scaler ) {
+ case 'hookaborted':
+ # Handled by the hook above
+ $err = $mto->isError() ? $mto : false;
+ break;
+ case 'im':
+ $err = $this->transformImageMagick( $image, $scalerParams );
+ break;
+ case 'custom':
+ $err = $this->transformCustom( $image, $scalerParams );
+ break;
+ case 'imext':
+ $err = $this->transformImageMagickExt( $image, $scalerParams );
+ break;
+ case 'gd':
+ default:
+ $err = $this->transformGd( $image, $scalerParams );
+ break;
+ }
+ }
+
+ # Remove the file if a zero-byte thumbnail was created, or if there was an error
+ $removed = $this->removeBadFile( $dstPath, (bool)$err );
+ if ( $err ) {
+ # transform returned MediaTransforError
+ return $err;
+ } elseif ( $removed ) {
+ # Thumbnail was zero-byte and had to be removed
+ return new MediaTransformError( 'thumbnail_error',
+ $scalerParams['clientWidth'], $scalerParams['clientHeight'],
+ wfMessage( 'unknown-error' )->text()
+ );
+ } elseif ( $mto ) {
+ return $mto;
+ } else {
+ $newParams = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+ if ( isset( $params['quality'] ) ) {
+ $newParams['quality'] = $params['quality'];
+ }
+ if ( isset( $params['page'] ) && $params['page'] ) {
+ $newParams['page'] = $params['page'];
+ }
+ return new ThumbnailImage( $image, $dstUrl, $dstPath, $newParams );
+ }
+ }
+
+ /**
+ * Get the source file for the transform
+ *
+ * @param $file File
+ * @param $params Array
+ * @return Array Array with keys width, height and path.
+ */
+ protected function getThumbnailSource( $file, $params ) {
+ return $file->getThumbnailSource( $params );
+ }
+
+ /**
+ * Returns what sort of scaler type should be used.
+ *
+ * Values can be one of client, im, custom, gd, imext, or an array
+ * of object, method-name to call that specific method.
+ *
+ * If specifying a custom scaler command with array( Obj, method ),
+ * the method in question should take 2 parameters, a File object,
+ * and a $scalerParams array with various options (See doTransform
+ * for what is in $scalerParams). On error it should return a
+ * MediaTransformError object. On success it should return false,
+ * and simply make sure the thumbnail file is located at
+ * $scalerParams['dstPath'].
+ *
+ * If there is a problem with the output path, it returns "client"
+ * to do client side scaling.
+ *
+ * @param string $dstPath
+ * @param bool $checkDstPath Check that $dstPath is valid
+ * @return string|Callable One of client, im, custom, gd, imext, or a Callable array.
+ */
+ abstract protected function getScalerType( $dstPath, $checkDstPath = true );
+
+ /**
+ * Get a ThumbnailImage that respresents an image that will be scaled
+ * client side
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $scalerParams Array with scaler params
+ * @return ThumbnailImage
+ *
+ * @todo FIXME: No rotation support
+ */
+ protected function getClientScalingThumbnailImage( $image, $scalerParams ) {
+ $params = array(
+ 'width' => $scalerParams['clientWidth'],
+ 'height' => $scalerParams['clientHeight']
+ );
+
+ return new ThumbnailImage( $image, $image->getURL(), null, $params );
+ }
+
+ /**
+ * Transform an image using ImageMagick
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformImageMagick( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Transform an image using the Imagick PHP extension
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformImageMagickExt( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Transform an image using a custom command
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformCustom( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Get a MediaTransformError with error 'thumbnail_error'
+ *
+ * @param array $params Parameter array as passed to the transform* functions
+ * @param string $errMsg Error message
+ * @return MediaTransformError
+ */
+ public function getMediaTransformError( $params, $errMsg ) {
+ return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
+ $params['clientHeight'], $errMsg );
+ }
+
+ /**
+ * Transform an image using the built in GD library
+ *
+ * This is a stub method. The real method is in BitmapHander.
+ *
+ * @param File $image File associated with this thumbnail
+ * @param array $params Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
+ */
+ protected function transformGd( $image, $params ) {
+ return $this->getMediaTransformError( $params, "Unimplemented" );
+ }
+
+ /**
+ * Escape a string for ImageMagick's property input (e.g. -set -comment)
+ * See InterpretImageProperties() in magick/property.c
+ * @param string $s
+ * @return string
+ */
+ function escapeMagickProperty( $s ) {
+ // Double the backslashes
+ $s = str_replace( '\\', '\\\\', $s );
+ // Double the percents
+ $s = str_replace( '%', '%%', $s );
+ // Escape initial - or @
+ if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
+ $s = '\\' . $s;
+ }
+
+ return $s;
+ }
+
+ /**
+ * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
+ * and GetPathComponent() in magick/utility.c.
+ *
+ * This won't work with an initial ~ or @, so input files should be prefixed
+ * with the directory name.
+ *
+ * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
+ * it's broken in a way that doesn't involve trying to convert every file
+ * in a directory, so we're better off escaping and waiting for the bugfix
+ * to filter down to users.
+ *
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
+ * @return string
+ */
+ function escapeMagickInput( $path, $scene = false ) {
+ # Die on initial metacharacters (caller should prepend path)
+ $firstChar = substr( $path, 0, 1 );
+ if ( $firstChar === '~' || $firstChar === '@' ) {
+ throw new MWException( __METHOD__ . ': cannot escape this path name' );
+ }
+
+ # Escape glob chars
+ $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
+
+ return $this->escapeMagickPath( $path, $scene );
+ }
+
+ /**
+ * Escape a string for ImageMagick's output filename. See
+ * InterpretImageFilename() in magick/image.c.
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @return string
+ */
+ function escapeMagickOutput( $path, $scene = false ) {
+ $path = str_replace( '%', '%%', $path );
+
+ return $this->escapeMagickPath( $path, $scene );
+ }
+
+ /**
+ * Armour a string against ImageMagick's GetPathComponent(). This is a
+ * helper function for escapeMagickInput() and escapeMagickOutput().
+ *
+ * @param string $path The file path
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
+ * @return string
+ */
+ protected function escapeMagickPath( $path, $scene = false ) {
+ # Die on format specifiers (other than drive letters). The regex is
+ # meant to match all the formats you get from "convert -list format"
+ if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
+ if ( wfIsWindows() && is_dir( $m[0] ) ) {
+ // OK, it's a drive letter
+ // ImageMagick has a similar exception, see IsMagickConflict()
+ } else {
+ throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
+ }
+ }
+
+ # If there are square brackets, add a do-nothing scene specification
+ # to force a literal interpretation
+ if ( $scene === false ) {
+ if ( strpos( $path, '[' ) !== false ) {
+ $path .= '[0--1]';
+ }
+ } else {
+ $path .= "[$scene]";
+ }
+
+ return $path;
+ }
+
+ /**
+ * Retrieve the version of the installed ImageMagick
+ * You can use PHPs version_compare() to use this value
+ * Value is cached for one hour.
+ * @return string Representing the IM version.
+ */
+ protected function getMagickVersion() {
+ global $wgMemc;
+
+ $cache = $wgMemc->get( "imagemagick-version" );
+ if ( !$cache ) {
+ global $wgImageMagickConvertCommand;
+ $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
+ wfDebug( __METHOD__ . ": Running convert -version\n" );
+ $retval = '';
+ $return = wfShellExec( $cmd, $retval );
+ $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
+ if ( $x != 1 ) {
+ wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
+
+ return null;
+ }
+ $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
+
+ return $matches[1];
+ }
+
+ return $cache;
+ }
+
+ /**
+ * Returns whether the current scaler supports rotation.
+ *
+ * @since 1.24 No longer static
+ * @return bool
+ */
+ public function canRotate() {
+ return false;
+ }
+
+ /**
+ * Should we automatically rotate an image based on exif
+ *
+ * @since 1.24 No longer static
+ * @see $wgEnableAutoRotation
+ * @return bool Whether auto rotation is enabled
+ */
+ public function autoRotateEnabled() {
+ return false;
+ }
+
+ /**
+ * Rotate a thumbnail.
+ *
+ * This is a stub. See BitmapHandler::rotate.
+ *
+ * @param File $file
+ * @param array $params Rotate parameters.
+ * 'rotation' clockwise rotation in degrees, allowed are multiples of 90
+ * @since 1.24 Is non-static. From 1.21 it was static
+ * @return bool
+ */
+ public function rotate( $file, $params ) {
+ return new MediaTransformError( 'thumbnail_error', 0, 0,
+ get_class( $this ) . ' rotation not implemented' );
+ }
+
+ /**
+ * Returns whether the file needs to be rendered. Returns true if the
+ * file requires rotation and we are able to rotate it.
+ *
+ * @param File $file
+ * @return bool
+ */
+ public function mustRender( $file ) {
+ return $this->canRotate() && $this->getRotation( $file ) != 0;
+ }
+}
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
index e77d3842..48b7a47c 100644
--- a/includes/media/XCF.php
+++ b/includes/media/XCF.php
@@ -33,9 +33,8 @@
* @ingroup Media
*/
class XCFHandler extends BitmapHandler {
-
/**
- * @param $file
+ * @param File $file
* @return bool
*/
function mustRender( $file ) {
@@ -45,9 +44,9 @@ class XCFHandler extends BitmapHandler {
/**
* Render files as PNG
*
- * @param $ext
- * @param $mime
- * @param $params
+ * @param string $ext
+ * @param string $mime
+ * @param array $params
* @return array
*/
function getThumbType( $ext, $mime, $params = null ) {
@@ -57,12 +56,33 @@ class XCFHandler extends BitmapHandler {
/**
* Get width and height from the XCF header.
*
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
* @return array
*/
function getImageSize( $image, $filename ) {
- return self::getXCFMetaData( $filename );
+ $header = self::getXCFMetaData( $filename );
+ if ( !$header ) {
+ return false;
+ }
+
+ # Forge a return array containing metadata information just like getimagesize()
+ # See PHP documentation at: http://www.php.net/getimagesize
+ $metadata = array();
+ $metadata[0] = $header['width'];
+ $metadata[1] = $header['height'];
+ $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
+ $metadata[3] = sprintf(
+ 'height="%s" width="%s"', $header['height'], $header['width']
+ );
+ $metadata['mime'] = 'image/x-xcf';
+ $metadata['channels'] = null;
+ $metadata['bits'] = 8; # Always 8-bits per color
+
+ assert( '7 == count($metadata); ' .
+ '# return array must contains 7 elements just like getimagesize() return' );
+
+ return $metadata;
}
/**
@@ -73,7 +93,7 @@ class XCFHandler extends BitmapHandler {
* @author Hashar
*
* @param string $filename Full path to a XCF file
- * @return bool|array metadata array just like PHP getimagesize()
+ * @return bool|array Metadata Array just like PHP getimagesize()
*/
static function getXCFMetaData( $filename ) {
# Decode master structure
@@ -103,12 +123,12 @@ class XCFHandler extends BitmapHandler {
# (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
try {
$header = wfUnpack(
- "A9magic" # A: space padded
- . "/a5version" # a: zero padded
- . "/Nwidth" # \
- . "/Nheight" # N: unsigned long 32bit big endian
- . "/Nbase_type" # /
- , $binaryHeader
+ "A9magic" . # A: space padded
+ "/a5version" . # a: zero padded
+ "/Nwidth" . # \
+ "/Nheight" . # N: unsigned long 32bit big endian
+ "/Nbase_type", # /
+ $binaryHeader
);
} catch ( MWException $mwe ) {
return false;
@@ -117,36 +137,97 @@ class XCFHandler extends BitmapHandler {
# Check values
if ( $header['magic'] !== 'gimp xcf' ) {
wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
+
return false;
}
# TODO: we might want to check for sane values of width and height
- wfDebug( __METHOD__ . ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
+ wfDebug( __METHOD__ .
+ ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
- # Forge a return array containing metadata information just like getimagesize()
- # See PHP documentation at: http://www.php.net/getimagesize
+ return $header;
+ }
+
+ /**
+ * Store the channel type
+ *
+ * Greyscale files need different command line options.
+ *
+ * @param File $file The image object, or false if there isn't one.
+ * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
+ * @param string $filename The filename
+ * @return string
+ */
+ public function getMetadata( $file, $filename ) {
+ $header = self::getXCFMetadata( $filename );
$metadata = array();
- $metadata[0] = $header['width'];
- $metadata[1] = $header['height'];
- $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
- $metadata[3] = sprintf(
- 'height="%s" width="%s"', $header['height'], $header['width']
- );
- $metadata['mime'] = 'image/x-xcf';
- $metadata['channels'] = null;
- $metadata['bits'] = 8; # Always 8-bits per color
+ if ( $header ) {
+ // Try to be consistent with the names used by PNG files.
+ // Unclear from base media type if it has an alpha layer,
+ // so just assume that it does since it "potentially" could.
+ switch ( $header['base_type'] ) {
+ case 0:
+ $metadata['colorType'] = 'truecolour-alpha';
+ break;
+ case 1:
+ $metadata['colorType'] = 'greyscale-alpha';
+ break;
+ case 2:
+ $metadata['colorType'] = 'index-coloured';
+ break;
+ default:
+ $metadata['colorType'] = 'unknown';
- assert( '7 == count($metadata); # return array must contains 7 elements just like getimagesize() return' );
+ }
+ } else {
+ // Marker to prevent repeated attempted extraction
+ $metadata['error'] = true;
+ }
+ return serialize( $metadata );
+ }
- return $metadata;
+ /**
+ * Should we refresh the metadata
+ *
+ * @param File $file The file object for the file in question
+ * @param string $metadata Serialized metadata
+ * @return bool One of the self::METADATA_(BAD|GOOD|COMPATIBLE) constants
+ */
+ public function isMetadataValid( $file, $metadata ) {
+ if ( !$metadata ) {
+ // Old metadata when we just put an empty string in there
+ return self::METADATA_BAD;
+ } else {
+ return self::METADATA_GOOD;
+ }
}
/**
* Must use "im" for XCF
*
+ * @param string $dstPath
+ * @param bool $checkDstPath
* @return string
*/
- protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ protected function getScalerType( $dstPath, $checkDstPath = true ) {
return "im";
}
+
+ /**
+ * Can we render this file?
+ *
+ * Image magick doesn't support indexed xcf files as of current
+ * writing (as of 6.8.9-3)
+ * @param File $file
+ * @return bool
+ */
+ public function canRender( $file ) {
+ wfSuppressWarnings();
+ $xcfMeta = unserialize( $file->getMetadata() );
+ wfRestoreWarnings();
+ if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) {
+ return false;
+ }
+ return parent::canRender( $file );
+ }
}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 7eb3d19e..cdbd5ab2 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -23,7 +23,7 @@
/**
* Class for reading xmp data containing properties relevant to
- * images, and spitting out an array that FormatExif accepts.
+ * images, and spitting out an array that FormatMetadata accepts.
*
* Note, this is not meant to recognize every possible thing you can
* encode in XMP. It should recognize all the properties we want.
@@ -34,12 +34,12 @@
*
* The public methods one would call in this class are
* - parse( $content )
- * Reads in xmp content.
- * Can potentially be called multiple times with partial data each time.
+ * Reads in xmp content.
+ * Can potentially be called multiple times with partial data each time.
* - parseExtended( $content )
- * Reads XMPExtended blocks (jpeg files only).
+ * Reads XMPExtended blocks (jpeg files only).
* - getResults
- * Outputs a results array.
+ * Outputs a results array.
*
* Note XMP kind of looks like rdf. They are not the same thing - XMP is
* encoded as a specific subset of rdf. This class can read XMP. It cannot
@@ -47,20 +47,38 @@
*
*/
class XMPReader {
+ /** @var array XMP item configuration array */
+ protected $items;
+
+ /** @var array Array to hold the current element (and previous element, and so on) */
+ private $curItem = array();
+
+ /** @var bool|string The structure name when processing nested structures. */
+ private $ancestorStruct = false;
+
+ /** @var bool|string Temporary holder for character data that appears in xmp doc. */
+ private $charContent = false;
+
+ /** @var array Stores the state the xmpreader is in (see MODE_FOO constants) */
+ private $mode = array();
+
+ /** @var array Array to hold results */
+ private $results = array();
+
+ /** @var bool If we're doing a seq or bag. */
+ private $processingArray = false;
- private $curItem = array(); // array to hold the current element (and previous element, and so on)
- private $ancestorStruct = false; // the structure name when processing nested structures.
- private $charContent = false; // temporary holder for character data that appears in xmp doc.
- private $mode = array(); // stores the state the xmpreader is in (see MODE_FOO constants)
- private $results = array(); // array to hold results
- private $processingArray = false; // if we're doing a seq or bag.
- private $itemLang = false; // used for lang alts only
+ /** @var bool|string Used for lang alts only */
+ private $itemLang = false;
+ /** @var resource A resource handle for the XML parser */
private $xmlParser;
+
+ /** @var bool|string Character set like 'UTF-8' */
private $charset = false;
- private $extendedXMPOffset = 0;
- protected $items;
+ /** @var int */
+ private $extendedXMPOffset = 0;
/**
* These are various mode constants.
@@ -105,8 +123,8 @@ class XMPReader {
$this->items = XMPInfo::getItems();
$this->resetXMLParser();
-
}
+
/**
* Main use is if a single item has multiple xmp documents describing it.
* For example in jpeg's with extendedXMP
@@ -141,8 +159,8 @@ class XMPReader {
/** Get the result array. Do some post-processing before returning
* the array, and transform any metadata that is special-cased.
*
- * @return Array array of results as an array of arrays suitable for
- * FormatMetadata::getFormattedData().
+ * @return array Array of results as an array of arrays suitable for
+ * FormatMetadata::getFormattedData().
*/
public function getResults() {
// xmp-special is for metadata that affects how stuff
@@ -155,7 +173,7 @@ class XMPReader {
$data = $this->results;
- wfRunHooks( 'XMPGetResults', Array( &$data ) );
+ wfRunHooks( 'XMPGetResults', array( &$data ) );
if ( isset( $data['xmp-special']['AuthorsPosition'] )
&& is_string( $data['xmp-special']['AuthorsPosition'] )
@@ -237,10 +255,10 @@ class XMPReader {
* debug log, blanks result array and returns false.
*
* @param string $content XMP data
- * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
- * @param $reset Boolean: does xml parser need to be reset. Default false
+ * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
+ * @param bool $reset Does xml parser need to be reset. Default false
* @throws MWException
- * @return Boolean success.
+ * @return bool Success.
*/
public function parse( $content, $allOfIt = true, $reset = false ) {
if ( $reset ) {
@@ -301,8 +319,10 @@ class XMPReader {
} catch ( MWException $e ) {
wfDebugLog( 'XMP', 'XMP parse error: ' . $e );
$this->results = array();
+
return false;
}
+
return true;
}
@@ -311,36 +331,43 @@ class XMPReader {
* @todo In serious need of testing
* @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
* @param string $content XMPExtended block minus the namespace signature
- * @return Boolean If it succeeded.
+ * @return bool If it succeeded.
*/
public function parseExtended( $content ) {
// @todo FIXME: This is untested. Hard to find example files
// or programs that make such files..
$guid = substr( $content, 0, 32 );
if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
- || $this->results['xmp-special']['HasExtendedXMP'] !== $guid ) {
- wfDebugLog( 'XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
+ || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
+ ) {
+ wfDebugLog( 'XMP', __METHOD__ .
+ " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
+
return false;
}
$len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
if ( !$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
wfDebugLog( 'XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.' );
+
return false;
}
- // we're not very robust here. we should accept it in the wrong order. To quote
- // the xmp standard:
- // "A JPEG writer should write the ExtendedXMP marker segments in order, immediately following the
- // StandardXMP. However, the JPEG standard does not require preservation of marker segment order. A
- // robust JPEG reader should tolerate the marker segments in any order."
+ // we're not very robust here. we should accept it in the wrong order.
+ // To quote the XMP standard:
+ // "A JPEG writer should write the ExtendedXMP marker segments in order,
+ // immediately following the StandardXMP. However, the JPEG standard
+ // does not require preservation of marker segment order. A robust JPEG
+ // reader should tolerate the marker segments in any order."
//
- // otoh the probability that an image will have more than 128k of metadata is rather low...
- // so the probability that it will have > 128k, and be in the wrong order is very low...
+ // otoh the probability that an image will have more than 128k of
+ // metadata is rather low... so the probability that it will have
+ // > 128k, and be in the wrong order is very low...
if ( $len['offset'] !== $this->extendedXMPOffset ) {
wfDebugLog( 'XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
. $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
+
return false;
}
@@ -361,6 +388,7 @@ class XMPReader {
}
wfDebugLog( 'XMP', __METHOD__ . 'Parsing a XMPExtended block' );
+
return $this->parse( $actualContent, $atEnd );
}
@@ -376,9 +404,9 @@ class XMPReader {
* <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
* and are processing the 0/10 bit.
*
- * @param $parser XMLParser reference to the xml parser
+ * @param XMLParser $parser XMLParser reference to the xml parser
* @param string $data Character data
- * @throws MWException on invalid data
+ * @throws MWException On invalid data
*/
function char( $parser, $data ) {
@@ -407,7 +435,6 @@ class XMPReader {
} else {
$this->charContent .= $data;
}
-
}
/** When we hit a closing element in MODE_IGNORE
@@ -436,7 +463,7 @@ class XMPReader {
* Or it could be if we hit the end element of a property
* of a compound data structure (like a member of an array).
*
- * @param string $elm namespace, space, and tag name.
+ * @param string $elm Namespace, space, and tag name.
*/
private function endElementModeSimple( $elm ) {
if ( $this->charContent !== false ) {
@@ -453,7 +480,6 @@ class XMPReader {
}
array_shift( $this->curItem );
array_shift( $this->mode );
-
}
/**
@@ -471,7 +497,7 @@ class XMPReader {
*
* This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
*
- * @param string $elm namespace . space . tag name.
+ * @param string $elm Namespace . space . tag name.
* @throws MWException
*/
private function endElementNested( $elm ) {
@@ -482,7 +508,8 @@ class XMPReader {
&& !( $elm === self::NS_RDF . ' Description'
&& $this->mode[0] === self::MODE_STRUCT )
) {
- throw new MWException( "nesting mismatch. got a </$elm> but expected a </" . $this->curItem[0] . '>' );
+ throw new MWException( "nesting mismatch. got a </$elm> but expected a </" .
+ $this->curItem[0] . '>' );
}
// Validate structures.
@@ -499,7 +526,6 @@ class XMPReader {
if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
// This can happen if all the members of the struct failed validation.
wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> has no valid members." );
-
} elseif ( is_callable( $validate ) ) {
$val =& $this->results['xmp-' . $info['map_group']][$finalName];
call_user_func_array( $validate, array( $info, &$val, false ) );
@@ -538,7 +564,7 @@ class XMPReader {
* (For comparison, we call endElementModeSimple when we
* hit the "</rdf:li>")
*
- * @param string $elm namespace . ' ' . element name
+ * @param string $elm Namespace . ' ' . element name
* @throws MWException
*/
private function endElementModeLi( $elm ) {
@@ -552,6 +578,7 @@ class XMPReader {
if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
wfDebugLog( 'XMP', __METHOD__ . " Empty compund element $finalName." );
+
return;
}
@@ -564,7 +591,6 @@ class XMPReader {
if ( $info['mode'] === self::MODE_LANG ) {
$this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
}
-
} else {
throw new MWException( __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm." );
}
@@ -578,13 +604,14 @@ class XMPReader {
* Qualifiers aren't all that common, and we don't do anything
* with them.
*
- * @param string $elm namespace and element
+ * @param string $elm Namespace and element
*/
private function endElementModeQDesc( $elm ) {
if ( $elm === self::NS_RDF . ' value' ) {
list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
$this->saveValue( $ns, $tag, $this->charContent );
+
return;
} else {
array_shift( $this->mode );
@@ -601,15 +628,15 @@ class XMPReader {
* Ignores the outer wrapping elements that are optional in
* xmp and have no meaning.
*
- * @param $parser XMLParser
- * @param string $elm namespace . ' ' . element name
+ * @param XMLParser $parser
+ * @param string $elm Namespace . ' ' . element name
* @throws MWException
*/
function endElement( $parser, $elm ) {
if ( $elm === ( self::NS_RDF . ' RDF' )
|| $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta' )
- {
+ || $elm === 'adobe:ns:meta/ xapmeta'
+ ) {
// ignore these.
return;
}
@@ -626,6 +653,7 @@ class XMPReader {
// that forgets the namespace on some things.
// (Luckily they are unimportant things).
wfDebugLog( 'XMP', __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
+
return;
}
@@ -684,7 +712,7 @@ class XMPReader {
* in which case we add it to the item stack, so we can ignore things
* that are nested, correctly.
*
- * @param string $elm namespace . ' ' . tag name
+ * @param string $elm Namespace . ' ' . tag name
*/
private function startElementModeIgnore( $elm ) {
if ( $elm === $this->curItem[0] ) {
@@ -697,8 +725,8 @@ class XMPReader {
* Start element in MODE_BAG (unordered array)
* this should always be <rdf:Bag>
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Bag>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Bag>
*/
private function startElementModeBag( $elm ) {
if ( $elm === self::NS_RDF . ' Bag' ) {
@@ -706,15 +734,14 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Bag> but got $elm." );
}
-
}
/**
* Start element in MODE_SEQ (ordered array)
* this should always be <rdf:Seq>
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Seq>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Seq>
*/
private function startElementModeSeq( $elm ) {
if ( $elm === self::NS_RDF . ' Seq' ) {
@@ -727,7 +754,6 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Seq> but got $elm." );
}
-
}
/**
@@ -741,8 +767,8 @@ class XMPReader {
* which are really only used for thumbnails, which
* we don't care about.
*
- * @param string $elm namespace . ' ' . tag
- * @throws MWException if we have an element that's not <rdf:Alt>
+ * @param string $elm Namespace . ' ' . tag
+ * @throws MWException If we have an element that's not <rdf:Alt>
*/
private function startElementModeLang( $elm ) {
if ( $elm === self::NS_RDF . ' Alt' ) {
@@ -750,7 +776,6 @@ class XMPReader {
} else {
throw new MWException( "Expected <rdf:Seq> but got $elm." );
}
-
}
/**
@@ -767,7 +792,7 @@ class XMPReader {
*
* This method is called when processing the <rdf:Description> element
*
- * @param string $elm namespace and tag names separated by space.
+ * @param string $elm Namespace and tag names separated by space.
* @param array $attribs Attributes of the element.
* @throws MWException
*/
@@ -784,15 +809,14 @@ class XMPReader {
} elseif ( $elm === self::NS_RDF . ' value' ) {
// This should not be here.
throw new MWException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
-
} else {
// something else we don't recognize, like a qualifier maybe.
- wfDebugLog( 'XMP', __METHOD__ . " Encountered element <$elm> where only expecting character data as value of " . $this->curItem[0] );
+ wfDebugLog( 'XMP', __METHOD__ .
+ " Encountered element <$elm> where only expecting character data as value of " .
+ $this->curItem[0] );
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $elm );
-
}
-
}
/**
@@ -806,7 +830,7 @@ class XMPReader {
* </exif:DigitalZoomRatio>
* Called when processing the <rdf:value> or <foo:someQualifier>.
*
- * @param string $elm namespace and tag name separated by a space.
+ * @param string $elm Namespace and tag name separated by a space.
*
*/
private function startElementModeQDesc( $elm ) {
@@ -827,8 +851,8 @@ class XMPReader {
* This is generally where most properties start.
*
* @param string $ns Namespace
- * @param string $tag tag name (without namespace prefix)
- * @param array $attribs array of attributes
+ * @param string $tag Tag name (without namespace prefix)
+ * @param array $attribs Array of attributes
* @throws MWException
*/
private function startElementModeInitial( $ns, $tag, $attribs ) {
@@ -846,6 +870,7 @@ class XMPReader {
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $ns . ' ' . $tag );
+
return;
}
$mode = $this->items[$ns][$tag]['mode'];
@@ -865,9 +890,9 @@ class XMPReader {
wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $ns . ' ' . $tag );
+
return;
}
-
}
// process attributes
$this->doAttribs( $attribs );
@@ -887,9 +912,9 @@ class XMPReader {
* <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
* <exif:Mode>1</exif:Mode></exif:Flash>
*
- * @param string $ns namespace
- * @param string $tag tag name (no ns)
- * @param array $attribs array of attribs w/ values.
+ * @param string $ns Namespace
+ * @param string $tag Tag name (no ns)
+ * @param array $attribs Array of attribs w/ values.
* @throws MWException
*/
private function startElementModeStruct( $ns, $tag, $attribs ) {
@@ -897,8 +922,8 @@ class XMPReader {
if ( isset( $this->items[$ns][$tag] ) ) {
if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
- && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] ) )
- {
+ && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
+ ) {
// This assumes that we don't have inter-namespace nesting
// which we don't in all the properties we're interested in.
throw new MWException( " <$tag> appeared nested in <" . $this->ancestorStruct
@@ -909,14 +934,15 @@ class XMPReader {
if ( $this->charContent !== false ) {
// Something weird.
// Should not happen in valid XMP.
- throw new MWException( "tag <$tag> nested in non-whitespace characters (" . $this->charContent . ")." );
+ throw new MWException( "tag <$tag> nested in non-whitespace characters (" .
+ $this->charContent . ")." );
}
} else {
array_unshift( $this->mode, self::MODE_IGNORE );
array_unshift( $this->curItem, $elm );
+
return;
}
-
}
if ( $ns === self::NS_RDF && $tag === 'Description' ) {
@@ -935,9 +961,9 @@ class XMPReader {
* </rdf:Seq> </exif:ISOSpeedRatings>
* This method is called when we hit the <rdf:li> element.
*
- * @param string $elm namespace . ' ' . tagname
+ * @param string $elm Namespace . ' ' . tagname
* @param array $attribs Attributes. (needed for BAGSTRUCTS)
- * @throws MWException if gets a tag other than <rdf:li>
+ * @throws MWException If gets a tag other than <rdf:li>
*/
private function startElementModeLi( $elm, $attribs ) {
if ( ( $elm ) !== self::NS_RDF . ' li' ) {
@@ -965,7 +991,6 @@ class XMPReader {
? $this->items[$curNS][$curTag]['map_name'] : $curTag;
$this->doAttribs( $attribs );
-
} else {
// Normal BAG or SEQ containing simple values.
array_unshift( $this->mode, self::MODE_SIMPLE );
@@ -974,7 +999,6 @@ class XMPReader {
array_unshift( $this->curItem, $this->curItem[0] );
$this->processingArray = true;
}
-
}
/**
@@ -987,17 +1011,17 @@ class XMPReader {
*
* This method is called when we hit the <rdf:li> element.
*
- * @param string $elm namespace . ' ' . tag
- * @param array $attribs array of elements (most importantly xml:lang)
- * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
+ * @param string $elm Namespace . ' ' . tag
+ * @param array $attribs Array of elements (most importantly xml:lang)
+ * @throws MWException If gets a tag other than <rdf:li> or if no xml:lang
*/
private function startElementModeLiLang( $elm, $attribs ) {
if ( $elm !== self::NS_RDF . ' li' ) {
throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
}
if ( !isset( $attribs[self::NS_XML . ' lang'] )
- || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] ) )
- {
+ || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
+ ) {
throw new MWException( __METHOD__
. " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
}
@@ -1017,17 +1041,17 @@ class XMPReader {
* Generally just calls a helper based on what MODE we're in.
* Also does some initial set up for the wrapper element
*
- * @param $parser XMLParser
- * @param string $elm namespace "<space>" element
- * @param array $attribs attribute name => value
+ * @param XMLParser $parser
+ * @param string $elm Namespace "<space>" element
+ * @param array $attribs Attribute name => value
* @throws MWException
*/
function startElement( $parser, $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' RDF'
|| $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta' )
- {
+ || $elm === 'adobe:ns:meta/ xapmeta'
+ ) {
/* ignore. */
return;
} elseif ( $elm === self::NS_RDF . ' Description' ) {
@@ -1049,6 +1073,7 @@ class XMPReader {
if ( strpos( $elm, ' ' ) === false ) {
// This probably shouldn't happen.
wfDebugLog( 'XMP', __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
+
return;
}
@@ -1104,23 +1129,24 @@ class XMPReader {
* Often the initial "<rdf:Description>" tag just has all the simple
* properties as attributes.
*
+ * @codingStandardsIgnoreStart Long line that cannot be broken
* @par Example:
* @code
* <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
* @endcode
+ * @codingStandardsIgnoreEnd
*
- * @param array $attribs attribute=>value array.
+ * @param array $attribs Array attribute=>value
* @throws MWException
*/
private function doAttribs( $attribs ) {
-
// first check for rdf:parseType attribute, as that can change
// how the attributes are interperted.
if ( isset( $attribs[self::NS_RDF . ' parseType'] )
&& $attribs[self::NS_RDF . ' parseType'] === 'Resource'
- && $this->mode[0] === self::MODE_SIMPLE )
- {
+ && $this->mode[0] === self::MODE_SIMPLE
+ ) {
// this is equivalent to having an inner rdf:Description
$this->mode[0] = self::MODE_QDESC;
}
@@ -1158,9 +1184,9 @@ class XMPReader {
* $this->processingArray to determine what name to
* save the value under. (in addition to $tag).
*
- * @param string $ns namespace of tag this is for
- * @param string $tag tag name
- * @param string $val value to save
+ * @param string $ns Namespace of tag this is for
+ * @param string $tag Tag name
+ * @param string $val Value to save
*/
private function saveValue( $ns, $tag, $val ) {
@@ -1177,6 +1203,7 @@ class XMPReader {
// is to be consistent between here and validating structures.
if ( is_null( $val ) ) {
wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
+
return;
}
} else {
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index f0b2cb5e..7e47ec14 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -27,17 +27,17 @@
* extract.
*/
class XMPInfo {
-
- /** get the items array
- * @return Array XMP item configuration array.
+ /** Get the items array
+ * @return array XMP item configuration array.
*/
public static function getItems() {
if ( !self::$ranHooks ) {
// This is for if someone makes a custom metadata extension.
// For example, a medical wiki might want to decode DICOM xmp properties.
- wfRunHooks( 'XMPGetInfo', Array( &self::$items ) );
+ wfRunHooks( 'XMPGetInfo', array( &self::$items ) );
self::$ranHooks = true; // Only want to do this once.
}
+
return self::$items;
}
@@ -53,382 +53,388 @@ class XMPInfo {
* each containing an array of tags
* each tag is an array of information about the
* tag, including:
- * * map_group - what group (used for precedence during conflicts)
- * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
- * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
- * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
- * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
- * * children - for MODE_STRUCT items, allowed children.
- * * structPart - Indicates that this element can only appear as a member of a structure.
+ * * map_group - What group (used for precedence during conflicts).
+ * * mode - What type of item (self::MODE_SIMPLE usually, see above for
+ * all values).
+ * * validate - Method to validate input. Could also post-process the
+ * input. A string value is assumed to be a static method of
+ * XMPValidate. Can also take a array( 'className', 'methodName' ).
+ * * choices - Array of potential values (format of 'value' => true ).
+ * Only used with validateClosed.
+ * * rangeLow and rangeHigh - Alternative to choices for numeric ranges.
+ * Again for validateClosed only.
+ * * children - For MODE_STRUCT items, allowed children.
+ * * structPart - Indicates that this element can only appear as a member
+ * of a structure.
*
- * currently this just has a bunch of exif values as this class is only half-done
+ * Currently this just has a bunch of EXIF values as this class is only half-done.
*/
static private $items = array(
'http://ns.adobe.com/exif/1.0/' => array(
'ApertureValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'BrightnessValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'CompressedBitsPerPixel' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'DigitalZoomRatio' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureBiasValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureIndex' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ExposureTime' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FlashEnergy' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'FNumber' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalLength' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalPlaneXResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'FocalPlaneYResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSAltitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'GPSDestBearing' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSDestDistance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSDOP' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSImgDirection' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSSpeed' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'GPSTrack' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
- 'MaxApertureValue' => array(
+ 'MaxApertureValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
'ShutterSpeedValue' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
- 'SubjectDistance' => array(
+ 'SubjectDistance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
),
/* Flash */
- 'Flash' => array(
- 'mode' => XMPReader::MODE_STRUCT,
- 'children' => array(
- 'Fired' => true,
- 'Function' => true,
- 'Mode' => true,
+ 'Flash' => array(
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'children' => array(
+ 'Fired' => true,
+ 'Function' => true,
+ 'Mode' => true,
'RedEyeMode' => true,
- 'Return' => true,
+ 'Return' => true,
),
- 'validate' => 'validateFlash',
+ 'validate' => 'validateFlash',
'map_group' => 'exif',
),
- 'Fired' => array(
+ 'Fired' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
- 'Function' => array(
+ 'Function' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
- 'Mode' => array(
+ 'Mode' => array(
'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => array( '0' => true, '1' => true,
- '2' => true, '3' => true ),
- 'structPart'=> true,
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true, '1' => true,
+ '2' => true, '3' => true ),
+ 'structPart' => true,
),
- 'Return' => array(
+ 'Return' => array(
'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => array( '0' => true,
- '2' => true, '3' => true ),
- 'structPart'=> true,
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true,
+ '2' => true, '3' => true ),
+ 'structPart' => true,
),
- 'RedEyeMode' => array(
+ 'RedEyeMode' => array(
'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* End Flash */
- 'ISOSpeedRatings' => array(
+ 'ISOSpeedRatings' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger'
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger'
),
/* end rational things */
- 'ColorSpace' => array(
+ 'ColorSpace' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '65535' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '65535' => true ),
),
- 'ComponentsConfiguration' => array(
+ 'ComponentsConfiguration' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
- '5' => true, '6' => true )
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
+ '5' => true, '6' => true )
),
- 'Contrast' => array(
+ 'Contrast' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true, '2' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true, '2' => true )
),
- 'CustomRendered' => array(
+ 'CustomRendered' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
),
- 'DateTimeOriginal' => array(
+ 'DateTimeOriginal' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
'DateTimeDigitized' => array( /* xmp:CreateDate */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
/* todo: there might be interesting information in
* exif:DeviceSettingDescription, but need to find an
* example
*/
- 'ExifVersion' => array(
+ 'ExifVersion' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'ExposureMode' => array(
+ 'ExposureMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
- 'ExposureProgram' => array(
+ 'ExposureProgram' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 8,
),
- 'FileSource' => array(
+ 'FileSource' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '3' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '3' => true )
),
- 'FlashpixVersion' => array(
+ 'FlashpixVersion' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'FocalLengthIn35mmFilm' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
'FocalPlaneResolutionUnit' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
),
- 'GainControl' => array(
+ 'GainControl' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 4,
),
/* this value is post-processed out later */
- 'GPSAltitudeRef' => array(
+ 'GPSAltitudeRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
),
'GPSAreaInformation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'GPSDestBearingRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
),
'GPSDestDistanceRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'K' => true, 'M' => true,
- 'N' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
),
- 'GPSDestLatitude' => array(
+ 'GPSDestLatitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSDestLongitude' => array(
+ 'GPSDestLongitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSDifferential' => array(
+ 'GPSDifferential' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
),
'GPSImgDirectionRef' => array(
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true ),
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
),
- 'GPSLatitude' => array(
+ 'GPSLatitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSLongitude' => array(
+ 'GPSLongitude' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
),
- 'GPSMapDatum' => array(
+ 'GPSMapDatum' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSMeasureMode' => array(
+ 'GPSMeasureMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true )
),
'GPSProcessingMethod' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSSatellites' => array(
+ 'GPSSatellites' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'GPSSpeedRef' => array(
+ 'GPSSpeedRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'K' => true, 'M' => true,
- 'N' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
),
- 'GPSStatus' => array(
+ 'GPSStatus' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'A' => true, 'V' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'A' => true, 'V' => true )
),
- 'GPSTimeStamp' => array(
+ 'GPSTimeStamp' => array(
'map_group' => 'exif',
// Note: in exif, GPSDateStamp does not include
// the time, where here it does.
- 'map_name' => 'GPSDateStamp',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'map_name' => 'GPSDateStamp',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
- 'GPSTrackRef' => array(
+ 'GPSTrackRef' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( 'T' => true, 'M' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true )
),
- 'GPSVersionID' => array(
+ 'GPSVersionID' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'ImageUniqueID' => array(
+ 'ImageUniqueID' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'LightSource' => array(
+ 'LightSource' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
/* can't use a range, as it skips... */
- 'choices' => array( '0' => true, '1' => true,
+ 'choices' => array( '0' => true, '1' => true,
'2' => true, '3' => true, '4' => true,
'9' => true, '10' => true, '11' => true,
'12' => true, '13' => true,
@@ -440,217 +446,217 @@ class XMPInfo {
'255' => true,
),
),
- 'MeteringMode' => array(
+ 'MeteringMode' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 6,
- 'choices' => array( '255' => true ),
+ 'choices' => array( '255' => true ),
),
/* Pixel(X|Y)Dimension are rather useless, but for
* completeness since we do it with exif.
*/
- 'PixelXDimension' => array(
+ 'PixelXDimension' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'PixelYDimension' => array(
+ 'PixelYDimension' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Saturation' => array(
+ 'Saturation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
- 'SceneCaptureType' => array(
+ 'SceneCaptureType' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 3,
),
- 'SceneType' => array(
+ 'SceneType' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true ),
),
// Note, 6 is not valid SensingMethod.
- 'SensingMethod' => array(
+ 'SensingMethod' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 1,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 1,
'rangeHigh' => 5,
- 'choices' => array( '7' => true, 8 => true ),
+ 'choices' => array( '7' => true, 8 => true ),
),
- 'Sharpness' => array(
+ 'Sharpness' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 2,
),
'SpectralSensitivity' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// This tag should perhaps be displayed to user better.
- 'SubjectArea' => array(
+ 'SubjectArea' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
'SubjectDistanceRange' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
'rangeHigh' => 3,
),
- 'SubjectLocation' => array(
+ 'SubjectLocation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
- 'UserComment' => array(
+ 'UserComment' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'WhiteBalance' => array(
+ 'WhiteBalance' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '0' => true, '1' => true )
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
),
),
'http://ns.adobe.com/tiff/1.0/' => array(
- 'Artist' => array(
+ 'Artist' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'BitsPerSample' => array(
+ 'BitsPerSample' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
),
- 'Compression' => array(
+ 'Compression' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '6' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '6' => true ),
),
/* this prop should not be used in XMP. dc:rights is the correct prop */
- 'Copyright' => array(
+ 'Copyright' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'DateTime' => array( /* proper prop is xmp:ModifyDate */
+ 'DateTime' => array( /* proper prop is xmp:ModifyDate */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
),
- 'ImageDescription' => array( /* proper one is dc:description */
+ 'ImageDescription' => array( /* proper one is dc:description */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
+ 'mode' => XMPReader::MODE_LANG,
),
- 'ImageLength' => array(
+ 'ImageLength' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'ImageWidth' => array(
+ 'ImageWidth' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Make' => array(
+ 'Make' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'Model' => array(
+ 'Model' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
/**** Do not extract this property
* It interferes with auto exif rotation.
* 'Orientation' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SIMPLE,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
- * '6' => true, '7' => true, '8' => true ),
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SIMPLE,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
+ * '6' => true, '7' => true, '8' => true ),
*),
******/
'PhotometricInterpretation' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '6' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '6' => true ),
),
'PlanerConfiguration' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
),
'PrimaryChromaticities' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
'ReferenceBlackWhite' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'ResolutionUnit' => array(
+ 'ResolutionUnit' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '2' => true, '3' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
),
- 'SamplesPerPixel' => array(
+ 'SamplesPerPixel' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
),
- 'Software' => array( /* see xmp:CreatorTool */
+ 'Software' => array( /* see xmp:CreatorTool */
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
/* ignore TransferFunction */
- 'WhitePoint' => array(
+ 'WhitePoint' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'XResolution' => array(
+ 'XResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
- 'YResolution' => array(
+ 'YResolution' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
),
'YCbCrCoefficients' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
),
- 'YCbCrPositioning' => array(
+ 'YCbCrPositioning' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true ),
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
),
/********
* Disable extracting this property (bug 31944)
@@ -663,170 +669,170 @@ class XMPInfo {
* just disable this prop for now, until such
* XMPReader is more graceful (bug 32172)
* 'YCbCrSubSampling' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SEQ,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true ),
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SEQ,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true ),
* ),
*/
),
'http://ns.adobe.com/exif/1.0/aux/' => array(
- 'Lens' => array(
+ 'Lens' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'SerialNumber' => array(
+ 'SerialNumber' => array(
'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'OwnerName' => array(
+ 'OwnerName' => array(
'map_group' => 'exif',
- 'map_name' => 'CameraOwnerName',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CameraOwnerName',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
'http://purl.org/dc/elements/1.1/' => array(
- 'title' => array(
+ 'title' => array(
'map_group' => 'general',
- 'map_name' => 'ObjectName',
- 'mode' => XMPReader::MODE_LANG
+ 'map_name' => 'ObjectName',
+ 'mode' => XMPReader::MODE_LANG
),
- 'description' => array(
+ 'description' => array(
'map_group' => 'general',
- 'map_name' => 'ImageDescription',
- 'mode' => XMPReader::MODE_LANG
+ 'map_name' => 'ImageDescription',
+ 'mode' => XMPReader::MODE_LANG
),
- 'contributor' => array(
+ 'contributor' => array(
'map_group' => 'general',
- 'map_name' => 'dc-contributor',
- 'mode' => XMPReader::MODE_BAG
+ 'map_name' => 'dc-contributor',
+ 'mode' => XMPReader::MODE_BAG
),
- 'coverage' => array(
+ 'coverage' => array(
'map_group' => 'general',
- 'map_name' => 'dc-coverage',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'dc-coverage',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'creator' => array(
+ 'creator' => array(
'map_group' => 'general',
- 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
- 'mode' => XMPReader::MODE_SEQ,
+ 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
+ 'mode' => XMPReader::MODE_SEQ,
),
- 'date' => array(
+ 'date' => array(
'map_group' => 'general',
// Note, not mapped with other date properties, as this type of date is
// non-specific: "A point or period of time associated with an event in
// the lifecycle of the resource"
- 'map_name' => 'dc-date',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateDate',
+ 'map_name' => 'dc-date',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateDate',
),
- /* Do not extract dc:format, as we've got better ways to determine mimetype */
- 'identifier' => array(
+ /* Do not extract dc:format, as we've got better ways to determine MIME type */
+ 'identifier' => array(
'map_group' => 'deprecated',
- 'map_name' => 'Identifier',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'Identifier',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'language' => array(
+ 'language' => array(
'map_group' => 'general',
- 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateLangCode',
+ 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateLangCode',
),
- 'publisher' => array(
+ 'publisher' => array(
'map_group' => 'general',
- 'map_name' => 'dc-publisher',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-publisher',
+ 'mode' => XMPReader::MODE_BAG,
),
// for related images/resources
- 'relation' => array(
+ 'relation' => array(
'map_group' => 'general',
- 'map_name' => 'dc-relation',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-relation',
+ 'mode' => XMPReader::MODE_BAG,
),
- 'rights' => array(
+ 'rights' => array(
'map_group' => 'general',
- 'map_name' => 'Copyright',
- 'mode' => XMPReader::MODE_LANG,
+ 'map_name' => 'Copyright',
+ 'mode' => XMPReader::MODE_LANG,
),
// Note: source is not mapped with iptc source, since iptc
// source describes the source of the image in terms of a person
// who provided the image, where this is to describe an image that the
// current one is based on.
- 'source' => array(
+ 'source' => array(
'map_group' => 'general',
- 'map_name' => 'dc-source',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'dc-source',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
- 'subject' => array(
+ 'subject' => array(
'map_group' => 'general',
- 'map_name' => 'Keywords', /* maps to iptc 2:25 */
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'Keywords', /* maps to iptc 2:25 */
+ 'mode' => XMPReader::MODE_BAG,
),
- 'type' => array(
+ 'type' => array(
'map_group' => 'general',
- 'map_name' => 'dc-type',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'dc-type',
+ 'mode' => XMPReader::MODE_BAG,
),
),
'http://ns.adobe.com/xap/1.0/' => array(
'CreateDate' => array(
'map_group' => 'general',
'map_name' => 'DateTimeDigitized',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateDate',
),
'CreatorTool' => array(
'map_group' => 'general',
- 'map_name' => 'Software',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'map_name' => 'Software',
+ 'mode' => XMPReader::MODE_SIMPLE
),
'Identifier' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
+ 'mode' => XMPReader::MODE_BAG,
),
'Label' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'ModifyDate' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTime',
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTime',
+ 'validate' => 'validateDate',
),
'MetadataDate' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
// map_name to be consistent with other date names.
- 'map_name' => 'DateTimeMetadata',
- 'validate' => 'validateDate',
+ 'map_name' => 'DateTimeMetadata',
+ 'validate' => 'validateDate',
),
'Nickname' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Rating' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRating',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRating',
),
),
'http://ns.adobe.com/xap/1.0/rights/' => array(
'Certificate' => array(
'map_group' => 'general',
- 'map_name' => 'RightsCertificate',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'RightsCertificate',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Marked' => array(
'map_group' => 'general',
- 'map_name' => 'Copyrighted',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateBoolean',
+ 'map_name' => 'Copyrighted',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateBoolean',
),
'Owner' => array(
'map_group' => 'general',
- 'map_name' => 'CopyrightOwner',
- 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'CopyrightOwner',
+ 'mode' => XMPReader::MODE_BAG,
),
// this seems similar to dc:rights.
'UsageTerms' => array(
@@ -844,7 +850,7 @@ class XMPInfo {
// as well do this too.
'OriginalDocumentID' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// It might also be useful to do xmpMM:LastURL
// and xmpMM:DerivedFrom as you can potentially,
@@ -855,31 +861,31 @@ class XMPInfo {
),
'http://creativecommons.org/ns#' => array(
'license' => array(
- 'map_name' => 'LicenseUrl',
+ 'map_name' => 'LicenseUrl',
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'morePermissions' => array(
- 'map_name' => 'MorePermissionsUrl',
+ 'map_name' => 'MorePermissionsUrl',
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'attributionURL' => array(
'map_group' => 'general',
- 'map_name' => 'AttributionUrl',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'AttributionUrl',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'attributionName' => array(
'map_group' => 'general',
- 'map_name' => 'PreferredAttributionName',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'PreferredAttributionName',
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
//Note, this property affects how jpeg metadata is extracted.
'http://ns.adobe.com/xmp/note/' => array(
'HasExtendedXMP' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
),
/* Note, in iptc schemas, the legacy properties are denoted
@@ -890,41 +896,41 @@ class XMPInfo {
'http://ns.adobe.com/photoshop/1.0/' => array(
'City' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CityDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CityDest',
),
'Country' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryDest',
),
'State' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'ProvinceOrStateDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'ProvinceOrStateDest',
),
'DateCreated' => array(
'map_group' => 'deprecated',
// marking as deprecated as the xmp prop preferred
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTimeOriginal',
- 'validate' => 'validateDate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTimeOriginal',
+ 'validate' => 'validateDate',
// note this prop is an XMP, not IPTC date
),
'CaptionWriter' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'Writer',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'Writer',
),
'Instructions' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SpecialInstructions',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SpecialInstructions',
),
'TransmissionReference' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'OriginalTransmissionRef',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'OriginalTransmissionRef',
),
'AuthorsPosition' => array(
/* This corresponds with 2:85
@@ -932,46 +938,46 @@ class XMPInfo {
* handled weirdly to correspond
* with iptc/exif. */
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'mode' => XMPReader::MODE_SIMPLE
),
'Credit' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Source' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Urgency' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'Category' => array(
// Note, this prop is deprecated, but in general
// group since it doesn't have a replacement.
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'iimCategory',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'iimCategory',
),
'SupplementalCategories' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'iimSupplementalCategory',
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'iimSupplementalCategory',
),
'Headline' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE
+ 'mode' => XMPReader::MODE_SIMPLE
),
),
'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => array(
'CountryCode' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryCodeDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryCodeDest',
),
'IntellectualGenre' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
// Note, this is a six digit code.
// See: http://cv.iptc.org/newscodes/scene/
@@ -979,9 +985,9 @@ class XMPInfo {
// we just show the number.
'Scene' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateInteger',
- 'map_name' => 'SceneCode',
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'SceneCode',
),
/* Note: SubjectCode should be an 8 ascii digits.
* it is not really an integer (has leading 0's,
@@ -990,14 +996,14 @@ class XMPInfo {
*/
'SubjectCode' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'SubjectNewsCode',
- 'validate' => 'validateInteger'
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'SubjectNewsCode',
+ 'validate' => 'validateInteger'
),
'Location' => array(
'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SublocationDest',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SublocationDest',
),
'CreatorContactInfo' => array(
/* Note this maps to 2:118 in iim
@@ -1007,94 +1013,94 @@ class XMPInfo {
* is more structured.
*/
'map_group' => 'general',
- 'mode' => XMPReader::MODE_STRUCT,
- 'map_name' => 'Contact',
- 'children' => array(
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'map_name' => 'Contact',
+ 'children' => array(
'CiAdrExtadr' => true,
- 'CiAdrCity' => true,
- 'CiAdrCtry' => true,
+ 'CiAdrCity' => true,
+ 'CiAdrCtry' => true,
'CiEmailWork' => true,
- 'CiTelWork' => true,
- 'CiAdrPcode' => true,
+ 'CiTelWork' => true,
+ 'CiAdrPcode' => true,
'CiAdrRegion' => true,
- 'CiUrlWork' => true,
+ 'CiUrlWork' => true,
),
),
'CiAdrExtadr' => array( /* address */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrCity' => array( /* city */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrCtry' => array( /* country */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiEmailWork' => array( /* email (possibly separated by ',') */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiTelWork' => array( /* telephone */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrPcode' => array( /* postal code */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiAdrRegion' => array( /* province/state */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CiUrlWork' => array( /* url. Multiple may be separated by comma. */
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* End contact info struct properties */
),
'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => array(
'Event' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
+ 'mode' => XMPReader::MODE_SIMPLE,
),
'OrganisationInImageName' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'OrganisationInImage'
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'OrganisationInImage'
),
'PersonInImage' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
+ 'mode' => XMPReader::MODE_BAG,
),
'MaxAvailHeight' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageHeight',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageHeight',
),
'MaxAvailWidth' => array(
'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageWidth',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageWidth',
),
// LocationShown and LocationCreated are handled
// specially because they are hierarchical, but we
// also want to merge with the old non-hierarchical.
'LocationShown' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => array(
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
'WorldRegion' => true,
'CountryCode' => true, /* iso code */
'CountryName' => true,
@@ -1105,8 +1111,8 @@ class XMPInfo {
),
'LocationCreated' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => array(
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
'WorldRegion' => true,
'CountryCode' => true, /* iso code */
'CountryName' => true,
@@ -1117,35 +1123,35 @@ class XMPInfo {
),
'WorldRegion' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CountryCode' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'CountryName' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
- 'map_name' => 'Country',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
+ 'map_name' => 'Country',
),
'ProvinceState' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
- 'map_name' => 'ProvinceOrState',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
+ 'map_name' => 'ProvinceOrState',
),
'City' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
'Sublocation' => array(
'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart'=> true,
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart' => true,
),
/* Other props that might be interesting but
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index 87f8abfe..0fa60117 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -28,7 +28,7 @@
* Each of these functions take the same parameters
* * an info array which is a subset of the XMPInfo::items array
* * A value (passed as reference) to validate. This can be either a
- * simple value or an array
+ * simple value or an array
* * A boolean to determine if this is validating a simple or complex values
*
* It should be noted that when an array is being validated, typically the validation
@@ -42,11 +42,11 @@
*/
class XMPValidate {
/**
- * function to validate boolean properties ( True or False )
+ * Function to validate boolean properties ( True or False )
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateBoolean( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -57,15 +57,14 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected True or False but got $val" );
$val = null;
}
-
}
/**
* function to validate rational properties ( 12/10 )
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateRational( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -76,7 +75,6 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected rational but got $val" );
$val = null;
}
-
}
/**
@@ -85,9 +83,9 @@ class XMPValidate {
* if its outside of range put it into range.
*
* @see MWG spec
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateRating( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -99,6 +97,7 @@ class XMPValidate {
) {
wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" );
$val = null;
+
return;
} else {
$nVal = (float)$val;
@@ -108,11 +107,13 @@ class XMPValidate {
// as -1 is meant as a special reject rating.
wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
$val = '-1';
+
return;
}
if ( $nVal > 5 ) {
wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5" );
$val = '5';
+
return;
}
}
@@ -121,9 +122,9 @@ class XMPValidate {
/**
* function to validate integers
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateInteger( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -134,16 +135,15 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected integer but got $val" );
$val = null;
}
-
}
/**
* function to validate properties with a fixed number of allowed
* choices. (closed choice)
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateClosed( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -171,9 +171,9 @@ class XMPValidate {
/**
* function to validate and modify flash structure
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateFlash( $info, &$val, $standalone ) {
if ( $standalone ) {
@@ -205,9 +205,9 @@ class XMPValidate {
* @see rfc 3066
* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateLangCode( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -219,7 +219,6 @@ class XMPValidate {
wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" );
$val = null;
}
-
}
/**
@@ -233,11 +232,11 @@ class XMPValidate {
* YYYY-MM-DDThh:mm:ssTZD
* YYYY-MM-DDThh:mm:ss.sTZD
*
- * @param array $info information about current property
- * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
- * in cases where there's only a partial date, it will give things like
- * 2011:04.
- * @param $standalone Boolean if this is a simple property or array
+ * @param array $info Information about current property
+ * @param mixed &$val Current value to validate. Converts to TS_EXIF as a side-effect.
+ * in cases where there's only a partial date, it will give things like
+ * 2011:04.
+ * @param bool $standalone If this is a simple property or array
*/
public static function validateDate( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -245,11 +244,14 @@ class XMPValidate {
return;
}
$res = array();
+ // @codingStandardsIgnoreStart Long line that cannot be broken
if ( !preg_match(
/* ahh! scary regex... */
'/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
$val, $res )
) {
+ // @codingStandardsIgnoreEnd
+
wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" );
$val = null;
} else {
@@ -270,6 +272,7 @@ class XMPValidate {
if ( $res[1] === '0000' ) {
wfDebugLog( 'XMP', __METHOD__ . " Invalid date (year 0): $val" );
$val = null;
+
return;
}
@@ -282,6 +285,7 @@ class XMPValidate {
if ( isset( $res[3] ) ) {
$val .= ':' . $res[3];
}
+
return;
}
@@ -292,6 +296,7 @@ class XMPValidate {
if ( isset( $res[6] ) && $res[6] !== '' ) {
$val .= ':' . $res[6];
}
+
return;
}
@@ -320,7 +325,6 @@ class XMPValidate {
$val = substr( $val, 0, -3 );
}
}
-
}
/** function to validate, and more importantly
@@ -330,10 +334,10 @@ class XMPValidate {
* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
* section 1.2.7.4 on page 23
*
- * @param array $info unused (info about prop)
- * @param &$val String GPS string in either DDD,MM,SSk or
- * or DDD,MM.mmk form
- * @param $standalone Boolean if its a simple prop (should always be true)
+ * @param array $info Unused (info about prop)
+ * @param string &$val GPS string in either DDD,MM,SSk or
+ * or DDD,MM.mmk form
+ * @param bool $standalone If its a simple prop (should always be true)
*/
public static function validateGPS( $info, &$val, $standalone ) {
if ( !$standalone ) {
@@ -352,6 +356,7 @@ class XMPValidate {
$coord = -$coord;
}
$val = $coord;
+
return;
} elseif ( preg_match(
'/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
@@ -363,12 +368,13 @@ class XMPValidate {
$coord = -$coord;
}
$val = $coord;
- return;
+ return;
} else {
wfDebugLog( 'XMP', __METHOD__
. " Expected GPSCoordinate, but got $val." );
$val = null;
+
return;
}
}