Fixed parts of EGW Stylite Bug #8260. Very wide images do not longer get displayed very broadly in the filemanager select widget.

To achive this, some changes to the CSS were made and the ability to return thumbnails with a given size was added to etemplate/thumbnail.php. Miniature images with dimensions smaller than one pixel are now no longer created.
In this progress I discovered a minor security issue which might cause multiple egroupware instances to mix up their thumbnails in rare occasions. This was fixed by now hashing the image path together with the webserver url and the thumbnail size. In this progress, the thumbnail generation code has been restructured, making it faster and much easier to understand.
This commit is contained in:
Andreas Stöckel 2010-12-30 19:54:43 +00:00
parent f7ea18ade4
commit 2a71bab988
4 changed files with 276 additions and 108 deletions

View File

@ -320,7 +320,8 @@ class vfs_widget
{ {
$path = parse_url(egw_vfs::resolve_url_symlinks($path),PHP_URL_PATH); $path = parse_url(egw_vfs::resolve_url_symlinks($path),PHP_URL_PATH);
} }
$value = $GLOBALS['egw']->link('/etemplate/thumbnail.php',array('path' => $path)); // Append the "thsize" option, which will only create 16x16 thumbnails
$value = $GLOBALS['egw']->link('/etemplate/thumbnail.php',array('path' => $path, 'thsize' => '16'));
} }
else else
{ {

View File

@ -119,7 +119,7 @@
font-family: monospace; font-weight: bold; font-family: monospace; font-weight: bold;
} }
.vfsMimeIcon img { .vfsMimeIcon img {
height: 16px; /* height: 16px; << deactivated as images may be scaled strangely */
} }
.vfsFilename { .vfsFilename {
white-space: nowrap; white-space: nowrap;
@ -152,4 +152,4 @@
max-height: 6.75em; max-height: 6.75em;
overflow: auto; overflow: auto;
padding-right: 15px; padding-right: 15px;
} }

View File

