source: tags/2.1.5/lib/ImageThumb.inc.php

Last change on this file was 377, checked in by quinn, 14 years ago

Releasing trunk as stable version 2.1.5

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