source: branches/eli_branch/lib/ImageThumb.inc.php @ 527

Last change on this file since 527 was 439, checked in by anonymous, 11 years ago

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

File size: 37.9 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
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/**
24 * ImageThumb.inc.php
25 *
26 * @author   Quinn Comendant <quinn@strangecode.com>
27 * @requires Netpbm <http://sourceforge.net/projects/netpbm/> and libjpeg or GD.
28 * @version  2.0
29 */
30
31// Image proprtion options.
32define('IMAGETHUMB_FIT_WIDTH', 1);
33define('IMAGETHUMB_FIT_HEIGHT', 2);
34define('IMAGETHUMB_FIT_LARGER', 3);
35define('IMAGETHUMB_STRETCH', 4);
36define('IMAGETHUMB_NO_SCALE', 5);
37
38// Image resize options.
39define('IMAGETHUMB_METHOD_NETPBM', 6);
40define('IMAGETHUMB_METHOD_GD', 7);
41
42class ImageThumb {
43   
44    // General object parameters.
45    private $_params = array(
46        // The location for images to create thumbnails from.
47        'source_dir' => null,
48
49        // Existing files will be overwritten when there is a name conflict?
50        'allow_overwriting' => false,
51
52        // The file permissions of the uploaded files. Remember, files will be owned by the web server user.
53        'dest_file_perms' => 0600,
54
55        // Permissions of auto-created directories. Must be at least 0700 with owner=apache.
56        'dest_dir_perms' => 0700,
57
58        // Require file to have one of the following file name extensions.
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,
63
64        // Netpbm and libjpeg binary locations.
65        'anytopnm_binary' => '/usr/bin/anytopnm',
66        'pnmscale_binary' => '/usr/bin/pnmscale',
67        'cjpeg_binary' => '/usr/bin/cjpeg',
68
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.
74    private $_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).
77        'dest_dir' => null,
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')
83        'dest_file_extension' => 'jpg',
84       
85        // Type of scaling to perform, and sizes used to calculate max dimensions.
86        'scaling_type' => IMAGETHUMB_FIT_LARGER,
87        'width' => null,
88        'height' => null,
89
90        // Percentage quality of image compression output 0-100.
91        'quality' => 65,
92
93        // Create progressive jpegs?
94        'progressive' => false,
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
102        // If source image is smaller than thumbnail, allow upscaling?
103        'allow_upscaling' => false,
104
105        // If thumb exists and filesize is smaller than this, do not overwrite the thumb.
106        'keep_filesize' => null,
107    );
108
109    // Final specifications for image sizes, set with setSpec().
110    private $_image_specs = array();
111
112    /**
113     * Set (or overwrite existing) parameters by passing an array of new parameters.
114     *
115     * @access public
116     * @param  array    $params     Array of parameters (key => val pairs).
117     */
118    public function setParam($params)
119    {
120        $app =& App::getInstance();
121
122        if (isset($params) && is_array($params)) {
123
124            // Enforce valid source_dir parameter.
125            if (isset($params['source_dir'])) {
126                $params['source_dir'] = realpath($params['source_dir']);
127                // Source must be directory.
128                if (!is_dir($params['source_dir'])) {
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                    }
140                }
141                // Source must be readable.
142                if (!is_readable($params['source_dir'])) {
143                    $app->logMsg(sprintf('Source directory not readable: %s', $params['source_dir']), LOG_ERR, __FILE__, __LINE__);
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 {
151            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
152        }
153    }
154
155    /**
156     * Return the value of a parameter, if it exists.
157     *
158     * @access public
159     * @param string $param        Which parameter to return.
160     * @return mixed               Configured parameter value.
161     */
162    public function getParam($param)
163    {
164        $app =& App::getInstance();
165   
166        if (isset($this->_params[$param])) {
167            return $this->_params[$param];
168        } else {
169            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
170            return null;
171        }
172    }
173
174    /**
175     * Set the specification of thumbnails.
176     *
177     * @access  public
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
180     *                          Use to overwrite existing spec array values.
181     */
182    public function setSpec($spec, $index=null)
183    {
184        $app =& App::getInstance();
185
186        // A little sanity checking.
187        if (!isset($spec['dest_dir']) || '' == trim($spec['dest_dir'])) {
188            $app->logMsg('setSpec error: dest_dir not specified.', LOG_ERR, __FILE__, __LINE__);
189        } else {
190            $spec['dest_dir'] = trim($spec['dest_dir']);           
191        }
192        if (isset($spec['dest_file_type'])) {
193            switch ($spec['dest_file_type']) {
194            case IMG_JPG :
195                if (imagetypes() & IMG_JPG == 0) {
196                    $app->logMsg(sprintf('IMG_JPG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
197                }
198                $spec['dest_file_extension'] = 'jpg';
199                break;
200            case IMG_PNG :
201                if (imagetypes() & IMG_PNG == 0) {
202                    $app->logMsg(sprintf('IMG_PNG is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
203                }
204                $spec['dest_file_extension'] = 'png';
205                break;
206            case IMG_GIF :
207                if (imagetypes() & IMG_GIF == 0) {
208                    $app->logMsg(sprintf('IMG_GIF is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
209                }
210                $spec['dest_file_extension'] = 'gif';
211                break;
212            case IMG_WBMP :
213                if (imagetypes() & IMG_WBMP == 0) {
214                    $app->logMsg(sprintf('IMG_WBMP is not supported by this version of PHP GD.', null), LOG_ERR, __FILE__, __LINE__);
215                }
216                $spec['dest_file_extension'] = 'wbmp';
217                break;
218            default :
219                $app->logMsg(sprintf('Invalid dest_file_type: %s', $spec['dest_file_type']), LOG_ERR, __FILE__, __LINE__);
220                break;
221            }
222        }
223        if (!isset($spec['width']) || !is_int($spec['width'])) {
224            $app->logMsg('setSpec error: width not specified.', LOG_ERR, __FILE__, __LINE__);
225        }
226        if (!isset($spec['height']) || !is_int($spec['height'])) {
227            $app->logMsg('setSpec error: height not specified.', LOG_ERR, __FILE__, __LINE__);
228        }
229        if (isset($spec['quality']) && IMG_JPG != $spec['dest_file_type']) {
230            $app->logMsg('The "quality" specification is not used unless IMG_JPG is the dest_file_type.', LOG_INFO, __FILE__, __LINE__);
231        }
232        if (isset($spec['progressive']) && IMG_JPG != $spec['dest_file_type']) {
233            $app->logMsg('The "progressive" specification is not used unless IMG_JPG is the dest_file_type.', LOG_INFO, __FILE__, __LINE__);
234        }
235       
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;
241        } else {
242            // Merge with spec defaults.
243            $final_spec = array_merge($this->_default_image_specs, $spec);           
244            $this->_image_specs[] = $final_spec;
245        }
246       
247        return $final_spec;
248    }
249   
250    /*
251    * Retrieve a value of a thumb specification.
252    *
253    * @access   public
254    * @param    string  $key    Key to return. See _default_image_specs above for a list.
255    * @param    int     $index  The index in the spec array of the value to retrieve. The first if not specified.
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    public function getSpec($key, $index=null)
262    {
263        $index = isset($index) ? $index : 0;
264        return $this->_image_specs[$index][$key];
265    }
266
267    /**
268     * Process an entire directory of images.
269     *
270     * @access  public
271     * @return  bool true on success, false on failure.
272     */
273    public function processAll($runtime_specs=null)
274    {
275        $app =& App::getInstance();
276
277        // Ensure we have a source.
278        if ('' == $this->getParam('source_dir')) {
279            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
280            return false;
281        }
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).
287            if (!preg_match('/^\./', $file) && in_array(mb_strtolower(mb_substr($file, mb_strrpos($file, '.') + 1)), $this->getParam('valid_file_extensions'))) {
288                $files[] = $file;
289            }
290        }
291
292        // Process each found file.
293        if (is_array($files) && !empty($files)) {
294            $return_val = 0;
295            foreach ($files as $file_name) {
296                $return_val += (false === $this->processFile($file_name, $runtime_specs) ? 1 : 0);
297            }
298            $this->_raiseMsg(sprintf(_("Resized %s images."), sizeof($files)), MSG_SUCCESS, __FILE__, __LINE__);
299            $this->_raiseMsg(sprintf(_("Resized %s images."), sizeof($files)), MSG_SUCCESS, __FILE__, __LINE__);
300            return 0 === $return_val;
301        } else {
302            $app->logMsg(sprintf('No source images found in directory: %s', $this->getParam('source_dir')), LOG_NOTICE, __FILE__, __LINE__);
303            return false;
304        }
305    }
306
307    /**
308     * Generate thumbnails for the specified file.
309     *
310     * @access  public
311     * @param   string $file_name Name of file with extension.
312     * @param   array $runtime_specs Array of specifications that will override all configured specifications.
313     * @return  bool true on success, false on failure.
314     */
315    public function processFile($file_name, $runtime_specs=null)
316    {
317        $app =& App::getInstance();
318
319        // Source file determined by provided file_name.
320        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
321       
322        // Ensure we have a source.
323        if (sizeof($this->_image_specs) < 1) {
324            if (is_array($runtime_specs)) {
325                $this->setSpec($runtime_specs, 0);
326            } else {
327                $app->logMsg(sprintf('Image specifications not set before processing.'), LOG_ERR, __FILE__, __LINE__);
328                return false;               
329            }
330        }
331
332        // Ensure we have a source.
333        if ('' == $this->getParam('source_dir')) {
334            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
335            return false;
336        }
337
338        // Confirm source image exists.
339        if (!file_exists($source_file)) {
340            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> was not found."), $file_name), MSG_ERR, __FILE__, __LINE__);
341            $app->logMsg(sprintf('Source image not found: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
342            return false;
343        }
344
345        // Confirm source image is readable.
346        if (!is_readable($source_file)) {
347            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> is not readable."), $file_name), MSG_ERR, __FILE__, __LINE__);
348            $app->logMsg(sprintf('Source image not readable: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
349            return false;
350        }
351
352        // Confirm source image contains data.
353        if (filesize($source_file) <= 0) {
354            $this->_raiseMsg(sprintf(_("Image resizing failed: source image <em>%s</em> is zero bytes."), $file_name), MSG_ERR, __FILE__, __LINE__);
355            $app->logMsg(sprintf('Source image is zero bytes: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
356            return false;
357        }
358
359        // Confirm source image has a valid file extension.
360        if (!$this->_validFileExtension($file_name)) {
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__);
362            $app->logMsg(sprintf('Image resizing failed: source image not of valid type: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
363            return false;
364        }
365
366        // Ensure destination directories are created. This will only be called once per page load.
367        if (!$this->_createDestDirs()) {
368            $app->logMsg('Image resizing failed: unable to create dest dirs.', LOG_WARNING, __FILE__, __LINE__);
369            return false;
370        }
371       
372        // To keep this script running even if user tries to stop browser.
373        ignore_user_abort(true);
374        ini_set('max_execution_time', 300);
375        ini_set('max_input_time', 300);
376
377        // This remains zero until something goes wrong.
378        $return_val = 0;
379
380        foreach ($this->_image_specs as $index => $spec) {
381           
382            if (is_array($runtime_specs)) {
383                // Override with runtime specs.
384                $spec = $this->setSpec($runtime_specs, $index);
385            }
386           
387            // Destination filename uses the extension defined by dest_file_extension.
388            if ('/' == $spec['dest_dir']{0}) {
389                // Absolute path.
390                $dest_file = sprintf('%s/%s.%s', $spec['dest_dir'], mb_substr($file_name, 0, mb_strrpos($file_name, '.')), $spec['dest_file_extension']);
391            } else {
392                // Relative path.
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']);
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            }
405
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']) {
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__);
411                    continue;
412                }
413            }
414
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;
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__);
420            }
421
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.
425                $app->logMsg(sprintf('Resizing with Netpbm: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
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.
429                $app->logMsg(sprintf('Resizing with GD: %s', $source_file), LOG_DEBUG, __FILE__, __LINE__);
430                $return_val += $this->_resizeWithGD($source_file, $dest_file, $spec);
431            } else {
432                $app->logMsg(sprintf('Image thumbnailing canceled. Neither Netpbm or GD is available.', null), LOG_ERR, __FILE__, __LINE__);
433                return false;
434            }
435        }
436
437        // If > 0, there was a problem thumb-nailing.
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.
449    * @return   int                     0 if no error, n > 0 if errors.
450    * @author   Quinn Comendant <quinn@strangecode.com>
451    * @version  1.0
452    * @since    19 May 2006 13:55:46
453    */
454    private function _resizeWithNetpbm($source_file, $dest_file, $spec)
455    {
456        $app =& App::getInstance();
457
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;
476        }
477
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.
483        $command = sprintf('%s %s | %s %s | %s %s > %s',
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),
490            escapeshellcmd($dest_file)
491        );
492        $app->logMsg(sprintf('ImageThumb Netpbm command: %s', $command), LOG_DEBUG, __FILE__, __LINE__);
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'));
501            $app->logMsg(sprintf('Successfully resized image %s', $spec['dest_dir'] . '/' . basename($dest_file), $return_val), LOG_DEBUG, __FILE__, __LINE__);
502        } else {
503            // An error occurred.
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__);
505        }
506
507        // Return from the command will be > 0 if there was an error.
508        return $return_val;
509    }
510
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.
518    * @return   int                     0 if no error, n > 0 if errors.
519    * @author   Quinn Comendant <quinn@strangecode.com>
520    * @version  1.0
521    * @since    19 May 2006 15:46:02
522    */
523    private function _resizeWithGD($source_file, $dest_file, $spec)
524    {
525        $app =& App::getInstance();
526
527        // Get original file dimensions and type.
528        list($source_image_width, $source_image_height, $source_image_type) = getimagesize($source_file);
529
530        // Define destination image dimensions.
531        switch ($spec['scaling_type']) {
532        case IMAGETHUMB_FIT_WIDTH :
533            $dest_image_width = $spec['width'];
534            $dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
535            break;
536        case IMAGETHUMB_FIT_HEIGHT :
537            $dest_image_height = $spec['height'];
538            $dest_image_width = $source_image_width * ($spec['height'] / $source_image_height);
539            break;
540        case IMAGETHUMB_FIT_LARGER :
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);
545            } else {
546                // Width is larger.
547                $dest_image_width = $spec['width'];
548                $dest_image_height = $source_image_height * ($spec['width'] / $source_image_width);
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) {
564        case IMAGETYPE_JPEG : // = 2
565            $source_image_resource = imagecreatefromjpeg($source_file);
566            break;
567        case IMAGETYPE_PNG : // = 3
568            $source_image_resource = imagecreatefrompng($source_file);
569            break;
570        case IMAGETYPE_GIF : // = 1
571            $source_image_resource = imagecreatefromgif($source_file);
572            break;
573        case IMAGETYPE_WBMP : // = 15
574            $source_image_resource = imagecreatefromwbmp($source_file);
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
587        default :
588            $app->logMsg(sprintf('Source image type %s not supported.', $source_image_type), LOG_WARNING, __FILE__, __LINE__);
589            return 1;
590        }
591        if (!$source_image_resource) {
592            $app->logMsg(sprintf('Error creating %s image in memory from %s', $source_image_type, $source_file), LOG_WARNING, __FILE__, __LINE__);
593            return 1;
594        }
595       
596        // Create destination image data in memory.
597        $dest_image_resource = imagecreatetruecolor($dest_image_width, $dest_image_height);
598
599        // Resample!
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)) {
601            // Always cleanup images from memory.
602            imagedestroy($source_image_resource);
603            imagedestroy($dest_image_resource);
604            $app->logMsg(sprintf('Error resampling image %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
605            return 1;
606        }
607       
608        // Sharpen image using a custom filter matrix.
609        if (phpversion() > '5.1' && true === $spec['sharpen'] && $spec['sharpen_value'] > 0) {
610            $sharpen_value = round((((48 - 10) / (100 - 1)) * (100 - $spec['sharpen_value'])) + 10); /// WTF is this math?
611            imageconvolution($dest_image_resource, array(array(-1,-1,-1),array(-1,$sharpen_value,-1),array(-1,-1,-1)), ($sharpen_value - 8), 0);
612        }
613
614        // Save image.
615        $return_val = true;
616        switch ($spec['dest_file_type']) {
617        case IMG_JPG :
618            imageinterlace($dest_image_resource, (true == $spec['progressive'] ? 1 : 0));
619            $return_val = imagejpeg($dest_image_resource, $dest_file, $spec['quality']);
620            break;
621        case IMG_PNG :
622            $return_val = imagepng($dest_image_resource, $dest_file);
623            break;
624        case IMG_GIF :
625            $return_val = imagegif($dest_image_resource, $dest_file);
626            break;
627        case IMG_WBMP :
628            $return_val = imagewbmp($dest_image_resource, $dest_file);
629            break;
630        default :
631            $app->logMsg(sprintf('Destination image type %s not supported for image %s.', $spec['dest_file_type'], $dest_file), LOG_WARNING, __FILE__, __LINE__);
632            // Always cleanup images from memory.
633            imagedestroy($source_image_resource);
634            imagedestroy($dest_image_resource);
635            return 1;
636            break;
637        }
638
639        // Always cleanup images from memory.
640        imagedestroy($source_image_resource);
641        imagedestroy($dest_image_resource);
642
643        if ($return_val) {
644            // Success!
645            // Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
646            chmod($dest_file, $this->getParam('dest_file_perms'));
647            $app->logMsg(sprintf('Successfully resized image: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
648            return 0;
649        } else {
650            // An error occurred.
651            $app->logMsg(sprintf('Failed resizing image: %s', $dest_file), LOG_ERR, __FILE__, __LINE__);
652            return 1;
653        }
654    }
655
656    /**
657     * Delete the thumbnails for the specified file name.
658     *
659     * @access  public
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)
662     * @return  bool true on success, false on failure.
663     */
664    public function deleteThumbs($file_name, $use_glob=false)
665    {
666        $app =& App::getInstance();
667
668        // Ensure we have a source.
669        if ('' == $this->getParam('source_dir')) {
670            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
671            return false;
672        }
673
674        $return_val = 0;
675        foreach ($this->_image_specs as $spec) {
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)));               
680            } else {
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           
684            if (file_exists($dest_file)) {
685                if (!unlink($dest_file)) {
686                    $return_val++;
687                    $app->logMsg(sprintf('Delete thumbs failed: %s', $dest_file), LOG_WARNING, __FILE__, __LINE__);
688                }
689            }
690        }
691        $app->logMsg(sprintf('Thumbnails deleted for file: %s', $dest_file), LOG_DEBUG, __FILE__, __LINE__);
692        return 0 === $return_val;
693    }
694
695    /**
696     * Delete the source image with the specified file name.
697     *
698     * @access  public
699     * @param   string $file_name The file name to delete, with extension.
700     * @return  bool true on success, false on failure.
701     */
702    public function deleteOriginal($file_name)
703    {
704        $app =& App::getInstance();
705
706        // Ensure we have a source.
707        if ('' == $this->getParam('source_dir')) {
708            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
709            return false;
710        }
711
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
718        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
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 {
723            $app->logMsg(sprintf('Delete original failed: %s', $source_file), LOG_WARNING, __FILE__, __LINE__);
724            return false;
725        }
726    }
727
728    /**
729     * Returns true if file exists.
730     *
731     * @access  public
732     * @param   string $file_name The file name to test, with extension.
733     * @return  bool true on success, false on failure.
734     */
735    public function exists($file_name)
736    {
737        $app =& App::getInstance();
738
739        // Ensure we have a source.
740        if ('' == $this->getParam('source_dir')) {
741            $app->logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
742            return false;
743        }
744
745        $source_file = realpath(sprintf('%s/%s', $this->getParam('source_dir'), $file_name));
746        return file_exists($source_file);
747    }
748
749    /**
750     * Tests if extension of $file_name is in the array valid_file_extensions.
751     *
752     * @access  public
753     * @param   string  $file_name  A file name.
754     * @return  bool    True on success, false on failure.
755     */
756    private function _validFileExtension($file_name)
757    {
758        preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
759        return !empty($ext) && in_array(mb_strtolower($ext[1]), $this->getParam('valid_file_extensions'));       
760    }
761
762    /**
763     * Make directory for each specified thumbnail size, if it doesn't exist.
764     *
765     * @access  public
766     * @return  bool true on success, false on failure.
767     */
768    private function _createDestDirs($filename=null)
769    {
770        $app =& App::getInstance();
771
772        // Keep track of directories we've already created.
773        $dd_hash = md5(isset($filename) ? dirname($filename) : 'none');
774        static $already_checked = array();
775
776        $return_val = 0;
777
778        if (!isset($already_checked[$dd_hash])) {
779            // Ensure we have a source.
780            if ('' == $this->getParam('source_dir')) {
781                $app->logMsg(sprintf('Source directory not set before creating destination directories.'), LOG_ERR, __FILE__, __LINE__);
782                return false;
783            }
784
785            // Loop through specs and ensure all dirs are created.
786            foreach ($this->_image_specs as $spec) {
787                if (isset($filename)) {
788                    $dest_dir = dirname($filename);
789                } else {
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']);
792                }
793                if (!file_exists($dest_dir)) {
794                    $app->logMsg(sprintf('Creating dest dir: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
795                    if (phpversion() > '5') {
796                        // Recursive.
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__);
800                        }
801                    } else {
802                        // Recursive mkdir for php 4.
803                        $path = '';
804                        $depth = 0;
805                        $ret = true;
806                        foreach (array_diff(explode('/', $dest_dir), array('')) as $dir) {
807                            $path .= '/' . $dir;
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')))) {
811                                $return_val++;
812                                $app->logMsg(sprintf('mkdir failure: %s', $path), LOG_ERR, __FILE__, __LINE__);
813                                break;
814                            } else {
815                                $app->logMsg(sprintf('mkdir attempt: %s', $path), LOG_DEBUG, __FILE__, __LINE__);
816                            }
817                        }
818                    }
819
820                    if ($ret) {
821                        $app->logMsg(sprintf('mkdir success: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);                       
822                    }
823                } else {
824                    $app->logMsg(sprintf('Dest dir exists: %s', $dest_dir), LOG_DEBUG, __FILE__, __LINE__);
825                }
826            }
827        }
828
829        $already_checked[$dd_hash] = true;
830
831        // If > 0, there was a problem creating dest dirs.
832        return 0 === $return_val;
833    }
834
835    /**
836     * An alias for $app->raiseMsg that only sends messages configured by display_messages.
837     *
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     */
846    private function _raiseMsg($message, $type, $file, $line)
847    {
848        $app =& App::getInstance();
849
850        if ($this->getParam('display_messages') === true || (is_int($this->getParam('display_messages')) && $this->getParam('display_messages') & $type > 0)) {
851            $app->raiseMsg($message, $type, $file, $line);
852        }
853    }
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    private 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    }
875
876} // End of class.
Note: See TracBrowser for help on using the repository browser.