* @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 ImageThumb() { // 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) { // 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_ALERT, __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_ALERT, __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_ALERT, __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(realpath($this->source_dir . '/' . $file_name)); if ($image_size['0'] <= $s['width'] && $image_size['1'] <= $s['height'] && !$s['allow_upscaling']) { $pnmscale_args = ' 1 '; logMsg(sprintf('Image %s smaller than specified %s thumbnail size. Keeping original size.', $file_name, $s['dest_dir']), 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/%s', escapeshellcmd($this->anytopnm_binary), escapeshellcmd($this->source_dir), escapeshellcmd($file_name), 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. ?>