* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* ImageThumb.inc.php
*
* @author Quinn Comendant
* @requires Netpbm and libjpeg or GD.
* @version 2.0
*/
// Image proportion options.
define('IMAGETHUMB_FIT_WIDTH', 1);
define('IMAGETHUMB_FIT_HEIGHT', 2);
define('IMAGETHUMB_FIT_LARGER', 3);
define('IMAGETHUMB_STRETCH', 4);
define('IMAGETHUMB_NO_SCALE', 5);
// Image resize options.
define('IMAGETHUMB_METHOD_NETPBM', 6);
define('IMAGETHUMB_METHOD_GD', 7);
class ImageThumb
{
// General object parameters.
protected $_params = array(
// The location for images to create thumbnails from.
'source_dir' => null,
// Existing files will be overwritten when there is a name conflict?
'allow_overwriting' => false,
// The file permissions of the uploaded files. Remember, files will be owned by the web server user.
'dest_file_perms' => 0600,
// Permissions of auto-created directories. Must be at least 0700 with owner=apache.
'dest_dir_perms' => 0700,
// Require file to have one of the following file name extensions.
'valid_file_extensions' => array('jpg', 'jpeg', 'gif', 'png'),
// Method to use for resizing. (IMAGETHUMB_METHOD_NETPBM or IMAGETHUMB_METHOD_GD)
'resize_method' => IMAGETHUMB_METHOD_NETPBM,
// Netpbm and libjpeg binary locations.
'anytopnm_binary' => '/usr/bin/anytopnm',
'pnmscale_binary' => '/usr/bin/pnmscale',
'cjpeg_binary' => '/usr/bin/cjpeg',
// Which messages do we pass to raiseMsg? Use one of the MSG_* constants or false to disable.
'display_messages' => MSG_ALL,
);
// Default image size specs.
protected $_default_image_specs = array(
// The destination for an image thumbnail size.
// Use initial / to specify absolute paths, leave off to specify a path relative to source_dir (eg: ../thumbs).
'dest_dir' => null,
// Destination file types. (IMG_JPG, IMG_PNG, IMG_GIF, IMG_WBMP)
'dest_file_type' => IMG_JPG,
// Destination file types. ('jpg', 'png', 'gif', 'wbmp')
'dest_file_extension' => 'jpg',
// Type of scaling to perform, and sizes used to calculate max dimensions.
'scaling_type' => IMAGETHUMB_FIT_LARGER,
'width' => null,
'height' => null,
// Percentage quality of image compression output 0-100.
'quality' => 65,
// Create progressive jpegs?
'progressive' => false,
// If using GD method, apply sharpen filter. Requires PHP > 5.1.
'sharpen' => true,
// Integers between 1-100, useful values are 65-85.
'sharpen_value' => 75,
// If source image is smaller than thumbnail, allow upscaling?
'allow_upscaling' => false,
// If thumb exists and filesize is smaller than this, do not overwrite the thumb.
'keep_filesize' => null,
);
// Final specifications for image sizes, set with setSpec().
protected $_image_specs = array();
/**
* Set (or overwrite existing) parameters by passing an array of new parameters.
*
* @access public
* @param array $params Array of parameters (key => val pairs).
*/
public function setParam($params)
{
$app =& App::getInstance();
if (isset($params) && is_array($params)) {
// Enforce valid source_dir parameter.
if (isset($params['source_dir'])) {
// Source must be directory.
if (!is_dir($params['source_dir'])) {
$app->logMsg(sprintf('Attempting to auto-create source directory: %s', $params['source_dir']), LOG_NOTICE, __FILE__, __LINE__);
if (version_compare(PHP_VERSION, '5.0.0', '>=')) {
// Recursive.
mkdir($params['source_dir'], isset($params['dest_dir_perms']) ? $params['dest_dir_perms'] : $this->getParam('dest_dir_perms'), true);
} else {
mkdir($params['source_dir'], isset($params['dest_dir_perms']) ? $params['dest_dir_perms'] : $this->getParam('dest_dir_perms'));
}
if (!is_dir($params['source_dir'])) {
$app->logMsg(sprintf('Source directory invalid: %s', $params['source_dir']), LOG_ERR, __FILE__, __LINE__);
trigger_error(sprintf('Source directory invalid: %s', $params['source_dir']), E_USER_ERROR);
}
}
// Source must be readable.
if (!is_readable($params['source_dir'])) {
$app->logMsg(sprintf('Source directory not readable: %s', $params['source_dir']), LOG_ERR, __FILE__, __LINE__);
trigger_error(sprintf('Source directory not readable: %s', $params['source_dir']), E_USER_ERROR);
}
}
// Merge new parameters with old overriding only those passed.
$this->_params = array_merge($this->_params, $params);
} else {
$app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
}
}
/**
* Return the value of a parameter, if it exists.
*
* @access public
* @param string $param Which parameter to return.
* @return mixed Configured parameter value.
*/
public function getParam($param)
{
$app =& App::getInstance();
if (array_key_exists($param, $this->_params)) {
return $this->_params[$param];
} else {
$app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
}
/**
* Set the specification of thumbnails.
*
* @access public
* @param array $spec The specifications for a size of output image.
* @param int $index The position of the specification in the spec array
* Use to overwrite existing spec array values.
*/
public function setSpec($spec, $index=null)
{
$app =& App::getInstance();
// A little sanity checking.
if (!isset($spec['dest_dir']) || '' == trim($spec['dest_dir'])) {
$app->logMsg('setSpec error: dest_dir not specified.', LOG_ERR, __FILE__, __LINE__);
} else {
$spec['dest_dir'] = trim($spec['dest_dir']);
}
if (isset($spec['dest_file_type'])) {
switch ($spec['dest_file_type']) {
case IMG_JPG :
if (imagetypes() & IMG_JPG == 0) {
$app->logMsg(sprintf('IMG_JPG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
}
$spec['dest_file_extension'] = 'jpg';
break;
case IMG_PNG :
if (imagetypes() & IMG_PNG == 0) {
$app->logMsg(sprintf('IMG_PNG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
}
$spec['dest_file_extension'] = 'png';
break;
case IMG_GIF :
if (imagetypes() & IMG_GIF == 0) {
$app->logMsg(sprintf('IMG_GIF is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
}
$spec['dest_file_extension'] = 'gif';
break;
case IMG_WBMP :
if (imagetypes() & IMG_WBMP == 0) {
$app->logMsg(sprintf('IMG_WBMP is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
}
$spec['dest_file_extension'] = 'wbmp';
break;
default :
$app->logMsg(sprintf('Invalid dest_file_type: %s', $spec['dest_file_type']), LOG_ERR, __FILE__, __LINE__);
break;
}
}
if (IMAGETHUMB_FIT_HEIGHT != $spec['scaling_type'] && (!isset($spec['width']) || !is_numeric($spec['width']))) {
$app->logMsg(sprintf('setSpec error: invalid width: %s', $spec['width']), LOG_ERR, __FILE__, __LINE__);
}
if (IMAGETHUMB_FIT_WIDTH != $spec['scaling_type'] && (!isset($spec['height']) || !is_numeric($spec['height']))) {
$app->logMsg(sprintf('setSpec error: invalid height: %s', $spec['height']), LOG_ERR, __FILE__, __LINE__);
}
if (isset($spec['quality']) && IMG_JPG != $spec['dest_file_type']) {
$app->logMsg('The "quality" specification is not used unless IMG_JPG is the dest_file_type.', LOG_NOTICE, __FILE__, __LINE__);
}
if (isset($spec['progressive']) && IMG_JPG != $spec['dest_file_type']) {
$app->logMsg('The "progressive" specification is not used unless IMG_JPG is the dest_file_type.', LOG_NOTICE, __FILE__, __LINE__);
}
// Add to _image_specs array.
if (isset($index) && isset($this->_image_specs[$index])) {
// Merge with existing spec if index is provided.
$final_spec = array_merge($this->_image_specs[$index], $spec);
$this->_image_specs[$index] = $final_spec;
} else {
// Merge with spec defaults.
$final_spec = array_merge($this->_default_image_specs, $spec);
$this->_image_specs[] = $final_spec;
}
return $final_spec;
}
/*
* Retrieve a value of a thumb specification.
*
* @access public
* @param string $key Key to return. See _default_image_specs above for a list.
* @param int $index The index in the spec array of the value to retrieve. The first if not specified.
* @param string $dest_dir Return the spec matching this dest_dir (if used, the provided $index is ignored).
* @return mixed Value of requested $key for the spec selected by $index or $dest_dir.
* @author Quinn Comendant
* @version 1.0
* @since 08 May 2007 15:26:39
*/
public function getSpec($key, $index=null, $dest_dir=null)
{
$index = isset($index) ? $index : 0;
if (isset($dest_dir)) {
$index = array_search($dest_dir, array_column($this->_image_specs, 'dest_dir'));
}
return $this->_image_specs[$index][$key];
}
/**
* Process an entire directory of images.
*
* @access public
* @return bool true on success, false on failure.
*/
public function processAll($runtime_specs=null)
{
$app =& App::getInstance();
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Get all files in source directory.
$dir_handle = opendir($this->getParam('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(mb_strtolower(mb_substr($file, mb_strrpos($file, '.') + 1)), $this->getParam('valid_file_extensions'))) {
$files[] = $file;
}
}
// Process each found file.
if (is_array($files) && !empty($files)) {
$return_val = 0;
foreach ($files as $file_name) {
$return_val += (false === $this->processFile($file_name, $runtime_specs) ? 1 : 0);
}
$this->_raiseMsg(sprintf(_("Resized %s images."), sizeof($files)), MSG_SUCCESS, __FILE__, __LINE__);
return 0 === $return_val;
} else {
$app->logMsg(sprintf('No source images found in directory: %s', $this->getParam('source_dir')), LOG_NOTICE, __FILE__, __LINE__);
return false;
}
}
/**
* Generate thumbnails for the specified file.
*
* @access public
* @param string $file_name Name of file with extension.
* @param array $runtime_specs Array of specifications that will override all configured specifications.
* @return bool true on success, false on failure.
*/
public function processFile($file_name, $runtime_specs=null)
{
$app =& App::getInstance();
// Source file determined by provided file_name.
$source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
// Ensure we have a source.
if (sizeof($this->_image_specs) < 1) {
if (is_array($runtime_specs)) {
$this->setSpec($runtime_specs, 0);
} else {
$app->logMsg(sprintf('Image specifications not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
}
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Confirm source image exists.
if (!file_exists($source_file)) {
$this->_raiseMsg(sprintf(_("Image resizing failed: source image %s was not found."), $file_name), MSG_ERR, __FILE__, __LINE__);
$app->logMsg(sprintf('Source image not found: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image is readable.
if (!is_readable($source_file)) {
$this->_raiseMsg(sprintf(_("Image resizing failed: source image %s is not readable."), $file_name), MSG_ERR, __FILE__, __LINE__);
$app->logMsg(sprintf('Source image not readable: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image contains data.
if (filesize($source_file) <= 0) {
$this->_raiseMsg(sprintf(_("Image resizing failed: source image %s is zero bytes."), $file_name), MSG_ERR, __FILE__, __LINE__);
$app->logMsg(sprintf('Source image is zero bytes: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Confirm source image has a valid file extension.
if (!$this->_validFileExtension($file_name)) {
$this->_raiseMsg(sprintf(_("Image resizing failed: source image %s not a valid type. It must have one of the following file name extensions: %s"), $file_name, join(', ', $this->getParam('valid_file_extensions'))), MSG_ERR, __FILE__, __LINE__);
$app->logMsg(sprintf('Image resizing failed: source image not of valid type: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Ensure destination directories are created. This will only be called once per page load.
if (!$this->_createDestDirs()) {
$app->logMsg('Image resizing failed: unable to create dest dirs.', LOG_WARNING, __FILE__, __LINE__);
return false;
}
// To keep this script running even if user tries to stop browser.
ignore_user_abort(true);
ini_set('max_execution_time', 300);
ini_set('max_input_time', 300);
// This remains zero until something goes wrong.
$return_val = 0;
foreach ($this->_image_specs as $index => $spec) {
if (is_array($runtime_specs)) {
// Override with runtime specs.
$spec = $this->setSpec($runtime_specs, $index);
}
// Destination filename uses the extension defined by dest_file_extension.
if ('/' == $spec['dest_dir'][0]) {
// Absolute path.
$dest_file = sprintf('%s/%s.%s', $spec['dest_dir'], mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']);
} else {
// Relative path.
$dest_file = sprintf('%s/%s/%s.%s', $this->getParam('source_dir'), $spec['dest_dir'], mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']);
}
// Ensure destination directory exists and is writable.
if (!is_dir(dirname($dest_file)) || !is_writable(dirname($dest_file))) {
$this->_createDestDirs($dest_file);
if (!is_dir(dirname($dest_file)) || !is_writable(dirname($dest_file))) {
$app->logMsg(sprintf('Image resizing failed, dest_dir invalid: %s', dirname($dest_file)), LOG_ERR, __FILE__, __LINE__);
$return_val++;
continue;
}
}
// Skip existing thumbnails with file size below $spec['keep_filesize'].
if (isset($spec['keep_filesize']) && file_exists($dest_file)) {
$file_size = filesize($dest_file);
if (false !== $file_size && $file_size < $spec['keep_filesize']) {
$app->logMsg(sprintf('Skipping thumbnail %s. File already exists and file size is less than %s bytes.', $spec['dest_dir'] . '/' . $file_name, $spec['keep_filesize']), LOG_INFO, __FILE__, __LINE__);
continue;
}
}
// Determine if original file size is smaller than specified thumbnail size. Do not scale-up if $spec['allow_upscaling'] config is set to false.
$image_size = getimagesize($source_file);
if ($image_size['0'] <= $spec['width'] && $image_size['1'] <= $spec['height'] && !$spec['allow_upscaling']) {
$spec['scaling_type'] = IMAGETHUMB_NO_SCALE;
$app->logMsg(sprintf('Image %s smaller than specified %s thumbnail size. Keeping original size.', $file_name, basename($spec['dest_dir'])), LOG_INFO, __FILE__, __LINE__);
}
// DO IT! Based on available method.
if (IMAGETHUMB_METHOD_NETPBM === $this->getParam('resize_method') && file_exists($this->getParam('anytopnm_binary')) && file_exists($this->getParam('pnmscale_binary')) && file_exists($this->getParam('cjpeg_binary'))) {
// Resize using Netpbm binaries.
$app->logMsg(sprintf('Resizing with Netpbm: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
$return_val += $this->_resizeWithNetpbm($source_file, $dest_file, $spec);
} else if (IMAGETHUMB_METHOD_GD === $this->getParam('resize_method') && extension_loaded('gd')) {
// Resize with GD.
$app->logMsg(sprintf('Resizing with GD: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
$return_val += $this->_resizeWithGD($source_file, $dest_file, $spec);
} else {
$app->logMsg(sprintf('Image thumbnailing canceled. Neither Netpbm or GD is available.', null), LOG_ERR, __FILE__, __LINE__);
return false;
}
}
// If > 0, there was a problem thumb-nailing.
return 0 === $return_val;
}
/*
* Use the Netpbm and libjpg cjpeg tools to generate a rescaled compressed image.
* This is the preferred method over GD which has (supposedly) less quality.
*
* @access private
* @param string $source_file Full path to source image file.
* @param string $dest_file Full path to destination image file.
* @param array $spec Array of image size specifications.
* @return int 0 if no error, n > 0 if errors.
* @author Quinn Comendant
* @version 1.0
* @since 19 May 2006 13:55:46
*/
protected function _resizeWithNetpbm($source_file, $dest_file, $spec)
{
$app =& App::getInstance();
// Define pnmscale arguments.
switch ($spec['scaling_type']) {
case IMAGETHUMB_FIT_WIDTH :
$pnmscale_args = sprintf(' -width %s ', escapeshellarg($spec['width']));
break;
case IMAGETHUMB_FIT_HEIGHT :
$pnmscale_args = sprintf(' -height %s ', escapeshellarg($spec['height']));
break;
case IMAGETHUMB_FIT_LARGER :
$pnmscale_args = sprintf(' -xysize %s %s ', escapeshellarg($spec['width']), escapeshellarg($spec['height']));
break;
case IMAGETHUMB_STRETCH :
$pnmscale_args = sprintf(' -width %s -height %s ', escapeshellarg($spec['width']), escapeshellarg($spec['height']));
break;
case IMAGETHUMB_NO_SCALE :
default :
$pnmscale_args = ' 1 ';
break;
}
// Define cjpeg arguments.
$cjpeg_args = sprintf(' -optimize -quality %s ', escapeshellarg($spec['quality']));
$cjpeg_args .= (true === $spec['progressive']) ? ' -progressive ' : '';
// Format the command that creates the thumbnail.
$command = sprintf('%s %s | %s %s | %s %s > %s',
escapeshellcmd($this->getParam('anytopnm_binary')),
escapeshellcmd($source_file),
escapeshellcmd($this->getParam('pnmscale_binary')),
escapeshellcmd($pnmscale_args),
escapeshellcmd($this->getParam('cjpeg_binary')),
escapeshellcmd($cjpeg_args),
escapeshellcmd($dest_file)
);
$app->logMsg(sprintf('ImageThumb Netpbm command: %s', $command), LOG_DEBUG, __FILE__, __LINE__);
// Execute!
exec($command, $output, $return_val);
if (0 === $return_val) {
// Success!
// Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
chmod($dest_file, $this->getParam('dest_file_perms'));
$app->logMsg(sprintf('Successfully resized image %s', $spec['dest_dir'] . '/' . basename($dest_file), $return_val), LOG_DEBUG, __FILE__, __LINE__);
} else {
// An error occurred.
$app->logMsg(sprintf('Image %s failed resizing with return value: %s%s', $spec['dest_dir'] . '/' . basename($dest_file), $return_val, empty($output) ? '' : ' (' . truncate(getDump($output, true), 128, 'end') . ')'), LOG_ERR, __FILE__, __LINE__);
}
// Return from the command will be > 0 if there was an error.
return $return_val;
}
/*
* Use PHP's built-in GD tools to generate a rescaled compressed image.
*
* @access private
* @param string $source_file Full path to source image file.
* @param string $dest_file Full path to destination image file.
* @param array $spec Array of image size specifications.
* @return int 0 if no error, n > 0 if errors.
* @author Quinn Comendant
* @version 1.0
* @since 19 May 2006 15:46:02
*/
protected function _resizeWithGD($source_file, $dest_file, $spec)
{
$app =& App::getInstance();
// Get original file dimensions and type.
list($source_image_width, $source_image_height, $source_image_type) = getimagesize($source_file);
// Define destination image dimensions.
switch ($spec['scaling_type']) {
case IMAGETHUMB_FIT_WIDTH :
$dest_image_width = $spec['width'];
$dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
break;
case IMAGETHUMB_FIT_HEIGHT :
$dest_image_height = $spec['height'];
$dest_image_width = $source_image_width * ($spec['height'] / $source_image_height);
break;
case IMAGETHUMB_FIT_LARGER :
if (($source_image_width * ($spec['height'] / $source_image_height)) <= $spec['width']) {
// Height is larger.
$dest_image_height = $spec['height'];
$dest_image_width = $source_image_width * ($spec['height'] / $source_image_height);
} else {
// Width is larger.
$dest_image_width = $spec['width'];
$dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
}
break;
case IMAGETHUMB_STRETCH :
$dest_image_width = $spec['width'];
$dest_image_height = $spec['height'];
break;
case IMAGETHUMB_NO_SCALE :
default :
$dest_image_width = $source_image_width;
$dest_image_height = $source_image_height;
break;
}
// Create source image data in memory.
switch ($source_image_type) {
case IMAGETYPE_JPEG : // = 2
$source_image_resource = imagecreatefromjpeg($source_file);
break;
case IMAGETYPE_PNG : // = 3
$source_image_resource = imagecreatefrompng($source_file);
break;
case IMAGETYPE_GIF : // = 1
$source_image_resource = imagecreatefromgif($source_file);
break;
case IMAGETYPE_WBMP : // = 15
$source_image_resource = imagecreatefromwbmp($source_file);
case IMAGETYPE_SWF : // = 4
case IMAGETYPE_PSD : // = 5
case IMAGETYPE_BMP : // = 6
case IMAGETYPE_TIFF_II : // = 7
case IMAGETYPE_TIFF_MM : // = 8
case IMAGETYPE_JPC : // = 9
case IMAGETYPE_JP2 : // = 10
case IMAGETYPE_JPX : // = 11
case IMAGETYPE_JB2 : // = 12
case IMAGETYPE_SWC : // = 13
case IMAGETYPE_IFF : // = 14
case IMAGETYPE_XBM : // = 16
default :
$app->logMsg(sprintf('Source image type %s not supported.', $source_image_type), LOG_WARNING, __FILE__, __LINE__);
return 1;
}
if (!$source_image_resource) {
$app->logMsg(sprintf('Error creating %s image in memory from %s', $source_image_type, $source_file), LOG_WARNING, __FILE__, __LINE__);
return 1;
}
// Create destination image data in memory.
$dest_image_resource = imagecreatetruecolor($dest_image_width, $dest_image_height);
// Resample!
if (!imagecopyresampled($dest_image_resource, $source_image_resource, 0, 0, 0, 0, $dest_image_width, $dest_image_height, $source_image_width, $source_image_height)) {
// Always cleanup images from memory.
imagedestroy($source_image_resource);
imagedestroy($dest_image_resource);
$app->logMsg(sprintf('Error resampling image %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return 1;
}
// Sharpen image using a custom filter matrix.
if (version_compare(PHP_VERSION, '5.1.0', '>=') && true === $spec['sharpen'] && $spec['sharpen_value'] > 0) {
$sharpen_value = round((((48 - 10) / (100 - 1)) * (100 - $spec['sharpen_value'])) + 10); // TODO: WTF is this math?
imageconvolution($dest_image_resource, array(array(-1, -1, -1), array(-1, $sharpen_value, -1),array(-1, -1, -1)), ($sharpen_value - 8), 0);
}
// Save image.
$return_val = true;
switch ($spec['dest_file_type']) {
case IMG_JPG :
imageinterlace($dest_image_resource, (true == $spec['progressive'] ? 1 : 0));
$return_val = imagejpeg($dest_image_resource, $dest_file, $spec['quality']);
break;
case IMG_PNG :
$return_val = imagepng($dest_image_resource, $dest_file);
break;
case IMG_GIF :
$return_val = imagegif($dest_image_resource, $dest_file);
break;
case IMG_WBMP :
$return_val = imagewbmp($dest_image_resource, $dest_file);
break;
default :
$app->logMsg(sprintf('Destination image type %s not supported for image %s.', $spec['dest_file_type'], $dest_file), LOG_WARNING, __FILE__, __LINE__);
// Always cleanup images from memory.
imagedestroy($source_image_resource);
imagedestroy($dest_image_resource);
return 1;
break;
}
// Always cleanup images from memory.
imagedestroy($source_image_resource);
imagedestroy($dest_image_resource);
if ($return_val) {
// Success!
// Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
chmod($dest_file, $this->getParam('dest_file_perms'));
$app->logMsg(sprintf('Successfully resized image: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
return 0;
} else {
// An error occurred.
$app->logMsg(sprintf('Failed resizing image: %s', $dest_file), LOG_ERR, __FILE__, __LINE__);
return 1;
}
}
/**
* Delete the thumbnails for the specified file name.
*
* @access public
* @param string $file_name The file name to delete, with extension.
* @param bool $use_glob Set true to use glob to find the filename (using $file_name as a pattern)
* @return bool true on success, false on failure.
*/
public function deleteThumbs($file_name, $use_glob=false)
{
$app =& App::getInstance();
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
$return_val = 0;
foreach ($this->_image_specs as $spec) {
// Get base directory depending on if the spec's dest_dir is an absolute path or not.
$dest_dir = '/' == $spec['dest_dir'][0] ? $spec['dest_dir'] : sprintf('%s/%s', $this->getParam('source_dir'), $spec['dest_dir']);
if ($use_glob) {
$dest_file = realpath(sprintf('%s/%s', $dest_dir, $this->_glob($file_name, $dest_dir)));
} else {
$dest_file = realpath(sprintf('%s/%s.%s', $dest_dir, mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']));
}
if (file_exists($dest_file)) {
if (!unlink($dest_file)) {
$return_val++;
$app->logMsg(sprintf('Delete thumbs failed: %s', $dest_file), LOG_WARNING, __FILE__, __LINE__);
}
}
}
$app->logMsg(sprintf('Thumbnails deleted for file: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
return 0 === $return_val;
}
/**
* Delete the source image with the specified file name.
*
* @access public
* @param string $file_name The file name to delete, with extension.
* @return bool true on success, false on failure.
*/
public function deleteOriginal($file_name)
{
$app =& App::getInstance();
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Ensure we have a source.
if ('' == $file_name) {
$app->logMsg(sprintf('Cannot delete, filename empty.'), LOG_WARNING, __FILE__, __LINE__);
return false;
}
$source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
if (is_file($source_file) && unlink($source_file)) {
$app->logMsg(sprintf('Original file successfully deleted: %s', $file_name), LOG_INFO, __FILE__, __LINE__);
return true;
} else {
$app->logMsg(sprintf('Delete original failed: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
return false;
}
}
/**
* Returns true if file exists.
*
* @access public
* @param string $file_name The file name to test, with extension.
* @return bool true on success, false on failure.
*/
public function exists($file_name)
{
$app =& App::getInstance();
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
$source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
return file_exists($source_file);
}
/**
* Tests if extension of $file_name is in the array valid_file_extensions.
*
* @access public
* @param string $file_name A file name.
* @return bool True on success, false on failure.
*/
protected function _validFileExtension($file_name)
{
preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
return !empty($ext) && in_array(mb_strtolower($ext[1]), $this->getParam('valid_file_extensions'));
}
/**
* Make directory for each specified thumbnail size, if it doesn't exist.
*
* @access public
* @return bool true on success, false on failure.
*/
protected function _createDestDirs($filename=null)
{
$app =& App::getInstance();
// Keep track of directories we've already created.
$dd_hash = md5(isset($filename) ? dirname($filename) : 'none');
static $already_checked = array();
$return_val = 0;
if (!isset($already_checked[$dd_hash])) {
// Ensure we have a source.
if ('' == $this->getParam('source_dir')) {
$app->logMsg(sprintf('Source directory not set before creating destination directories.'), LOG_ERR, __FILE__, __LINE__);
return false;
}
// Loop through specs and ensure all dirs are created.
foreach ($this->_image_specs as $spec) {
if (isset($filename)) {
$dest_dir = dirname($filename);
} else {
// Get base directory depending on if the spec's dest_dir is an absolute path or not.
$dest_dir = '/' == $spec['dest_dir'][0] ? $spec['dest_dir'] : sprintf('%s/%s', $this->getParam('source_dir'), $spec['dest_dir']);
}
if (!file_exists($dest_dir)) {
// Recursively create destination directory.
$app->logMsg(sprintf('Creating dest dir: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
if (!file_exists($dest_dir) && !($ret = mkdir($dest_dir, $this->getParam('dest_dir_perms'), true))) {
$return_val++;
$app->logMsg(sprintf('mkdir failure: %s', $dest_dir), LOG_ERR, __FILE__, __LINE__);
}
if ($ret) {
$app->logMsg(sprintf('mkdir success: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
}
} else {
$app->logMsg(sprintf('Dest dir exists: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
}
}
}
$already_checked[$dd_hash] = true;
// If > 0, there was a problem creating dest dirs.
return 0 === $return_val;
}
/**
* An alias for $app->raiseMsg that only sends messages configured by display_messages.
*
* @access public
*
* @param string $message The text description of the message.
* @param int $type The type of message: MSG_NOTICE,
* MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
* @param string $file __FILE__.
* @param string $line __LINE__.
*/
protected function _raiseMsg($message, $type, $file, $line)
{
$app =& App::getInstance();
if ($this->getParam('display_messages') === true || (is_int($this->getParam('display_messages')) && $this->getParam('display_messages') & $type > 0)) {
$app->raiseMsg($message, $type, $file, $line);
}
}
/**
* Get filename by glob pattern. Searches a directory for an image that matches the
* specified glob pattern and returns the filename of the first file found.
*
* @access public
* @param string $pattern Pattern to match filename.
* @param string $directory Pattern to match containing directory.
* @return string filename on success, empty string on failure.
* @author Quinn Comendant
* @since 15 Nov 2005 20:55:22
*/
protected function _glob($pattern, $directory)
{
$file_list = glob(sprintf('%s/%s', $directory, $pattern));
if (isset($file_list[0])) {
return basename($file_list[0]);
} else {
return '';
}
}
} // End of class.