* Introduced media handler modules for file-type specific operations: thumbnailing, img_metadata, capabilities, etc.

* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
* Fixed interaction of page parameter to ImagePage with the HTML file cache
* Improved error reporting for image thumbnailing
* Fixed MIME type for SVG files, will be silently changed from image/svg to image/svg+xml after loading from the database.
* Workaround for djvutoxml bug #1704049 (poor performance). Use djvudump instead.
* Fixed odd behaviour in ImagePage on DjVu thumbnailing errors
* Improved error reporting for image thumbnailing
* Added sharpening option for ImageMagick thumbnailing
* Removed Image::selectPage(), added page parameters to getWidth() and getHeight(), deprecated Image::renderThumb() and Image::getThumbnail()
* Changed default contents of img_metadata to empty string instead of a:0:{}
* Moved responsibility for respecting $wgGenerateThumbnailOnParse from the UI to Image.php
This commit is contained in:
Tim Starling 2007-04-20 12:31:36 +00:00
parent ff9b558c05
commit b15d8cffc4
23 changed files with 1658 additions and 984 deletions

View file

@ -31,6 +31,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
* Added rate limiter for Special:Emailuser
* Private logs can now be created using $wgLogRestrictions
* (Bug 8590) limited HTML is now always enabled ($wgUserHtml = true).
* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
== New features since 1.9 ==
@ -117,6 +118,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
* Introduce 'SearchUpdate' hook; see docs/hooks.txt for more information
* Introduce 'mywatchlist' message; used on personal menu to link to watchlist page
* Introduce magic word {{NUMBEROFEDITS}}
* Introduced media handlers for file-type specific operations.
== Bugfixes since 1.9 ==
@ -327,6 +329,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
a random page, and will give an error message if none really can be found
instead of sending the user to the main page like they used to
* Fix object variable used for displaying "not-patrolled" CSS class on list
* Fixed interaction of page parameter to ImagePage with the HTML file cache
*
== Maintenance ==

View file

