source: branches/1.1dev/lib/ImageThumb.inc.php

Last change on this file was 708, checked in by anonymous, 4 years ago

Update class constructor method names to construct

File size: 15.7 KB
Line 
1<?php
2/**
3 * ImageThumb.inc.php
4 * Code by Strangecode :: www.strangecode.com :: This document contains copyrighted information
5 */
6
7/**
8 * The ImageThumb class resizes images.
9 *
10 * @author   Quinn Comendant <quinn@strangecode.com>
11 * @requires Netpbm binaries from http://sourceforge.net/projects/netpbm/
12 * @version  1.0
13 */
14
15require_once dirname(__FILE__) . '/App.inc.php';
16
17define('IMAGETHUMB_FIT_WIDTH', 1);
18define('IMAGETHUMB_FIT_HEIGHT', 2);
19define('IMAGETHUMB_FIT_LARGER', 3);
20define('IMAGETHUMB_STRETCH', 4);
21define('IMAGETHUMB_NO_SCALE', 5);
22
23class ImageThumb {
24   
25    // The location for images to create thumbnails from.
26    var $source_dir = null;
27   
28    // Specifications for thumbnail images.
29    var $spec; 
30   
31    // Array of acceptable file extensions (lowercase).
32    var $valid_file_extensions = array('jpg', 'jpeg', 'gif', 'png');
33   
34    // The uploaded files will be owned by user 'apache'. Set world-read/write
35    // if the website admin needs to read/delete these files. Must be at least 0400 with owner=apache.
36    var $dest_file_perms = 0644;
37   
38    // Must be at least 0700 with owner=apache.
39    var $dest_dir_perms = 0777;
40
41    // Executable binary locations.
42    var $anytopnm_binary = '/usr/bin/anytopnm';
43    var $pnmscale_binary = '/usr/bin/pnmscale';
44    var $cjpeg_binary = '/usr/bin/cjpeg';
45    var $_valid_binaries = true;
46
47    /**
48     *
49     */
50    function __construct()
51    {
52//         if (!file_exists($this->anytopnm_binary)) {
53//             logMsg(sprintf('ImageThumb error: %s not found.', $this->anytopnm_binary), LOG_ERR, __FILE__, __LINE__);
54//             $this->_valid_binaries = false;
55//             return false;
56//         }
57//         if (!file_exists($this->pnmscale_binary)) {
58//             logMsg(sprintf('ImageThumb error: %s not found.', $this->pnmscale_binary), LOG_ERR, __FILE__, __LINE__);
59//             $this->_valid_binaries = false;
60//             return false;
61//         }
62//         if (!file_exists($this->cjpeg_binary)) {
63//             logMsg(sprintf('ImageThumb error: %s not found.', $this->cjpeg_binary), LOG_ERR, __FILE__, __LINE__);
64//             $this->_valid_binaries = false;
65//             return false;
66//         }
67        return true;
68    }
69
70    /**
71     *
72     */
73    function setSourceDirectory($source_dir)
74    {
75       
76        // Set the source directory path, stripping any extra slashes if needed.
77        $this->source_dir = preg_replace('!/+$!', '', $source_dir);
78       
79        if (!is_dir($this->source_dir)) {
80            logMsg(sprintf('ImageThumb error: source directory not found: %s', $this->source_dir), LOG_ERR, __FILE__, __LINE__);
81            return false;
82        }
83        if (!is_readable($this->source_dir)) {
84            logMsg(sprintf('ImageThumb error: source directory not readable: %s', $this->source_dir), LOG_ERR, __FILE__, __LINE__);
85            return false;
86        }
87        return true;
88    }
89
90    /**
91     *
92     */
93    function setSpec($spec = array())
94    {
95        $dest_dir        = preg_replace('!/+$!', '', $spec['dest_dir']);
96        $width           = $spec['width'];
97        $height          = $spec['height'];
98        $scaling_type    = $spec['scaling_type'];
99        $quality         = isset($spec['quality']) ? $spec['quality'] : 75;
100        $progressive     = isset($spec['progressive']) ? $spec['progressive'] : false;
101        $allow_upscaling = isset($spec['allow_upscaling']) ? $spec['allow_upscaling'] : false;
102        $keep_filesize   = isset($spec['keep_filesize']) ? $spec['keep_filesize'] : null;
103   
104        // Define pnmscale arguments.
105        switch ($scaling_type) {
106        case IMAGETHUMB_FIT_WIDTH :
107            if (empty($width)) {
108                logMsg('ImageThumb error: width not specified for IMAGETHUMB_FIT_WIDTH.', LOG_ERR, __FILE__, __LINE__);
109                return false;
110            }
111            $pnmscale_args = sprintf(' -width %s ', escapeshellcmd($width));
112            break;
113        case IMAGETHUMB_FIT_HEIGHT :
114            if (empty($height)) {
115                logMsg('ImageThumb error: height not specified for IMAGETHUMB_FIT_HEIGHT.', LOG_ERR, __FILE__, __LINE__);
116                return false;
117            }
118            $pnmscale_args = sprintf(' -height %s ', escapeshellcmd($height));
119            break;
120        case IMAGETHUMB_FIT_LARGER :
121            if (empty($width) || empty($height)) {
122                logMsg('ImageThumb error: width or height not specified for IMAGETHUMB_FIT_LARGER.', LOG_ERR, __FILE__, __LINE__);
123                return false;
124            }
125            $pnmscale_args = sprintf(' -xysize %s %s ', escapeshellcmd($width), escapeshellcmd($height));
126            break;
127        case IMAGETHUMB_STRETCH :
128            if (empty($width) || empty($height)) {
129                logMsg('ImageThumb error: width or height not specified for IMAGETHUMB_STRETCH.', LOG_ERR, __FILE__, __LINE__);
130                return false;
131            }
132            $pnmscale_args = sprintf(' -width %s -height %s ', escapeshellcmd($width), escapeshellcmd($height));
133            break;
134        case IMAGETHUMB_NO_SCALE :
135        default :
136            $pnmscale_args = ' 1 ';
137            break;
138        }
139       
140        // Define cjpeg arguments.
141        $cjpeg_args = sprintf(' -optimize -quality %s ', escapeshellcmd($quality));
142        $cjpeg_args .= (true === $progressive) ? ' -progressive ' : '';
143       
144        $this->spec[] = array(
145            'dest_dir' => $dest_dir,
146            'width' => $width,
147            'height' => $height,
148            'scaling_type' => $scaling_type,
149            'quality' => $quality,
150            'progressive' => $progressive,
151            'pnmscale_args' => $pnmscale_args,
152            'cjpeg_args' => $cjpeg_args,
153            'allow_upscaling' => $allow_upscaling,
154            'keep_filesize' => $keep_filesize,
155        );
156    }
157
158    /**
159     *
160     */
161    function createDestDirs()
162    {
163        // Ensure we have a source.
164        if (!isset($this->source_dir)) {
165            logMsg(sprintf('Source directory not set before creating destination directories.'), LOG_ERR, __FILE__, __LINE__);
166            return false;
167        }
168        $return_val = 0;
169        foreach ($this->spec as $s) {
170            if (!is_dir($this->source_dir . '/' . $s['dest_dir'])) {
171                if (!mkdir($this->source_dir . '/' . $s['dest_dir'], $this->dest_dir_perms)) {
172                    $return_val += 1;
173                    logMsg(sprintf('mkdir failure: %s', $this->source_dir . '/' . $s['dest_dir']), LOG_ERR, __FILE__, __LINE__);
174                }
175            }
176        }
177               
178        // If > 0, there was a problem creating dest dirs.
179        return (0 == $return_val);
180    }
181
182    /**
183     *
184     */
185    function processFile($file_name)
186    {
187        // Source file determined by provided file_name.
188        $source_file = realpath($this->source_dir . '/' . $file_name);
189
190        // Ensure we have valid binaries.
191        if (!$this->_valid_binaries) {
192            logMsg(sprintf('Netpnm binaries invalid.'), LOG_ERR, __FILE__, __LINE__);
193            return false;
194        }
195       
196        // Ensure we have a source.
197        if (!isset($this->source_dir)) {
198            logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
199            return false;
200        }
201       
202        // To keep this script running even if user tries to stop browser.
203        ignore_user_abort(true); 
204        if (!ini_get('safe_mode')) { 
205            set_time_limit(300); 
206        }
207       
208        // Confirm source image exists.
209        if (!file_exists($this->source_dir . '/' . $file_name)) {
210//             raiseMsg(sprintf(_("Image resizing failed: source image not found: <strong>%s</strong>"), $file_name), MSG_ERR, __FILE__, __LINE__);
211            logMsg(sprintf('Source image not found: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
212            return false;
213        }
214       
215        // Confirm source image is readable.
216        if (!is_readable($this->source_dir . '/' . $file_name)) {
217//             raiseMsg(sprintf(_("Image resizing failed: source image not readable: <strong>%s</strong>"), $file_name), MSG_ERR, __FILE__, __LINE__);
218            logMsg(sprintf('Source image not readable: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
219            return false;
220        }
221       
222        // Confirm source image contains data.
223        if (filesize($this->source_dir . '/' . $file_name) < 1) {
224//             raiseMsg(sprintf(_("Image resizing failed: source image corrupt: <strong>%s</strong>"), $file_name), MSG_ERR, __FILE__, __LINE__);
225            logMsg(sprintf('Source image is zero bytes: %s', $file_name), LOG_WARNING, __FILE__, __LINE__);
226            return false;
227        }
228       
229        // Confirm source image has a valid file extension.
230        if (!$this->validFileExtension($file_name)) {
231//             raiseMsg(sprintf(_("Image resizing failed: source image not of valid type: <strong>%s</strong>"), $file_name), MSG_ERR, __FILE__, __LINE__);
232            logMsg(sprintf('Image resizing failed: source image not of valid type: %s', $file_name), LOG_ERR, __FILE__, __LINE__);
233            return false;
234        }
235       
236        // Output file will be a jpg. Set file extension. // FIXME: is there a better way to do this?
237        preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
238        if (!in_array(strtolower($ext[1]), array('jpg', 'jpeg'))) {
239            $file_name = substr($file_name, 0, strrpos($file_name, '.')) . '.jpg';
240        }
241
242        // This remains zero until something goes wrong.
243        $final_return_val = 0;
244       
245        foreach ($this->spec as $s) {
246       
247            // Skip existing thumbnails with file size below $s['keep_filesize'].
248            if (file_exists(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name)) && isset($s['keep_filesize'])) {
249                $file_size = filesize(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name));
250                if ($file_size && $file_size < $s['keep_filesize']) {
251                    logMsg(sprintf('Skipping thumbnail %s. File already exists and file size is less than %s bytes.', $s['dest_dir'] . '/' . $file_name, $s['keep_filesize']), LOG_DEBUG, __FILE__, __LINE__);
252                    continue;
253                }
254            }
255           
256            // Determine if original file size is smaller than specified thumbnail size. Do not scale-up if allow_upscaling config is set to false.
257            $image_size = getimagesize($source_file);
258            if ($image_size['0'] <= $s['width'] && $image_size['1'] <= $s['height'] && !$s['allow_upscaling']) {
259                $pnmscale_args = ' 1 ';
260                logMsg(sprintf('Image %s smaller (%sx%s) than specified %s thumbnail size (%sx%s). Keeping original size.', $source_file, $image_size['0'], $image_size['1'], $s['dest_dir'], $s['width'], $s['height']), LOG_DEBUG, __FILE__, __LINE__);
261            } else {
262                $pnmscale_args = $s['pnmscale_args'];
263            }
264           
265            // Execute the command that creates the thumbnail.
266            $command = sprintf('%s %s | %s %s | %s %s > %s/%s',
267                escapeshellcmd($this->anytopnm_binary),
268                escapeshellcmd($source_file),
269                escapeshellcmd($this->pnmscale_binary),
270                escapeshellcmd($pnmscale_args),
271                escapeshellcmd($this->cjpeg_binary),
272                escapeshellcmd($s['cjpeg_args']), 
273                escapeshellcmd(realpath($this->source_dir . '/' . $s['dest_dir'])),
274                escapeshellcmd($file_name)
275            );
276            logMsg(sprintf('ImageThumb command: %s', $command), LOG_DEBUG, __FILE__, __LINE__);
277            exec($command, $output, $return_val);
278
279            if (0 == $return_val) {
280                // Make the thumbnail writable so the user can delete it over ftp without being 'apache'.
281                chmod(realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name), $this->dest_file_perms);
282                logMsg(sprintf('Successfully resized image %s', $s['dest_dir'] . '/' . $file_name, $return_val), LOG_DEBUG, __FILE__, __LINE__);
283            } else {
284                logMsg(sprintf('Image %s failed resizing with return value: %s%s', $s['dest_dir'] . '/' . $file_name, $return_val, empty($output) ? '' : ' (' . getDump($output) . ')'), LOG_ERR, __FILE__, __LINE__);
285            }
286            // Return from the command will be > 0 if there was an error.
287            $final_return_val += $return_val;
288        }
289       
290        // If > 0, there was a problem thumbnailing.
291        return (0 == $final_return_val);
292    }
293
294    /**
295     *
296     */
297    function processAll()
298    {
299        // Ensure we have a source.
300        if (!isset($this->source_dir)) {
301            logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
302            return false;
303        }
304       
305        // Get all files in source directory.
306        $dir_handle = opendir($this->source_dir);
307        while ($dir_handle && ($file = readdir($dir_handle)) !== false) {
308            // If the file name does not start with a dot (. or .. or .htaccess).
309            if (!preg_match('/^\./', $file) && in_array(strtolower(substr($file, strrpos($file, '.') + 1)), $this->valid_file_extensions)) {
310                $files[] = $file;
311            }
312        }
313       
314        // Process each found file.
315        if (is_array($files) && !empty($files)) {
316            foreach ($files as $file_name) {
317                $this->processFile($file_name);
318            }
319            return sizeof($files);
320        } else {
321            logMsg(sprintf('No images found to thumbnail in directory %s.', $this->source_dir), LOG_NOTICE, __FILE__, __LINE__);
322            return 0;
323        }
324    }
325
326    /**
327     *
328     */
329    function deleteThumbs($file_name)
330    {
331        // Ensure we have a source.
332        if (!isset($this->source_dir)) {
333            logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
334            return false;
335        }
336       
337        $ret = 0;
338        foreach ($this->spec as $s) {
339            $file_path_name = realpath($this->source_dir . '/' . $s['dest_dir'] . '/' . $file_name);
340            if (file_exists($file_path_name)) {
341                if (!unlink($file_path_name)) {
342                    $ret++;
343                    logMsg(sprintf(_("Delete thumbs failed: %s"), $file_path_name), LOG_WARNING, __FILE__, __LINE__);
344                }
345            }
346        }
347//         raiseMsg(sprintf(_("The thumbnails for file <strong>%s</strong> have been deleted."), $file_name), MSG_SUCCESS, __FILE__, __LINE__);
348        return (0 == $ret);
349    }
350
351    /**
352     *
353     */
354    function deleteOriginal($file_name)
355    {
356        // Ensure we have a source.
357        if (!isset($this->source_dir)) {
358            logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
359            return false;
360        }
361       
362        $file_path_name = $this->source_dir . '/' . $file_name;
363        if (!unlink($file_path_name)) {
364            logMsg(sprintf(_("Delete original failed: %s"), $file_path_name), LOG_WARNING, __FILE__, __LINE__);
365            return false;
366        }
367//         raiseMsg(sprintf(_("The original file <strong>%s</strong> has been deleted."), $file_name), MSG_SUCCESS, __FILE__, __LINE__);
368        return true;
369    }
370   
371    /**
372     *
373     */
374    function exists($file_name)
375    {
376        // Ensure we have a source.
377        if (!isset($this->source_dir)) {
378            logMsg(sprintf('Source directory not set before processing.'), LOG_ERR, __FILE__, __LINE__);
379            return false;
380        }
381       
382        return file_exists($this->source_dir . '/' . $file_name);
383    }
384   
385    /**
386     *
387     */
388    function validFileExtension($file_name)
389    {
390        preg_match('/.*?\.(\w+)$/i', $file_name, $ext);
391        return in_array(strtolower($ext[1]), $this->valid_file_extensions);
392    }
393
394} // End of class.
395?>
Note: See TracBrowser for help on using the repository browser.