Optional thumbnail generation by client request, using thumb.php. This removes any need for access to image files on page view. Experimental, some aspects still haven't been tested.

This commit is contained in:
Tim Starling 2005-04-16 04:33:34 +00:00
parent 16bcc74aa3
commit 9411d91b49
7 changed files with 306 additions and 180 deletions

View file

@ -13,6 +13,7 @@ define( 'MEDIAWIKI', true );
require_once( 'includes/Defines.php' );
require_once( './LocalSettings.php' );
require_once( 'includes/Setup.php' );
require_once( 'includes/StreamFile.php' );
if( !isset( $_SERVER['PATH_INFO'] ) ) {
wfForbidden();
@ -40,142 +41,7 @@ if( is_dir( $filename ) ) {
}
# Write file
$type = wfGetType( $filename );
if ( $type ) {
header('Content-type: '.$type);
} else {
header('Content-type: application/x-wiki');
}
readfile( $filename );
function wfGetType( $filename ) {
# There's probably a better way to do this
$types = <<<END_STRING
application/andrew-inset ez
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mathml+xml mathml
application/msword doc
application/octet-stream bin dms lha lzh exe class so dll
application/oda oda
application/ogg ogg
application/pdf pdf
application/postscript ai eps ps
application/rdf+xml rdf
application/smil smi smil
application/srgs gram
application/srgs+xml grxml
application/vnd.mif mif
application/vnd.ms-excel xls
application/vnd.ms-powerpoint ppt
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/voicexml+xml vxml
application/x-bcpio bcpio
application/x-cdlink vcd
application/x-chess-pgn pgn
application/x-cpio cpio
application/x-csh csh
application/x-director dcr dir dxr
application/x-dvi dvi
application/x-futuresplash spl
application/x-gtar gtar
application/x-hdf hdf
application/x-javascript js
application/x-koan skp skd skt skm
application/x-latex latex
application/x-netcdf nc cdf
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-stuffit sit
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-tar tar
application/x-tcl tcl
application/x-tex tex
application/x-texinfo texinfo texi
application/x-troff t tr roff
application/x-troff-man man
application/x-troff-me me
application/x-troff-ms ms
application/x-ustar ustar
application/x-wais-source src
application/xhtml+xml xhtml xht
application/xslt+xml xslt
application/xml xml xsl
application/xml-dtd dtd
application/zip zip
audio/basic au snd
audio/midi mid midi kar
audio/mpeg mpga mp2 mp3
audio/x-aiff aif aiff aifc
audio/x-mpegurl m3u
audio/x-pn-realaudio ram rm
audio/x-pn-realaudio-plugin rpm
audio/x-realaudio ra
audio/x-wav wav
chemical/x-pdb pdb
chemical/x-xyz xyz
image/bmp bmp
image/cgm cgm
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
image/png png
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
image/vnd.wap.wbmp wbmp
image/x-cmu-raster ras
image/x-icon ico
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
model/iges igs iges
model/mesh msh mesh silo
model/vrml wrl vrml
text/calendar ics ifb
text/css css
text/richtext rtx
text/rtf rtf
text/sgml sgml sgm
text/tab-separated-values tsv
text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/x-setext etx
video/mpeg mpeg mpg mpe
video/quicktime qt mov
video/vnd.mpegurl mxu
video/x-msvideo avi
video/x-sgi-movie movie
x-conference/x-cooltalk ice
END_STRING;
// Needed for windows servers who use \r\n not \n
$endl = "
";
$types = explode( $endl, $types );
if ( !preg_match( "/\.([^.]*?)$/", $filename, $matches ) ) {
return false;
}
foreach( $types as $type ) {
$extensions = explode( " ", $type );
for ( $i=1; $i<count( $extensions ); $i++ ) {
if ( $extensions[$i] == $matches[1] ) {
return $extensions[0];
}
}
}
return false;
}
wfStreamFile( $filename );
function wfForbidden() {
header( 'HTTP/1.0 403 Forbidden' );

View file

@ -139,6 +139,18 @@ $wgSharedUploadDBname = false;
/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */
$wgCacheSharedUploads = true;
/**
* Give a path here to use thumb.php for thumbnail generation on client request, instead of
* generating them on render and outputting a static URL. This is necessary if some of your
* apache servers don't have read/write access to the thumbnail path.
*
* Example:
* $wgThumnailScriptPath = "{$wgScriptPath}/thumb.php";
*/
$wgThumbnailScriptPath = false;
$wgSharedThumbnailScriptPath = false;
/**
* Set the following to false especially if you have a set of files that need to
* be accessible by all wikis, and you do not want to use the hash (path/a/aa/)

View file

@ -48,11 +48,11 @@ class Image
/**
* Obsolete factory function, use constructor
*/
function newFromTitle( &$title ) {
function newFromTitle( $title ) {
return new Image( $title );
}
function Image( &$title ) {
function Image( $title ) {
$this->title =& $title;
$this->name = $title->getDBkey();
@ -105,6 +105,7 @@ class Image
$this->height = $commonsCachedValues['height'];
$this->bits = $commonsCachedValues['bits'];
$this->type = $commonsCachedValues['type'];
$this->size = $commonsCachedValues['size'];
$this->fromSharedDirectory = true;
$this->dataLoaded = true;
}
@ -118,6 +119,7 @@ class Image
$this->height = $cachedValues['height'];
$this->bits = $cachedValues['bits'];
$this->type = $cachedValues['type'];
$this->size = $cachedValues['size'];
$this->fromSharedDirectory = false;
$this->dataLoaded = true;
}
@ -145,7 +147,8 @@ class Image
'width' => $this->width,
'height' => $this->height,
'bits' => $this->bits,
'type' => $this->type);
'type' => $this->type,
'size' => $this->size);
$wgMemc->set( $keys[0], $cachedValues );
}
@ -155,23 +158,25 @@ class Image
* Load metadata from the file itself
*/
function loadFromFile() {
global $wgUseSharedUploads, $wgSharedUploadDirectory;
global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgLang;
$fname = 'Image::loadFromFile';
wfProfileIn( $fname );
$this->imagePath = $this->getFullPath();
$this->fileExists = file_exists( $this->imagePath );
$gis = false;
# If the file is not found, and a non-wiki shared upload directory is used, look for it there.
# If the file is not found, and a shared upload directory is used, look for it there.
if (!$this->fileExists && $wgUseSharedUploads && $wgSharedUploadDirectory) {
# In case we're on a wgCapitalLinks=false wiki, we
# capitalize the first letter of the filename before
# looking it up in the shared repository.
$this->name = $wgLang->ucfirst($name);
$this->imagePath = $this->getFullPath(true);
$this->fileExists = file_exists( $this->imagePath);
$this->fromSharedDirectory = true;
$sharedImage = Image::newFromName( $wgLang->ucfirst($this->name) );
$this->fileExists = file_exists( $sharedImage->getFullPath(true) );
if ( $this->fileExists ) {
$this->name = $sharedImage->name;
$this->imagePath = $this->getFulPath(true);
$this->fromSharedDirectory = true;
}
}
if ( $this->fileExists ) {
@ -505,6 +510,7 @@ class Image
if ( $height == -1 ) {
return $this->renderThumb( $width );
}
$this->load();
if ( $width < $this->width ) {
$thumbheight = $this->height * $width / $this->width;
$thumbwidth = $width;
@ -534,7 +540,7 @@ class Image
$path = '/common/images/' . $icon;
$filepath = $wgStyleDirectory . $path;
if( file_exists( $filepath ) ) {
return new ThumbnailImage( $filepath, $wgStylePath . $path );
return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
}
}
return null;
@ -552,13 +558,15 @@ class Image
* @return ThumbnailImage
* @access private
*/
function /* private */ renderThumb( $width ) {
function /* private */ renderThumb( $width, $useScript = true ) {
global $wgImageMagickConvertCommand;
global $wgUseImageMagick;
global $wgUseSquid, $wgInternalServer;
global $wgThumbnailScriptPath, $wgSharedThumbnailScriptPath;
$width = IntVal( $width );
$this->load();
$thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
$thumbPath = wfImageThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).'/'.$thumbName;
$thumbUrl = $this->thumbUrl( $width );
@ -577,7 +585,30 @@ class Image
if( $width > $this->width && !$this->mustRender() ) {
# Don't make an image bigger than the source
return new ThumbnailImage( $this->getImagePath(), $this->getViewURL() );
return new ThumbnailImage( $this->getViewURL(), $this->getWidth(), $this->getHeight() );
}
$height = floor( $this->height * ( $width/$this->width ) );
// Generate thumb.php URL if possible
$thumbScript = false;
$scriptUrl = false;
if ( $this->fromSharedDirectory ) {
if ( $wgSharedThumbnailScriptPath ) {
$thumbScript = $wgSharedThumbnailScriptPath;
}
} else {
if ( $wgThumbnailScriptPath ) {
$thumbScript = $wgThumbnailScriptPath;
}
}
if ( $thumbScript ) {
$scriptUrl = $thumbScript . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
if ( $useScript ) {
// Use thumb.php to render the image
return new ThumbnailImage( , $width, $height );
}
}
if ( (! file_exists( $thumbPath ) ) || ( filemtime($thumbPath) < filemtime($this->imagePath) ) ) {
@ -635,7 +666,6 @@ class Image
return 'Image type not supported';
break;
}
$height = floor( $this->height * ( $width/$this->width ) );
if ( $truecolor ) {
$dst_image = imagecreatetruecolor( $width, $height );
} else {
@ -683,10 +713,13 @@ class Image
$urlArr = Array(
$wgInternalServer.$thumbUrl
);
if ( $scriptUrl ) {
$urlArr[] = $scriptUrl;
}
wfPurgeSquidServers($urlArr);
}
}
return new ThumbnailImage( $thumbPath, $thumbUrl );
return new ThumbnailImage( $thumbUrl, $width, $height );
} // END OF function createThumb
/**
@ -889,6 +922,8 @@ class Image
* Returns the image directory of an image
* If the directory does not exist, it is created.
* The result is an absolute path.
*
* This function is called from thumb.php before Setup.php is included
*
* @param string $fname file name of the image file
* @access public
@ -913,6 +948,8 @@ function wfImageDir( $fname ) {
* Returns the image directory of an image's thubnail
* If the directory does not exist, it is created.
* The result is an absolute path.
*
* This function is called from thumb.php before Setup.php is included
*
* @param string $fname file name of the thumbnail file, including file size prefix
* @param string $subdir (optional) subdirectory of the image upload directory that should be used for storing the thumbnail. Default is 'thumb'
@ -927,6 +964,8 @@ function wfImageThumbDir( $fname , $subdir='thumb', $shared=false) {
* Returns the image directory of an image's old version
* If the directory does not exist, it is created.
* The result is an absolute path.
*
* This function is called from thumb.php before Setup.php is included
*
* @param string $fname file name of the thumbnail file, including file size prefix
* @param string $subdir (optional) subdirectory of the image upload directory that should be used for storing the old version. Default is 'archive'
@ -1090,17 +1129,10 @@ class ThumbnailImage {
* @param string $url URL path to the thumb
* @access private
*/
function ThumbnailImage( $path, $url ) {
function ThumbnailImage( $url, $width, $height ) {
$this->url = $url;
$this->path = $path;
$size = @getimagesize( $this->path );
if( $size ) {
$this->width = $size[0];
$this->height = $size[1];
} else {
$this->width = 0;
$this->height = 0;
}
$this->width = $width;
$this->height = $height;
}
/**
@ -1136,18 +1168,5 @@ class ThumbnailImage {
return $html;
}
/**
* Return the size of the thumbnail file, in bytes or false if the file
* can't be stat().
* @access public
*/
function getSize() {
$st = stat( $this->path );
if( $st ) {
return $st['size'];
} else {
return false;
}
}
}
?>

View file

@ -51,7 +51,7 @@ class ImageGallery
/**
* isEmpty() returns false iff the gallery doesn't contain any images
* isEmpty() returns true if the gallery contains no images
*/
function isEmpty() {
return empty( $this->mImages );
@ -119,13 +119,13 @@ class ImageGallery
} else {
$nb = wfMsg( 'filemissing' );
}
$nb = htmlspecialchars( $nb ) . '<br />';
$nb = htmlspecialchars( $nb ) . "<br />\n";
} else {
$nb = '';
}
$textlink = $this->mShowFilename ?
$sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20, '...' ) ) ) . '<br />' :
$sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20, '...' ) ) ) . "<br />\n" :
'' ;
$s .= ($i%4==0) ? '<tr>' : '';

