* 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 ==
* 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 ==
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 ==
}
}
-$conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick;
-
$conf->IP = dirname( dirname( __FILE__ ) );
print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n";
}
function writeLocalSettings( $conf ) {
- $conf->UseImageResize = $conf->UseImageResize ? 'true' : 'false';
$conf->PasswordSender = $conf->EmergencyContact;
$magic = ($conf->ImageMagick ? "" : "# ");
$convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" );
## 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}\";
$diff = $wgRequest->getVal( 'diff' );
$redirect = $wgRequest->getVal( 'redirect' );
$printable = $wgRequest->getVal( 'printable' );
+ $page = $wgRequest->getVal( 'page' );
return $wgUseFileCache
and (!$wgShowIPinHeader)
and (!isset($diff))
and (!isset($redirect))
and (!isset($printable))
+ and !isset($page)
and (!$this->mRedirectedFrom);
}
global $wgAutoloadClasses;
static $localClasses = array(
+ # Includes
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
'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',
'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',
'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',
'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',
# 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
/** 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
*/
$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;
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
);
* 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;
/**
* DJVU settings
- * Path of the djvutoxml executable
+ * Path of the djvudump executable
* Enable this and $wgDjvuRenderer to enable djvu rendering
*/
-# $wgDjvuToXML = 'djvutoxml';
-$wgDjvuToXML = null;
+# $wgDjvuDump = 'djvudump';
+$wgDjvuDump = null;
/**
* Path of the ddjvu DJVU renderer
- * Enable this and $wgDjvuToXML to enable djvu rendering
+ * 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;
+
+
/**
* Shell command for the DJVU post processor
* Default: pnmtopng, since ddjvu generates ppm output
* @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;
+ }
}
var $basename;
/**
- * The private log to log to
+ * The private log to log to, e.g. 'exif'
*/
- var $log = 'exif';
+ var $log = false;
//@}
* @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' )
* @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" );
const DELETED_FILE = 1;
const DELETED_COMMENT = 2;
const DELETED_USER = 4;
- const DELETED_RESTRICTED = 8;
+ const DELETED_RESTRICTED = 8;
+ const RENDER_NOW = 1;
/**#@+
* @private
}
$this->title =& $title;
$this->name = $title->getDBkey();
- $this->metadata = serialize ( array() ) ;
+ $this->metadata = '';
$n = strrpos( $this->name, '.' );
$this->extension = Image::normalizeExtension( $n ?
substr( $this->name, $n + 1 ) : '' );
$this->historyLine = 0;
- $this->page = 1;
$this->dataLoaded = false;
}
$this->fileExists = file_exists( $this->imagePath );
$this->fromSharedDirectory = false;
$gis = array();
- $deja = false;
if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n");
$this->mime = $magic->guessMimeType($this->imagePath,true);
$this->type = $magic->getMediaType($this->imagePath,$this->mime);
+ $handler = MediaHandler::getHandler( $this->mime );
# Get size in bytes
$this->size = filesize( $this->imagePath );
- # Height and width
- $gis = self::getImageSize( $this->imagePath, $this->mime, $deja );
+ # Height, width and metadata
+ if ( $handler ) {
+ $gis = $handler->getImageSize( $this, $this->imagePath );
+ $this->metadata = $handler->getMetadata( $this, $this->imagePath );
+ } else {
+ $gis = false;
+ $this->metadata = '';
+ }
wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
}
else {
$this->mime = NULL;
$this->type = MEDIATYPE_UNKNOWN;
+ $this->metadata = '';
wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
}
# as ther's only one thread of execution, this should be safe anyway.
$this->dataLoaded = true;
-
- if ( $deja ) {
- $this->metadata = $deja->retrieveMetaData();
- } else {
- $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
- }
-
if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
else $this->bits = 0;
$this->loadFromRow( $row );
$this->imagePath = $this->getFullPath();
// Check for rows from a previous schema, quietly upgrade them
- if ( is_null($this->type) ) {
- $this->upgradeRow();
- }
+ $this->maybeUpgradeRow();
} elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
# In case we're on a wgCapitalLinks=false wiki, we
# capitalize the first letter of the filename before
$this->loadFromRow( $row );
// Check for rows from a previous schema, quietly upgrade them
- if ( is_null($this->type) ) {
- $this->upgradeRow();
- }
+ $this->maybeUpgradeRow();
}
}
$this->type = 0;
$this->fileExists = false;
$this->fromSharedDirectory = false;
- $this->metadata = serialize ( array() ) ;
+ $this->metadata = '';
$this->mime = false;
}
if (!$minor) $minor= "unknown";
$this->mime = $major.'/'.$minor;
}
-
$this->metadata = $row->img_metadata;
- if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
$this->dataLoaded = true;
}
}
/**
- * Metadata was loaded from the database, but the row had a marker indicating it needs to be
- * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row.
+ * Upgrade a row if it needs it
+ */
+ function maybeUpgradeRow() {
+ if ( is_null($this->type) || $this->mime == 'image/svg' ) {
+ $this->upgradeRow();
+ } else {
+ $handler = $this->getHandler();
+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
+ $this->upgradeRow();
+ }
+ }
+ }
+
+ /**
+ * Fix assorted version-related problems with the image row by reloading it from the file
*/
function upgradeRow() {
global $wgDBname, $wgSharedUploadDBname;
list( $major, $minor ) = self::splitMime( $this->mime );
- wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n");
+ wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
$dbw->update( 'image',
array(
/**
* Return the width of the image
*
- * Returns -1 if the file specified is not a known image type
+ * Returns false on error
* @public
*/
- function getWidth() {
+ function getWidth( $page = 1 ) {
$this->load();
- return $this->width;
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['width'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->width;
+ }
}
/**
* Return the height of the image
*
- * Returns -1 if the file specified is not a known image type
+ * Returns false on error
* @public
*/
- function getHeight() {
+ function getHeight( $page = 1 ) {
+ $this->load();
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['height'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->height;
+ }
+ }
+
+ /**
+ * Get handler-specific metadata
+ */
+ function getMetadata() {
$this->load();
- return $this->height;
+ return $this->metadata;
}
/**
* @todo remember the result of this check.
*/
function canRender() {
- global $wgUseImageMagick, $wgDjvuRenderer;
-
- if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
-
- $mime= $this->getMimeType();
-
- if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
-
- #if it's SVG, check if there's a converter enabled
- if ($mime === 'image/svg' || $mime == 'image/svg+xml' ) {
- global $wgSVGConverters, $wgSVGConverter;
-
- if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
- wfDebug( "Image::canRender: SVG is ready!\n" );
- return true;
- } else {
- wfDebug( "Image::canRender: SVG renderer missing\n" );
- }
- }
-
- #image formats available on ALL browsers
- if ( $mime === 'image/gif'
- || $mime === 'image/png'
- || $mime === 'image/jpeg' ) return true;
-
- #image formats that can be converted to the above formats
- if ($wgUseImageMagick) {
- #convertable by ImageMagick (there are more...)
- if ( $mime === 'image/vnd.wap.wbmp'
- || $mime === 'image/x-xbitmap'
- || $mime === 'image/x-xpixmap'
- #|| $mime === 'image/x-icon' #file may be split into multiple parts
- || $mime === 'image/x-portable-anymap'
- || $mime === 'image/x-portable-bitmap'
- || $mime === 'image/x-portable-graymap'
- || $mime === 'image/x-portable-pixmap'
- #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM!
- || $mime === 'image/x-rgb'
- || $mime === 'image/x-bmp'
- || $mime === 'image/tiff' ) return true;
- }
- else {
- #convertable by the PHP GD image lib
- if ( $mime === 'image/vnd.wap.wbmp'
- || $mime === 'image/x-xbitmap' ) return true;
- }
- if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
-
- return false;
+ $handler = $this->getHandler();
+ return $handler && $handler->canRender();
}
-
/**
* Return true if the file is of a type that can't be directly
* rendered by typical browsers and needs to be re-rasterized.
* @return bool
*/
function mustRender() {
- $mime= $this->getMimeType();
-
- if ( $mime === "image/gif"
- || $mime === "image/png"
- || $mime === "image/jpeg" ) return false;
-
- return true;
+ $handler = $this->getHandler();
+ return $handler && $handler->mustRender();
}
/**
* @public
*/
function getEscapeLocalURL( $query=false) {
- $this->getTitle();
- if ( $query === false ) {
- if ( $this->page != 1 ) {
- $query = 'page=' . $this->page;
- } else {
- $query = '';
- }
- }
- return $this->title->escapeLocalURL( $query );
+ return $this->getTitle()->escapeLocalURL( $query );
}
/**
* @todo document
* @private
*/
- function thumbUrl( $width, $subdir='thumb') {
+ function thumbUrl( $thumbName ) {
global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
- global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
-
- // Generate thumb.php URL if possible
- $script = false;
- $url = false;
+ if($this->fromSharedDirectory) {
+ $base = '';
+ $path = $wgSharedUploadPath;
+ } else {
+ $base = $wgUploadBaseUrl;
+ $path = $wgUploadPath;
+ }
+ if ( Image::isHashed( $this->fromSharedDirectory ) ) {
+ $url = "{$base}{$path}/thumb" .
+ wfGetHashPath($this->name, $this->fromSharedDirectory)
+ . $this->name.'/'.$thumbName;
+ $url = wfUrlencode( $url );
+ } else {
+ $url = "{$base}{$path}/thumb/{$thumbName}";
+ }
+ return $url;
+ }
+ function getTransformScript() {
+ global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
if ( $this->fromSharedDirectory ) {
- if ( $wgSharedThumbnailScriptPath ) {
- $script = $wgSharedThumbnailScriptPath;
- }
+ $script = $wgSharedThumbnailScriptPath;
} else {
- if ( $wgThumbnailScriptPath ) {
- $script = $wgThumbnailScriptPath;
- }
+ $script = $wgThumbnailScriptPath;
}
if ( $script ) {
- $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
- if( $this->mustRender() ) {
- $url.= '&r=1';
- }
+ return "$script?f=" . urlencode( $this->name );
} else {
- $name = $this->thumbName( $width );
- if($this->fromSharedDirectory) {
- $base = '';
- $path = $wgSharedUploadPath;
- } else {
- $base = $wgUploadBaseUrl;
- $path = $wgUploadPath;
- }
- if ( Image::isHashed( $this->fromSharedDirectory ) ) {
- $url = "{$base}{$path}/{$subdir}" .
- wfGetHashPath($this->name, $this->fromSharedDirectory)
- . $this->name.'/'.$name;
- $url = wfUrlencode( $url );
- } else {
- $url = "{$base}{$path}/{$subdir}/{$name}";
- }
+ return false;
}
- return array( $script !== false, $url );
}
/**
- * Return the file name of a thumbnail of the specified width
+ * Get a ThumbnailImage which is the same size as the source
+ */
+ function getUnscaledThumb( $page = false ) {
+ if ( $page ) {
+ $params = array(
+ 'page' => $page,
+ 'width' => $this->getWidth( $page )
+ );
+ } else {
+ $params = array( 'width' => $this->getWidth() );
+ }
+ return $this->transform( $params );
+ }
+
+ /**
+ * Return the file name of a thumbnail with the specified parameters
*
- * @param integer $width Width of the thumbnail image
- * @param boolean $shared Does the thumbnail come from the shared repository?
+ * @param array $params Handler-specific parameters
* @private
*/
- function thumbName( $width ) {
- global $wgDjvuOutputExtension;
- $thumb = $width."px-".$this->name;
- if ( $this->page != 1 ) {
- $thumb = "page{$this->page}-$thumb";
+ function thumbName( $params ) {
+ $handler = $this->getHandler();
+ if ( !$handler ) {
+ return null;
}
-
- if( $this->mustRender() ) {
- if( $this->canRender() ) {
- list( $ext, $mime ) = self::getThumbType( $this->extension, $this->mime );
- if ( $ext != $this->extension ) {
- $thumb .= ".$ext";
- }
- }
- else {
- #should we use iconThumb here to get a symbolic thumbnail?
- #or should we fail with an internal error?
- return NULL; //can't make bitmap
- }
+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+ $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
+ if ( $thumbExt != $this->extension ) {
+ $thumbName .= ".$thumbExt";
}
- return $thumb;
+ return $thumbName;
}
/**
* @param integer $height maximum height of the image (optional)
* @public
*/
- function createThumb( $width, $height=-1 ) {
- $thumb = $this->getThumbnail( $width, $height );
- if( is_null( $thumb ) ) return '';
+ function createThumb( $width, $height = -1 ) {
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
+ }
+ $thumb = $this->transform( $params );
+ if( is_null( $thumb ) || $thumb->isError() ) return '';
return $thumb->getUrl();
}
*
* @return ThumbnailImage or null on failure
* @public
+ *
+ * @deprecated use transform()
*/
function getThumbnail( $width, $height=-1, $render = true ) {
- wfProfileIn( __METHOD__ );
- if ($this->canRender()) {
- if ( $height > 0 ) {
- $this->load();
- if ( $width > $this->width * $height / $this->height ) {
- $width = wfFitBoxWidth( $this->width, $this->height, $height );
- }
- }
- if ( $render ) {
- $thumb = $this->renderThumb( $width );
- } else {
- // Don't render, just return the URL
- if ( $this->validateThumbParams( $width, $height ) ) {
- if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
- $url = $this->getURL();
- } else {
- list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
- }
- $thumb = new ThumbnailImage( $url, $width, $height );
- } else {
- $thumb = null;
- }
- }
- } else {
- // not a bitmap or renderable image, don't try.
- $thumb = $this->iconThumb();
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
}
- wfProfileOut( __METHOD__ );
- return $thumb;
+ $flags = $render ? self::RENDER_NOW : 0;
+ return $this->transform( $params, $flags );
}
-
- /**
- * @return ThumbnailImage
- */
- function iconThumb() {
- global $wgStylePath, $wgStyleDirectory;
-
- $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
- foreach( $try as $icon ) {
- $path = '/common/images/icons/' . $icon;
- $filepath = $wgStyleDirectory . $path;
- if( file_exists( $filepath ) ) {
- return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
- }
- }
- return null;
- }
-
+
/**
- * Validate thumbnail parameters and fill in the correct height
+ * Transform a media file
*
- * @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.
+ * @param array $params An associative array of handler-specific parameters. Typical
+ * keys are width, height and page.
+ * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
+ * @return MediaTransformOutput
*/
- function validateThumbParams( &$width, &$height ) {
- global $wgSVGMaxSize, $wgMaxImageArea;
+ function transform( $params, $flags = 0 ) {
+ global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
- $this->load();
+ wfProfileIn( __METHOD__ );
+ do {
+ $handler = $this->getHandler();
+ if ( !$handler || !$handler->canRender() ) {
+ // not a bitmap or renderable image, don't try.
+ $thumb = $this->iconThumb();
+ break;
+ }
- if ( ! $this->exists() )
- {
- # If there is no image, there will be no thumbnail
- return false;
- }
+ $script = $this->getTransformScript();
+ if ( $script && !($flags & self::RENDER_NOW) ) {
+ // Use a script to transform on client request
+ $thumb = $handler->getScriptedTransform( $this, $script, $params );
+ break;
+ }
- $width = intval( $width );
+ $handler->normaliseParams( $this, $params );
+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+ $thumbName = $this->thumbName( $params );
+ $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
+ $thumbUrl = $this->thumbUrl( $thumbName );
- # Sanity check $width
- if( $width <= 0 || $this->width <= 0) {
- # BZZZT
- return false;
- }
+ $this->migrateThumbFile( $thumbName );
- # 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 ( $this->getMediaType() == MEDIATYPE_BITMAP &&
- $this->getMimeType() !== 'image/jpeg' &&
- $this->width * $this->height > $wgMaxImageArea )
- {
- return false;
- }
+ if ( file_exists( $thumbPath ) ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
- # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs
- if ( $this->mustRender() ) {
- $width = min( $width, $wgSVGMaxSize );
- } elseif ( $width > $this->width - 1 ) {
- $width = $this->width;
- $height = $this->height;
- return true;
- }
+ if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
+ $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+
+ // Ignore errors if requested
+ if ( !$thumb ) {
+ $thumb = null;
+ } elseif ( $thumb->isError() ) {
+ $this->lastError = $thumb->toText();
+ if ( $wgIgnoreImageErrors ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ }
+ }
+
+ if ( $wgUseSquid ) {
+ wfPurgeSquidServers( array( $thumbUrl ) );
+ }
+ } while (false);
- $height = self::scaleHeight( $this->width, $this->height, $width );
- return true;
+ wfProfileOut( __METHOD__ );
+ return $thumb;
}
/**
- * Create a thumbnail of the image having the specified width.
- * The thumbnail will not be created if the width is larger than the
- * image's width. Let the browser do the scaling in this case.
- * The thumbnail is stored on disk and is only computed if the thumbnail
- * file does not exist OR if it is older than the image.
- * Returns an object which can return the pathname, URL, and physical
- * pixel size of the thumbnail -- or null on failure.
- *
- * @return ThumbnailImage or null on failure
- * @private
+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
*/
- function renderThumb( $width, $useScript = true ) {
- global $wgUseSquid, $wgThumbnailEpoch;
-
- wfProfileIn( __METHOD__ );
-
- $this->load();
- $height = -1;
- if ( !$this->validateThumbParams( $width, $height ) ) {
- # Validation error
- wfProfileOut( __METHOD__ );
- return null;
- }
-
- if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
- # validateThumbParams (or the user) wants us to return the unscaled image
- $thumb = new ThumbnailImage( $this->getURL(), $width, $height );
- wfProfileOut( __METHOD__ );
- return $thumb;
- }
-
- list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
- if ( $isScriptUrl && $useScript ) {
- // Use thumb.php to render the image
- $thumb = new ThumbnailImage( $url, $width, $height );
- wfProfileOut( __METHOD__ );
- return $thumb;
- }
-
- $thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
+ function migrateThumbFile( $thumbName ) {
$thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
- $thumbPath = $thumbDir.'/'.$thumbName;
-
+ $thumbPath = "$thumbDir/$thumbName";
if ( is_dir( $thumbPath ) ) {
// Directory where file should be
// This happened occasionally due to broken migration code in 1.5
break;
}
}
- // Code below will ask if it exists, and the answer is now no
+ // Doesn't exist anymore
clearstatcache();
}
-
- $done = true;
- if ( !file_exists( $thumbPath ) ||
- filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) )
- {
- // Create the directory if it doesn't exist
- if ( is_file( $thumbDir ) ) {
- // File where thumb directory should be, destroy if possible
- @unlink( $thumbDir );
- }
- wfMkdirParents( $thumbDir );
-
- $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
- '/'.$thumbName;
- $done = false;
-
- // Migration from old directory structure
- if ( is_file( $oldThumbPath ) ) {
- if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) {
- if ( file_exists( $thumbPath ) ) {
- if ( !is_dir( $thumbPath ) ) {
- // Old image in the way of rename
- unlink( $thumbPath );
- } else {
- // This should have been dealt with already
- throw new MWException( "Directory where image should be: $thumbPath" );
- }
- }
- // Rename the old image into the new location
- rename( $oldThumbPath, $thumbPath );
- $done = true;
- } else {
- unlink( $oldThumbPath );
- }
- }
- if ( !$done ) {
- $this->lastError = self::reallyRenderThumb( $this->imagePath, $thumbPath, $this->mime,
- $width, $height, $this->page );
- if ( $this->lastError === true ) {
- $done = true;
- } elseif( $GLOBALS['wgIgnoreImageErrors'] ) {
- // Log the error but output anyway.
- // With luck it's a transitory error...
- $done = true;
- }
-
- # Purge squid
- # This has to be done after the image is updated and present for all machines on NFS,
- # or else the old version might be stored into the squid again
- if ( $wgUseSquid ) {
- $urlArr = array( $url );
- wfPurgeSquidServers($urlArr);
- }
- }
- }
-
- if ( $done ) {
- $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
- } else {
- $thumb = null;
+ if ( is_file( $thumbDir ) ) {
+ // File where directory should be
+ unlink( $thumbDir );
+ // Doesn't exist anymore
+ clearstatcache();
}
- wfProfileOut( __METHOD__ );
- return $thumb;
- } // END OF function renderThumb
-
- /**
- * Really render a thumbnail
- * Call this only for images for which canRender() returns true.
- *
- * @param string $source Source filename
- * @param string $destination Destination filename
- * @param string $mime MIME type of source
- * @param integer $width Destination width in pixels
- * @param integer $height Destination height in pixels
- * @param integer $page Which page of a multi-page document to display. Ignored
- * for source MIME types which do not support multiple pages.
- */
- static function reallyRenderThumb( $source, $destination, $mime, $width, $height, $page = false ) {
- global $wgSVGConverters, $wgSVGConverter;
- global $wgUseImageMagick, $wgImageMagickConvertCommand;
- global $wgCustomConvertCommand;
- global $wgDjvuRenderer, $wgDjvuPostProcessor;
-
- $err = false;
- $cmd = "";
- $retval = 0;
-
- if( $mime == "image/svg" || $mime == 'image/svg+xml' ) {
- #Right now we have only SVG
-
- global $wgSVGConverters, $wgSVGConverter;
- if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
- global $wgSVGConverterPath;
- $cmd = str_replace(
- array( '$path/', '$width', '$height', '$input', '$output' ),
- array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
- intval( $width ),
- intval( $height ),
- wfEscapeShellArg( $source ),
- wfEscapeShellArg( $destination ) ),
- $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
- wfProfileIn( 'rsvg' );
- wfDebug( "reallyRenderThumb SVG: $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'rsvg' );
- }
- } elseif ( $mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
- // DJVU image
- // The file contains several images. First, extract the
- // page in hi-res, if it doesn't yet exist. Then, thumbnail
- // it.
-
- $cmd = wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size=${width}x${height} " .
- wfEscapeShellArg( $source );
- if ( $wgDjvuPostProcessor ) {
- $cmd .= " | {$wgDjvuPostProcessor}";
- }
- $cmd .= ' > ' . wfEscapeShellArg($destination);
- wfProfileIn( 'ddjvu' );
- wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'ddjvu' );
-
- } elseif ( $wgUseImageMagick ) {
- # use ImageMagick
-
- if ( $mime == 'image/jpeg' ) {
- $quality = "-quality 80"; // 80%
- } elseif ( $mime == '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 {$width}" and NOT "-size {$width}x{$height}".
- # 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 {$width} ".
- wfEscapeShellArg($source) .
- // 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( "{$width}x{$height}!" ) .
- " -depth 8 " .
- wfEscapeShellArg($destination) . " 2>&1";
- wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } elseif( $wgCustomConvertCommand ) {
- # Use a custom convert command
- # Variables: %s %d %w %h
- $src = wfEscapeShellArg( $source );
- $dst = wfEscapeShellArg( $destination );
- $cmd = $wgCustomConvertCommand;
- $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
- $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
- wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } else {
- # 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.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
- );
- if( !isset( $typemap[$mime] ) ) {
- $err = 'Image type not supported';
- wfDebug( "$err\n" );
- return $err;
- }
- list( $loader, $colorStyle, $saveType ) = $typemap[$mime];
+ /**
+ * Get a MediaHandler instance for this image
+ */
+ function getHandler() {
+ return MediaHandler::getHandler( $this->getMimeType() );
+ }
- if( !function_exists( $loader ) ) {
- $err = "Incomplete GD library configuration: missing function $loader";
- wfDebug( "$err\n" );
- return $err;
- }
+ /**
+ * Get a ThumbnailImage representing a file type icon
+ * @return ThumbnailImage
+ */
+ function iconThumb() {
+ global $wgStylePath, $wgStyleDirectory;
- $src_image = call_user_func( $loader, $source );
- $dst_image = imagecreatetruecolor( $width, $height );
- imagecopyresampled( $dst_image, $src_image,
- 0,0,0,0,
- $width, $height, imagesx( $src_image ), imagesy( $src_image ) );
- call_user_func( $saveType, $dst_image, $destination );
- imagedestroy( $dst_image );
- imagedestroy( $src_image );
- }
-
- #
- # Check for zero-sized thumbnails. Those can be generated when
- # no disk space is available or some other error occurs
- #
- $removed = false;
- if( file_exists( $destination ) ) {
- $thumbstat = stat( $destination );
- if( $thumbstat['size'] == 0 || $retval != 0 ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'Removing bad %d-byte thumbnail "%s"',
- $thumbstat['size'], $destination ) );
- unlink( $destination );
- $removed = true;
+ $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
+ foreach( $try as $icon ) {
+ $path = '/common/images/icons/' . $icon;
+ $filepath = $wgStyleDirectory . $path;
+ if( file_exists( $filepath ) ) {
+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
}
}
- if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
- return wfMsg( 'thumbnail_error', $err );
- } else {
- return true;
- }
+ return null;
}
+ /**
+ * Get last thumbnailing error.
+ * Largely obsolete.
+ */
function getLastError() {
return $this->lastError;
}
- static function imageJpegWrapper( $dst_image, $thumbPath ) {
- imageinterlace( $dst_image );
- imagejpeg( $dst_image, $thumbPath, 95 );
- }
-
/**
* Get all thumbnail names previously generated for this image
*/
*/
function purgeMetadataCache() {
clearstatcache();
- $this->loadFromFile();
+ $this->upgradeRow();
$this->saveToCache();
}
foreach ( $files as $file ) {
$m = array();
if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
- list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
+ $url = $this->thumbUrl( $m[1] );
$urls[] = $url;
@unlink( "$dir/$file" );
}
$update->doUpdate();
}
+ /**
+ * Check the image table schema on the given connection for subtle problems
+ */
function checkDBSchema(&$db) {
static $checkDone = false;
global $wgCheckDBSchema;
return $retVal;
}
- /**
- * Retrive Exif data from the file and prune unrecognized tags
- * and/or tags with invalid contents
- *
- * @param $filename
- * @return array
- */
- private function retrieveExifData( $filename ) {
- global $wgShowEXIF;
-
- /*
- if ( $this->getMimeType() !== "image/jpeg" )
- return array();
- */
-
- if( $wgShowEXIF && file_exists( $filename ) ) {
- $exif = new Exif( $filename );
- return $exif->getFilteredData();
- }
-
- return array();
- }
-
function getExifData() {
global $wgRequest;
- if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
+ $handler = $this->getHandler();
+ if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
return array();
-
- $purge = $wgRequest->getVal( 'action' ) == 'purge';
- $ret = unserialize( $this->metadata );
-
- $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0;
- $newver = Exif::version();
-
- if ( !count( $ret ) || $purge || $oldver != $newver ) {
- $this->purgeMetadataCache();
- $this->updateExifData( $newver );
}
- if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) )
- unset( $ret['MEDIAWIKI_EXIF_VERSION'] );
- $format = new FormatExif( $ret );
-
- return $format->getFormattedData();
- }
-
- function updateExifData( $version ) {
- if ( $this->getImagePath() === false ) # Not a local image
- return;
-
- # Get EXIF data from image
- $exif = $this->retrieveExifData( $this->imagePath );
- if ( count( $exif ) ) {
- $exif['MEDIAWIKI_EXIF_VERSION'] = $version;
- $this->metadata = serialize( $exif );
- } else {
- $this->metadata = '0';
+ if ( !$this->metadata ) {
+ return array();
}
+ $exif = unserialize( $this->metadata );
+ if ( !$exif ) {
+ return array();
+ }
+ $format = new FormatExif( $exif );
- # Update EXIF data in database
- $dbw = wfGetDB( DB_MASTER );
-
- $this->checkDBSchema($dbw);
-
- $dbw->update( 'image',
- array( 'img_metadata' => $this->metadata ),
- array( 'img_name' => $this->name ),
- __METHOD__
- );
+ return $format->getFormattedData();
}
/**
// an archived file revision.
if( is_null( $row->fa_metadata ) ) {
$tempFile = $store->filePath( $row->fa_storage_key );
- $metadata = serialize( $this->retrieveExifData( $tempFile ) );
$magic = MimeMagic::singleton();
$mime = $magic->guessMimeType( $tempFile, true );
$media_type = $magic->getMediaType( $tempFile, $mime );
list( $major_mime, $minor_mime ) = self::splitMime( $mime );
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ $metadata = $handler->getMetadata( $image, $tempFile );
+ } else {
+ $metadata = '';
+ }
} else {
$metadata = $row->fa_metadata;
$major_mime = $row->fa_major_mime;
return $revisions;
}
- /**
- * Select a page from a multipage document. Determines the page used for
- * rendering thumbnails.
- *
- * @param $page Integer: page number, starting with 1
- */
- function selectPage( $page ) {
- if( $this->initializeMultiPageXML() ) {
- wfDebug( __METHOD__." selecting page $page \n" );
- $this->page = $page;
- $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
- $this->height = intval( $o['height'] );
- $this->width = intval( $o['width'] );
- } else {
- wfDebug( __METHOD__." selectPage($page) for bogus multipage xml on '$this->name'\n" );
- return;
- }
- }
-
- /**
- * Lazy-initialize multipage XML metadata for DjVu files.
- * @return bool true if $this->multiPageXML is set up and ready;
- * false if corrupt or otherwise failing
- */
- function initializeMultiPageXML() {
- $this->load();
- if ( isset( $this->multiPageXML ) ) {
- return true;
- }
-
- #
- # Check for files uploaded prior to DJVU support activation,
- # or damaged.
- #
- if( empty( $this->metadata ) || $this->metadata == serialize( array() ) ) {
- $deja = new DjVuImage( $this->imagePath );
- $this->metadata = $deja->retrieveMetaData();
- $this->purgeMetadataCache();
-
- # Update metadata in the database
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'image',
- array( 'img_metadata' => $this->metadata ),
- array( 'img_name' => $this->name ),
- __METHOD__
- );
- }
- wfSuppressWarnings();
- try {
- $this->multiPageXML = new SimpleXMLElement( $this->metadata );
- } catch( Exception $e ) {
- wfDebug( "Bogus multipage XML metadata on '$this->name'\n" );
- $this->multiPageXML = null;
- }
- wfRestoreWarnings();
- return isset( $this->multiPageXML );
- }
-
/**
* Returns 'true' if this image is a multipage document, e.g. a DJVU
* document.
* @return Bool
*/
function isMultipage() {
- return ( $this->mime == 'image/vnd.djvu' );
+ $handler = $this->getHandler();
+ return $handler && $handler->isMultiPage();
}
/**
* documents which aren't multipage documents
*/
function pageCount() {
- if ( ! $this->isMultipage() ) {
- return null;
- }
- if( $this->initializeMultiPageXML() ) {
- return count( $this->multiPageXML->xpath( '//OBJECT' ) );
+ $handler = $this->getHandler();
+ if ( $handler && $handler->isMultiPage() ) {
+ return $handler->pageCount( $this );
} else {
- wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" );
return null;
}
}
*/
static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
// Exact integer multiply followed by division
- return round( $srcHeight * $dstWidth / $srcWidth );
+ if ( $srcWidth == 0 ) {
+ return 0;
+ } else {
+ return round( $srcHeight * $dstWidth / $srcWidth );
+ }
}
/**
* can't be determined.
*
* @param string $fileName The filename
- * @param string $mimeType The MIME type of the file
- * @param object $deja Filled with a DjVu object if the mime type is image/vnd.djvu
* @return array
*/
- static function getImageSize( $fileName, $mimeType, &$deja ) {
- $magic =& MimeMagic::singleton();
- if( $mimeType == 'image/svg' || $mimeType == 'image/svg+xml' ) {
- $gis = wfGetSVGsize( $fileName );
- } elseif( $mimeType == 'image/vnd.djvu' ) {
- wfSuppressWarnings();
- $deja = new DjVuImage( $fileName );
- $gis = $deja->getImageSize();
- wfRestoreWarnings();
- } elseif ( !$magic->isPHPImageType( $mimeType ) ) {
- # Don't try to get the width and height of sound and video files, that's bad for performance
- $gis = false;
- } else {
- wfSuppressWarnings();
- $gis = getimagesize( $fileName );
- wfRestoreWarnings();
- }
- return $gis;
+ function getImageSize( $fileName ) {
+ $handler = $this->getHandler();
+ return $handler->getImageSize( $this, $fileName );
}
/**
* @return array thumbnail extension and MIME type
*/
static function getThumbType( $ext, $mime ) {
- switch ( $mime ) {
- case 'image/svg':
- case 'image/svg+xml':
- $ext = 'png';
- $mime = 'image/png';
- break;
- case 'image/vnd.djvu':
- $ext = $GLOBALS['wgDjvuOutputExtension'];
- $magic = MimeMagic::singleton();
- $mime = $magic->guessTypesForExtension( $ext );
- break;
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ return $handler->getThumbType( $ext, $mime );
+ } else {
+ return array( $ext, $mime );
}
- return array( $ext, $mime );
}
-
} //class
class ArchivedFile
}
}
-/**
- * Wrapper class for thumbnail images
- */
-class ThumbnailImage {
- /**
- * @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;
- $this->width = round( $width );
- $this->height = round( $height );
- # 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->path = $path;
- }
-
- /**
- * @return string The thumbnail URL
- */
- function getUrl() {
- return $this->url;
- }
-
- /**
- * 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.
- *
- * @param array $attribs
- * @return string
- * @public
- */
- function toHtml( $attribs = array() ) {
- $attribs['src'] = $this->url;
- $attribs['width'] = $this->width;
- $attribs['height'] = $this->height;
- if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
-
- $html = '<img ';
- foreach( $attribs as $name => $data ) {
- $html .= $name . '="' . htmlspecialchars( $data ) . '" ';
- }
- $html .= '/>';
- return $html;
- }
-
-}
-
/**
* Aliases for backwards compatibility with 1.6
*/
*
*/
function toHTML() {
- global $wgLang, $wgGenerateThumbnailOnParse;
+ global $wgLang;
$sk = $this->getSkin();
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];
# 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>';
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' );
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;
$height = $height_orig;
$mime = $this->img->getMimeType();
$showLink = false;
+ $linkAttribs = array( 'href' => $full_url );
if ( $this->img->allowInlineDisplay() and $width and $height) {
# image
# 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();
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 = '';
}
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.
}
/** @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 )
{
$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
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);
+ if ( $params['width'] && $img->exists() ) {
+ # Create a resized image, without the additional thumbnail features
+ $thumb = $img->transform( $params );
+ } else {
+ $thumb = false;
+ }
- 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 ( $page ) {
+ $query = 'page=' . urlencode( $page );
} else {
- $width = $img->width;
- $height = $img->height;
+ $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
+ );
- wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" );
- $u = $nt->escapeLocalURL();
- if ( $error ) {
- $s = $error;
- } elseif ( $url == '' ) {
+ 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>";
* 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;
+ $page = isset( $params['page'] ) ? $params['page'] : false;
+
+ if ( empty( $params['width'] ) ) {
+ $params['width'] = 180;
}
- if ( $framed ) {
+ $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 );
+ $thumb = $manual_img->getUnscaledThumb();
+ }
+ } elseif ( $framed ) {
// Use image dimensions, don't scale
- $boxwidth = $width;
- $boxheight = $height;
- $thumbUrl = $img->getViewURL();
+ $thumb = $img->getUnscaledThumb( $page );
} 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();
- }
- }
+ $thumb = $img->transform( $params );
}
- $oboxwidth = $boxwidth + 2;
- if ( $manual_thumb != '' ) # Use manually specified thumbnail
- {
- $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $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 ;
- }
- }
+ if ( $thumb ) {
+ $outerWidth = $thumb->getWidth() + 2;
+ } else {
+ $outerWidth = $params['width'] + 2;
}
- $u = $img->getEscapeLocalURL();
+ $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 {
--- /dev/null
+<?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' ) );
+ }
+}
+
+?>
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
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]
* 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
# * bottom
# * text-bottom
+
$part = array_map( 'trim', explode( '|', $options) );
$mwAlign = array();
$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
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;
$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 );
}
/**
--- /dev/null
+<?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;
+ }
+
+}
+
+?>
--- /dev/null
+<?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;
+ }
+ }
+}
+
+?>
--- /dev/null
+<?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;
+ }
+}
+
+?>
--- /dev/null
+<?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' );
+ }
+}
+?>
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]
'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',
* Nederlands|nl",
# Multipage image navigation
-'imgmultipageprev' => '← previous page',
-'imgmultipagenext' => 'next page →',
+'imgmultipageprev' => '← previous page',
+'imgmultipagenext' => 'next page →',
'imgmultigo' => 'Go!',
'imgmultigotopre' => 'Go to page',
'imgmultigotopost' => '',
#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%;
+}
+
.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%;
+}
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
$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;
}
$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>
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 );
+}
+
?>