* @requires Netpbm binaries from http://sourceforge.net/projects/netpbm/
* @version 1.0
*/
require_once dirname(__FILE__) . '/App.inc.php';
define('IMAGETHUMB_FIT_WIDTH', 1);
define('IMAGETHUMB_FIT_HEIGHT', 2);
define('IMAGETHUMB_FIT_LARGER', 3);
define('IMAGETHUMB_STRETCH', 4);
define('IMAGETHUMB_NO_SCALE', 5);
class ImageThumb {
// The location for images to create thumbnails from.
var $source_dir = null;
// Specifications for thumbnail images.
var $spec;
// Array of acceptable file extensions (lowercase).
var $valid_file_extensions = array('jpg', 'jpeg', 'gif', 'png');
// The uploaded files will be owned by user 'apache'. Set world-read/write
// if the website admin needs to read/delete these files. Must be at least 0400 with owner=apache.
var $dest_file_perms = 0644;
// Must be at least 0700 with owner=apache.
var $dest_dir_perms = 0777;
// Executable binary locations.
var $anytopnm_binary = '/usr/bin/anytopnm';
var $pnmscale_binary = '/usr/bin/pnmscale';
var $cjpeg_binary = '/usr/bin/cjpeg';
var $_valid_binaries = true;
/**
*
*/
function __construct()
{
// if (!file_exists($this->anytopnm_binary)) {
// logMsg(sprintf('ImageThumb error: %s not found.', $this->anytopnm_binary), LOG_ERR, __FILE__, __LINE__);
// $this->_valid_binaries = false;
// return false;
// }
// if (!file_exists($this->pnmscale_binary)) {
// logMsg(sprintf('ImageThumb error: %s not found.', $this->pnmscale_binary), LOG_ERR, __FILE__, __LINE__);
// $this->_valid_binaries = false;
// return false;
// }
// if (!file_exists($this->cjpeg_binary)) {
// logMsg(sprintf('ImageThumb error: %s not found.', $this->cjpeg_binary), LOG_ERR, __FILE__, __LINE__);
// $this->_valid_binaries = false;
// return false;
// }
return true;
}
/**
*
*/
function setSourceDirectory($source_dir)
{
// Set the source directory path, stripping any extra slashes if needed.
$this->source_dir = preg_replace('!/+$!', '', $source_dir);
if (!is_dir($this->source_dir)) {
logMsg(sprintf('ImageThumb error: source directory not found: %s', $this->source_dir), LOG_ERR, __FILE__, __LINE__);
return false;
}
if (!is_readable($this->source_dir)) {
logMsg(sprintf('ImageThumb error: source directory not readable: %s', $this->source_dir), LOG_ERR, __FILE__, __LINE__);
return false;
}
return true;
}
/**
*
*/
function setSpec($spec = array())
{
$dest_dir = preg_replace('!/+$!', '', $spec['dest_dir']);
$width = $spec['width'];
$height = $spec['height'];
$scaling_type = $spec['scaling_type'];
$quality = isset($spec['quality']) ? $spec['quality'] : 75;
$progressive = isset($spec['progressive']) ? $spec['progressive'] : false;
$allow_upscaling = isset($spec['allow_upscaling']) ? $spec['allow_upscaling'] : false;
$keep_filesize = isset($spec['keep_filesize']) ? $spec['keep_filesize'] : null;
// Define pnmscale arguments.
switch ($scaling_type) {
case IMAGETHUMB_FIT_WIDTH :
if (empty($width)) {
logMsg('ImageThumb error: width not specified for IMAGETHUMB_FIT_WIDTH.', LOG_ERR, __FILE__, __LINE__);
return false;
}
$pnmscale_args = sprintf(' -width %s ', escapeshellcmd($width));
break;
case IMAGETHUMB_FIT_HEIGHT :
if (empty($height)) {
logMsg('ImageThumb error: height not specified for IMAGETHUMB_FIT_HEIGHT.', LOG_ERR, __FILE__, __LINE__);
return false;
}
$pnmscale_args = sprintf(' -height %s ', escapeshellcmd($height));
break;
case IMAGETHUMB_FIT_LARGER :
if (empty($width) || empty($height)) {
logMsg('ImageThumb error: width or height not specified for IMAGETHUMB_FIT_LARGER.', LOG_ERR, __FILE__, __LINE__);
return false;
}
$pnmscale_args = sprintf(' -xysize %s %s ', escapeshellcmd($width), escapeshellcmd($height));
break;
case IMAGETHUMB_STRETCH :
if (empty($width) || empty($height)) {
logMsg('ImageThumb error: width or height not specified for IMAGETHUMB_STRETCH.', LOG_ERR, __FILE__, __LINE__);
return false;
}
$pnmscale_args = sprintf(' -width %s -height %s ', escapeshellcmd($width), escapeshellcmd($height));
break;
case IMAGETHUMB_NO_SCALE :
default :
$pnmscale_args = ' 1 ';
break;
}
// Define cjpeg arguments.
$cjpeg_args = sprintf(' -optimize -quality %s ', escapeshellcmd($quality));
$cjpeg_args .= (true === $progressive) ? ' -progressive ' : '';
$this->spec[] = array(
'dest_dir' => $dest_dir,
'width' => $width,
'height' => $height,
'scaling_type' => $scaling_type,
'quality' => $quality,
'progressive' => $progressive,
'pnmscale_args' => $pnmscale_args,
'cjpeg_args' => $cjpeg_args,
'allow_upscaling' => $allow_upscaling,
'keep_filesize' => $keep_filesize,
);
}
/**
*
*/
function createDestDirs()
{
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before creating destination directories.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
$return_val = 0;
foreach ($this->spec as $s) {
if (!is_dir($this->source_dir . '/' . $s['dest_dir'])) {
if (!mkdir($this->source_dir . '/' . $s['dest_dir'], $this->dest_dir_perms)) {
$return_val += 1;
logMsg(sprintf('mkdir failure: %s', $this->source_dir . '/' . $s['dest_dir']), LOG_ERR, __FILE__, __LINE__);
}
}
}
// If > 0, there was a problem creating dest dirs.
return (0 == $return_val);
}
/**
*
*/
function processFile($file_name)
{
// Source file determined by provided file_name.
$source_file = realpath($this->source_dir . '/' . $file_name);
// Ensure we have valid binaries.
if (!$this->_valid_binaries) {
logMsg(sprintf('Netpnm binaries invalid.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// To keep this script running even if user tries to stop browser.
ignore_user_abort(true);
if (!ini_get('safe_mode')) {
set_time_limit(300);
}
// Confirm source image exists.
if (!file_exists($this->source_dir . '/' . $file_name)) {
// raiseMsg(sprintf(_("Image resizing failed: source image not found: %s"), $file_name), MSG_ERR, __FILE__, __LINE__);
logMsg(sprintf('Source image not found: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image is readable.
if (!is_readable($this->source_dir . '/' . $file_name)) {
// raiseMsg(sprintf(_("Image resizing failed: source image not readable: %s"), $file_name), MSG_ERR, __FILE__, __LINE__);
logMsg(sprintf('Source image not readable: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image contains data.
if (filesize($this->source_dir . '/' . $file_name) < 1) {
// raiseMsg(sprintf(_("Image resizing failed: source image corrupt: %s"), $file_name), MSG_ERR, __FILE__, __LINE__);
logMsg(sprintf('Source image is zero bytes: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image has a valid file extension.
if (!$this->validFileExtension($file_name)) {
// raiseMsg(sprintf(_("Image resizing failed: source image not of valid type: %s"), $file_name), MSG_ERR, __FILE__, __LINE__);
logMsg(sprintf('Image resizing failed: source image not of valid type: %s', $file_name), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Output file will be a jpg. Set file extension. // FIXME: is there a better way to do this?
preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
if (!in_array(strtolower($ext[1]), array('jpg', 'jpeg'))) {
$file_name = substr($file_name, 0, strrpos($file_name, '.')) . '.jpg';
}
// This remains zero until something goes wrong.
$final_return_val = 0;
foreach ($this->spec as $s) {
// Skip existing thumbnails with file size below $s['keep_filesize'].
if (file_exists(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name)) && isset($s['keep_filesize'])) {
$file_size = filesize(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name));
if ($file_size && $file_size < $s['keep_filesize']) {
logMsg(sprintf('Skipping thumbnail %s. File already exists and file size is less than %s bytes.', $s['dest_dir'] . '/' . $file_name, $s['keep_filesize']), LOG_DEBUG, __FILE__, __LINE__);
continue;
}
}
// Determine if original file size is smaller than specified thumbnail size. Do not scale-up if allow_upscaling config is set to false.
$image_size = getimagesize($source_file);
if ($image_size['0'] <= $s['width'] && $image_size['1'] <= $s['height'] && !$s['allow_upscaling']) {
$pnmscale_args = ' 1 ';
logMsg(sprintf('Image %s smaller (%sx%s) than specified %s thumbnail size (%sx%s). Keeping original size.', $source_file, $image_size['0'], $image_size['1'], $s['dest_dir'], $s['width'], $s['height']), LOG_DEBUG, __FILE__, __LINE__);
} else {
$pnmscale_args = $s['pnmscale_args'];
}
// Execute the command that creates the thumbnail.
$command = sprintf('%s %s | %s %s | %s %s > %s/%s',
escapeshellcmd($this->anytopnm_binary),
escapeshellcmd($source_file),
escapeshellcmd($this->pnmscale_binary),
escapeshellcmd($pnmscale_args),
escapeshellcmd($this->cjpeg_binary),
escapeshellcmd($s['cjpeg_args']),
escapeshellcmd(realpath($this->source_dir . '/' . $s['dest_dir'])),
escapeshellcmd($file_name)
);
logMsg(sprintf('ImageThumb command: %s', $command), LOG_DEBUG, __FILE__, __LINE__);
exec($command, $output, $return_val);
if (0 == $return_val) {
// Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
chmod(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name), $this->dest_file_perms);
logMsg(sprintf('Successfully resized image %s', $s['dest_dir'] . '/' . $file_name, $return_val), LOG_DEBUG, __FILE__, __LINE__);
} else {
logMsg(sprintf('Image %s failed resizing with return value: %s%s', $s['dest_dir'] . '/' . $file_name, $return_val, empty($output) ? '' : ' (' . getDump($output) . ')'), LOG_ERR, __FILE__, __LINE__);
}
// Return from the command will be > 0 if there was an error.
$final_return_val += $return_val;
}
// If > 0, there was a problem thumbnailing.
return (0 == $final_return_val);
}
/**
*
*/
function processAll()
{
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Get all files in source directory.
$dir_handle = opendir($this->source_dir);
while ($dir_handle && ($file = readdir($dir_handle)) !== false) {
// If the file name does not start with a dot (. or .. or .htaccess).
if (!preg_match('/^\./', $file) && in_array(strtolower(substr($file, strrpos($file, '.') + 1)), $this->valid_file_extensions)) {
$files[] = $file;
}
}
// Process each found file.
if (is_array($files) && !empty($files)) {
foreach ($files as $file_name) {
$this->processFile($file_name);
}
return sizeof($files);
} else {
logMsg(sprintf('No images found to thumbnail in directory %s.', $this->source_dir), LOG_NOTICE, __FILE__, __LINE__);
return 0;
}
}
/**
*
*/
function deleteThumbs($file_name)
{
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
$ret = 0;
foreach ($this->spec as $s) {
$file_path_name = realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name);
if (file_exists($file_path_name)) {
if (!unlink($file_path_name)) {
$ret++;
logMsg(sprintf(_("Delete thumbs failed: %s"), $file_path_name), LOG_WARNING, __FILE__, __LINE__);
}
}
}
// raiseMsg(sprintf(_("The thumbnails for file %s have been deleted."), $file_name), MSG_SUCCESS, __FILE__, __LINE__);
return (0 == $ret);
}
/**
*
*/
function deleteOriginal($file_name)
{
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
$file_path_name = $this->source_dir . '/' . $file_name;
if (!unlink($file_path_name)) {
logMsg(sprintf(_("Delete original failed: %s"), $file_path_name), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// raiseMsg(sprintf(_("The original file %s has been deleted."), $file_name), MSG_SUCCESS, __FILE__, __LINE__);
return true;
}
/**
*
*/
function exists($file_name)
{
// Ensure we have a source.
if (!isset($this->source_dir)) {
logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
return file_exists($this->source_dir . '/' . $file_name);
}
/**
*
*/
function validFileExtension($file_name)
{
preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
return in_array(strtolower($ext[1]), $this->valid_file_extensions);
}
} // End of class.
?>