View file

@ -88,9 +88,9 @@ function wfSpecialNewimages() {
$img = Image::newFromTitle( $nt );
$ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
$gallery->add( $img, $ul.'<br /><i>'.$wgLang->timeanddate( $s->img_timestamp, true ).'</i><br />' );
$gallery->add( $img, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
$timestamp = wfTImestamp( TS_MW, $s->img_timestamp );
$timestamp = wfTimestamp( TS_MW, $s->img_timestamp );
if( empty( $firstTimestamp ) ) {
$firstTimestamp = $timestamp;
}

157
includes/StreamFile.php Normal file
View file

@ -0,0 +1,157 @@
<?php
function wfStreamFile( $fname ) {
global $wgSquidMaxage;
$stat = stat( $fname );
if ( !$stat ) {
header( 'HTTP/1.0 404 Not Found' );
echo "<html><body>
<h1>File not found</h1>
<p>Although this PHP script ({$_SERVER['SCRIPT_NAME']}) exists, the file requested for output
does not.</p>
</body></html>";
return;
}
$type = wfGetType( $fname );
if ( $type ) {
header("Content-type: $type");
} else {
header('Content-type: application/x-wiki');
}
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) );
header( "Cache-Control: s-maxage=$wgSquidMaxage, must-revalidate, max-age=0" );
readfile( $fname );
exit;
}
function wfGetType( $filename ) {
# There's probably a better way to do this
$types = <<<END_STRING
application/andrew-inset ez
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mathml+xml mathml
application/msword doc
application/octet-stream bin dms lha lzh exe class so dll
application/oda oda
application/ogg ogg
application/pdf pdf
application/postscript ai eps ps
application/rdf+xml rdf
application/smil smi smil
application/srgs gram
application/srgs+xml grxml
application/vnd.mif mif
application/vnd.ms-excel xls
application/vnd.ms-powerpoint ppt
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/voicexml+xml vxml
application/x-bcpio bcpio
application/x-cdlink vcd
application/x-chess-pgn pgn
application/x-cpio cpio
application/x-csh csh
application/x-director dcr dir dxr
application/x-dvi dvi
application/x-futuresplash spl
application/x-gtar gtar
application/x-hdf hdf
application/x-javascript js
application/x-koan skp skd skt skm
application/x-latex latex
application/x-netcdf nc cdf
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-stuffit sit
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-tar tar
application/x-tcl tcl
application/x-tex tex
application/x-texinfo texinfo texi
application/x-troff t tr roff
application/x-troff-man man
application/x-troff-me me
application/x-troff-ms ms
application/x-ustar ustar
application/x-wais-source src
application/xhtml+xml xhtml xht
application/xslt+xml xslt
application/xml xml xsl
application/xml-dtd dtd
application/zip zip
audio/basic au snd
audio/midi mid midi kar
audio/mpeg mpga mp2 mp3
audio/x-aiff aif aiff aifc
audio/x-mpegurl m3u
audio/x-pn-realaudio ram rm
audio/x-pn-realaudio-plugin rpm
audio/x-realaudio ra
audio/x-wav wav
chemical/x-pdb pdb
chemical/x-xyz xyz
image/bmp bmp
image/cgm cgm
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
image/png png
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
image/vnd.wap.wbmp wbmp
image/x-cmu-raster ras
image/x-icon ico
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
model/iges igs iges
model/mesh msh mesh silo
model/vrml wrl vrml
text/calendar ics ifb
text/css css
text/richtext rtx
text/rtf rtf
text/sgml sgml sgm
text/tab-separated-values tsv
text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/x-setext etx
video/mpeg mpeg mpg mpe
video/quicktime qt mov
video/vnd.mpegurl mxu
video/x-msvideo avi
video/x-sgi-movie movie
x-conference/x-cooltalk ice
END_STRING;
// Needed for windows servers who use \r\n not \n
$endl = "
";
$types = explode( $endl, $types );
if ( !preg_match( "/\.([^.]*?)$/", $filename, $matches ) ) {
return false;
}
foreach( $types as $type ) {
$extensions = explode( " ", $type );
for ( $i=1; $i<count( $extensions ); $i++ ) {
if ( $extensions[$i] == $matches[1] ) {
return $extensions[0];
}
}
}
return false;
}
?>