@ -5,23 +5,15 @@
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @author Nathan Gray * @author Nathan Gray
* @author Andreas Stöckel
* @package etemplate * @package etemplate
* @version $Id$ * @version $Id$
*/ */
if (isset($_GET['app']))
{
$app = $_GET['app'];
}
elseif(isset($_GET['path']))
{
list(,$apps,$app) = explode('/',$_GET['path']);
if ($apps !== 'apps') $app = 'filemanager';
}
if (!preg_match('/^[a-z0-9_-]+$/i',$app)) die('Stop'); // just to prevent someone doing nasty things
//Set all necessary info and fire up egroupware
$GLOBALS['egw_info']['flags'] = array( $GLOBALS['egw_info']['flags'] = array(
'currentapp' => $app, 'currentapp' => get_app(),
'noheader' => true, 'noheader' => true,
'nonavbar' => true 'nonavbar' => true
); );
@ -36,125 +28,300 @@ if (get_magic_quotes_gpc() && $_GET)
// no need to keep the session open (it stops other parallel calls) // no need to keep the session open (it stops other parallel calls)
$GLOBALS['egw']->session->commit_session(); $GLOBALS['egw']->session->commit_session();
if (isset($_GET['path'])) if (!read_thumbnail(get_srcfile()))
{ {
$g_srcfile = $_GET['path'];
}
else
{
$g_srcfile = egw_link::vfs_path($_GET['app'],$_GET['id'],$_GET['file'],true);
}
$g_dstfile = $GLOBALS['egw_info']['server']['temp_dir'] . '/egw-thumbs'.$g_srcfile;
$g_srcfile = egw_vfs::PREFIX.$g_srcfile;
if (!file_exists($g_srcfile) || !egw_vfs::is_readable($g_srcfile))
{
//error_log("file_exists('$g_srcfile')=".(int)file_exists($g_srcfile).", egw_vfs::is_readable('$g_srcfile')=".(int)egw_vfs::is_readable($g_srcfile));
header('404 Not found'); header('404 Not found');
exit;
}
// Check for existing thumbnail
if(file_exists($g_dstfile) && filemtime($g_dstfile) >= filemtime($g_srcfile)) {
header('Content-Type: image/png');
readfile($g_dstfile);
return;
}
$thumbnail = get_thumbnail($file, true);
if($thumbnail) {
header('Content-Type: image/png');
imagepng( $thumbnail );
imagedestroy($thumbnail);
} }
/** /**
* Private function to get a thumbnail image for a linked image file. * Reads the source file from the path parameters
* */
* This function creates a thumbnail of the given image, if possible, and stores it in $GLOBALS['egw_info']['server']['temp_dir']. function get_srcfile()
* Returns the image, or false if the file could not be thumbnailed. Thumbnails are PNGs.
*
* @param array $file VFS File array to thumbnail
* @return image or false
*
* @author Nathan Gray
*/
function get_thumbnail($file, $return_data = true)
{ {
global $g_srcfile,$g_dstfile; if (isset($_GET['path']))
{
$g_srcfile = $_GET['path'];
}
else
{
$g_srcfile = egw_link::vfs_path($_GET['app'], $_GET['id'], $_GET['file'], true);
}
$max_width = $max_height = (string)$GLOBALS['egw_info']['server']['link_list_thumbnail'] == '' ? 32 : return egw_vfs::PREFIX.$g_srcfile;
}
/**
* Returns the currently active app.
*/
function get_app()
{
if (isset($_GET['app']))
{
$app = $_GET['app'];
}
elseif (isset($_GET['path']))
{
list(, $apps, $app) = explode('/', $_GET['path']);
if ($apps !== 'apps')
{
$app = 'filemanager';
}
}
if (!preg_match('/^[a-z0-9_-]+$/i',$app))
{
die('Stop'); // just to prevent someone doing nasty things
}
return $app;
}
/**
* Returns the maximum width/height of a thumbnail
*/
function get_maxsize()
{
$preset = (string)$GLOBALS['egw_info']['server']['link_list_thumbnail'] == '' ? 32 :
$GLOBALS['egw_info']['server']['link_list_thumbnail']; $GLOBALS['egw_info']['server']['link_list_thumbnail'];
//error_log(__METHOD__."() src=$g_srcfile, dst=$g_dstfile, size=$max_width"); // Another maximum size may be passed if thumbnails are turned on
if ($preset != 0 && isset($_GET['thsize']) && is_numeric($_GET['thsize']))
{
$preset = (int)$_GET['thsize'];
}
if($max_width == 0) { return $preset;
// thumbnailing disabled }
return false;
} elseif( !gdVersion() ) { /**
// GD disabled or not installed * Either loads the thumbnail for the given file form cache or generates a new
* one
*
* @param string $file is the file of which a thumbnail should be created
* @returns false if the file doesn't exist or any other error occured.
*/
function read_thumbnail($src)
{
//Check whether the source file is readable and exists
if (!file_exists($src) || !egw_vfs::is_readable($src))
{
return false; return false;
} }
// Quality // Get the maxsize of an thumbnail. If thumbnailing is turned off, the value
$g_imgcomp=55; // will be 0
$maxsize = get_maxsize();
$dst_dir = dirname($g_dstfile); // Check whether the destination directory exists, if not, create it. If this
// files dont exist, if you have no access permission // process failes, dont' return an image.
if((file_exists($dst_dir) || mkdir($dst_dir, 0700, true)) && file_exists($g_srcfile)) { $dst = gen_dstfile($src, $maxsize);
$g_is=getimagesize($g_srcfile); $dst_dir = dirname($dst);
if($g_is[0] < $max_width && $g_is[1] < $max_height) { if(file_exists($dst_dir) || mkdir($dst_dir, 0700, true))
$g_iw = $g_is[0]; {
$g_ih = $g_is[1]; // Check whether the destination file already exists and is newer than
} elseif(($g_is[0]-$max_width)>=($g_is[1]-$max_height)) { // the source file. Assume the file doesn't exist if thumbnailing is turned off.
$g_iw=$max_width; $exists = ($maxsize > 0) && (file_exists($dst) && filemtime($dst) >= filemtime($src));
$g_ih=($max_width/$g_is[0])*$g_is[1];
} else { // Only generate the thumbnail if the destination file does not match the
$g_ih=$max_height; // conditions mentioned above. Abort if $maxsize is 0.
$g_iw=($g_ih/$g_is[1])*$g_is[0]; $gen_thumb = ($maxsize > 0) && (!$exists);
if ($gen_thumb && ($thumb = gd_image_thumbnail($src, $maxsize, $maxsize)))
{
// Save the file to disk...
imagepng($thumb, $dst);
// Previous versions generated a new copy of the png to output it -
// as encoding pngs is quite cpu-intensive I think it might
// be better to just read it from the temp dir again - as it is probably
// still in the fs-cache
$exists = true;
imagedestroy($thumb);
} }
// Get mime type $output_mime = 'image/png';
list($type, $image_type) = explode('/',egw_vfs::mime_content_type($g_srcfile));
if($type != 'image') { // If some error occured during thumbnail generation or thumbnailing is turned off,
return false; // simply output the mime type icon
if (!$exists)
{
$mime = egw_vfs::mime_content_type($src);
list($app, $icon) = explode('/', egw_vfs::mime_icon($mime), 2);
list(, $path) = explode($GLOBALS['egw_info']['server']['webserver_url'],
$GLOBALS['egw']->common->image($app, $icon), 2);
$dst = EGW_SERVER_ROOT.$path;
$output_mime = mime_content_type($dst);
} }
switch ($image_type) { if ($dst)
{
header('Content-Type: '.$output_mime);
readfile($dst);
return true;
}
}
return false;
}
function gen_dstfile($src, $maxsize)
{
// Previous versions of this code didn't use an md5-sum of $src but appended
// it directly - this might have been an security issue as thumbnails from
// multiple instances might get mixed up.
return $GLOBALS['egw_info']['server']['temp_dir'] . '/egw-thumbs/thumb_' .
md5($src . $GLOBALS['egw_info']['server']['webserver_url'] . $maxsize).'.png';
}
/**
* Function which calculates the sizes of an image with the width w and the height
* h, which should be scaled to an thumbnail of the maximum dimensions maxw and
* maxh
*
* @param int $w original width of the image
* @param int $h original height of the image
* @param int $maxw maximum width of the image
* @param int $maxh maximum height of the image
* @returns an array with two elements, w, h or "false" if the original dimensions
* of the image are that "odd", that one of the output sizes is smaller than one pixel.
*
* TODO: As this is a general purpose function, it might probably be moved
* to some other php file or an "image utils" class.
*/
function get_scaled_image_size($w, $h, $maxw, $maxh)
{
//Set the output width to zero
$wout = 0.0;
$hout = 0.0;
//Scale will contain the factor by which the image has to be scaled down
$scale = 1.0;
//Select the constraining dimension
if ($w > $h) //The constraining factor will be $maxw
{
$scale = $maxw / $w;
}
else //The constraning factor will be $maxh
{
$scale = $maxh / $h;
}
// Don't scale images up
if ($scale > 1.0)
{
$scale = 1.0;
}
$wout = $w * $scale;
$hout = $h * $scale;
//Return the calculated values
if ($wout < 1 || $hout < 1)
{
return false;
}
else
{
return array(round($wout), round($hout));
}
}
/**
* Loads the given imagefile - returns "false" if the file wasn't an image,
* otherwise the gd-image is returned.
*
* @param string $file the file which to load
* @returns false or a gd_image
*/
function gd_image_load($file)
{
// Get mime type
list($type, $image_type) = explode('/', egw_vfs::mime_content_type($file));
// Call the according gd constructor depending on the file type
if($type == 'image')
{
switch ($image_type)
{
case 'png': case 'png':
$img_src = imagecreatefrompng($g_srcfile); return imagecreatefrompng($file);
break;
case 'jpg': case 'jpg':
case 'jpeg': case 'jpeg':
$img_src = imagecreatefromjpeg($g_srcfile); return imagecreatefromjpeg($file);
break;
case 'gif': case 'gif':
$img_src = imagecreatefromgif($g_srcfile); return imagecreatefromgif($file);
break;
case 'bmp': case 'bmp':
$img_src = imagecreatefromwbmp($g_srcfile); return imagecreatefromwbmp($file);
break;
default:
return false;
}
if(!($gdVersion = gdVersion())) {
return false;
} elseif ($gdVersion >= 2) {
$img_dst=imagecreatetruecolor($g_iw,$g_ih);
imageSaveAlpha($img_dst, true);
$trans_color = imagecolorallocatealpha($img_dst, 0, 0, 0, 127);
imagefill($img_dst, 0, 0, $trans_color);
} else {
$img_dst = imagecreate($g_iw, $g_ih);
} }
}
imagecopyresampled($img_dst, $img_src, 0, 0, 0, 0, $g_iw, $g_ih, $g_is[0], $g_is[1]); return false;
imagepng($img_dst, $g_dstfile); }
return $return_data ? $img_dst : $g_dstfile;
} else { /**
* Create an gd_image with transparent background.
*
* @param int $w the width of the resulting image
* @param int $h the height of the resutling image
*/
function gd_create_transparent_image($w, $h)
{
if (!($gdVersion = gdVersion()))
{
//Looking up the gd version failed, return false
return false; return false;
} }
elseif ($gdVersion >= 2)
{
//Create an 32-bit image and fill it with transparency.
$img_dst = imagecreatetruecolor($w, $h);
imageSaveAlpha($img_dst, true);
$trans_color = imagecolorallocatealpha($img_dst, 0, 0, 0, 127);
imagefill($img_dst, 0, 0, $trans_color);
return $img_dst;
}
else
{
//Just crate a simple image
return imagecreate($w, $h);
}
}
/**
* Creates a scaled version of the given image - returns the gd-image or false if the
* process failed.
*
* @param string $file the filename of the file
* @param int $maxw the maximum width of the thumbnail
* @param int $maxh the maximum height of the thumbnail
* @return the gd_image or false if one of the steps taken to produce the thumbnail
* failed.
*/
function gd_image_thumbnail($file, $maxw, $maxh)
{
//Load the image
if (($img_src = gd_image_load($file)) !== false)
{
//Get the constraints of the image
$w = imagesx($img_src);
$h = imagesy($img_src);
//Calculate the actual size of the thumbnail
$scaled = get_scaled_image_size($w, $h, $maxw, $maxh);
if ($scaled !== false)
{
list($sw, $sh) = $scaled;
//Now scale it down
$img_dst = gd_create_transparent_image($sw, $sh);
imagecopyresampled($img_dst, $img_src, 0, 0, 0, 0, $sw, $sh, $w, $h);
return $img_dst;
}
}
return false;
} }
/** /**

View File

@ -19,6 +19,6 @@
.selectPathContainer { width: 480px; overflow: auto; } .selectPathContainer { width: 480px; overflow: auto; }
.selectName input { width: 100%; } .selectName input { width: 100%; }
.selectMime select { width: 100%; } .selectMime select { width: 100%; }
.selectIcon img { height: 16px; } /*.selectIcon img { height: 16px; }*/
.displayNone input { display: none; } .displayNone input { display: none; }
.selectFiles td div { border-style: groove; } .selectFiles td div { border-style: groove; }