";
+ if ( !$thumb ) {
+ $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
$zoomicon = '';
} elseif( !$img->exists() ) {
$s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
$zoomicon = '';
} else {
- $s .= '
'.
- '
';
+ $imgAttribs = array(
+ 'alt' => $alt,
+ 'longdesc' => $u,
+ 'class' => 'thumbimage'
+ );
+ $linkAttribs = array(
+ 'href' => $u,
+ 'title' => $alt,
+ 'class' => 'internal'
+ );
+
+ $s .= $thumb->toHtml( $imgAttribs, $linkAttribs );
if ( $framed ) {
$zoomicon="";
} else {
diff --git a/includes/MediaTransformOutput.php b/includes/MediaTransformOutput.php
new file mode 100644
index 00000000000..5004fcc9cc3
--- /dev/null
+++ b/includes/MediaTransformOutput.php
@@ -0,0 +1,158 @@
+width;
+ }
+
+ /**
+ * Get the height of the output box
+ */
+ function getHeight() {
+ return $this->height;
+ }
+
+ /**
+ * @return string The thumbnail URL
+ */
+ function getUrl() {
+ return $this->url;
+ }
+
+ /**
+ * @return string Destination file path (local filesystem)
+ */
+ function getPath() {
+ return $this->path;
+ }
+
+ /**
+ * Fetch HTML for this transform output
+ * @param array $attribs Advisory associative array of HTML attributes supplied
+ * by the linker. These can be incorporated into the output in any way.
+ * @param array $linkAttribs Attributes of a suggested enclosing
tag.
+ * May be ignored.
+ */
+ abstract function toHtml( $attribs = array() , $linkAttribs = false );
+
+ /**
+ * This will be overridden to return true in error classes
+ */
+ function isError() {
+ return false;
+ }
+
+ /**
+ * Wrap some XHTML text in an anchor tag with the given attributes
+ */
+ protected function linkWrap( $linkAttribs, $contents ) {
+ if ( $linkAttribs ) {
+ return Xml::tags( 'a', $linkAttribs, $contents );
+ } else {
+ return $contents;
+ }
+ }
+}
+
+
+/**
+ * Media transform output for images
+ */
+class ThumbnailImage extends MediaTransformOutput {
+ /**
+ * @param string $path Filesystem path to the thumb
+ * @param string $url URL path to the thumb
+ * @private
+ */
+ function ThumbnailImage( $url, $width, $height, $path = false ) {
+ $this->url = $url;
+ # These should be integers when they get here.
+ # If not, there's a bug somewhere. But let's at
+ # least produce valid HTML code regardless.
+ $this->width = round( $width );
+ $this->height = round( $height );
+ $this->path = $path;
+ }
+
+ /**
+ * Return HTML
tag for the thumbnail, will include
+ * width and height attributes and a blank alt text (as required).
+ *
+ * You can set or override additional attributes by passing an
+ * associative array of name => data pairs. The data will be escaped
+ * for HTML output, so should be in plaintext.
+ *
+ * If $linkAttribs is given, the image will be enclosed in an tag.
+ *
+ * @param array $attribs
+ * @param array $linkAttribs
+ * @return string
+ * @public
+ */
+ function toHtml( $attribs = array(), $linkAttribs = false ) {
+ $attribs['src'] = $this->url;
+ $attribs['width'] = $this->width;
+ $attribs['height'] = $this->height;
+ if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
+ return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
+ }
+
+}
+
+/**
+ * Basic media transform error class
+ */
+class MediaTransformError extends MediaTransformOutput {
+ var $htmlMsg, $textMsg, $width, $height, $url, $path;
+
+ function __construct( $msg, $width, $height /*, ... */ ) {
+ $args = array_slice( func_get_args(), 3 );
+ $htmlArgs = array_map( 'htmlspecialchars', $args );
+ $htmlArgs = array_map( 'nl2br', $htmlArgs );
+
+ $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs );
+ $this->textMsg = wfMsgReal( $msg, $args );
+ $this->width = intval( $width );
+ $this->height = intval( $height );
+ $this->url = false;
+ $this->path = false;
+ }
+
+ function toHtml( $attribs = array(), $linkAttribs = false ) {
+ return "";
+ }
+
+ function toText() {
+ return $this->textMsg;
+ }
+
+ function getHtmlMsg() {
+ return $this->htmlMsg;
+ }
+
+ function isError() {
+ return true;
+ }
+}
+
+/**
+ * Shortcut class for parameter validation errors
+ */
+class TransformParameterError extends MediaTransformError {
+ function __construct( $params ) {
+ parent::__construct( 'thumbnail_error',
+ max( @$params['width'], 180 ), max( @$params['height'], 180 ),
+ wfMsg( 'thumbnail_invalid_params' ) );
+ }
+}
+
+?>
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 9142ccebda4..516a3cda510 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -22,7 +22,7 @@ image/x-bmp bmp
image/gif gif
image/jpeg jpeg jpg jpe
image/png png
-image/svg+xml svg
+image/svg+xml image/svg svg
image/tiff tiff tif
image/vnd.djvu djvu
image/x-portable-pixmap ppm
@@ -51,7 +51,7 @@ image/x-bmp image/bmp [BITMAP]
image/gif [BITMAP]
image/jpeg [BITMAP]
image/png [BITMAP]
-image/svg image/svg+xml [DRAWING]
+image/svg+xml [DRAWING]
image/tiff [BITMAP]
image/vnd.djvu [BITMAP]
image/x-portable-pixmap [BITMAP]
diff --git a/includes/Parser.php b/includes/Parser.php
index 89ab0915b28..7f34fa1e7b2 100644
--- a/includes/Parser.php
+++ b/includes/Parser.php
@@ -4387,8 +4387,8 @@ class Parser
* Parse image options text and use it to make an image
*/
function makeImage( $nt, $options ) {
- global $wgUseImageResize, $wgDjvuRenderer;
-
+ # @TODO: let the MediaHandler specify its transform parameters
+ #
# Check if the options text is of the form "options|alt text"
# Options are:
# * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -4408,6 +4408,7 @@ class Parser
# * bottom
# * text-bottom
+
$part = array_map( 'trim', explode( '|', $options) );
$mwAlign = array();
@@ -4422,13 +4423,14 @@ class Parser
$mwPage =& MagicWord::get( 'img_page' );
$caption = '';
- $width = $height = $framed = $thumb = false;
- $page = null;
+ $params = array();
+ $framed = $thumb = false;
$manual_thumb = '' ;
$align = $valign = '';
+ $sk = $this->mOptions->getSkin();
foreach( $part as $val ) {
- if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
+ if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
$thumb=true;
} elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
# use manually specified thumbnail
@@ -4446,19 +4448,18 @@ class Parser
continue 2;
}
}
- if ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
- && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+ if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
# Select a page in a multipage document
- $page = $match;
- } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
+ $params['page'] = $match;
+ } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
wfDebug( "img_width match: $match\n" );
# $match is the image width in pixels
$m = array();
if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
- $width = intval( $m[1] );
- $height = intval( $m[2] );
+ $params['width'] = intval( $m[1] );
+ $params['height'] = intval( $m[2] );
} else {
- $width = intval($match);
+ $params['width'] = intval($match);
}
} elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
$framed=true;
@@ -4477,8 +4478,7 @@ class Parser
$alt = Sanitizer::stripAllTags( $alt );
# Linker does the rest
- $sk = $this->mOptions->getSkin();
- return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page, $valign );
+ return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign );
}
/**
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
new file mode 100644
index 00000000000..02e665a5b91
--- /dev/null
+++ b/includes/media/Bitmap.php
@@ -0,0 +1,226 @@
+getMimeType();
+ $srcWidth = $image->getWidth( $params['page'] );
+ $srcHeight = $image->getHeight( $params['page'] );
+
+ # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
+ # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
+ # an exception for it.
+ if ( $mimeType !== 'image/jpeg' &&
+ $srcWidth * $srcHeight > $wgMaxImageArea )
+ {
+ return false;
+ }
+
+ # Don't make an image bigger than the source
+ $params['physicalWidth'] = $params['width'];
+ $params['physicalHeight'] = $params['height'];
+
+ if ( $params['physicalWidth'] >= $srcWidth ) {
+ $params['physicalWidth'] = $srcWidth;
+ $params['physicalHeight'] = $srcHeight;
+ return true;
+ }
+
+ return true;
+ }
+
+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+ global $wgUseImageMagick, $wgImageMagickConvertCommand;
+ global $wgCustomConvertCommand;
+ global $wgSharpenParameter, $wgSharpenReductionThreshold;
+
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return new TransformParameterError( $params );
+ }
+ $physicalWidth = $params['physicalWidth'];
+ $physicalHeight = $params['physicalHeight'];
+ $clientWidth = $params['width'];
+ $clientHeight = $params['height'];
+ $srcWidth = $image->getWidth();
+ $srcHeight = $image->getHeight();
+ $mimeType = $image->getMimeType();
+ $srcPath = $image->getImagePath();
+ $retval = 0;
+ wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
+
+ if ( $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
+ # normaliseParams (or the user) wants us to return the unscaled image
+ wfDebug( __METHOD__.": returning unscaled image\n" );
+ return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+ }
+
+ if ( $wgUseImageMagick ) {
+ $scaler = 'im';
+ } elseif ( $wgCustomConvertCommand ) {
+ $scaler = 'custom';
+ } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+ $scaler = 'gd';
+ } else {
+ $scaler = 'client';
+ }
+
+ if ( $scaler == 'client' ) {
+ # Client-side image scaling, use the source URL
+ # Using the destination URL in a TRANSFORM_LATER request would be incorrect
+ return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+ }
+
+ if ( $flags & self::TRANSFORM_LATER ) {
+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
+ }
+
+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
+ wfMsg( 'thumbnail_dest_directory' ) );
+ }
+
+ if ( $scaler == 'im' ) {
+ # use ImageMagick
+
+ $sharpen = '';
+ if ( $mimeType == 'image/jpeg' ) {
+ $quality = "-quality 80"; // 80%
+ # Sharpening, see bug 6193
+ if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
+ $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
+ }
+ } elseif ( $mimeType == 'image/png' ) {
+ $quality = "-quality 95"; // zlib 9, adaptive filtering
+ } else {
+ $quality = ''; // default
+ }
+
+ # Specify white background color, will be used for transparent images
+ # in Internet Explorer/Windows instead of default black.
+
+ # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
+ # It seems that ImageMagick has a bug wherein it produces thumbnails of
+ # the wrong size in the second case.
+
+ $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
+ " {$quality} -background white -size {$physicalWidth} ".
+ wfEscapeShellArg($srcPath) .
+ // Coalesce is needed to scale animated GIFs properly (bug 1017).
+ ' -coalesce ' .
+ // For the -resize option a "!" is needed to force exact size,
+ // or ImageMagick may decide your ratio is wrong and slice off
+ // a pixel.
+ " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
+ " -depth 8 $sharpen " .
+ wfEscapeShellArg($dstPath) . " 2>&1";
+ wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
+ wfProfileIn( 'convert' );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'convert' );
+ } elseif( $scaler == 'custom' ) {
+ # Use a custom convert command
+ # Variables: %s %d %w %h
+ $src = wfEscapeShellArg( $srcPath );
+ $dst = wfEscapeShellArg( $dstPath );
+ $cmd = $wgCustomConvertCommand;
+ $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+ $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
+ wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
+ wfProfileIn( 'convert' );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'convert' );
+ } else /* $scaler == 'gd' */ {
+ # Use PHP's builtin GD library functions.
+ #
+ # First find out what kind of file this is, and select the correct
+ # 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' ),
+ );
+ if( !isset( $typemap[$mimeType] ) ) {
+ $err = 'Image type not supported';
+ wfDebug( "$err\n" );
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ }
+ list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
+
+ if( !function_exists( $loader ) ) {
+ $err = "Incomplete GD library configuration: missing function $loader";
+ wfDebug( "$err\n" );
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ }
+
+ $src_image = call_user_func( $loader, $srcPath );
+ $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
+ imagecopyresampled( $dst_image, $src_image,
+ 0,0,0,0,
+ $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
+ call_user_func( $saveType, $dst_image, $dstPath );
+ imagedestroy( $dst_image );
+ imagedestroy( $src_image );
+ $retval = 0;
+ }
+
+ $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 ) );
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ } else {
+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
+ }
+ }
+
+ static function imageJpegWrapper( $dst_image, $thumbPath ) {
+ imageinterlace( $dst_image );
+ imagejpeg( $dst_image, $thumbPath, 95 );
+ }
+
+
+ function getMetadata( $image, $filename ) {
+ global $wgShowEXIF;
+ if( $wgShowEXIF && file_exists( $filename ) ) {
+ $exif = new Exif( $filename );
+ return serialize( $exif->getFilteredData() );
+ } else {
+ return '';
+ }
+ }
+
+ function getMetadataType( $image ) {
+ return 'exif';
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ # Metadata disabled and so an empty field is expected
+ return true;
+ }
+ if ( $metadata === '0' ) {
+ # Special value indicating that there is no EXIF data in the file
+ return true;
+ }
+ $exif = @unserialize( $metadata );
+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
+ $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
+ {
+ # Wrong version
+ return false;
+ }
+ return true;
+ }
+
+}
+
+?>
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
new file mode 100644
index 00000000000..a04de9687ae
--- /dev/null
+++ b/includes/media/DjVu.php
@@ -0,0 +1,203 @@
+ $m[1], 'page' => $m[2] );
+ } else {
+ return false;
+ }
+ }
+
+ function getScriptParams( $params ) {
+ return array(
+ 'width' => $params['width'],
+ 'page' => $params['page'],
+ );
+ }
+
+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+ global $wgDjvuRenderer, $wgDjvuPostProcessor;
+
+ // Fetch XML and check it, to give a more informative error message than the one which
+ // normaliseParams will inevitably give.
+ $xml = $image->getMetadata();
+ if ( !$xml ) {
+ return new MediaTransformError( 'thumbnail_error', @$params['width'], @$params['height'],
+ wfMsg( 'djvu_no_xml' ) );
+ }
+
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return new TransformParameterError( $params );
+ }
+ $width = $params['width'];
+ $height = $params['height'];
+ $srcPath = $image->getImagePath();
+ $page = $params['page'];
+ $pageCount = $this->pageCount( $image );
+ if ( $page > $this->pageCount( $image ) ) {
+ return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
+ }
+
+ if ( $flags & self::TRANSFORM_LATER ) {
+ return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
+ }
+
+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) );
+ }
+
+ # 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( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size={$width}x{$height} " .
+ wfEscapeShellArg( $srcPath );
+ if ( $wgDjvuPostProcessor ) {
+ $cmd .= " | {$wgDjvuPostProcessor}";
+ }
+ $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1';
+ wfProfileIn( 'ddjvu' );
+ wfDebug( __METHOD__.": $cmd\n" );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'ddjvu' );
+
+ $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 ) );
+ return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+ } else {
+ return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
+ }
+ }
+
+ /**
+ * Cache an instance of DjVuImage in an Image object, return that instance
+ */
+ function getDjVuImage( $image, $path ) {
+ if ( !$image ) {
+ $deja = new DjVuImage( $path );
+ } elseif ( !isset( $image->dejaImage ) ) {
+ $deja = $image->dejaImage = new DjVuImage( $path );
+ } else {
+ $deja = $image->dejaImage;
+ }
+ return $deja;
+ }
+
+ /**
+ * Cache a document tree for the DjVu XML metadata
+ */
+ function getMetaTree( $image ) {
+ if ( isset( $image->dejaMetaTree ) ) {
+ return $image->dejaMetaTree;
+ }
+
+ $metadata = $image->getMetadata();
+ if ( !$this->isMetadataValid( $image, $metadata ) ) {
+ wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+ return false;
+ }
+ wfProfileIn( __METHOD__ );
+
+ wfSuppressWarnings();
+ try {
+ $image->dejaMetaTree = new SimpleXMLElement( $metadata );
+ } catch( Exception $e ) {
+ wfDebug( "Bogus multipage XML metadata on '$image->name'\n" );
+ // Set to false rather than null to avoid further attempts
+ $image->dejaMetaTree = false;
+ }
+ wfRestoreWarnings();
+ wfProfileOut( __METHOD__ );
+ return $image->dejaMetaTree;
+ }
+
+ function getImageSize( $image, $path ) {
+ return $this->getDjVuImage( $image, $path )->getImageSize();
+ }
+
+ function getThumbType( $ext, $mime ) {
+ global $wgDjvuOutputExtension;
+ static $mime;
+ if ( !isset( $mime ) ) {
+ $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();
+ }
+
+ function getMetadataType( $image ) {
+ return 'djvuxml';
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ return !empty( $metadata ) && $metadata != serialize(array());
+ }
+
+ function pageCount( $image ) {
+ $tree = $this->getMetaTree( $image );
+ if ( !$tree ) {
+ return false;
+ }
+ return count( $tree->xpath( '//OBJECT' ) );
+ }
+
+ function getPageDimensions( $image, $page ) {
+ $tree = $this->getMetaTree( $image );
+ if ( !$tree ) {
+ return false;
+ }
+
+ $o = $tree->BODY[0]->OBJECT[$page-1];
+ if ( $o ) {
+ return array(
+ 'width' => intval( $o['width'] ),
+ 'height' => intval( $o['height'] )
+ );
+ } else {
+ return false;
+ }
+ }
+}
+
+?>
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
new file mode 100644
index 00000000000..44c08d7cc21
--- /dev/null
+++ b/includes/media/Generic.php
@@ -0,0 +1,292 @@
+isEnabled() ) {
+ self::$handlers[$class] = false;
+ }
+ }
+ return self::$handlers[$class];
+ }
+
+ /*
+ * Validate a thumbnail parameter at parse time.
+ * Return true to accept the parameter, and false to reject it.
+ * If you return false, the parser will do something quiet and forgiving.
+ */
+ abstract function validateParam( $name, $value );
+
+ /**
+ * Merge a parameter array into a string appropriate for inclusion in filenames
+ */
+ abstract function makeParamString( $params );
+
+ /**
+ * Parse a param string made with makeParamString back into an array
+ */
+ abstract function parseParamString( $str );
+
+ /**
+ * Changes the parameter array as necessary, ready for transformation.
+ * Should be idempotent.
+ * Returns false if the parameters are unacceptable and the transform should fail
+ */
+ abstract function normaliseParams( $image, &$params );
+
+ /**
+ * Get an image size array like that returned by getimagesize(), or false if it
+ * can't be determined.
+ *
+ * @param Image $image The image object, or false if there isn't one
+ * @param string $fileName The filename
+ * @return array
+ */
+ abstract function getImageSize( $image, $path );
+
+ /**
+ * Get handler-specific metadata which will be saved in the img_metadata field.
+ *
+ * @param Image $image The image object, or false if there isn't one
+ * @param string $fileName The filename
+ * @return string
+ */
+ function getMetadata( $image, $path ) { return ''; }
+
+ /**
+ * Get a string describing the type of metadata, for display purposes.
+ */
+ function getMetadataType( $image ) { return false; }
+
+ /**
+ * Check if the metadata string is valid for this handler.
+ * If it returns false, Image will reload the metadata from the file and update the database
+ */
+ function isMetadataValid( $image, $metadata ) { return true; }
+
+ /**
+ * Get a MediaTransformOutput object representing the transformed output. Does not
+ * actually do the transform.
+ *
+ * @param Image $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()
+ */
+ function getTransform( $image, $dstPath, $dstUrl, $params ) {
+ return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
+ }
+
+ /**
+ * Get a MediaTransformOutput object representing the transformed output. Does the
+ * transform unless $flags contains self::TRANSFORM_LATER.
+ *
+ * @param Image $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()
+ * @param integer $flags A bitfield, may contain self::TRANSFORM_LATER
+ */
+ abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
+
+ /**
+ * Get the thumbnail extension and MIME type for a given source MIME type
+ * @return array thumbnail extension and MIME type
+ */
+ function getThumbType( $ext, $mime ) {
+ return array( $ext, $mime );
+ }
+
+ /**
+ * True if the handled types can be transformed
+ */
+ function canRender() { return true; }
+ /**
+ * True if handled types cannot be displayed directly in a browser
+ * but can be rendered
+ */
+ function mustRender() { return false; }
+ /**
+ * True if the type has multi-page capabilities
+ */
+ function isMultiPage() { return false; }
+ /**
+ * Page count for a multi-page document, false if unsupported or unknown
+ */
+ function pageCount() { return false; }
+ /**
+ * False if the handler is disabled for all files
+ */
+ function isEnabled() { return true; }
+
+ /**
+ * Get an associative array of page dimensions
+ * Currently "width" and "height" are understood, but this might be
+ * expanded in the future.
+ * Returns false if unknown or if the document is not multi-page.
+ */
+ function getPageDimensions( $image, $page ) {
+ $gis = $this->getImageSize( $image, $image->getImagePath() );
+ return array(
+ 'width' => $gis[0],
+ 'height' => $gis[1]
+ );
+ }
+}
+
+/**
+ * Media handler abstract base class for images
+ */
+abstract class ImageHandler extends MediaHandler {
+ function validateParam( $name, $value ) {
+ if ( in_array( $name, array( 'width', 'height' ) ) ) {
+ if ( $value <= 0 ) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ function makeParamString( $params ) {
+ if ( isset( $params['physicalWidth'] ) ) {
+ $width = $params['physicalWidth'];
+ } else {
+ $width = $params['width'];
+ }
+ $width = intval( $width );
+ return "{$width}px";
+ }
+
+ function parseParamString( $str ) {
+ $m = false;
+ if ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
+ return array( 'width' => $m[1] );
+ } else {
+ return false;
+ }
+ }
+
+ function getScriptParams( $params ) {
+ return array( 'width' => $params['width'] );
+ }
+
+ function normaliseParams( $image, &$params ) {
+ $mimeType = $image->getMimeType();
+
+ if ( !isset( $params['width'] ) ) {
+ return false;
+ }
+ if ( !isset( $params['page'] ) ) {
+ $params['page'] = 1;
+ }
+ $srcWidth = $image->getWidth( $params['page'] );
+ $srcHeight = $image->getHeight( $params['page'] );
+ if ( isset( $params['height'] ) && $params['height'] != -1 ) {
+ if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
+ $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
+ }
+ }
+ $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
+ if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get a transform output object without actually doing the transform
+ */
+ function getTransform( $image, $dstPath, $dstUrl, $params ) {
+ return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
+ }
+
+ /**
+ * Validate thumbnail parameters and fill in the correct height
+ *
+ * @param integer &$width Specified width (input/output)
+ * @param integer &$height Height (output only)
+ * @return false to indicate that an error should be returned to the user.
+ */
+ function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
+ $width = intval( $width );
+
+ # 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;
+ }
+
+ $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
+ return true;
+ }
+
+ function getScriptedTransform( $image, $script, $params ) {
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return false;
+ }
+ $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) );
+ return new ThumbnailImage( $url, $params['width'], $params['height'] );
+ }
+
+ /**
+ * Check for zero-sized thumbnails. These can be generated when
+ * no disk space is available or some other error occurs
+ *
+ * @param $dstPath The location of the suspect file
+ * @param $retval Return value of some shell process, file will be deleted if this is non-zero
+ * @return true if removed, false otherwise
+ */
+ function removeBadFile( $dstPath, $retval = 0 ) {
+ $removed = false;
+ if( file_exists( $dstPath ) ) {
+ $thumbstat = stat( $dstPath );
+ if( $thumbstat['size'] == 0 || $retval != 0 ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Removing bad %d-byte thumbnail "%s"',
+ $thumbstat['size'], $dstPath ) );
+ unlink( $dstPath );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function getImageSize( $image, $path ) {
+ wfSuppressWarnings();
+ $gis = getimagesize( $path );
+ wfRestoreWarnings();
+ return $gis;
+ }
+}
+
+?>
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
new file mode 100644
index 00000000000..407760da23f
--- /dev/null
+++ b/includes/media/SVG.php
@@ -0,0 +1,94 @@
+ $wgSVGMaxSize ) {
+ $srcWidth = $image->getWidth( $params['page'] );
+ $srcHeight = $image->getHeight( $params['page'] );
+ $params['physicalWidth'] = $wgSVGMaxSize;
+ $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
+ }
+ return true;
+ }
+
+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+ global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
+
+ if ( !$this->normaliseParams( $image, $params ) ) {
+ return new TransformParameterError( $params );
+ }
+ $clientWidth = $params['width'];
+ $clientHeight = $params['height'];
+ $physicalWidth = $params['physicalWidth'];
+ $physicalHeight = $params['physicalHeight'];
+ $srcWidth = $image->getWidth();
+ $srcHeight = $image->getHeight();
+ $srcPath = $image->getImagePath();
+
+ if ( $flags & self::TRANSFORM_LATER ) {
+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
+ }
+
+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
+ wfMsg( 'thumbnail_dest_directory' ) );
+ }
+
+ $err = false;
+ if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+ $cmd = str_replace(
+ array( '$path/', '$width', '$height', '$input', '$output' ),
+ array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
+ intval( $physicalWidth ),
+ intval( $physicalHeight ),
+ wfEscapeShellArg( $srcPath ),
+ wfEscapeShellArg( $dstPath ) ),
+ $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
+ wfProfileIn( 'rsvg' );
+ wfDebug( __METHOD__.": $cmd\n" );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'rsvg' );
+ }
+
+ $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 ) );
+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ } else {
+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
+ }
+ }
+
+ function getImageSize( $image, $path ) {
+ return wfGetSVGsize( $path );
+ }
+
+ function getThumbType( $ext, $mime ) {
+ return array( 'png', 'image/png' );
+ }
+}
+?>
diff --git a/includes/mime.info b/includes/mime.info
index b2ae92efd12..a960f0233cd 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -19,7 +19,7 @@ image/x-portable-graymap image/x-portable-greymap [BITMAP]
image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP]
image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP]
-image/svg image/svg+xml application/svg+xml application/svg [DRAWING]
+image/svg+xml application/svg+xml application/svg image/svg [DRAWING]
application/postscript [DRAWING]
application/x-latex [DRAWING]
application/x-tex [DRAWING]
diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php
index e7be6c0c68a..3f88b0f1980 100644
--- a/languages/messages/MessagesEn.php
+++ b/languages/messages/MessagesEn.php
@@ -2139,6 +2139,10 @@ In the latter case you can also use a link, e.g. [[{{ns:Special}}:Export/{{Media
'missingimage' => '
Missing image$1',
'filemissing' => 'File missing',
'thumbnail_error' => 'Error creating thumbnail: $1',
+'djvu_page_error' => 'DjVu page out of range',
+'djvu_no_xml' => 'Unable to fetch XML for DjVu file',
+'thumbnail_invalid_params' => 'Invalid thumbnail parameters',
+'thumbnail_dest_directory' => 'Unable to create destination directory',
# Special:Import
'import' => 'Import pages',
@@ -2816,8 +2820,8 @@ Please confirm that really want to recreate this page.',
* Nederlands|nl",
# Multipage image navigation
-'imgmultipageprev' => '← previous page',
-'imgmultipagenext' => 'next page →',
+'imgmultipageprev' => '← previous page',
+'imgmultipagenext' => 'next page →',
'imgmultigo' => 'Go!',
'imgmultigotopre' => 'Go to page',
'imgmultigotopost' => '',
diff --git a/skins/common/common.css b/skins/common/common.css
index cb91c5d475f..e39910c329a 100644
--- a/skins/common/common.css
+++ b/skins/common/common.css
@@ -480,4 +480,15 @@ p.mw-ipb-conveniencelinks {
#file img, .gallerybox .thumb img {
background: url(images/Checker-16x16.png) repeat;
}
-*/
\ No newline at end of file
+*/
+.MediaTransformError {
+ border: thin solid #777;
+ background-color: #ccc;
+ padding: 0.1em;
+}
+.MediaTransformError td {
+ text-align: center;
+ vertical-align: middle;
+ font-size: 90%;
+}
+
diff --git a/skins/monobook/main.css b/skins/monobook/main.css
index bd68c37da8d..bd7c77391b1 100644
--- a/skins/monobook/main.css
+++ b/skins/monobook/main.css
@@ -1626,3 +1626,13 @@ p.mw-ipb-conveniencelinks {
.texvc { direction: ltr; unicode-bidi: embed; }
/* Stop floats from intruding into edit area in previews */
#toolbar, #wpTextbox1 { clear: both; }
+
+.MediaTransformError {
+ background-color: #ccc;
+ padding: 0.1em;
+}
+.MediaTransformError td {
+ text-align: center;
+ vertical-align: middle;
+ font-size: 90%;
+}
diff --git a/thumb.php b/thumb.php
index 42bc5497a55..c67468b694e 100644
--- a/thumb.php
+++ b/thumb.php
@@ -9,44 +9,49 @@ define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
require_once( './includes/WebStart.php' );
wfProfileIn( 'thumb.php' );
wfProfileIn( 'thumb.php-start' );
-require_once( './includes/GlobalFunctions.php' );
-require_once( './includes/ImageFunctions.php' );
+require_once( "$IP/includes/GlobalFunctions.php" );
+require_once( "$IP/includes/ImageFunctions.php" );
$wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
-require_once( './includes/Image.php' );
-require_once( './includes/StreamFile.php' );
+require_once( "$IP/includes/StreamFile.php" );
+require_once( "$IP/includes/AutoLoader.php" );
// Get input parameters
-$fileName = isset( $_REQUEST['f'] ) ? $_REQUEST['f'] : '';
-$width = isset( $_REQUEST['w'] ) ? intval( $_REQUEST['w'] ) : 0;
-$page = isset( $_REQUEST['p'] ) ? intval( $_REQUEST['p'] ) : null;
-
if ( get_magic_quotes_gpc() ) {
- $fileName = stripslashes( $fileName );
+ $params = array_map( 'stripslashes', $_REQUEST );
+} else {
+ $params = $_REQUEST;
}
-$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0";
+$fileName = isset( $params['f'] ) ? $params['f'] : '';
+unset( $params['f'] );
+
+// Backwards compatibility parameters
+if ( isset( $params['w'] ) ) {
+ $params['width'] = $params['w'];
+ unset( $params['w'] );
+}
+if ( isset( $params['p'] ) ) {
+ $params['page'] = $params['p'];
+}
+unset( $params['r'] );
// Some basic input validation
$fileName = strtr( $fileName, '\\/', '__' );
// Work out paths, carefully avoiding constructing an Image object because that won't work yet
+$handler = thumbGetHandler( $fileName );
+if ( $handler ) {
+ $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
+ $thumbName = $handler->makeParamString( $params ) . "-$fileName";
+ $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
-$imagePath = wfImageDir( $fileName ) . '/' . $fileName;
-$thumbName = "{$width}px-$fileName";
-if ( ! is_null( $page ) ) {
- $thumbName = 'page' . $page . '-' . $thumbName;
-}
-if ( $pre_render ) {
- $thumbName .= '.png';
-}
-$thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
-
-if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
- wfStreamFile( $thumbPath );
- // Can't log profiling data with no Setup.php
- exit;
+ if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
+ wfStreamFile( $thumbPath );
+ // Can't log profiling data with no Setup.php
+ exit;
+ }
}
// OK, no valid thumbnail, time to get out the heavy machinery
@@ -57,10 +62,7 @@ wfProfileIn( 'thumb.php-render' );
$img = Image::newFromName( $fileName );
try {
if ( $img ) {
- if ( ! is_null( $page ) ) {
- $img->selectPage( $page );
- }
- $thumb = $img->renderThumb( $width, false );
+ $thumb = $img->transform( $params, Image::RENDER_NOW );
} else {
$thumb = false;
}
@@ -69,13 +71,33 @@ try {
$thumb = false;
}
-if ( $thumb && $thumb->path ) {
- wfStreamFile( $thumb->path );
+if ( $thumb && $thumb->getPath() ) {
+ wfStreamFile( $thumb->getPath() );
+} elseif ( $img ) {
+ header( 'Cache-Control: no-cache' );
+ header( 'Content-Type: text/html; charset=utf-8' );
+ header( 'HTTP/1.1 500 Internal server error' );
+ if ( !$thumb ) {
+ $msg = wfMsgHtml( 'thumbnail_error', 'Image::transform() returned false' );
+ } elseif ( $thumb->isError() ) {
+ $msg = $thumb->toHtml();
+ } else {
+ $msg = wfMsgHtml( 'thumbnail_error', 'No path supplied in thumbnail object' );
+ }
+ echo <<
Error generating thumbnail
+
+$msg
+
+