72
thumb.php Normal file
View file

@ -0,0 +1,72 @@
<?php
/**
* PHP script to stream out an image thumbnail.
* If the file exists, we make do with abridged MediaWiki initialisation.
*/
unset( $IP );
define( 'MEDIAWIKI', true );
require_once( './includes/Defines.php' );
require_once( './LocalSettings.php' );
require_once( 'Image.php' );
require_once( 'StreamFile.php' );
// Get input parameters
if ( get_magic_quotes_gpc() ) {
$fileName = stripslashes( $_REQUEST['f'] );
$width = stripslashes( $_REQUEST['w'] );
} else {
$fileName = $_REQUEST['f'];
$width = $_REQUEST['w'];
}
// Some basic input validation
$width = intval( $width );
$fileName = str_replace( '/', '_', $fileName );
// Work out paths, carefully avoiding constructing an Image object because that won't work yet
$imagePath = wfImageDir( $fileName ) . '/' . $fileName;
$thumbName = "{$width}px-$fileName";
if ( preg_match( '/\.svg$/', $fileName ) ) {
$thumbName .= '.png';
}
$thumbPath = wfImageThumbDir( $thumbName ) . '/' . $thumbName;
if ( file_exists( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
wfStreamFile( $thumbPath );
exit;
}
// OK, no valid thumbnail, time to get out the heavy machinery
require_once( 'Setup.php' );
// Force renderThumb() to actually do something
$wgThumbnailScriptPath = false;
$wgSharedThumbnailScriptPath = false;
$img = Image::newFromName( $fileName );
if ( $img ) {
$thumb = $img->renderThumb( $width );
} else {
$thumb = false;
}
if ( $thumb ) {
wfStreamFile( $thumb->path );
} else {
$badtitle = wfMsg( 'badtitle' );
$badtitletext = wfMsg( 'badtitletext' );
echo "<html><head>
<title>$badtitle</title>
<body>
<h1>$badtitle</h1>
<p>$badtitletext</p>
</body></html>";
}
?>