Passed
Push — master ( 6ebac0...a0be99 )
by Michael
19:32
created

ImageEditor::getDefaultSaveFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 9
rs 10
1
<?php
2
/**
3
 * Image Editor. Editing tools, crop, rotate, scale and save.
4
 * @author  $Author:ray $
5
 * @version $Id:ImageEditor.php 938 2008-01-22 20:13:47Z ray $
6
 * @package ImageManager
7
 */
8
9
require_once('../ImageManager/Classes/Transform.php');
10
11
/**
12
 * Handles the basic image editing capbabilities.
13
 * @author     $Author:ray $
14
 * @version    $Id:ImageEditor.php 938 2008-01-22 20:13:47Z ray $
15
 * @package    ImageManager
16
 * @subpackage Editor
17
 */
18
class ImageEditor
19
{
20
    /**
21
     * ImageManager instance.
22
     */
23
    public $manager;
24
25
    /**
26
     * user based on IP address
27
     */
28
    public $_uid;
29
30
    /**
31
     * tmp file storage time.
32
     */
33
    public $lapse_time = 900; //15 mins
34
35
    public $filesaved = 0;
36
37
    /**
38
     * Create a new ImageEditor instance. Editing requires a
39
     * tmp file, which is saved in the current directory where the
40
     * image is edited. The tmp file is assigned by md5 hash of the
41
     * user IP address. This hashed is used as an ID for cleaning up
42
     * the tmp files. In addition, any tmp files older than the
43
     * the specified period will be deleted.
44
     * @param ImageManager $manager the image manager, we need this
45
     *                              for some file and path handling functions.
46
     */
47
    public function __construct($manager)
48
    {
49
        $this->manager = $manager;
50
        $this->_uid    = md5($_SERVER['REMOTE_ADDR']);
51
    }
52
53
    /**
54
     * Did we save a file?
55
     * @return int 1 if the file was saved sucessfully,
56
     * 0 no save operation, -1 file save error.
57
     */
58
    public function isFileSaved()
59
    {
60
        Return $this->filesaved;
61
    }
62
63
    /**
64
     * Process the image, if not action, just display the image.
65
     * @return array with image information, empty array if not an image.
66
     * <code>array('src'=>'url of the image', 'dimensions'=>'width="xx" height="yy"',
67
     * 'file'=>'image file, relative', 'fullpath'=>'full path to the image');</code>
68
     */
69
    public function processImage()
70
    {
71
        if (isset($_GET['img'])) {
72
            $relative = rawurldecode($_GET['img']);
73
        } else {
74
            Return [];
75
        }
76
77
        //$relative = '/Series2004NoteFront.jpg';
78
79
        $imgURL   = $this->manager->getFileURL($relative);
80
        $fullpath = $this->manager->getFullPath($relative);
81
82
        $imgInfo = @getImageSize($fullpath);
83
        if (!is_array($imgInfo)) {
84
            Return [];
85
        }
86
87
        $action = $this->getAction();
88
89
        if (!is_null($action)) {
0 ignored issues
show
introduced by
The condition is_null($action) is always false.
Loading history...
90
            $image = $this->processAction($action, $relative, $fullpath);
91
        } else {
92
            $image['src']        = $imgURL;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$image was never initialized. Although not strictly required by PHP, it is generally a good practice to add $image = array(); before regardless.
Loading history...
93
            $image['dimensions'] = $imgInfo[3];
94
            $image['file']       = $relative;
95
            $image['fullpath']   = $fullpath;
96
            $image['filesize']   = @filesize($fullpath);
97
        }
98
99
        Return $image;
100
    }
101
102
    /**
103
     * Process the actions, crop, scale(resize), rotate, flip, and save.
104
     * When ever an action is performed, the result is save into a
105
     * temporary image file, see createUnique on the filename specs.
106
     * It does not return the saved file, alway returning the tmp file.
107
     * @param string $action   , should be 'crop', 'scale', 'rotate','flip', or 'save'
108
     * @param string $relative the relative image filename
109
     * @param string $fullpath the fullpath to the image file
110
     * @return array with image information
111
     *                         <code>array('src'=>'url of the image', 'dimensions'=>'width="xx" height="yy"',
112
     *                         'file'=>'image file, relative', 'fullpath'=>'full path to the image');</code>
113
     */
114
    public function processAction($action, $relative, $fullpath)
115
    {
116
        $params = '';
117
118
        if (isset($_GET['params'])) {
119
            $params = $_GET['params'];
120
        }
121
122
        $values   = explode(',', $params);
123
        $saveFile = $this->getSaveFileName($values[0]);
124
125
        $img = Image_Transform::factory(IMAGE_CLASS);
0 ignored issues
show
Bug Best Practice introduced by
The method Image_Transform::factory() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

125
        /** @scrutinizer ignore-call */ 
126
        $img = Image_Transform::factory(IMAGE_CLASS);
Loading history...
126
        $img->load($fullpath);
127
128
        if (is_callable([$img, 'paletteToTrueColorWithTransparency']) && !imageistruecolor($img->imageHandle)) {
129
            $img->paletteToTrueColorWithTransparency();
130
        }
131
        switch ($action) {
132
            case 'crop':
133
                $img->crop(
134
                    intval($values[0]),
135
                    intval($values[1]),
136
                    intval($values[2]),
137
                    intval($values[3])
138
                );
139
                break;
140
            case 'scale':
141
                $img->resize(intval($values[0]), intval($values[1]));
142
                break;
143
            case 'rotate':
144
                $img->rotate(floatval($values[0]));
145
                break;
146
            case 'flip':
147
                if ('hoz' == $values[0]) {
148
                    $img->flip(true);
149
                } elseif ('ver' == $values[0]) {
150
                    $img->flip(false);
151
                }
152
                break;
153
            case 'save':
154
                if (!is_null($saveFile)) {
155
                    $quality = intval($values[1]);
156
                    if ($quality < 0) {
157
                        $quality = 85;
158
                    }
159
                    $newSaveFile = $this->makeRelative($relative, $saveFile);
160
                    $newSaveFile = $this->getUniqueFilename($newSaveFile);
161
162
                    //get unique filename just returns the filename, so
163
                    //we need to make the relative path once more.
164
                    $newSaveFile       = $this->makeRelative($relative, $newSaveFile);
165
                    $image['saveFile'] = $newSaveFile;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$image was never initialized. Although not strictly required by PHP, it is generally a good practice to add $image = array(); before regardless.
Loading history...
166
                    $newSaveFullpath   = $this->manager->getFullPath($newSaveFile);
167
                    if ('gif' == $values[0] && is_callable([$img, 'preserveTransparencyForPalette'])) {
168
                        $img->preserveTransparencyForPalette();
169
                    }
170
                    $img->save($newSaveFullpath, $values[0], $quality);
171
                    if (is_file($newSaveFullpath)) {
172
                        $this->filesaved = 1;
173
                    } else {
174
                        $this->filesaved = -1;
175
                    }
176
                }
177
                break;
178
            case 'preview':
179
                $quality = intval($values[1]);
0 ignored issues
show
Unused Code introduced by
The assignment to $quality is dead and can be removed.
Loading history...
180
181
                $image['file']     = $relative;
182
                $image['fullpath'] = $fullpath;
183
184
                //create the tmp image file
185
                $filename    = $this->createUnique($fullpath);
186
                $newRelative = $this->makeRelative($relative, $filename);
187
                $newFullpath = $this->manager->getFullPath($newRelative);
188
                $newURL      = $this->manager->getFileURL($newRelative);
189
190
                if ('gif' == $values[0] && is_callable([$img, 'preserveTransparencyForPalette'])) {
191
                    $img->preserveTransparencyForPalette();
192
                }
193
                $img->save($newFullpath, $values[0]);
194
                $img->free();
195
196
                //get the image information
197
                $imgInfo = @getimagesize($newFullpath);
198
199
                $image['src']        = $newURL;
200
                $image['width']      = $imgInfo[0];
201
                $image['height']     = $imgInfo[1];
202
                $image['dimensions'] = $imgInfo[3];
203
                $image['file']       = $relative;
204
                $image['fullpath']   = $fullpath;
205
                $image['filesize']   = @filesize($newFullpath);
206
207
                Return $image;
208
209
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
210
        }
211
212
        //create the tmp image file
213
        $filename    = $this->createUnique($fullpath);
214
        $newRelative = $this->makeRelative($relative, $filename);
215
        $newFullpath = $this->manager->getFullPath($newRelative);
216
        $newURL      = $this->manager->getFileURL($newRelative);
217
218
        //save the file.
219
        $img->save($newFullpath, 'png');
220
        $img->free();
221
222
        //get the image information
223
        $imgInfo = @getimagesize($newFullpath);
224
225
        $image['src']        = $newURL;
226
        $image['width']      = $imgInfo[0];
227
        $image['height']     = $imgInfo[1];
228
        $image['dimensions'] = $imgInfo[3];
229
        $image['file']       = $newRelative;
230
        $image['fullpath']   = $newFullpath;
231
        $image['filesize']   = @filesize($newFullpath);
232
        $image['type']       = image_type_to_mime_type($imgInfo[2]);
233
234
        Return $image;
235
    }
236
237
    /**
238
     * Get the file name base on the save name
239
     * and the save type.
240
     * @param string $type image type, 'jpeg', 'png', or 'gif'
241
     * @return string the filename according to save type
242
     */
243
    public function getSaveFileName($type)
244
    {
245
        if (!isset($_GET['file'])) {
246
            Return null;
247
        }
248
249
        $filename = Files::escape(rawurldecode($_GET['file']));
0 ignored issues
show
Bug Best Practice introduced by
The method Files::escape() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
        /** @scrutinizer ignore-call */ 
250
        $filename = Files::escape(rawurldecode($_GET['file']));
Loading history...
250
        $index    = strrpos($filename, '.');
251
        $base     = substr($filename, 0, $index);
252
        $ext      = strtolower(substr($filename, $index + 1, strlen($filename)));
253
254
        if ('jpeg' == $type && !('jpeg' == $ext || 'jpg' == $ext)) {
255
            Return $base . '.jpeg';
256
        }
257
        if ('png' == $type && 'png' != $ext) {
258
            Return $base . '.png';
259
        }
260
        if ('gif' == $type && 'gif' != $ext) {
261
            Return $base . '.gif';
262
        }
263
264
        // Ensure type is in acceptable image types
265
        $valid_extensions = $this->manager->config['allowed_image_extensions'];
266
        if (!in_array($ext, $valid_extensions)) {
267
            return $base . '.' . strtolower($type ? $type : 'jpg');
268
        }
269
270
        Return $filename;
271
    }
272
273
    /**
274
     * Get the default save file name, used by editor.php.
275
     * @return string a suggestive filename, this should be unique
276
     */
277
    public function getDefaultSaveFile()
278
    {
279
        if (isset($_GET['img'])) {
280
            $relative = rawurldecode($_GET['img']);
281
        } else {
282
            Return null;
283
        }
284
285
        Return $this->getUniqueFilename($relative);
286
    }
287
288
    /**
289
     * Get a unique filename. If the file exists, the filename
290
     * base is appended with an increasing integer.
291
     * @param string $relative the relative filename to the base_dir
292
     * @return string a unique filename in the current path
293
     */
294
    public function getUniqueFilename($relative)
295
    {
296
        $fullpath = $this->manager->getFullPath($relative);
297
298
        $pathinfo = pathinfo($fullpath);
299
300
        $path = Files::fixPath($pathinfo['dirname']);
0 ignored issues
show
Bug Best Practice introduced by
The method Files::fixPath() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
        /** @scrutinizer ignore-call */ 
301
        $path = Files::fixPath($pathinfo['dirname']);
Loading history...
301
        $file = Files::escape($pathinfo['basename']);
0 ignored issues
show
Bug Best Practice introduced by
The method Files::escape() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

301
        /** @scrutinizer ignore-call */ 
302
        $file = Files::escape($pathinfo['basename']);
Loading history...
302
303
        $filename = $file;
304
305
        $dotIndex = strrpos($file, '.');
306
        $ext      = '';
307
308
        if (is_int($dotIndex)) {
0 ignored issues
show
introduced by
The condition is_int($dotIndex) is always true.
Loading history...
309
            $ext  = substr($file, $dotIndex);
310
            $base = substr($file, 0, $dotIndex);
311
        }
312
313
        $counter = 0;
314
        while (is_file($path . $filename)) {
315
            $counter++;
316
            $filename = $base . '_' . $counter . $ext;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $base does not seem to be defined for all execution paths leading up to this point.
Loading history...
317
        }
318
319
        Return $filename;
320
    }
321
322
    /**
323
     * Specifiy the original relative path, a new filename
324
     * and return the new filename with relative path.
325
     * i.e. $pathA (-filename) + $file
326
     * @param string $pathA the relative file
327
     * @param string $file  the new filename
328
     * @return string relative path with the new filename
329
     */
330
    public function makeRelative($pathA, $file)
331
    {
332
        $index = strrpos($pathA, '/');
333
        if (!is_int($index)) {
0 ignored issues
show
introduced by
The condition is_int($index) is always true.
Loading history...
334
            Return $file;
335
        }
336
337
        $path = substr($pathA, 0, $index);
338
        Return Files::fixPath($path) . $file;
0 ignored issues
show
Bug Best Practice introduced by
The method Files::fixPath() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

338
        Return Files::/** @scrutinizer ignore-call */ fixPath($path) . $file;
Loading history...
339
    }
340
341
    /**
342
     * Get the action GET parameter
343
     * @return string action parameter
344
     */
345
    public function getAction()
346
    {
347
        $action = null;
348
        if (isset($_GET['action'])) {
349
            $action = $_GET['action'];
350
        }
351
        Return $action;
352
    }
353
354
    /**
355
     * Generate a unique string based on md5(microtime()).
356
     * Well not so uniqe, as it is limited to 6 characters
357
     * @return string unique string.
358
     */
359
    public function uniqueStr()
360
    {
361
        return substr(md5(microtime()), 0, 6);
362
    }
363
364
    /**
365
     * Create unique tmp image file name.
366
     * The filename is based on the tmp file prefix
367
     * specified in config.inc.php plus
368
     * the UID (basically a md5 of the remote IP)
369
     * and some random 6 character string.
370
     * This function also calls to clean up the tmp files.
371
     * @param string $file the fullpath to a file
372
     * @return string a unique filename for that path
373
     *                     NOTE: it only returns the filename, path no included.
374
     */
375
    public function createUnique($file)
376
    {
377
        $pathinfo = pathinfo($file);
378
        $path     = Files::fixPath($pathinfo['dirname']);
0 ignored issues
show
Bug Best Practice introduced by
The method Files::fixPath() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

378
        /** @scrutinizer ignore-call */ 
379
        $path     = Files::fixPath($pathinfo['dirname']);
Loading history...
379
        $imgType  = $this->getImageType($file);
380
381
        $unique_str = $this->manager->getTmpPrefix() . $this->_uid . '_' . $this->uniqueStr() . '.' . $imgType;
382
383
        //make sure the the unique temp file does not exists
384
        while (file_exists($path . $unique_str)) {
385
            $unique_str = $this->manager->getTmpPrefix() . $this->_uid . '_' . $this->uniqueStr() . '.' . $imgType;
386
        }
387
388
        $this->cleanUp($path, $pathinfo['basename']);
389
390
        Return $unique_str;
391
    }
392
393
    /**
394
     * Delete any tmp image files.
395
     * @param string $path the full path
396
     *                     where the clean should take place.
397
     */
398
    public function cleanUp($path, $file)
399
    {
400
        $path = Files::fixPath($path);
0 ignored issues
show
Bug Best Practice introduced by
The method Files::fixPath() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

400
        /** @scrutinizer ignore-call */ 
401
        $path = Files::fixPath($path);
Loading history...
401
402
        if (!is_dir($path)) {
403
            Return false;
404
        }
405
406
        $d = @dir($path);
407
408
        $tmp    = $this->manager->getTmpPrefix();
409
        $tmpLen = strlen($tmp);
410
411
        $prefix = $tmp . $this->_uid;
412
        $len    = strlen($prefix);
413
414
        while (false !== ($entry = $d->read())) {
415
            //echo $entry."<br>";
416
            if (is_file($path . $entry) && $this->manager->isTmpFile($entry)) {
417
                if (substr($entry, 0, $len) == $prefix && $entry != $file) {
418
                    Files::delFile($path . $entry);
0 ignored issues
show
Bug Best Practice introduced by
The method Files::delFile() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

418
                    Files::/** @scrutinizer ignore-call */ 
419
                           delFile($path . $entry);
Loading history...
419
                } elseif (substr($entry, 0, $tmpLen) == $tmp && $entry != $file) {
420
                    if (filemtime($path . $entry) + $this->lapse_time < time()) {
421
                        Files::delFile($path . $entry);
422
                    }
423
                }
424
            }
425
        }
426
        $d->close();
427
    }
428
429
    /**
430
     * Get the image type base on an image file.
431
     * @param string $file the full path to the image file.
432
     * @return string of either 'gif', 'jpeg', 'png' or 'bmp'
433
     *                     otherwise it will return null.
434
     */
435
    public function getImageType($file)
436
    {
437
        $imageInfo = @getImageSize($file);
438
439
        if (!is_array($imageInfo)) {
440
            Return null;
441
        }
442
443
        switch ($imageInfo[2]) {
444
            case 1:
445
                Return 'gif';
446
            case 2:
447
                Return 'jpeg';
448
            case 3:
449
                Return 'png';
450
            case 6:
451
                Return 'bmp';
452
        }
453
454
        Return null;
455
    }
456
457
    /**
458
     * Check if the specified image can be edit by GD
459
     * mainly to check that GD can read and save GIFs
460
     * @return int 0 if it is not a GIF file, 1 is GIF is editable, -1 if not editable.
461
     */
462
    public function isGDEditable()
463
    {
464
        if (isset($_GET['img'])) {
465
            $relative = rawurldecode($_GET['img']);
466
        } else {
467
            Return 0;
468
        }
469
        if (IMAGE_CLASS != 'GD') {
0 ignored issues
show
introduced by
The condition IMAGE_CLASS != 'GD' is always false.
Loading history...
470
            Return 0;
471
        }
472
473
        $fullpath = $this->manager->getFullPath($relative);
474
475
        $type = $this->getImageType($fullpath);
476
        if ('gif' != $type) {
477
            Return 0;
478
        }
479
480
        if (function_exists('ImageCreateFrom' . $type)
481
            && function_exists('image' . $type)) {
482
            Return 1;
483
        } else {
484
            Return -1;
485
        }
486
    }
487
488
    /**
489
     * Check if GIF can be edit by GD.
490
     * @return int 0 if it is not using the GD library, 1 is GIF is editable, -1 if not editable.
491
     */
492
    public function isGDGIFAble()
493
    {
494
        if (IMAGE_CLASS != 'GD') {
0 ignored issues
show
introduced by
The condition IMAGE_CLASS != 'GD' is always false.
Loading history...
495
            Return 0;
496
        }
497
498
        if (function_exists('ImageCreateFromGif')
499
            && function_exists('imagegif')) {
500
            Return 1;
501
        } else {
502
            Return -1;
503
        }
504
    }
505
}
506
507
508