source: trunk/lib/ImageThumb.inc.php @ 642

Last change on this file since 642 was 626, checked in by anonymous, 6 years ago

Fix setSpec validation

File size: 36.9 KB
RevLine 
[1]1<?php
2/**
[362]3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
[396]5 * Copyright 2001-2012 Strangecode, LLC
[468]6 *
[362]7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
[468]13 *
[362]14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
[468]18 *
[362]19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/**
[42]24 * ImageThumb.inc.php
[1]25 *
26 * @author   Quinn Comendant <quinn@strangecode.com>
[119]27 * @requires Netpbm <http://sourceforge.net/projects/netpbm/> and libjpeg or GD.
28 * @version  2.0
[1]29 */
30
[152]31// Image proprtion options.
[1]32define('IMAGETHUMB_FIT_WIDTH', 1);
33define('IMAGETHUMB_FIT_HEIGHT', 2);
34define('IMAGETHUMB_FIT_LARGER', 3);
35define('IMAGETHUMB_STRETCH', 4);
36define('IMAGETHUMB_NO_SCALE', 5);
[152]37
38// Image resize options.
[119]39define('IMAGETHUMB_METHOD_NETPBM', 6);
40define('IMAGETHUMB_METHOD_GD', 7);
[1]41
[502]42class ImageThumb
43{
[468]44
[119]45    // General object parameters.
[484]46    protected $_params = array(
[119]47        // The location for images to create thumbnails from.
48        'source_dir' => null,
[42]49
[119]50        // Existing files will be overwritten when there is a name conflict?
51        'allow_overwriting' => false,
[42]52
[119]53        // The file permissions of the uploaded files. Remember, files will be owned by the web server user.
54        'dest_file_perms' => 0600,
[42]55
[334]56        // Permissions of auto-created directories. Must be at least 0700 with owner=apache.
[152]57        'dest_dir_perms' => 0700,
[42]58
[199]59        // Require file to have one of the following file name extensions.
[119]60        'valid_file_extensions' => array('jpg', 'jpeg', 'gif', 'png'),
[468]61
[119]62        // Method to use for resizing. (IMAGETHUMB_METHOD_NETPBM or IMAGETHUMB_METHOD_GD)
63        'resize_method' => IMAGETHUMB_METHOD_NETPBM,
[1]64
[119]65        // Netpbm and libjpeg binary locations.
66        'anytopnm_binary' => '/usr/bin/anytopnm',
67        'pnmscale_binary' => '/usr/bin/pnmscale',
68        'cjpeg_binary' => '/usr/bin/cjpeg',
[42]69
[119]70        // Which messages do we pass to raiseMsg? Use one of the MSG_* constants or false to disable.
71        'display_messages' => MSG_ALL,
72    );
[468]73
[119]74    // Default image size specs.
[484]75    protected $_default_image_specs = array(
[468]76        // The destination for an image thumbnail size.
[152]77        // Use initial / to specify absolute paths, leave off to specify a path relative to source_dir (eg: ../thumbs).
[119]78        'dest_dir' => null,
[468]79
[120]80        // Destination file types. (IMG_JPG, IMG_PNG, IMG_GIF, IMG_WBMP)
81        'dest_file_type' => IMG_JPG,
82
83        // Destination file types. ('jpg', 'png', 'gif', 'wbmp')
[199]84        'dest_file_extension' => 'jpg',
[468]85
[334]86        // Type of scaling to perform, and sizes used to calculate max dimensions.
[119]87        'scaling_type' => IMAGETHUMB_FIT_LARGER,
88        'width' => null,
89        'height' => null,
[120]90
[119]91        // Percentage quality of image compression output 0-100.
92        'quality' => 65,
[120]93
[119]94        // Create progressive jpegs?
95        'progressive' => false,
[120]96
97        // If using GD method, apply sharpen filter. Requires PHP > 5.1.
98        'sharpen' => true,
[468]99
[120]100        // Integers between 1-100, useful values are 65-85.
101        'sharpen_value' => 75,
102
[119]103        // If source image is smaller than thumbnail, allow upscaling?
104        'allow_upscaling' => false,
[120]105
[119]106        // If thumb exists and filesize is smaller than this, do not overwrite the thumb.
107        'keep_filesize' => null,
108    );
[1]109
[119]110    // Final specifications for image sizes, set with setSpec().
[484]111    protected $_image_specs = array();
[119]112
[1]113    /**
[119]114     * Set (or overwrite existing) parameters by passing an array of new parameters.
[1]115     *
[119]116     * @access public
117     * @param  array    $params     Array of parameters (key => val pairs).
[1]118     */
[468]119    public function setParam($params)
[1]120    {
[479]121        $app =& App::getInstance();
[136]122
[119]123        if (isset($params) && is_array($params)) {
124
[152]125            // Enforce valid source_dir parameter.
[119]126            if (isset($params['source_dir'])) {
127                $params['source_dir'] = realpath($params['source_dir']);
[152]128                // Source must be directory.
[119]129                if (!is_dir($params['source_dir'])) {
[152]130                    $app->logMsg(sprintf('Attempting to auto-create source directory: %s', $params['source_dir']), LOG_NOTICE, __FILE__, __LINE__);
[477]131                    if (version_compare(PHP_VERSION, '5.0.0', '>=')) {
[152]132                        // Recursive.
133                        mkdir($params['source_dir'], isset($params['dest_dir_perms']) ? $params['dest_dir_perms'] : $this->getParam('dest_dir_perms'), true);
134                    } else {
135                        mkdir($params['source_dir'], isset($params['dest_dir_perms']) ? $params['dest_dir_perms'] : $this->getParam('dest_dir_perms'));
136                    }
137                    if (!is_dir($params['source_dir'])) {
138                        $app->logMsg(sprintf('Source directory invalid: %s', $params['source_dir']), LOG_ERR, __FILE__, __LINE__);
139                        trigger_error(sprintf('Source directory invalid: %s', $params['source_dir']), E_USER_ERROR);
140                    }
[119]141                }
[152]142                // Source must be readable.
[119]143                if (!is_readable($params['source_dir'])) {
[136]144                    $app->logMsg(sprintf('Source directory not readable: %s', $params['source_dir']), LOG_ERR, __FILE__, __LINE__);
[119]145                    trigger_error(sprintf('Source directory not readable: %s', $params['source_dir']), E_USER_ERROR);
146                }
147            }
148
149            // Merge new parameters with old overriding only those passed.
150            $this->_params = array_merge($this->_params, $params);
151        } else {
[136]152            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
[1]153        }
154    }
155
156    /**
[119]157     * Return the value of a parameter, if it exists.
[1]158     *
[119]159     * @access public
160     * @param string $param        Which parameter to return.
161     * @return mixed               Configured parameter value.
[1]162     */
[468]163    public function getParam($param)
[1]164    {
[479]165        $app =& App::getInstance();
[468]166
[478]167        if (array_key_exists($param, $this->_params)) {
[119]168            return $this->_params[$param];
169        } else {
[146]170            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[119]171            return null;
[1]172        }
173    }
174
175    /**
176     * Set the specification of thumbnails.
177     *
178     * @access  public
[235]179     * @param   array   $spec   The specifications for a size of output image.
180     * @param   int     $index  The position of the specification in the spec array
[334]181     *                          Use to overwrite existing spec array values.
[1]182     */
[468]183    public function setSpec($spec, $index=null)
[1]184    {
[479]185        $app =& App::getInstance();
[136]186
[119]187        // A little sanity checking.
[152]188        if (!isset($spec['dest_dir']) || '' == trim($spec['dest_dir'])) {
[136]189            $app->logMsg('setSpec error: dest_dir not specified.', LOG_ERR, __FILE__, __LINE__);
[152]190        } else {
[468]191            $spec['dest_dir'] = trim($spec['dest_dir']);
[1]192        }
[120]193        if (isset($spec['dest_file_type'])) {
194            switch ($spec['dest_file_type']) {
195            case IMG_JPG :
196                if (imagetypes() & IMG_JPG == 0) {
[136]197                    $app->logMsg(sprintf('IMG_JPG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
[120]198                }
[199]199                $spec['dest_file_extension'] = 'jpg';
[120]200                break;
201            case IMG_PNG :
202                if (imagetypes() & IMG_PNG == 0) {
[136]203                    $app->logMsg(sprintf('IMG_PNG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
[120]204                }
[199]205                $spec['dest_file_extension'] = 'png';
[120]206                break;
207            case IMG_GIF :
208                if (imagetypes() & IMG_GIF == 0) {
[136]209                    $app->logMsg(sprintf('IMG_GIF is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
[120]210                }
[199]211                $spec['dest_file_extension'] = 'gif';
[120]212                break;
213            case IMG_WBMP :
214                if (imagetypes() & IMG_WBMP == 0) {
[136]215                    $app->logMsg(sprintf('IMG_WBMP is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
[120]216                }
[199]217                $spec['dest_file_extension'] = 'wbmp';
[120]218                break;
219            default :
[136]220                $app->logMsg(sprintf('Invalid dest_file_type: %s', $spec['dest_file_type']), LOG_ERR, __FILE__, __LINE__);
[120]221                break;
222            }
223        }
[626]224        if (IMAGETHUMB_FIT_HEIGHT != $spec['scaling_type'] && (!isset($spec['width']) || !is_numeric($spec['width']))) {
225            $app->logMsg(sprintf('setSpec error: invalid width: %s', $spec['width']), LOG_ERR, __FILE__, __LINE__);
[119]226        }
[626]227        if (IMAGETHUMB_FIT_WIDTH != $spec['scaling_type'] && (!isset($spec['height']) || !is_numeric($spec['height']))) {
228            $app->logMsg(sprintf('setSpec error: invalid height: %s', $spec['height']), LOG_ERR, __FILE__, __LINE__);
[119]229        }
[120]230        if (isset($spec['quality']) && IMG_JPG != $spec['dest_file_type']) {
[626]231            $app->logMsg('The "quality" specification is not used unless IMG_JPG is the dest_file_type.', LOG_NOTICE, __FILE__, __LINE__);
[119]232        }
[120]233        if (isset($spec['progressive']) && IMG_JPG != $spec['dest_file_type']) {
[626]234            $app->logMsg('The "progressive" specification is not used unless IMG_JPG is the dest_file_type.', LOG_NOTICE, __FILE__, __LINE__);
[119]235        }
[468]236
[152]237        // Add to _image_specs array.
238        if (isset($index) && isset($this->_image_specs[$index])) {
239            // Merge with existing spec if index is provided.
240            $final_spec = array_merge($this->_image_specs[$index], $spec);
241            $this->_image_specs[$index] = $final_spec;
[120]242        } else {
[152]243            // Merge with spec defaults.
[468]244            $final_spec = array_merge($this->_default_image_specs, $spec);
[152]245            $this->_image_specs[] = $final_spec;
[120]246        }
[468]247
[121]248        return $final_spec;
[1]249    }
[468]250
[256]251    /*
[334]252    * Retrieve a value of a thumb specification.
[256]253    *
254    * @access   public
255    * @param    string  $key    Key to return. See _default_image_specs above for a list.
[334]256    * @param    int     $index  The index in the spec array of the value to retrieve. The first if not specified.
[256]257    * @return   mixed           Value of requested index.
258    * @author   Quinn Comendant <quinn@strangecode.com>
259    * @version  1.0
260    * @since    08 May 2007 15:26:39
261    */
[468]262    public function getSpec($key, $index=null)
[256]263    {
264        $index = isset($index) ? $index : 0;
265        return $this->_image_specs[$index][$key];
266    }
[1]267
268    /**
[119]269     * Process an entire directory of images.
[1]270     *
271     * @access  public
272     * @return  bool true on success, false on failure.
273     */
[468]274    public function processAll($runtime_specs=null)
[1]275    {
[479]276        $app =& App::getInstance();
[136]277
[1]278        // Ensure we have a source.
[119]279        if ('' == $this->getParam('source_dir')) {
[136]280            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[1]281            return false;
282        }
[119]283
284        // Get all files in source directory.
285        $dir_handle = opendir($this->getParam('source_dir'));
286        while ($dir_handle && ($file = readdir($dir_handle)) !== false) {
287            // If the file name does not start with a dot (. or .. or .htaccess).
[247]288            if (!preg_match('/^\./', $file) && in_array(mb_strtolower(mb_substr($file, mb_strrpos($file, '.') + 1)), $this->getParam('valid_file_extensions'))) {
[119]289                $files[] = $file;
[1]290            }
291        }
[42]292
[119]293        // Process each found file.
294        if (is_array($files) && !empty($files)) {
295            $return_val = 0;
296            foreach ($files as $file_name) {
[313]297                $return_val += (false === $this->processFile($file_name, $runtime_specs) ? 1 : 0);
[119]298            }
[120]299            $this->_raiseMsg(sprintf(_("Resized %s images."), sizeof($files)), MSG_SUCCESS, __FILE__, __LINE__);
[313]300            $this->_raiseMsg(sprintf(_("Resized %s images."), sizeof($files)), MSG_SUCCESS, __FILE__, __LINE__);
[119]301            return 0 === $return_val;
302        } else {
[152]303            $app->logMsg(sprintf('No source images found in directory: %s', $this->getParam('source_dir')), LOG_NOTICE, __FILE__, __LINE__);
[119]304            return false;
305        }
[1]306    }
307
308    /**
309     * Generate thumbnails for the specified file.
310     *
311     * @access  public
[199]312     * @param   string $file_name Name of file with extension.
[121]313     * @param   array $runtime_specs Array of specifications that will override all configured specifications.
[1]314     * @return  bool true on success, false on failure.
315     */
[468]316    public function processFile($file_name, $runtime_specs=null)
[1]317    {
[479]318        $app =& App::getInstance();
[136]319
[334]320        // Source file determined by provided file_name.
[119]321        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
[468]322
[119]323        // Ensure we have a source.
[152]324        if (sizeof($this->_image_specs) < 1) {
[121]325            if (is_array($runtime_specs)) {
326                $this->setSpec($runtime_specs, 0);
327            } else {
[136]328                $app->logMsg(sprintf('Image specifications not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[468]329                return false;
[121]330            }
[1]331        }
[42]332
[1]333        // Ensure we have a source.
[119]334        if ('' == $this->getParam('source_dir')) {
[136]335            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[1]336            return false;
337        }
[42]338
[1]339        // Confirm source image exists.
[119]340        if (!file_exists($source_file)) {
[199]341            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> was not found."), $file_name), MSG_ERR, __FILE__, __LINE__);
[152]342            $app->logMsg(sprintf('Source image not found: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[1]343            return false;
344        }
[42]345
[1]346        // Confirm source image is readable.
[119]347        if (!is_readable($source_file)) {
[199]348            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> is not readable."), $file_name), MSG_ERR, __FILE__, __LINE__);
[152]349            $app->logMsg(sprintf('Source image not readable: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[1]350            return false;
351        }
[42]352
[1]353        // Confirm source image contains data.
[119]354        if (filesize($source_file) <= 0) {
[199]355            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> is zero bytes."), $file_name), MSG_ERR, __FILE__, __LINE__);
[152]356            $app->logMsg(sprintf('Source image is zero bytes: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[1]357            return false;
358        }
[42]359
[1]360        // Confirm source image has a valid file extension.
[119]361        if (!$this->_validFileExtension($file_name)) {
[199]362            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> 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__);
[154]363            $app->logMsg(sprintf('Image resizing failed: source image not of valid type: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[1]364            return false;
365        }
[42]366
[119]367        // Ensure destination directories are created. This will only be called once per page load.
[152]368        if (!$this->_createDestDirs()) {
[278]369            $app->logMsg('Image resizing failed: unable to create dest dirs.', LOG_WARNING, __FILE__, __LINE__);
[152]370            return false;
371        }
[468]372
[119]373        // To keep this script running even if user tries to stop browser.
374        ignore_user_abort(true);
[121]375        ini_set('max_execution_time', 300);
376        ini_set('max_input_time', 300);
[1]377
378        // This remains zero until something goes wrong.
[119]379        $return_val = 0;
[42]380
[152]381        foreach ($this->_image_specs as $index => $spec) {
[468]382
[121]383            if (is_array($runtime_specs)) {
384                // Override with runtime specs.
385                $spec = $this->setSpec($runtime_specs, $index);
386            }
[468]387
[199]388            // Destination filename uses the extension defined by dest_file_extension.
[152]389            if ('/' == $spec['dest_dir']{0}) {
390                // Absolute path.
[247]391                $dest_file = sprintf('%s/%s.%s', $spec['dest_dir'], mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']);
[152]392            } else {
393                // Relative path.
[247]394                $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']);
[152]395            }
[468]396
[152]397            // Ensure destination directory exists and is writable.
398            if (!is_dir(dirname($dest_file)) || !is_writable(dirname($dest_file))) {
399                $this->_createDestDirs($dest_file);
400                if (!is_dir(dirname($dest_file)) || !is_writable(dirname($dest_file))) {
401                    $app->logMsg(sprintf('Image resizing failed, dest_dir invalid: %s', dirname($dest_file)), LOG_ERR, __FILE__, __LINE__);
402                    $return_val++;
403                    continue;
404                }
405            }
[42]406
[119]407            // Skip existing thumbnails with file size below $spec['keep_filesize'].
408            if (isset($spec['keep_filesize']) && file_exists($dest_file)) {
409                $file_size = filesize($dest_file);
410                if (false !== $file_size && $file_size < $spec['keep_filesize']) {
[201]411                    $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__);
[1]412                    continue;
413                }
414            }
[42]415
[119]416            // Determine if original file size is smaller than specified thumbnail size. Do not scale-up if $spec['allow_upscaling'] config is set to false.
417            $image_size = getimagesize($source_file);
418            if ($image_size['0'] <= $spec['width'] && $image_size['1'] <= $spec['height'] && !$spec['allow_upscaling']) {
419                $spec['scaling_type'] = IMAGETHUMB_NO_SCALE;
[201]420                $app->logMsg(sprintf('Image %s smaller than specified %s thumbnail size. Keeping original size.', $file_name, basename($spec['dest_dir'])), LOG_INFO, __FILE__, __LINE__);
[1]421            }
[42]422
[119]423            // DO IT! Based on available method.
424            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'))) {
425                // Resize using Netpbm binaries.
[201]426                $app->logMsg(sprintf('Resizing with Netpbm: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
[119]427                $return_val += $this->_resizeWithNetpbm($source_file, $dest_file, $spec);
428            } else if (IMAGETHUMB_METHOD_GD === $this->getParam('resize_method') && extension_loaded('gd')) {
429                // Resize with GD.
[201]430                $app->logMsg(sprintf('Resizing with GD: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
[119]431                $return_val += $this->_resizeWithGD($source_file, $dest_file, $spec);
[1]432            } else {
[152]433                $app->logMsg(sprintf('Image thumbnailing canceled. Neither Netpbm or GD is available.', null), LOG_ERR, __FILE__, __LINE__);
[119]434                return false;
[1]435            }
[119]436        }
[42]437
[334]438        // If > 0, there was a problem thumb-nailing.
[119]439        return 0 === $return_val;
440    }
[468]441
[119]442    /*
443    * Use the Netpbm and libjpg cjpeg tools to generate a rescaled compressed image.
444    * This is the preferred method over GD which has (supposedly) less quality.
445    *
446    * @access   private
447    * @param    string  $source_file    Full path to source image file.
448    * @param    string  $dest_file      Full path to destination image file.
449    * @param    array   $spec           Array of image size specifications.
[120]450    * @return   int                     0 if no error, n > 0 if errors.
[119]451    * @author   Quinn Comendant <quinn@strangecode.com>
452    * @version  1.0
453    * @since    19 May 2006 13:55:46
454    */
[484]455    protected function _resizeWithNetpbm($source_file, $dest_file, $spec)
[119]456    {
[479]457        $app =& App::getInstance();
[136]458
[119]459        // Define pnmscale arguments.
460        switch ($spec['scaling_type']) {
461        case IMAGETHUMB_FIT_WIDTH :
462            $pnmscale_args = sprintf(' -width %s ', escapeshellarg($spec['width']));
463            break;
464        case IMAGETHUMB_FIT_HEIGHT :
465            $pnmscale_args = sprintf(' -height %s ', escapeshellarg($spec['height']));
466            break;
467        case IMAGETHUMB_FIT_LARGER :
468            $pnmscale_args = sprintf(' -xysize %s %s ', escapeshellarg($spec['width']), escapeshellarg($spec['height']));
469            break;
470        case IMAGETHUMB_STRETCH :
471            $pnmscale_args = sprintf(' -width %s -height %s ', escapeshellarg($spec['width']), escapeshellarg($spec['height']));
472            break;
473        case IMAGETHUMB_NO_SCALE :
474        default :
475            $pnmscale_args = ' 1 ';
476            break;
[1]477        }
[42]478
[119]479        // Define cjpeg arguments.
480        $cjpeg_args = sprintf(' -optimize -quality %s ', escapeshellarg($spec['quality']));
481        $cjpeg_args .= (true === $spec['progressive']) ? ' -progressive ' : '';
482
483        // Format the command that creates the thumbnail.
[199]484        $command = sprintf('%s %s | %s %s | %s %s > %s',
[119]485            escapeshellcmd($this->getParam('anytopnm_binary')),
486            escapeshellcmd($source_file),
487            escapeshellcmd($this->getParam('pnmscale_binary')),
488            escapeshellcmd($pnmscale_args),
489            escapeshellcmd($this->getParam('cjpeg_binary')),
490            escapeshellcmd($cjpeg_args),
[199]491            escapeshellcmd($dest_file)
[119]492        );
[136]493        $app->logMsg(sprintf('ImageThumb Netpbm command: %s', $command), LOG_DEBUG, __FILE__, __LINE__);
[468]494
[119]495        // Execute!
496        exec($command, $output, $return_val);
497
498        if (0 === $return_val) {
499            // Success!
500            // Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
501            chmod($dest_file, $this->getParam('dest_file_perms'));
[136]502            $app->logMsg(sprintf('Successfully resized image %s', $spec['dest_dir'] . '/' . basename($dest_file), $return_val), LOG_DEBUG, __FILE__, __LINE__);
[119]503        } else {
504            // An error occurred.
[331]505            $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__);
[119]506        }
507
508        // Return from the command will be > 0 if there was an error.
509        return $return_val;
[1]510    }
511
[119]512    /*
513    * Use PHP's built-in GD tools to generate a rescaled compressed image.
514    *
515    * @access   private
516    * @param    string  $source_file    Full path to source image file.
517    * @param    string  $dest_file      Full path to destination image file.
518    * @param    array   $spec           Array of image size specifications.
[120]519    * @return   int                     0 if no error, n > 0 if errors.
[119]520    * @author   Quinn Comendant <quinn@strangecode.com>
521    * @version  1.0
522    * @since    19 May 2006 15:46:02
523    */
[484]524    protected function _resizeWithGD($source_file, $dest_file, $spec)
[1]525    {
[479]526        $app =& App::getInstance();
[136]527
[119]528        // Get original file dimensions and type.
529        list($source_image_width, $source_image_height, $source_image_type) = getimagesize($source_file);
530
[334]531        // Define destination image dimensions.
[119]532        switch ($spec['scaling_type']) {
533        case IMAGETHUMB_FIT_WIDTH :
[120]534            $dest_image_width = $spec['width'];
[119]535            $dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
536            break;
537        case IMAGETHUMB_FIT_HEIGHT :
[120]538            $dest_image_height = $spec['height'];
[119]539            $dest_image_width = $source_image_width * ($spec['height'] / $source_image_height);
540            break;
541        case IMAGETHUMB_FIT_LARGER :
[120]542            if (($source_image_width * ($spec['height'] / $source_image_height)) <= $spec['width']) {
543                // Height is larger.
544                $dest_image_height = $spec['height'];
545                $dest_image_width = $source_image_width * ($spec['height'] / $source_image_height);
[119]546            } else {
[120]547                // Width is larger.
548                $dest_image_width = $spec['width'];
549                $dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
[119]550            }
551            break;
552        case IMAGETHUMB_STRETCH :
553            $dest_image_width = $spec['width'];
554            $dest_image_height = $spec['height'];
555            break;
556        case IMAGETHUMB_NO_SCALE :
557        default :
558            $dest_image_width = $source_image_width;
559            $dest_image_height = $source_image_height;
560            break;
561        }
562
563        // Create source image data in memory.
564        switch ($source_image_type) {
[224]565        case IMAGETYPE_JPEG : // = 2
[120]566            $source_image_resource = imagecreatefromjpeg($source_file);
[119]567            break;
[224]568        case IMAGETYPE_PNG : // = 3
[120]569            $source_image_resource = imagecreatefrompng($source_file);
[119]570            break;
[224]571        case IMAGETYPE_GIF : // = 1
[120]572            $source_image_resource = imagecreatefromgif($source_file);
[119]573            break;
[224]574        case IMAGETYPE_WBMP : // = 15
[120]575            $source_image_resource = imagecreatefromwbmp($source_file);
[224]576        case IMAGETYPE_SWF : // = 4
577        case IMAGETYPE_PSD : // = 5
578        case IMAGETYPE_BMP : // = 6
579        case IMAGETYPE_TIFF_II : // = 7
580        case IMAGETYPE_TIFF_MM : // = 8
581        case IMAGETYPE_JPC : // = 9
582        case IMAGETYPE_JP2 : // = 10
583        case IMAGETYPE_JPX : // = 11
584        case IMAGETYPE_JB2 : // = 12
585        case IMAGETYPE_SWC : // = 13
586        case IMAGETYPE_IFF : // = 14
587        case IMAGETYPE_XBM : // = 16
[119]588        default :
[136]589            $app->logMsg(sprintf('Source image type %s not supported.', $source_image_type), LOG_WARNING, __FILE__, __LINE__);
[120]590            return 1;
[1]591        }
[120]592        if (!$source_image_resource) {
[136]593            $app->logMsg(sprintf('Error creating %s image in memory from %s', $source_image_type, $source_file), LOG_WARNING, __FILE__, __LINE__);
[120]594            return 1;
[119]595        }
[468]596
[119]597        // Create destination image data in memory.
[120]598        $dest_image_resource = imagecreatetruecolor($dest_image_width, $dest_image_height);
[42]599
[119]600        // Resample!
[120]601        if (!imagecopyresampled($dest_image_resource, $source_image_resource, 0, 0, 0, 0, $dest_image_width, $dest_image_height, $source_image_width, $source_image_height)) {
[152]602            // Always cleanup images from memory.
603            imagedestroy($source_image_resource);
604            imagedestroy($dest_image_resource);
[313]605            $app->logMsg(sprintf('Error resampling image %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[120]606            return 1;
[1]607        }
[468]608
[120]609        // Sharpen image using a custom filter matrix.
[477]610        if (version_compare(PHP_VERSION, '5.1.0', '>=') && true === $spec['sharpen'] && $spec['sharpen_value'] > 0) {
[468]611            $sharpen_value = round((((48 - 10) / (100 - 1)) * (100 - $spec['sharpen_value'])) + 10); // TODO: WTF is this math?
612            imageconvolution($dest_image_resource, array(array(-1, -1, -1), array(-1, $sharpen_value, -1),array(-1, -1, -1)), ($sharpen_value - 8), 0);
[120]613        }
[42]614
[119]615        // Save image.
616        $return_val = true;
[120]617        switch ($spec['dest_file_type']) {
[119]618        case IMG_JPG :
[120]619            imageinterlace($dest_image_resource, (true == $spec['progressive'] ? 1 : 0));
620            $return_val = imagejpeg($dest_image_resource, $dest_file, $spec['quality']);
[119]621            break;
622        case IMG_PNG :
[120]623            $return_val = imagepng($dest_image_resource, $dest_file);
[119]624            break;
625        case IMG_GIF :
[120]626            $return_val = imagegif($dest_image_resource, $dest_file);
[119]627            break;
628        case IMG_WBMP :
[120]629            $return_val = imagewbmp($dest_image_resource, $dest_file);
[119]630            break;
631        default :
[136]632            $app->logMsg(sprintf('Destination image type %s not supported for image %s.', $spec['dest_file_type'], $dest_file), LOG_WARNING, __FILE__, __LINE__);
[152]633            // Always cleanup images from memory.
634            imagedestroy($source_image_resource);
635            imagedestroy($dest_image_resource);
[120]636            return 1;
[119]637            break;
638        }
639
[152]640        // Always cleanup images from memory.
641        imagedestroy($source_image_resource);
642        imagedestroy($dest_image_resource);
643
[119]644        if ($return_val) {
645            // Success!
646            // Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
[315]647            chmod($dest_file, $this->getParam('dest_file_perms'));
[152]648            $app->logMsg(sprintf('Successfully resized image: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
[120]649            return 0;
[1]650        } else {
[119]651            // An error occurred.
[152]652            $app->logMsg(sprintf('Failed resizing image: %s', $dest_file), LOG_ERR, __FILE__, __LINE__);
[120]653            return 1;
[1]654        }
655    }
656
657    /**
658     * Delete the thumbnails for the specified file name.
659     *
660     * @access  public
[320]661     * @param   string  $file_name  The file name to delete, with extension.
662     * @param   bool    $use_glob   Set true to use glob to find the filename (using $file_name as a pattern)
[1]663     * @return  bool true on success, false on failure.
664     */
[468]665    public function deleteThumbs($file_name, $use_glob=false)
[1]666    {
[479]667        $app =& App::getInstance();
[136]668
[1]669        // Ensure we have a source.
[119]670        if ('' == $this->getParam('source_dir')) {
[136]671            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[1]672            return false;
673        }
[42]674
[119]675        $return_val = 0;
[152]676        foreach ($this->_image_specs as $spec) {
[320]677            // Get base directory depending on if the spec's dest_dir is an absolute path or not.
678            $dest_dir = '/' == $spec['dest_dir']{0} ? $spec['dest_dir'] : sprintf('%s/%s', $this->getParam('source_dir'), $spec['dest_dir']);
679            if ($use_glob) {
[468]680                $dest_file = realpath(sprintf('%s/%s', $dest_dir, $this->_glob($file_name, $dest_dir)));
[152]681            } else {
[320]682                $dest_file = realpath(sprintf('%s/%s.%s', $dest_dir, mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']));
[468]683            }
684
[119]685            if (file_exists($dest_file)) {
686                if (!unlink($dest_file)) {
687                    $return_val++;
[141]688                    $app->logMsg(sprintf('Delete thumbs failed: %s', $dest_file), LOG_WARNING, __FILE__, __LINE__);
[1]689                }
690            }
691        }
[320]692        $app->logMsg(sprintf('Thumbnails deleted for file: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
[119]693        return 0 === $return_val;
[1]694    }
695
696    /**
697     * Delete the source image with the specified file name.
698     *
699     * @access  public
[199]700     * @param   string $file_name The file name to delete, with extension.
[1]701     * @return  bool true on success, false on failure.
702     */
[468]703    public function deleteOriginal($file_name)
[1]704    {
[479]705        $app =& App::getInstance();
[136]706
[1]707        // Ensure we have a source.
[119]708        if ('' == $this->getParam('source_dir')) {
[136]709            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[1]710            return false;
711        }
[42]712
[201]713        // Ensure we have a source.
714        if ('' == $file_name) {
715            $app->logMsg(sprintf('Cannot delete, filename empty.'), LOG_WARNING, __FILE__, __LINE__);
716            return false;
717        }
718
[119]719        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
[201]720        if (is_file($source_file) && unlink($source_file)) {
721            $app->logMsg(sprintf('Original file successfully deleted: %s', $file_name), LOG_INFO, __FILE__, __LINE__);
722            return true;
723        } else {
[141]724            $app->logMsg(sprintf('Delete original failed: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
[1]725            return false;
726        }
727    }
[42]728
[1]729    /**
730     * Returns true if file exists.
731     *
732     * @access  public
[199]733     * @param   string $file_name The file name to test, with extension.
[1]734     * @return  bool true on success, false on failure.
735     */
[468]736    public function exists($file_name)
[1]737    {
[479]738        $app =& App::getInstance();
[136]739
[1]740        // Ensure we have a source.
[119]741        if ('' == $this->getParam('source_dir')) {
[136]742            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
[1]743            return false;
744        }
[42]745
[119]746        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
747        return file_exists($source_file);
[1]748    }
[42]749
[1]750    /**
[199]751     * Tests if extension of $file_name is in the array valid_file_extensions.
[1]752     *
753     * @access  public
754     * @param   string  $file_name  A file name.
755     * @return  bool    True on success, false on failure.
756     */
[484]757    protected function _validFileExtension($file_name)
[1]758    {
759        preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
[468]760        return !empty($ext) && in_array(mb_strtolower($ext[1]), $this->getParam('valid_file_extensions'));
[1]761    }
[42]762
[1]763    /**
[119]764     * Make directory for each specified thumbnail size, if it doesn't exist.
[1]765     *
[119]766     * @access  public
767     * @return  bool true on success, false on failure.
768     */
[484]769    protected function _createDestDirs($filename=null)
[119]770    {
[479]771        $app =& App::getInstance();
[136]772
[152]773        // Keep track of directories we've already created.
774        $dd_hash = md5(isset($filename) ? dirname($filename) : 'none');
775        static $already_checked = array();
[119]776
[152]777        $return_val = 0;
778
779        if (!isset($already_checked[$dd_hash])) {
[119]780            // Ensure we have a source.
781            if ('' == $this->getParam('source_dir')) {
[136]782                $app->logMsg(sprintf('Source directory not set before creating destination directories.'), LOG_ERR, __FILE__, __LINE__);
[119]783                return false;
784            }
[278]785
[119]786            // Loop through specs and ensure all dirs are created.
[152]787            foreach ($this->_image_specs as $spec) {
788                if (isset($filename)) {
789                    $dest_dir = dirname($filename);
790                } else {
[320]791                    // Get base directory depending on if the spec's dest_dir is an absolute path or not.
792                    $dest_dir = '/' == $spec['dest_dir']{0} ? $spec['dest_dir'] : sprintf('%s/%s', $this->getParam('source_dir'), $spec['dest_dir']);
[119]793                }
[152]794                if (!file_exists($dest_dir)) {
[468]795                    // Recursively create destination directory.
[278]796                    $app->logMsg(sprintf('Creating dest dir: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
[468]797                    if (!file_exists($dest_dir) && !($ret = mkdir($dest_dir, $this->getParam('dest_dir_perms'), true))) {
798                        $return_val++;
799                        $app->logMsg(sprintf('mkdir failure: %s', $dest_dir), LOG_ERR, __FILE__, __LINE__);
[152]800                    }
801                    if ($ret) {
[468]802                        $app->logMsg(sprintf('mkdir success: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
[152]803                    }
[278]804                } else {
805                    $app->logMsg(sprintf('Dest dir exists: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
[152]806                }
[119]807            }
808        }
809
[152]810        $already_checked[$dd_hash] = true;
811
812        // If > 0, there was a problem creating dest dirs.
813        return 0 === $return_val;
[119]814    }
815
816    /**
[136]817     * An alias for $app->raiseMsg that only sends messages configured by display_messages.
[119]818     *
[1]819     * @access public
820     *
821     * @param string $message The text description of the message.
822     * @param int    $type    The type of message: MSG_NOTICE,
823     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
824     * @param string $file    __FILE__.
825     * @param string $line    __LINE__.
826     */
[484]827    protected function _raiseMsg($message, $type, $file, $line)
[1]828    {
[479]829        $app =& App::getInstance();
[136]830
[119]831        if ($this->getParam('display_messages') === true || (is_int($this->getParam('display_messages')) && $this->getParam('display_messages') & $type > 0)) {
[136]832            $app->raiseMsg($message, $type, $file, $line);
[1]833        }
834    }
[468]835
[320]836    /**
837     * Get filename by glob pattern. Searches a directory for an image that matches the
838     * specified glob pattern and returns the filename of the first file found.
839     *
840     * @access  public
841     * @param   string  $pattern    Pattern to match filename.
842     * @param   string  $directory  Pattern to match containing directory.
843     * @return  string filename on success, empty string on failure.
844     * @author  Quinn Comendant <quinn@strangecode.com>
845     * @since   15 Nov 2005 20:55:22
846     */
[484]847    protected function _glob($pattern, $directory)
[320]848    {
849        $file_list = glob(sprintf('%s/%s', $directory, $pattern));
850        if (isset($file_list[0])) {
851            return basename($file_list[0]);
852        } else {
853            return '';
854        }
855    }
[1]856
857} // End of class.
Note: See TracBrowser for help on using the repository browser.