@ -483,8 +483,6 @@ if( $conf->HaveGD ) {
}
}
$conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick;
$conf->IP = dirname( dirname( __FILE__ ) );
print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n";
@ -1302,7 +1300,6 @@ function escapePhpString( $string ) {
}
function writeLocalSettings( $conf ) {
$conf->UseImageResize = $conf->UseImageResize ? 'true' : 'false';
$conf->PasswordSender = $conf->EmergencyContact;
$magic = ($conf->ImageMagick ? "" : "# ");
$convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" );
@ -1448,7 +1445,6 @@ if ( \$wgCommandLineMode ) {
## To enable image uploads, make sure the 'images' directory
## is writable, then set this to true:
\$wgEnableUploads = false;
\$wgUseImageResize = {$conf->UseImageResize};
{$magic}\$wgUseImageMagick = true;
{$magic}\$wgImageMagickConvertCommand = \"{$convert}\";

View file

@ -2460,6 +2460,7 @@ class Article {
$diff = $wgRequest->getVal( 'diff' );
$redirect = $wgRequest->getVal( 'redirect' );
$printable = $wgRequest->getVal( 'printable' );
$page = $wgRequest->getVal( 'page' );
return $wgUseFileCache
and (!$wgShowIPinHeader)
@ -2472,6 +2473,7 @@ class Article {
and (!isset($diff))
and (!isset($redirect))
and (!isset($printable))
and !isset($page)
and (!$this->mRedirectedFrom);
}

View file

@ -8,6 +8,7 @@ function __autoload($className) {
global $wgAutoloadClasses;
static $localClasses = array(
# Includes
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
@ -115,6 +116,10 @@ function __autoload($className) {
'MacBinary' => 'includes/MacBinary.php',
'MagicWord' => 'includes/MagicWord.php',
'MathRenderer' => 'includes/Math.php',
'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
'ThumbnailImage' => 'includes/MediaTransformOutput.php',
'MediaTransformError' => 'includes/MediaTransformOutput.php',
'TransformParameterError' => 'includes/MediaTransformOutput.php',
'MessageCache' => 'includes/MessageCache.php',
'MimeMagic' => 'includes/MimeMagic.php',
'Namespace' => 'includes/Namespace.php',
@ -128,6 +133,7 @@ function __autoload($className) {
'ParserOutput' => 'includes/ParserOutput.php',
'ParserOptions' => 'includes/ParserOptions.php',
'ParserCache' => 'includes/ParserCache.php',
'PatrolLog' => 'includes/PatrolLog.php',
'ProfilerSimple' => 'includes/ProfilerSimple.php',
'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
'Profiler' => 'includes/Profiler.php',
@ -196,6 +202,7 @@ function __autoload($className) {
'PopularPagesPage' => 'includes/SpecialPopularpages.php',
'PreferencesForm' => 'includes/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php',
'PasswordResetForm' => 'includes/SpecialResetpass.php',
'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php',
'RevisionDeleter' => 'includes/SpecialRevisiondelete.php',
'SpecialSearch' => 'includes/SpecialSearch.php',
@ -240,15 +247,26 @@ function __autoload($className) {
'Xml' => 'includes/Xml.php',
'ZhClient' => 'includes/ZhClient.php',
'memcached' => 'includes/memcached-client.php',
# Media
'BitmapHandler' => 'includes/media/Bitmap.php',
'DjVuHandler' => 'includes/media/DjVu.php',
'MediaHandler' => 'includes/media/Generic.php',
'ImageHandler' => 'includes/media/Generic.php',
'SvgHandler' => 'includes/media/SVG.php',
# Normal
'UtfNormal' => 'includes/normal/UtfNormal.php',
# Templates
'UsercreateTemplate' => 'includes/templates/Userlogin.php',
'UserloginTemplate' => 'includes/templates/Userlogin.php',
# Languages
'Language' => 'languages/Language.php',
'PasswordResetForm' => 'includes/SpecialResetpass.php',
'PatrolLog' => 'includes/PatrolLog.php',
'RandomPage' => 'includes/SpecialRandompage.php',
// API classes
# API
'ApiBase' => 'includes/api/ApiBase.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',

View file

@ -1443,8 +1443,18 @@ $wgSiteNotice = '';
# Images settings
#
/** dynamic server side image resizing ("Thumbnails") */
$wgUseImageResize = false;
/**
* Plugins for media file type handling.
* Each entry in the array maps a MIME type to a class name
*/
$wgMediaHandlers = array(
'image/jpeg' => 'BitmapHandler',
'image/png' => 'BitmapHandler',
'image/gif' => 'BitmapHandler',
'image/svg+xml' => 'SvgHandler',
'image/vnd.djvu' => 'DjVuHandler',
);
/**
* Resizing can be done using PHP's internal image libraries or using
@ -1458,6 +1468,12 @@ $wgUseImageMagick = false;
/** The convert command shipped with ImageMagick */
$wgImageMagickConvertCommand = '/usr/bin/convert';
/** Sharpening parameter to ImageMagick */
$wgSharpenParameter = '0x0.4';
/** Reduction in linear dimensions below which sharpening will be enabled */
$wgSharpenReductionThreshold = 0.85;
/**
* Use another resizing converter, e.g. GraphicMagick
* %s will be replaced with the source path, %d with the destination
@ -1523,6 +1539,10 @@ $wgIgnoreImageErrors = false;
*/
$wgGenerateThumbnailOnParse = true;
/** Obsolete, always true, kept for compatibility with extensions */
$wgUseImageResize = true;
/** Set $wgCommandLineMode if it's not set already, to avoid notices */
if( !isset( $wgCommandLineMode ) ) {
$wgCommandLineMode = false;
@ -2277,7 +2297,7 @@ $wgTrustedMediaFormats= array(
MEDIATYPE_BITMAP, //all bitmap formats
MEDIATYPE_AUDIO, //all audio formats
MEDIATYPE_VIDEO, //all plain video formats
"image/svg", //svg (only needed if inline rendering of svg is not supported)
"image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
"application/pdf", //PDF files
#"application/x-shockwave-flash", //flash/shockwave movie
);
@ -2380,7 +2400,7 @@ $wgReservedUsernames = array(
* MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
* perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
* crap files as images. When this directive is on, <title> will be allowed in files with
* an "image/svg" MIME type. You should leave this disabled if your web server is misconfigured
* an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
* and doesn't send appropriate MIME types for SVG images.
*/
$wgAllowTitlesInSVG = false;
@ -2406,18 +2426,31 @@ $wgMaxShellFileSize = 102400;
/**
* DJVU settings
* Path of the djvutoxml executable
* Path of the djvudump executable
* Enable this and $wgDjvuRenderer to enable djvu rendering
*/
# $wgDjvuDump = 'djvudump';
$wgDjvuDump = null;
/**
* Path of the ddjvu DJVU renderer
* Enable this and $wgDjvuDump to enable djvu rendering
*/
# $wgDjvuRenderer = 'ddjvu';
$wgDjvuRenderer = null;
/**
* Path of the djvutoxml executable
* This works like djvudump except much, much slower as of version 3.5.
*
* For now I recommend you use djvudump instead. The djvuxml output is
* probably more stable, so we'll switch back to it as soon as they fix
* the efficiency problem.
* http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
*/
# $wgDjvuToXML = 'djvutoxml';
$wgDjvuToXML = null;
/**
* Path of the ddjvu DJVU renderer
* Enable this and $wgDjvuToXML to enable djvu rendering
*/
# $wgDjvuRenderer = 'ddjvu';
$wgDjvuRenderer = null;
/**
* Shell command for the DJVU post processor

View file

@ -220,17 +220,121 @@ class DjVuImage {
* @return string
*/
function retrieveMetaData() {
global $wgDjvuToXML;
if ( isset( $wgDjvuToXML ) ) {
global $wgDjvuToXML, $wgDjvuDump;
if ( isset( $wgDjvuDump ) ) {
# djvudump is faster as of version 3.5
# http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
wfProfileIn( 'djvudump' );
$cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
$dump = wfShellExec( $cmd );
$xml = $this->convertDumpToXML( $dump );
wfProfileOut( 'djvudump' );
} elseif ( isset( $wgDjvuToXML ) ) {
wfProfileIn( 'djvutoxml' );
$cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
wfEscapeShellArg( $this->mFilename );
$xml = wfShellExec( $cmd );
wfProfileOut( 'djvutoxml' );
} else {
$xml = null;
}
return $xml;
}
/**
* Hack to temporarily work around djvutoxml bug
*/
function convertDumpToXML( $dump ) {
if ( strval( $dump ) == '' ) {
return false;
}
$xml = <<<EOT
<?xml version="1.0" ?>
<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
<DjVuXML>
<HEAD></HEAD>
<BODY>
EOT;
$dump = str_replace( "\r", '', $dump );
$line = strtok( $dump, "\n" );
$m = false;
$good = false;
if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) {
# Single-page
if ( $this->parseFormDjvu( $line, $xml ) ) {
$good = true;
} else {
return false;
}
} elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) {
# Multi-page
$parentLevel = strlen( $m[1] );
# Find DIRM
$line = strtok( "\n" );
while ( $line !== false ) {
$childLevel = strspn( $line, ' ' );
if ( $childLevel <= $parentLevel ) {
# End of chunk
break;
}
if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
return false;
}
if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
# Found page
if ( $this->parseFormDjvu( $line, $xml ) ) {
$good = true;
} else {
return false;
}
}
$line = strtok( "\n" );
}
}
if ( !$good ) {
return false;
}
$xml .= "</BODY>\n</DjVuXML>\n";
return $xml;
}
function parseFormDjvu( $line, &$xml ) {
$parentLevel = strspn( $line, ' ' );
$line = strtok( "\n" );
# Find INFO
while ( $line !== false ) {
$childLevel = strspn( $line, ' ' );
if ( $childLevel <= $parentLevel ) {
# End of chunk
break;
}
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',
'height' => $m[2],
'width' => $m[1],
#'usemap' => '',
),
"\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;
}
}

View file

@ -93,9 +93,9 @@ class Exif {
var $basename;
/**
* The private log to log to
* The private log to log to, e.g. 'exif'
*/
var $log = 'exif';
var $log = false;
//@}
@ -561,7 +561,10 @@ class Exif {
* @param $fname String:
* @param $action Mixed: , default NULL.
*/
function debug( $in, $fname, $action = NULL ) {
function debug( $in, $fname, $action = NULL ) {
if ( !$this->log ) {
return;
}
$type = gettype( $in );
$class = ucfirst( __CLASS__ );
if ( $type === 'array' )
@ -586,6 +589,9 @@ class Exif {
* @param $io Boolean: Specify whether we're beginning or ending
*/
function debugFile( $fname, $io ) {
if ( !$this->log ) {
return;
}
$class = ucfirst( __CLASS__ );
if ( $io ) {
wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );

File diff suppressed because it is too large Load diff

View file

@ -183,7 +183,7 @@ class ImageGallery
*
*/
function toHTML() {
global $wgLang, $wgGenerateThumbnailOnParse;
global $wgLang;
$sk = $this->getSkin();
@ -191,6 +191,7 @@ class ImageGallery
if( $this->mCaption )
$s .= "\n\t<caption>{$this->mCaption}</caption>";
$params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
$i = 0;
foreach ( $this->mImages as $pair ) {
$img =& $pair[0];
@ -206,7 +207,7 @@ class ImageGallery
# The image is blacklisted, just show it as a text link.
$thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
. $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>';
} elseif( !( $thumb = $img->getThumbnail( $this->mWidths, $this->mHeights, $wgGenerateThumbnailOnParse ) ) ) {
} elseif( !( $thumb = $img->transform( $params ) ) ) {
# Error generating thumbnail.
$thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';

View file

@ -166,11 +166,9 @@ class ImagePage extends Article {
function openShowImage() {
global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang;
global $wgUseImageResize, $wgGenerateThumbnailOnParse;
$full_url = $this->img->getURL();
$anchoropen = '';
$anchorclose = '';
$linkAttribs = false;
$sizeSel = intval( $wgUser->getOption( 'imagesize') );
if( !isset( $wgImageLimits[$sizeSel] ) ) {
$sizeSel = User::getDefaultOption( 'imagesize' );
@ -190,10 +188,11 @@ class ImagePage extends Article {
if ( $this->img->exists() ) {
# image
$page = $wgRequest->getIntOrNull( 'page' );
if ( ! is_null( $page ) ) {
$this->img->selectPage( $page );
} else {
if ( is_null( $page ) ) {
$params = array();
$page = 1;
} else {
$params = array( 'page' => $page );
}
$width_orig = $this->img->getWidth();
$width = $width_orig;
@ -201,6 +200,7 @@ class ImagePage extends Article {
$height = $height_orig;
$mime = $this->img->getMimeType();
$showLink = false;
$linkAttribs = array( 'href' => $full_url );
if ( $this->img->allowInlineDisplay() and $width and $height) {
# image
@ -223,39 +223,33 @@ class ImagePage extends Article {
# Note that $height <= $maxHeight now, but might not be identical
# because of rounding.
}
}
$params['width'] = $width;
$thumbnail = $this->img->transform( $params );
if( $wgUseImageResize ) {
$thumbnail = $this->img->getThumbnail( $width, -1, $wgGenerateThumbnailOnParse );
if ( $thumbnail == null ) {
$url = $this->img->getViewURL();
} else {
$url = $thumbnail->getURL();
}
} else {
# No resize ability? Show the full image, but scale
# it down in the browser so it fits on the page.
$url = $this->img->getViewURL();
}
$anchoropen = "<a href=\"{$full_url}\">";
$anchorclose = "</a><br />";
if( $this->img->mustRender() ) {
$showLink = true;
} else {
$anchorclose .= wfMsg('show-big-image-thumb', $width, $height ) .
'<br />' . "\n$anchoropen{$msgbig}</a> " . $msgsize;
}
} else {
$url = $this->img->getViewURL();
$anchorclose = "<br />";
if( $this->img->mustRender() ) {
$showLink = true;
} else {
$anchorclose .=
wfMsg('show-big-image-thumb', $width, $height ) .
'<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize;
}
if ( $this->img->isMultipage() ) {
$wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
}
$wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
"<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' );
$imgAttribs = array(
'border' => 0,
'alt' => $this->img->getTitle()->getPrefixedText()
);
if ( $thumbnail ) {
$wgOut->addHTML( '<div class="fullImageLink" id="file">' .
$thumbnail->toHtml( $imgAttribs, $linkAttribs ) .
$anchorclose . '</div>' );
}
if ( $this->img->isMultipage() ) {
$count = $this->img->pageCount();
@ -263,17 +257,17 @@ class ImagePage extends Article {
if ( $page > 1 ) {
$label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
$link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
$this->img->selectPage( $page - 1 );
$thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
$thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
array( 'page' => $page - 1 ) );
} else {
$thumb1 = '';
}
if ( $page < $count ) {
$label = wfMsg( 'imgmultipagenext' );
$this->img->selectPage( $page + 1 );
$link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
$thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
$thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
array( 'page' => $page + 1 ) );
} else {
$thumb2 = '';
}
@ -294,7 +288,7 @@ class ImagePage extends Article {
htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>';
$wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' .
"$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
"$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
}
} else {
#if direct link is allowed but it's not a renderable image, show an icon.

View file

@ -429,25 +429,19 @@ class Linker {
}
/** @todo document */
function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false,
$thumb = false, $manual_thumb = '', $page = null, $valign = '' )
function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
$thumb = false, $manual_thumb = '', $valign = '' )
{
global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse;
global $wgContLang, $wgUser, $wgThumbLimits;
$img = new Image( $nt );
if ( ! is_null( $page ) ) {
$img->selectPage( $page );
}
if ( !$img->allowInlineDisplay() && $img->exists() ) {
return $this->makeKnownLinkObj( $nt );
}
$url = $img->getViewURL();
$error = $prefix = $postfix = '';
wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" );
$page = isset( $params['page'] ) ? $params['page'] : false;
if ( 'center' == $align )
{
@ -456,6 +450,16 @@ class Linker {
$align = 'none';
}
if ( !isset( $params['width'] ) ) {
$wopt = $wgUser->getOption( 'thumbsize' );
if( !isset( $wgThumbLimits[$wopt] ) ) {
$wopt = User::getDefaultOption( 'thumbsize' );
}
$params['width'] = min( $img->getWidth( $page ), $wgThumbLimits[$wopt] );
}
if ( $thumb || $framed ) {
# Create a thumbnail. Alignment depends on language
@ -468,73 +472,39 @@ class Linker {
if ( $align == '' ) {
$align = $wgContLang->isRTL() ? 'left' : 'right';
}
if ( $width === false ) {
$wopt = $wgUser->getOption( 'thumbsize' );
if( !isset( $wgThumbLimits[$wopt] ) ) {
$wopt = User::getDefaultOption( 'thumbsize' );
}
$width = min( $img->getWidth(), $wgThumbLimits[$wopt] );
}
return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix;
return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
}
if ( $width && $img->exists() ) {
# Create a resized image, without the additional thumbnail
# features
if ( $height == false )
$height = -1;
if ( $manual_thumb == '') {
$thumb = $img->getThumbnail( $width, $height, $wgGenerateThumbnailOnParse );
if ( $thumb ) {
// In most cases, $width = $thumb->width or $height = $thumb->height.
// If not, we're scaling the image larger than it can be scaled,
// so we send to the browser a smaller thumbnail, and let the client do the scaling.
if ($height != -1 && $width > $thumb->width * $height / $thumb->height) {
// $height is the limiting factor, not $width
// set $width to the largest it can be, such that the resulting
// scaled height is at most $height
$width = floor($thumb->width * $height / $thumb->height);
}
$height = round($thumb->height * $width / $thumb->width);
wfDebug( "makeImageLinkObj: client-size set to '$width x $height'\n" );
$url = $thumb->getUrl();
} else {
$error = htmlspecialchars( $img->getLastError() );
// Do client-side scaling...
$height = intval( $img->getHeight() * $width / $img->getWidth() );
}
}
if ( $params['width'] && $img->exists() ) {
# Create a resized image, without the additional thumbnail features
$thumb = $img->transform( $params );
} else {
$width = $img->width;
$height = $img->height;
$thumb = false;
}
wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" );
$u = $nt->escapeLocalURL();
if ( $error ) {
$s = $error;
} elseif ( $url == '' ) {
if ( $page ) {
$query = 'page=' . urlencode( $page );
} else {
$query = '';
}
$u = $nt->getLocalURL( $query );
$imgAttribs = array(
'alt' => $alt,
'longdesc' => $u
);
if ( $valign ) {
$imgAttribs['style'] = "vertical-align: $valign";
}
$linkAttribs = array(
'href' => $u,
'class' => 'image',
'title' => $alt
);
if ( !$thumb ) {
$s = $this->makeBrokenImageLinkObj( $img->getTitle() );
//$s .= "<br />{$alt}<br />{$url}<br />\n";
} else {
$s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
'<img src="'.$url.'" alt="'.$alt.'" ' .
( $width
? ( 'width="'.$width.'" height="'.$height.'" ' )
: '' ) .
( $valign
? ( 'style="vertical-align: '.$valign.'" ' )
: '' ) .
'longdesc="'.$u.'" /></a>';
$s = $thumb->toHtml( $imgAttribs, $linkAttribs );
}
if ( '' != $align ) {
$s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
@ -546,86 +516,64 @@ class Linker {
* Make HTML for a thumbnail including image, border and caption
* $img is an Image object
*/
function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse;
function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
global $wgStylePath, $wgContLang;
$thumbUrl = '';
$error = '';
$width = $height = 0;
if ( $img->exists() ) {
$width = $img->getWidth();
$height = $img->getHeight();
}
if ( 0 == $width || 0 == $height ) {
$width = $height = 180;
}
if ( $boxwidth == 0 ) {
$boxwidth = 180;
}
if ( $framed ) {
// Use image dimensions, don't scale
$boxwidth = $width;
$boxheight = $height;
$thumbUrl = $img->getViewURL();
} else {
if ( $boxheight === false )
$boxheight = -1;
if ( '' == $manual_thumb ) {
$thumb = $img->getThumbnail( $boxwidth, $boxheight, $wgGenerateThumbnailOnParse );
if ( $thumb ) {
$thumbUrl = $thumb->getUrl();
$boxwidth = $thumb->width;
$boxheight = $thumb->height;
} else {
$error = $img->getLastError();
}
}
}
$oboxwidth = $boxwidth + 2;
$page = isset( $params['page'] ) ? $params['page'] : false;
if ( $manual_thumb != '' ) # Use manually specified thumbnail
{
$manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ;
if ( empty( $params['width'] ) ) {
$params['width'] = 180;
}
$thumb = false;
if ( $manual_thumb != '' ) {
# Use manually specified thumbnail
$manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
if( $manual_title ) {
$manual_img = new Image( $manual_title );
$thumbUrl = $manual_img->getViewURL();
if ( $manual_img->exists() )
{
$width = $manual_img->getWidth();
$height = $manual_img->getHeight();
$boxwidth = $width ;
$boxheight = $height ;
$oboxwidth = $boxwidth + 2 ;
}
$thumb = $manual_img->getUnscaledThumb();
}
} elseif ( $framed ) {
// Use image dimensions, don't scale
$thumb = $img->getUnscaledThumb( $page );
} else {
$thumb = $img->transform( $params );
}
$u = $img->getEscapeLocalURL();
if ( $thumb ) {
$outerWidth = $thumb->getWidth() + 2;
} else {
$outerWidth = $params['width'] + 2;
}
$query = $page ? 'page=' . urlencode( $page ) : '';
$u = $img->getTitle()->getLocalURL( $query );
$more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
$magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
$textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
$s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">";
if( $thumbUrl == '' ) {
// Couldn't generate thumbnail? Scale the image client-side.
$thumbUrl = $img->getViewURL();
if( $boxheight == -1 ) {
// Approximate...
$boxheight = round( $height * $boxwidth / $width );
}
}
if ( $error ) {
$s .= htmlspecialchars( $error );
$s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
if ( !$thumb ) {
$s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
$zoomicon = '';
} elseif( !$img->exists() ) {
$s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
$zoomicon = '';
} else {
$s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
'<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
'longdesc="'.$u.'" class="thumbimage" /></a>';
$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 {

View file

@ -0,0 +1,158 @@
<?php
/**
* Base class for the output of MediaHandler::doTransform() and Image::transform().
*/
abstract class MediaTransformOutput {
/**
* Get the width of the output box
*/
function getWidth() {
return $this->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 <a> 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 <img ... /> 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 <a> 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 "<table class=\"MediaTransformError\" style=\"" .
"width: {$this->width}px; height: {$this->height}px;\"><tr><td>" .
$this->htmlMsg .
"</td></tr></table>";
}
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' ) );
}
}
?>

View file

@ -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]

View file

@ -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 );
}
/**

226
includes/media/Bitmap.php Normal file
View file

@ -0,0 +1,226 @@
<?php
class BitmapHandler extends ImageHandler {
function normaliseParams( $image, &$params ) {
global $wgMaxImageArea;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
$mimeType = $image->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;
}
}
?>

203
includes/media/DjVu.php Normal file
View file

@ -0,0 +1,203 @@
<?php
class DjVuHandler extends ImageHandler {
function isEnabled() {
global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
return false;
} else {
return true;
}
}
function mustRender() { return true; }
function isMultiPage() { return true; }
function validateParam( $name, $value ) {
if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
if ( $value <= 0 ) {
return false;
} else {
return true;
}
} else {
return false;
}
}
function makeParamString( $params ) {
$page = isset( $params['page'] ) ? $params['page'] : 1;
if ( !isset( $params['width'] ) ) {
return false;
}
return "{$params['width']}px-page{$page}";
}
function parseParamString( $str ) {
$m = false;
if ( preg_match( '/^(\d+)px-page(\d+)$/', $str, $m ) ) {
return array( 'width' => $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;
}
}
}
?>

292
includes/media/Generic.php Normal file
View file

@ -0,0 +1,292 @@
<?php
/**
* Media-handling base classes and generic functionality
*/
/**
* Base media handler class
*/
abstract class MediaHandler {
const TRANSFORM_LATER = 1;
/**
* Instance cache
*/
static $handlers = array();
/**
* Get a MediaHandler for a given MIME type from the instance cache
*/
static function getHandler( $type ) {
global $wgMediaHandlers;
if ( !isset( $wgMediaHandlers[$type] ) ) {
return false;
}
$class = $wgMediaHandlers[$type];
if ( !isset( self::$handlers[$class] ) ) {
self::$handlers[$class] = new $class;
if ( !self::$handlers[$class]->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;
}
}
?>

94
includes/media/SVG.php Normal file
View file

@ -0,0 +1,94 @@
<?php
class SvgHandler extends ImageHandler {
function isEnabled() {
global $wgSVGConverters, $wgSVGConverter;
if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
return false;
} else {
return true;
}
}
function mustRender() {
return true;
}
function normaliseParams( $image, &$params ) {
global $wgSVGMaxSize;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
# Don't make an image bigger than wgMaxSVGSize
$params['physicalWidth'] = $params['width'];
$params['physicalHeight'] = $params['height'];
if ( $params['physicalWidth'] > $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' );
}
}
?>

View file

@ -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]

View file

@ -2139,6 +2139,10 @@ In the latter case you can also use a link, e.g. [[{{ns:Special}}:Export/{{Media
'missingimage' => '<b>Missing image</b><br /><i>$1</i>',
'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' => '&larr; previous page',
'imgmultipagenext' => 'next page &rarr;',
'imgmultipageprev' => ' previous page',
'imgmultipagenext' => 'next page ',
'imgmultigo' => 'Go!',
'imgmultigotopre' => 'Go to page',
'imgmultigotopost' => '',

View file

@ -480,4 +480,15 @@ p.mw-ipb-conveniencelinks {
#file img, .gallerybox .thumb img {
background: url(images/Checker-16x16.png) repeat;
}
*/
*/
.MediaTransformError {
border: thin solid #777;
background-color: #ccc;
padding: 0.1em;
}
.MediaTransformError td {
text-align: center;
vertical-align: middle;
font-size: 90%;
}

View file

@ -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%;
}

View file

@ -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 <<<EOT
<html><head><title>Error generating thumbnail</title></head>
<body>
$msg
</body>
</html>
EOT;
} else {
$badtitle = wfMsg( 'badtitle' );
$badtitletext = wfMsg( 'badtitletext' );
header( 'Cache-Control: no-cache' );
header( 'Content-Type: text/html; charset=utf-8' );
header( 'HTTP/1.1 500 Internal server error' );
echo "<html><head>
<title>$badtitle</title>
<body>
@ -89,4 +111,17 @@ wfProfileOut( 'thumb.php-render' );
wfProfileOut( 'thumb.php' );
wfLogProfilingData();
//--------------------------------------------------------------------------
function thumbGetHandler( $fileName ) {
// Determine type
$magic = MimeMagic::singleton();
$extPos = strrpos( $fileName, '.' );
if ( $extPos === false ) {
return false;
}
$mime = $magic->guessTypesForExtension( substr( $fileName, $extPos + 1 ) );
return MediaHandler::getHandler( $mime );
}
?>