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

Last change on this file since 334 was 334, checked in by quinn, 16 years ago

Fixed lots of misplings. I'm so embarrassed! ;P

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