Completed
Push — master ( a205d6...29743b )
by Mikael
03:11
created

CImage::convert2sRGBColorSpace()   C

Complexity

Conditions 10
Paths 16

Size

Total Lines 52
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110
Metric Value
dl 0
loc 52
ccs 0
cts 19
cp 0
rs 6.2553
cc 10
eloc 30
nc 16
nop 5
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Resize and crop images on the fly, store generated images in a cache.
4
 *
5
 * @author  Mikael Roos [email protected]
6
 * @example http://dbwebb.se/opensource/cimage
7
 * @link    https://github.com/mosbth/cimage
8
 */
9
class CImage
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
10
{
11
12
    /**
13
     * Constants type of PNG image
14
     */
15
    const PNG_GREYSCALE         = 0;
16
    const PNG_RGB               = 2;
17
    const PNG_RGB_PALETTE       = 3;
18
    const PNG_GREYSCALE_ALPHA   = 4;
19
    const PNG_RGB_ALPHA         = 6;
20
21
22
23
    /**
24
     * Constant for default image quality when not set
25
     */
26
    const JPEG_QUALITY_DEFAULT = 60;
27
28
29
30
    /**
31
     * Quality level for JPEG images.
32
     */
33
    private $quality;
34
35
36
37
    /**
38
     * Is the quality level set from external use (true) or is it default (false)?
39
     */
40
    private $useQuality = false;
41
42
43
44
    /**
45
     * Constant for default image quality when not set
46
     */
47
    const PNG_COMPRESSION_DEFAULT = -1;
48
49
50
51
    /**
52
     * Compression level for PNG images.
53
     */
54
    private $compress;
55
56
57
58
    /**
59
     * Is the compress level set from external use (true) or is it default (false)?
60
     */
61
    private $useCompress = false;
62
63
64
65
66
    /**
67
     * Add HTTP headers for outputing image.
68
     */
69
    private $HTTPHeader = array();
70
71
72
73
    /**
74
     * Default background color, red, green, blue, alpha.
75
     *
76
     * @todo remake when upgrading to PHP 5.5
77
     */
78
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
79
    const BACKGROUND_COLOR = array(
80
        'red'   => 0,
81
        'green' => 0,
82
        'blue'  => 0,
83
        'alpha' => null,
84
    );*/
85
86
87
88
    /**
89
     * Default background color to use.
90
     *
91
     * @todo remake when upgrading to PHP 5.5
92
     */
93
    //private $bgColorDefault = self::BACKGROUND_COLOR;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
94
    private $bgColorDefault = array(
95
        'red'   => 0,
96
        'green' => 0,
97
        'blue'  => 0,
98
        'alpha' => null,
99
    );
100
101
102
    /**
103
     * Background color to use, specified as part of options.
104
     */
105
    private $bgColor;
106
107
108
109
    /**
110
     * Where to save the target file.
111
     */
112
    private $saveFolder;
113
114
115
116
    /**
117
     * The working image object.
118
     */
119
    private $image;
120
121
122
123
    /**
124
     * Image filename, may include subdirectory, relative from $imageFolder
125
     */
126
    private $imageSrc;
127
128
129
130
    /**
131
     * Actual path to the image, $imageFolder . '/' . $imageSrc
132
     */
133
    private $pathToImage;
134
135
136
137
    /**
138
     * File type for source image, as provided by getimagesize()
139
     */
140
    private $fileType;
141
142
143
144
    /**
145
     * File extension to use when saving image.
146
     */
147
    private $extension;
148
149
150
151
    /**
152
     * Output format, supports null (image) or json.
153
     */
154
    private $outputFormat = null;
155
156
157
158
    /**
159
     * Verbose mode to print out a trace and display the created image
160
     */
161
    private $verbose = false;
162
163
164
165
    /**
166
     * Keep a log/trace on what happens
167
     */
168
    private $log = array();
169
170
171
172
    /**
173
     * Handle image as palette image
174
     */
175
    private $palette;
176
177
178
179
    /**
180
     * Target filename, with path, to save resulting image in.
181
     */
182
    private $cacheFileName;
183
184
185
186
    /**
187
     * Set a format to save image as, or null to use original format.
188
     */
189
    private $saveAs;
190
191
192
    /**
193
     * Path to command for filter optimize, for example optipng or null.
194
     */
195
    private $pngFilter;
196
    private $pngFilterCmd;
197
198
199
200
    /**
201
     * Path to command for deflate optimize, for example pngout or null.
202
     */
203
    private $pngDeflate;
204
    private $pngDeflateCmd;
205
206
207
208
    /**
209
     * Path to command to optimize jpeg images, for example jpegtran or null.
210
     */
211
     private $jpegOptimize;
212
     private $jpegOptimizeCmd;
213
214
215
216
    /**
217
     * Image dimensions, calculated from loaded image.
218
     */
219
    private $width;  // Calculated from source image
220
    private $height; // Calculated from source image
221
222
223
    /**
224
     * New image dimensions, incoming as argument or calculated.
225
     */
226
    private $newWidth;
227
    private $newWidthOrig;  // Save original value
228
    private $newHeight;
229
    private $newHeightOrig; // Save original value
230
231
232
    /**
233
     * Change target height & width when different dpr, dpr 2 means double image dimensions.
234
     */
235
    private $dpr = 1;
236
237
238
    /**
239
     * Always upscale images, even if they are smaller than target image.
240
     */
241
    const UPSCALE_DEFAULT = true;
242
    private $upscale = self::UPSCALE_DEFAULT;
243
244
245
246
    /**
247
     * Array with details on how to crop, incoming as argument and calculated.
248
     */
249
    public $crop;
250
    public $cropOrig; // Save original value
251
252
253
    /**
254
     * String with details on how to do image convolution. String
255
     * should map a key in the $convolvs array or be a string of
256
     * 11 float values separated by comma. The first nine builds
257
     * up the matrix, then divisor and last offset.
258
     */
259
    private $convolve;
260
261
262
    /**
263
     * Custom convolution expressions, matrix 3x3, divisor and offset.
264
     */
265
    private $convolves = array(
266
        'lighten'       => '0,0,0, 0,12,0, 0,0,0, 9, 0',
267
        'darken'        => '0,0,0, 0,6,0, 0,0,0, 9, 0',
268
        'sharpen'       => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0',
269
        'sharpen-alt'   => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0',
270
        'emboss'        => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0',
271
        'emboss-alt'    => '-2,-1,0, -1,1,1, 0,1,2, 1, 0',
272
        'blur'          => '1,1,1, 1,15,1, 1,1,1, 23, 0',
273
        'gblur'         => '1,2,1, 2,4,2, 1,2,1, 16, 0',
274
        'edge'          => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0',
275
        'edge-alt'      => '0,1,0, 1,-4,1, 0,1,0, 1, 0',
276
        'draw'          => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0',
277
        'mean'          => '1,1,1, 1,1,1, 1,1,1, 9, 0',
278
        'motion'        => '1,0,0, 0,1,0, 0,0,1, 3, 0',
279
    );
280
281
282
    /**
283
     * Resize strategy to fill extra area with background color.
284
     * True or false.
285
     */
286
    private $fillToFit;
287
288
289
290
    /**
291
     * To store value for option scale.
292
     */
293
    private $scale;
294
295
296
297
    /**
298
     * To store value for option.
299
     */
300
    private $rotateBefore;
301
302
303
304
    /**
305
     * To store value for option.
306
     */
307
    private $rotateAfter;
308
309
310
311
    /**
312
     * To store value for option.
313
     */
314
    private $autoRotate;
315
316
317
318
    /**
319
     * To store value for option.
320
     */
321
    private $sharpen;
322
323
324
325
    /**
326
     * To store value for option.
327
     */
328
    private $emboss;
329
330
331
332
    /**
333
     * To store value for option.
334
     */
335
    private $blur;
336
337
338
339
    /**
340
     * Used with option area to set which parts of the image to use.
341
     */
342
    private $offset;
343
344
345
346
    /**
347
    * Calculate target dimension for image when using fill-to-fit resize strategy.
348
    */
349
    private $fillWidth;
350
    private $fillHeight;
351
352
353
354
    /**
355
    * Allow remote file download, default is to disallow remote file download.
356
    */
357
    private $allowRemote = false;
358
359
360
361
    /**
362
     * Pattern to recognize a remote file.
363
     */
364
    //private $remotePattern = '#^[http|https]://#';
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
365
    private $remotePattern = '#^https?://#';
366
367
368
369
    /**
370
     * Use the cache if true, set to false to ignore the cached file.
371
     */
372
    private $useCache = true;
373
374
375
376
    /*
377
     * Set whitelist for valid hostnames from where remote source can be
378
     * downloaded.
379
     */
380
    private $remoteHostWhitelist = null;
381
382
383
384
    /*
385
     * Do verbose logging to file by setting this to a filename.
386
     */
387
    private $verboseFileName = null;
388
389
390
391
    /*
392
     * Output to ascii can take som options as an array.
393
     */
394
    private $asciiOptions = array();
395
396
397
398
    /*
399
     * Image copy strategy, defaults to RESAMPLE.
400
     */
401
     const RESIZE = 1;
402
     const RESAMPLE = 2;
403
     private $copyStrategy = NULL;
404
405
406
407
    /**
408
     * Properties, the class is mutable and the method setOptions()
409
     * decides (partly) what properties are created.
410
     *
411
     * @todo Clean up these and check if and how they are used
412
     */
413
414
    public $keepRatio;
415
    public $cropToFit;
416
    private $cropWidth;
417
    private $cropHeight;
418
    public $crop_x;
419
    public $crop_y;
420
    public $filters;
421
    private $attr; // Calculated from source image
422
423
424
425
426
    /**
427
     * Constructor, can take arguments to init the object.
428
     *
429
     * @param string $imageSrc    filename which may contain subdirectory.
430
     * @param string $imageFolder path to root folder for images.
431
     * @param string $saveFolder  path to folder where to save the new file or null to skip saving.
432
     * @param string $saveName    name of target file when saveing.
433
     */
434 7
    public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null)
435
    {
436 7
        $this->setSource($imageSrc, $imageFolder);
437 7
        $this->setTarget($saveFolder, $saveName);
438 7
    }
439
440
441
442
    /**
443
     * Set verbose mode.
444
     *
445
     * @param boolean $mode true or false to enable and disable verbose mode,
446
     *                      default is true.
447
     *
448
     * @return $this
449
     */
450
    public function setVerbose($mode = true)
451
    {
452
        $this->verbose = $mode;
453
        return $this;
454
    }
455
456
457
458
    /**
459
     * Set save folder, base folder for saving cache files.
460
     *
461
     * @todo clean up how $this->saveFolder is used in other methods.
462
     *
463
     * @param string $path where to store cached files.
464
     *
465
     * @return $this
466
     */
467 2
    public function setSaveFolder($path)
468
    {
469 2
        $this->saveFolder = $path;
470 2
        return $this;
471
    }
472
473
474
475
    /**
476
     * Use cache or not.
477
     *
478
     * @param boolean $use true or false to use cache.
479
     *
480
     * @return $this
481
     */
482
    public function useCache($use = true)
483
    {
484
        $this->useCache = $use;
485
        return $this;
486
    }
487
488
489
490
    /**
491
     * Create and save a dummy image. Use dimensions as stated in
492
     * $this->newWidth, or $width or default to 100 (same for height.
493
     *
494
     * @param integer $width  use specified width for image dimension.
495
     * @param integer $height use specified width for image dimension.
496
     *
497
     * @return $this
498
     */
499 2
    public function createDummyImage($width = null, $height = null)
500
    {
501 2
        $this->newWidth  = $this->newWidth  ?: $width  ?: 100;
502 2
        $this->newHeight = $this->newHeight ?: $height ?: 100;
503
504 2
        $this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
505
506 2
        return $this;
507
    }
508
509
510
511
    /**
512
     * Allow or disallow remote image download.
513
     *
514
     * @param boolean $allow   true or false to enable and disable.
515
     * @param string  $pattern to use to detect if its a remote file.
516
     *
517
     * @return $this
518
     */
519 2
    public function setRemoteDownload($allow, $pattern = null)
520
    {
521 2
        $this->allowRemote = $allow;
522 2
        $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern;
523
524 2
        $this->log(
525
            "Set remote download to: "
526 2
            . ($this->allowRemote ? "true" : "false")
527 2
            . " using pattern "
528 2
            . $this->remotePattern
529 2
        );
530
531 2
        return $this;
532
    }
533
534
535
536
    /**
537
     * Check if the image resource is a remote file or not.
538
     *
539
     * @param string $src check if src is remote.
540
     *
541
     * @return boolean true if $src is a remote file, else false.
542
     */
543 2
    public function isRemoteSource($src)
544
    {
545 2
        $remote = preg_match($this->remotePattern, $src);
546 2
        $this->log("Detected remote image: " . ($remote ? "true" : "false"));
547 2
        return !!$remote;
548
    }
549
550
551
552
    /**
553
     * Set whitelist for valid hostnames from where remote source can be
554
     * downloaded.
555
     *
556
     * @param array $whitelist with regexp hostnames to allow download from.
557
     *
558
     * @return $this
559
     */
560 2
    public function setRemoteHostWhitelist($whitelist = null)
561
    {
562 2
        $this->remoteHostWhitelist = $whitelist;
563 2
        $this->log(
564
            "Setting remote host whitelist to: "
565 2
            . (is_null($whitelist) ? "null" : print_r($whitelist, 1))
566 2
        );
567 2
        return $this;
568
    }
569
570
571
572
    /**
573
     * Check if the hostname for the remote image, is on a whitelist,
574
     * if the whitelist is defined.
575
     *
576
     * @param string $src the remote source.
577
     *
578
     * @return boolean true if hostname on $src is in the whitelist, else false.
579
     */
580 3
    public function isRemoteSourceOnWhitelist($src)
581
    {
582 3
        if (is_null($this->remoteHostWhitelist)) {
583 1
            $this->log("Remote host on whitelist not configured - allowing.");
584 1
            return true;
585
        }
586
587 2
        $whitelist = new CWhitelist();
588 2
        $hostname = parse_url($src, PHP_URL_HOST);
589 2
        $allow = $whitelist->check($hostname, $this->remoteHostWhitelist);
0 ignored issues
show
Security Bug introduced by
It seems like $hostname defined by parse_url($src, PHP_URL_HOST) on line 588 can also be of type false; however, CWhitelist::check() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
590
591 2
        $this->log(
592
            "Remote host is on whitelist: "
593 2
            . ($allow ? "true" : "false")
594 2
        );
595 2
        return $allow;
596
    }
597
598
599
600
    /**
601
     * Check if file extension is valid as a file extension.
602
     *
603
     * @param string $extension of image file.
604
     *
605
     * @return $this
606
     */
607
    private function checkFileExtension($extension)
608
    {
609
        $valid = array('jpg', 'jpeg', 'png', 'gif');
610
611
        in_array(strtolower($extension), $valid)
612
            or $this->raiseError('Not a valid file extension.');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
613
614
        return $this;
615
    }
616
617
618
619
    /**
620
     * Normalize the file extension.
621
     *
622
     * @param string $extension of image file or skip to use internal.
623
     *
624
     * @return string $extension as a normalized file extension.
625
     */
626 2
    private function normalizeFileExtension($extension = null)
627
    {
628 2
        $extension = strtolower($extension ? $extension : $this->extension);
629
630 2
        if ($extension == 'jpeg') {
631
                $extension = 'jpg';
632
            }
633
634 2
        return $extension;
635
    }
636
637
638
639
    /**
640
     * Download a remote image and return path to its local copy.
641
     *
642
     * @param string $src remote path to image.
643
     *
644
     * @return string as path to downloaded remote source.
645
     */
646
    public function downloadRemoteSource($src)
647
    {
648
        if (!$this->isRemoteSourceOnWhitelist($src)) {
649
            throw new Exception("Hostname is not on whitelist for remote sources.");
650
        }
651
652
        $remote = new CRemoteImage();
653
        $cache  = $this->saveFolder . "/remote/";
654
655
        if (!is_dir($cache)) {
656
            if (!is_writable($this->saveFolder)) {
657
                throw new Exception("Can not create remote cache, cachefolder not writable.");
658
            }
659
            mkdir($cache);
660
            $this->log("The remote cache does not exists, creating it.");
661
        }
662
663
        if (!is_writable($cache)) {
664
            $this->log("The remote cache is not writable.");
665
        }
666
667
        $remote->setCache($cache);
668
        $remote->useCache($this->useCache);
669
        $src = $remote->download($src);
670
671
        $this->log("Remote HTTP status: " . $remote->getStatus());
672
        $this->log("Remote item is in local cache: $src");
673
        $this->log("Remote details on cache:" . print_r($remote->getDetails(), true));
674
675
        return $src;
676
    }
677
678
679
680
    /**
681
     * Set source file to use as image source.
682
     *
683
     * @param string $src of image.
684
     * @param string $dir as optional base directory where images are.
685
     *
686
     * @return $this
687
     */
688 7
    public function setSource($src, $dir = null)
689
    {
690 7
        if (!isset($src)) {
691 7
            $this->imageSrc = null;
692 7
            $this->pathToImage = null;
693 7
            return $this;
694
        }
695
696 2
        if ($this->allowRemote && $this->isRemoteSource($src)) {
697
            $src = $this->downloadRemoteSource($src);
698
            $dir = null;
699
        }
700
701 2
        if (!isset($dir)) {
702
            $dir = dirname($src);
703
            $src = basename($src);
704
        }
705
706 2
        $this->imageSrc     = ltrim($src, '/');
707 2
        $imageFolder        = rtrim($dir, '/');
708 2
        $this->pathToImage  = $imageFolder . '/' . $this->imageSrc;
709
710 2
        return $this;
711
    }
712
713
714
715
    /**
716
     * Set target file.
717
     *
718
     * @param string $src of target image.
719
     * @param string $dir as optional base directory where images are stored.
720
     *                    Uses $this->saveFolder if null.
721
     *
722
     * @return $this
723
     */
724 7
    public function setTarget($src = null, $dir = null)
725
    {
726 7
        if (!isset($src)) {
727 7
            $this->cacheFileName = null;
728 7
            return $this;
729
        }
730
731 2
        if (isset($dir)) {
732
            $this->saveFolder = rtrim($dir, '/');
733
        }
734
735 2
        $this->cacheFileName  = $this->saveFolder . '/' . $src;
736
737
        // Sanitize filename
738 2
        $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
739 2
        $this->log("The cache file name is: " . $this->cacheFileName);
740
741 2
        return $this;
742
    }
743
744
745
746
    /**
747
     * Get filename of target file.
748
     *
749
     * @return Boolean|String as filename of target or false if not set.
750
     */
751 2
    public function getTarget()
752
    {
753 2
        return $this->cacheFileName;
754
    }
755
756
757
758
    /**
759
     * Set options to use when processing image.
760
     *
761
     * @param array $args used when processing image.
762
     *
763
     * @return $this
764
     */
765
    public function setOptions($args)
766
    {
767
        $this->log("Set new options for processing image.");
768
769
        $defaults = array(
770
            // Options for calculate dimensions
771
            'newWidth'    => null,
772
            'newHeight'   => null,
773
            'aspectRatio' => null,
774
            'keepRatio'   => true,
775
            'cropToFit'   => false,
776
            'fillToFit'   => null,
777
            'crop'        => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0),
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
778
            'area'        => null, //'0,0,0,0',
779
            'upscale'     => self::UPSCALE_DEFAULT,
780
781
            // Options for caching or using original
782
            'useCache'    => true,
783
            'useOriginal' => true,
784
785
            // Pre-processing, before resizing is done
786
            'scale'        => null,
787
            'rotateBefore' => null,
788
            'autoRotate'  => false,
789
790
            // General options
791
            'bgColor'     => null,
792
793
            // Post-processing, after resizing is done
794
            'palette'     => null,
795
            'filters'     => null,
796
            'sharpen'     => null,
797
            'emboss'      => null,
798
            'blur'        => null,
799
            'convolve'       => null,
800
            'rotateAfter' => null,
801
802
            // Output format
803
            'outputFormat' => null,
804
            'dpr'          => 1,
805
        );
806
807
        // Convert crop settings from string to array
808
        if (isset($args['crop']) && !is_array($args['crop'])) {
809
            $pices = explode(',', $args['crop']);
810
            $args['crop'] = array(
811
                'width'   => $pices[0],
812
                'height'  => $pices[1],
813
                'start_x' => $pices[2],
814
                'start_y' => $pices[3],
815
            );
816
        }
817
818
        // Convert area settings from string to array
819
        if (isset($args['area']) && !is_array($args['area'])) {
820
                $pices = explode(',', $args['area']);
821
                $args['area'] = array(
822
                    'top'    => $pices[0],
823
                    'right'  => $pices[1],
824
                    'bottom' => $pices[2],
825
                    'left'   => $pices[3],
826
                );
827
        }
828
829
        // Convert filter settings from array of string to array of array
830
        if (isset($args['filters']) && is_array($args['filters'])) {
831
            foreach ($args['filters'] as $key => $filterStr) {
832
                $parts = explode(',', $filterStr);
833
                $filter = $this->mapFilter($parts[0]);
834
                $filter['str'] = $filterStr;
835
                for ($i=1; $i<=$filter['argc']; $i++) {
836
                    if (isset($parts[$i])) {
837
                        $filter["arg{$i}"] = $parts[$i];
838
                    } else {
839
                        throw new Exception(
840
                            'Missing arg to filter, review how many arguments are needed at
841
                            http://php.net/manual/en/function.imagefilter.php'
842
                        );
843
                    }
844
                }
845
                $args['filters'][$key] = $filter;
846
            }
847
        }
848
849
        // Merge default arguments with incoming and set properties.
850
        //$args = array_merge_recursive($defaults, $args);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
851
        $args = array_merge($defaults, $args);
852
        foreach ($defaults as $key => $val) {
853
            $this->{$key} = $args[$key];
854
        }
855
856
        if ($this->bgColor) {
857
            $this->setDefaultBackgroundColor($this->bgColor);
858
        }
859
860
        // Save original values to enable re-calculating
861
        $this->newWidthOrig  = $this->newWidth;
862
        $this->newHeightOrig = $this->newHeight;
863
        $this->cropOrig      = $this->crop;
864
865
        return $this;
866
    }
867
868
869
870
    /**
871
     * Map filter name to PHP filter and id.
872
     *
873
     * @param string $name the name of the filter.
874
     *
875
     * @return array with filter settings
876
     * @throws Exception
877
     */
878
    private function mapFilter($name)
879
    {
880
        $map = array(
881
            'negate'          => array('id'=>0,  'argc'=>0, 'type'=>IMG_FILTER_NEGATE),
882
            'grayscale'       => array('id'=>1,  'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE),
883
            'brightness'      => array('id'=>2,  'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS),
884
            'contrast'        => array('id'=>3,  'argc'=>1, 'type'=>IMG_FILTER_CONTRAST),
885
            'colorize'        => array('id'=>4,  'argc'=>4, 'type'=>IMG_FILTER_COLORIZE),
886
            'edgedetect'      => array('id'=>5,  'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT),
887
            'emboss'          => array('id'=>6,  'argc'=>0, 'type'=>IMG_FILTER_EMBOSS),
888
            'gaussian_blur'   => array('id'=>7,  'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR),
889
            'selective_blur'  => array('id'=>8,  'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR),
890
            'mean_removal'    => array('id'=>9,  'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL),
891
            'smooth'          => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH),
892
            'pixelate'        => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE),
893
        );
894
895
        if (isset($map[$name])) {
896
            return $map[$name];
897
        } else {
898
            throw new Exception('No such filter.');
899
        }
900
    }
901
902
903
904
    /**
905
     * Load image details from original image file.
906
     *
907
     * @param string $file the file to load or null to use $this->pathToImage.
908
     *
909
     * @return $this
910
     * @throws Exception
911
     */
912
    public function loadImageDetails($file = null)
913
    {
914
        $file = $file ? $file : $this->pathToImage;
915
916
        is_readable($file)
917
            or $this->raiseError('Image file does not exist.');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
918
919
        // Get details on image
920
        $info = list($this->width, $this->height, $this->fileType, $this->attr) = getimagesize($file);
921
        if (empty($info)) {
922
            throw new Exception("The file doesn't seem to be a valid image.");
923
        }
924
925
        if ($this->verbose) {
926
            $this->log("Loading image details for: {$file}");
927
            $this->log(" Image width x height (type): {$this->width} x {$this->height} ({$this->fileType}).");
928
            $this->log(" Image filesize: " . filesize($file) . " bytes.");
929
            $this->log(" Image mimetype: " . image_type_to_mime_type($this->fileType));
930
        }
931
932
        return $this;
933
    }
934
935
936
937
    /**
938
     * Init new width and height and do some sanity checks on constraints, before any
939
     * processing can be done.
940
     *
941
     * @return $this
942
     * @throws Exception
943
     */
944
    public function initDimensions()
945
    {
946
        $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
947
948
        // width as %
949
        if ($this->newWidth[strlen($this->newWidth)-1] == '%') {
950
            $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100;
951
            $this->log("Setting new width based on % to {$this->newWidth}");
952
        }
953
954
        // height as %
955
        if ($this->newHeight[strlen($this->newHeight)-1] == '%') {
956
            $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100;
957
            $this->log("Setting new height based on % to {$this->newHeight}");
958
        }
959
960
        is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range');
0 ignored issues
show
Bug introduced by
The property aspectRatio does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
961
962
        // width & height from aspect ratio
963
        if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) {
964
            if ($this->aspectRatio >= 1) {
965
                $this->newWidth   = $this->width;
966
                $this->newHeight  = $this->width / $this->aspectRatio;
967
                $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}");
968
969
            } else {
970
                $this->newHeight  = $this->height;
971
                $this->newWidth   = $this->height * $this->aspectRatio;
972
                $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}");
973
            }
974
975
        } elseif ($this->aspectRatio && is_null($this->newWidth)) {
976
            $this->newWidth   = $this->newHeight * $this->aspectRatio;
977
            $this->log("Setting new width based on aspect ratio to {$this->newWidth}");
978
979
        } elseif ($this->aspectRatio && is_null($this->newHeight)) {
980
            $this->newHeight  = $this->newWidth / $this->aspectRatio;
981
            $this->log("Setting new height based on aspect ratio to {$this->newHeight}");
982
        }
983
984
        // Change width & height based on dpr
985
        if ($this->dpr != 1) {
986
            if (!is_null($this->newWidth)) {
987
                $this->newWidth  = round($this->newWidth * $this->dpr);
988
                $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}");
989
            }
990
            if (!is_null($this->newHeight)) {
991
                $this->newHeight = round($this->newHeight * $this->dpr);
992
                $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}");
993
            }
994
        }
995
996
        // Check values to be within domain
997
        is_null($this->newWidth)
998
            or is_numeric($this->newWidth)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
999
            or $this->raiseError('Width not numeric');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1000
1001
        is_null($this->newHeight)
1002
            or is_numeric($this->newHeight)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1003
            or $this->raiseError('Height not numeric');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1004
1005
        $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
1006
1007
        return $this;
1008
    }
1009
1010
1011
1012
    /**
1013
     * Calculate new width and height of image, based on settings.
1014
     *
1015
     * @return $this
1016
     */
1017
    public function calculateNewWidthAndHeight()
1018
    {
1019
        // Crop, use cropped width and height as base for calulations
1020
        $this->log("Calculate new width and height.");
1021
        $this->log("Original width x height is {$this->width} x {$this->height}.");
1022
        $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
1023
1024
        // Check if there is an area to crop off
1025
        if (isset($this->area)) {
1026
            $this->offset['top']    = round($this->area['top'] / 100 * $this->height);
0 ignored issues
show
Bug introduced by
The property area does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1027
            $this->offset['right']  = round($this->area['right'] / 100 * $this->width);
1028
            $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height);
1029
            $this->offset['left']   = round($this->area['left'] / 100 * $this->width);
1030
            $this->offset['width']  = $this->width - $this->offset['left'] - $this->offset['right'];
1031
            $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom'];
1032
            $this->width  = $this->offset['width'];
1033
            $this->height = $this->offset['height'];
1034
            $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%.");
1035
            $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px.");
1036
        }
1037
1038
        $width  = $this->width;
1039
        $height = $this->height;
1040
1041
        // Check if crop is set
1042
        if ($this->crop) {
1043
            $width  = $this->crop['width']  = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width'];
1044
            $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height'];
1045
1046
            if ($this->crop['start_x'] == 'left') {
1047
                $this->crop['start_x'] = 0;
1048
            } elseif ($this->crop['start_x'] == 'right') {
1049
                $this->crop['start_x'] = $this->width - $width;
1050
            } elseif ($this->crop['start_x'] == 'center') {
1051
                $this->crop['start_x'] = round($this->width / 2) - round($width / 2);
1052
            }
1053
1054
            if ($this->crop['start_y'] == 'top') {
1055
                $this->crop['start_y'] = 0;
1056
            } elseif ($this->crop['start_y'] == 'bottom') {
1057
                $this->crop['start_y'] = $this->height - $height;
1058
            } elseif ($this->crop['start_y'] == 'center') {
1059
                $this->crop['start_y'] = round($this->height / 2) - round($height / 2);
1060
            }
1061
1062
            $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px.");
1063
        }
1064
1065
        // Calculate new width and height if keeping aspect-ratio.
1066
        if ($this->keepRatio) {
1067
1068
            $this->log("Keep aspect ratio.");
1069
1070
            // Crop-to-fit and both new width and height are set.
1071
            if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) {
1072
1073
                // Use newWidth and newHeigh as width/height, image should fit in box.
1074
                $this->log("Use newWidth and newHeigh as width/height, image should fit in box.");
1075
1076
            } elseif (isset($this->newWidth) && isset($this->newHeight)) {
1077
1078
                // Both new width and height are set.
1079
                // Use newWidth and newHeigh as max width/height, image should not be larger.
1080
                $ratioWidth  = $width  / $this->newWidth;
1081
                $ratioHeight = $height / $this->newHeight;
1082
                $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight;
1083
                $this->newWidth  = round($width  / $ratio);
1084
                $this->newHeight = round($height / $ratio);
1085
                $this->log("New width and height was set.");
1086
1087
            } elseif (isset($this->newWidth)) {
1088
1089
                // Use new width as max-width
1090
                $factor = (float)$this->newWidth / (float)$width;
1091
                $this->newHeight = round($factor * $height);
1092
                $this->log("New width was set.");
1093
1094
            } elseif (isset($this->newHeight)) {
1095
1096
                // Use new height as max-hight
1097
                $factor = (float)$this->newHeight / (float)$height;
1098
                $this->newWidth = round($factor * $width);
1099
                $this->log("New height was set.");
1100
1101
            }
1102
1103
            // Get image dimensions for pre-resize image.
1104
            if ($this->cropToFit || $this->fillToFit) {
1105
1106
                // Get relations of original & target image
1107
                $ratioWidth  = $width  / $this->newWidth;
1108
                $ratioHeight = $height / $this->newHeight;
1109
1110
                if ($this->cropToFit) {
1111
1112
                    // Use newWidth and newHeigh as defined width/height,
1113
                    // image should fit the area.
1114
                    $this->log("Crop to fit.");
1115
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight;
1116
                    $this->cropWidth  = round($width  / $ratio);
1117
                    $this->cropHeight = round($height / $ratio);
1118
                    $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio).");
1119
1120
                } elseif ($this->fillToFit) {
1121
1122
                    // Use newWidth and newHeigh as defined width/height,
1123
                    // image should fit the area.
1124
                    $this->log("Fill to fit.");
1125
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth;
1126
                    $this->fillWidth  = round($width  / $ratio);
1127
                    $this->fillHeight = round($height / $ratio);
1128
                    $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio).");
1129
                }
1130
            }
1131
        }
1132
1133
        // Crop, ensure to set new width and height
1134
        if ($this->crop) {
1135
            $this->log("Crop.");
1136
            $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']);
1137
            $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']);
1138
        }
1139
1140
        // Fill to fit, ensure to set new width and height
1141
        /*if ($this->fillToFit) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1142
            $this->log("FillToFit.");
1143
            $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']);
1144
            $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']);
1145
        }*/
1146
1147
        // No new height or width is set, use existing measures.
1148
        $this->newWidth  = round(isset($this->newWidth) ? $this->newWidth : $this->width);
1149
        $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height);
1150
        $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}.");
1151
1152
        return $this;
1153
    }
1154
1155
1156
1157
    /**
1158
     * Re-calculate image dimensions when original image dimension has changed.
1159
     *
1160
     * @return $this
1161
     */
1162
    public function reCalculateDimensions()
1163
    {
1164
        $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight);
1165
1166
        $this->newWidth  = $this->newWidthOrig;
1167
        $this->newHeight = $this->newHeightOrig;
1168
        $this->crop      = $this->cropOrig;
1169
1170
        $this->initDimensions()
1171
             ->calculateNewWidthAndHeight();
1172
1173
        return $this;
1174
    }
1175
1176
1177
1178
    /**
1179
     * Set extension for filename to save as.
1180
     *
1181
     * @param string $saveas extension to save image as
0 ignored issues
show
Documentation introduced by
There is no parameter named $saveas. Did you maybe mean $saveAs?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1182
     *
1183
     * @return $this
1184
     */
1185
    public function setSaveAsExtension($saveAs = null)
1186
    {
1187
        if (isset($saveAs)) {
1188
            $saveAs = strtolower($saveAs);
1189
            $this->checkFileExtension($saveAs);
1190
            $this->saveAs = $saveAs;
1191
            $this->extension = $saveAs;
1192
        }
1193
1194
        $this->log("Prepare to save image as: " . $this->extension);
1195
1196
        return $this;
1197
    }
1198
1199
1200
1201
    /**
1202
     * Set JPEG quality to use when saving image
1203
     *
1204
     * @param int $quality as the quality to set.
1205
     *
1206
     * @return $this
1207
     */
1208
    public function setJpegQuality($quality = null)
1209
    {
1210
        if ($quality) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $quality of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1211
            $this->useQuality = true;
1212
        }
1213
1214
        $this->quality = isset($quality)
1215
            ? $quality
1216
            : self::JPEG_QUALITY_DEFAULT;
1217
1218
        (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1219
            or $this->raiseError('Quality not in range.');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1220
1221
        $this->log("Setting JPEG quality to {$this->quality}.");
1222
1223
        return $this;
1224
    }
1225
1226
1227
1228
    /**
1229
     * Set PNG compressen algorithm to use when saving image
1230
     *
1231
     * @param int $compress as the algorithm to use.
1232
     *
1233
     * @return $this
1234
     */
1235
    public function setPngCompression($compress = null)
1236
    {
1237
        if ($compress) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $compress of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1238
            $this->useCompress = true;
1239
        }
1240
1241
        $this->compress = isset($compress)
1242
            ? $compress
1243
            : self::PNG_COMPRESSION_DEFAULT;
1244
1245
        (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1246
            or $this->raiseError('Quality not in range.');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1247
1248
        $this->log("Setting PNG compression level to {$this->compress}.");
1249
1250
        return $this;
1251
    }
1252
1253
1254
1255
    /**
1256
     * Use original image if possible, check options which affects image processing.
1257
     *
1258
     * @param boolean $useOrig default is to use original if possible, else set to false.
1259
     *
1260
     * @return $this
1261
     */
1262
    public function useOriginalIfPossible($useOrig = true)
1263
    {
1264
        if ($useOrig
1265
            && ($this->newWidth == $this->width)
1266
            && ($this->newHeight == $this->height)
1267
            && !$this->area
1268
            && !$this->crop
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->crop of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1269
            && !$this->cropToFit
1270
            && !$this->fillToFit
1271
            && !$this->filters
1272
            && !$this->sharpen
1273
            && !$this->emboss
1274
            && !$this->blur
1275
            && !$this->convolve
1276
            && !$this->palette
1277
            && !$this->useQuality
1278
            && !$this->useCompress
1279
            && !$this->saveAs
1280
            && !$this->rotateBefore
1281
            && !$this->rotateAfter
1282
            && !$this->autoRotate
1283
            && !$this->bgColor
1284
            && ($this->upscale === self::UPSCALE_DEFAULT)
1285
        ) {
1286
            $this->log("Using original image.");
1287
            $this->output($this->pathToImage);
1288
        }
1289
1290
        return $this;
1291
    }
1292
1293
1294
1295
    /**
1296
     * Generate filename to save file in cache.
1297
     *
1298
     * @param string  $base      as optional basepath for storing file.
1299
     * @param boolean $useSubdir use or skip the subdir part when creating the
1300
     *                           filename.
1301
     * @param string  $prefix    to add as part of filename
1302
     *
1303
     * @return $this
1304 2
     */
1305
    public function generateFilename($base = null, $useSubdir = true, $prefix = null)
1306 2
    {
1307 2
        $filename     = basename($this->pathToImage);
1308 2
        $cropToFit    = $this->cropToFit    ? '_cf'                      : null;
1309 2
        $fillToFit    = $this->fillToFit    ? '_ff'                      : null;
1310 2
        $crop_x       = $this->crop_x       ? "_x{$this->crop_x}"        : null;
1311 2
        $crop_y       = $this->crop_y       ? "_y{$this->crop_y}"        : null;
1312 2
        $scale        = $this->scale        ? "_s{$this->scale}"         : null;
1313 2
        $bgColor      = $this->bgColor      ? "_bgc{$this->bgColor}"     : null;
1314 2
        $quality      = $this->quality      ? "_q{$this->quality}"       : null;
1315 2
        $compress     = $this->compress     ? "_co{$this->compress}"     : null;
1316 2
        $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null;
1317
        $rotateAfter  = $this->rotateAfter  ? "_ra{$this->rotateAfter}"  : null;
1318 2
1319 2
        $saveAs = $this->normalizeFileExtension();
1320
        $saveAs = $saveAs ? "_$saveAs" : null;
1321 2
1322 2
        $copyStrat = null;
1323
        if ($this->copyStrategy === self::RESIZE) {
1324
            $copyStrat = "_rs";
1325
        }
1326 2
1327 2
        $width  = $this->newWidth  ? '_' . $this->newWidth  : null;
1328
        $height = $this->newHeight ? '_' . $this->newHeight : null;
1329 2
1330 2
        $offset = isset($this->offset)
1331 2
            ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left']
1332
            : null;
1333 2
1334 2
        $crop = $this->crop
1335 2
            ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y']
1336
            : null;
1337 2
1338 2
        $filters = null;
1339
        if (isset($this->filters)) {
1340
            foreach ($this->filters as $filter) {
1341
                if (is_array($filter)) {
1342
                    $filters .= "_f{$filter['id']}";
1343
                    for ($i=1; $i<=$filter['argc']; $i++) {
1344
                        $filters .= "-".$filter["arg{$i}"];
1345
                    }
1346
                }
1347
            }
1348
        }
1349 2
1350 2
        $sharpen = $this->sharpen ? 's' : null;
1351 2
        $emboss  = $this->emboss  ? 'e' : null;
1352 2
        $blur    = $this->blur    ? 'b' : null;
1353
        $palette = $this->palette ? 'p' : null;
1354 2
1355
        $autoRotate = $this->autoRotate ? 'ar' : null;
1356 2
1357 2
        $optimize  = $this->jpegOptimize ? 'o' : null;
1358 2
        $optimize .= $this->pngFilter    ? 'f' : null;
1359
        $optimize .= $this->pngDeflate   ? 'd' : null;
1360 2
1361 2
        $convolve = null;
1362
        if ($this->convolve) {
1363
            $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve);
1364
        }
1365 2
1366 2
        $upscale = null;
1367
        if ($this->upscale !== self::UPSCALE_DEFAULT) {
1368
            $upscale = '_nu';
1369
        }
1370 2
1371 2
        $subdir = null;
1372
        if ($useSubdir === true) {
1373
            $subdir = str_replace('/', '-', dirname($this->imageSrc));
1374
            $subdir = ($subdir == '.') ? '_.' : $subdir;
1375
            $subdir .= '_';
1376
        }
1377 2
1378 2
        $file = $prefix . $subdir . $filename . $width . $height
1379 2
            . $offset . $crop . $cropToFit . $fillToFit
1380 2
            . $crop_x . $crop_y . $upscale
1381 2
            . $quality . $filters . $sharpen . $emboss . $blur . $palette
1382 2
            . $optimize . $compress
1383 2
            . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
1384
            . $convolve . $copyStrat . $saveAs;
1385 2
1386
        return $this->setTarget($file, $base);
1387
    }
1388
1389
1390
1391
    /**
1392
     * Use cached version of image, if possible.
1393
     *
1394
     * @param boolean $useCache is default true, set to false to avoid using cached object.
1395
     *
1396
     * @return $this
1397
     */
1398
    public function useCacheIfPossible($useCache = true)
1399
    {
1400
        if ($useCache && is_readable($this->cacheFileName)) {
1401
            $fileTime   = filemtime($this->pathToImage);
1402
            $cacheTime  = filemtime($this->cacheFileName);
1403
1404
            if ($fileTime <= $cacheTime) {
1405
                if ($this->useCache) {
1406
                    if ($this->verbose) {
1407
                        $this->log("Use cached file.");
1408
                        $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
1409
                    }
1410
                    $this->output($this->cacheFileName, $this->outputFormat);
1411
                } else {
1412
                    $this->log("Cache is valid but ignoring it by intention.");
1413
                }
1414
            } else {
1415
                $this->log("Original file is modified, ignoring cache.");
1416
            }
1417
        } else {
1418
            $this->log("Cachefile does not exists or ignoring it.");
1419
        }
1420
1421
        return $this;
1422
    }
1423
1424
1425
1426
    /**
1427
     * Load image from disk. Try to load image without verbose error message,
1428
     * if fail, load again and display error messages.
1429
     *
1430
     * @param string $src of image.
1431
     * @param string $dir as base directory where images are.
1432
     *
1433
     * @return $this
1434
     *
1435
     */
1436
    public function load($src = null, $dir = null)
1437
    {
1438
        if (isset($src)) {
1439
            $this->setSource($src, $dir);
1440
        }
1441
1442
        $this->loadImageDetails($this->pathToImage);
1443
1444
        $this->image = imagecreatefromstring(file_get_contents($this->pathToImage));
0 ignored issues
show
Security File Exposure introduced by
$this->pathToImage can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1445
        if ($this->image === false) {
1446
            throw new Exception("Could not load image.");
1447
        }
1448
1449
        /* Removed v0.7.7
0 ignored issues
show
Unused Code Comprehensibility introduced by
51% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1450
        if (image_type_to_mime_type($this->fileType) == 'image/png') {
1451
            $type = $this->getPngType();
1452
            $hasFewColors = imagecolorstotal($this->image);
1453
1454
            if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) {
1455
                if ($this->verbose) {
1456
                    $this->log("Handle this image as a palette image.");
1457
                }
1458
                $this->palette = true;
1459
            }
1460
        }
1461
        */
1462
1463
        if ($this->verbose) {
1464
            $this->log("### Image successfully loaded from file.");
1465
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
1466
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
1467
            $this->log(" Number of colors in image = " . $this->colorsTotal($this->image));
1468
            $index = imagecolortransparent($this->image);
1469
            $this->log(" Detected transparent color = " . ($index >= 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
1470
        }
1471
1472
        return $this;
1473
    }
1474
1475
1476
1477
    /**
1478
     * Get the type of PNG image.
1479
     *
1480
     * @param string $filename to use instead of default.
1481
     *
1482
     * @return int as the type of the png-image
1483
     *
1484
     */
1485
    public function getPngType($filename = null)
1486
    {
1487
        $filename = $filename ? $filename : $this->pathToImage;
1488
1489
        $pngType = ord(file_get_contents($filename, false, null, 25, 1));
0 ignored issues
show
Security File Exposure introduced by
$filename can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1490
1491
        if ($this->verbose) {
1492
            $this->log("Checking png type of: " . $filename);
1493
            $this->log($this->getPngTypeAsString($pngType));
1494
        }
1495
1496
        return $pngType;
1497
    }
1498
1499
1500
1501
    /**
1502
     * Get the type of PNG image as a verbose string.
1503
     *
1504
     * @param integer $type     to use, default is to check the type.
0 ignored issues
show
Bug introduced by
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1505
     * @param string  $filename to use instead of default.
1506
     *
1507
     * @return int as the type of the png-image
1508
     *
1509
     */
1510
    private function getPngTypeAsString($pngType = null, $filename = null)
1511
    {
1512
        if ($filename || !$pngType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1513
            $pngType = $this->getPngType($filename);
1514
        }
1515
1516
        $index = imagecolortransparent($this->image);
1517
        $transparent = null;
1518
        if ($index != -1) {
1519
            $transparent = " (transparent)";
1520
        }
1521
1522
        switch ($pngType) {
1523
1524
            case self::PNG_GREYSCALE:
1525
                $text = "PNG is type 0, Greyscale$transparent";
1526
                break;
1527
1528
            case self::PNG_RGB:
1529
                $text = "PNG is type 2, RGB$transparent";
1530
                break;
1531
1532
            case self::PNG_RGB_PALETTE:
1533
                $text = "PNG is type 3, RGB with palette$transparent";
1534
                break;
1535
1536
            case self::PNG_GREYSCALE_ALPHA:
1537
                $text = "PNG is type 4, Greyscale with alpha channel";
1538
                break;
1539
1540
            case self::PNG_RGB_ALPHA:
1541
                $text = "PNG is type 6, RGB with alpha channel (PNG 32-bit)";
1542
                break;
1543
1544
            default:
1545
                $text = "PNG is UNKNOWN type, is it really a PNG image?";
1546
        }
1547
1548
        return $text;
1549
    }
1550
1551
1552
1553
1554
    /**
1555
     * Calculate number of colors in an image.
1556
     *
1557
     * @param resource $im the image.
1558
     *
1559
     * @return int
1560
     */
1561
    private function colorsTotal($im)
1562
    {
1563
        if (imageistruecolor($im)) {
1564
            $this->log("Colors as true color.");
1565
            $h = imagesy($im);
1566
            $w = imagesx($im);
1567
            $c = array();
1568
            for ($x=0; $x < $w; $x++) {
1569
                for ($y=0; $y < $h; $y++) {
1570
                    @$c['c'.imagecolorat($im, $x, $y)]++;
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1571
                }
1572
            }
1573
            return count($c);
1574
        } else {
1575
            $this->log("Colors as palette.");
1576
            return imagecolorstotal($im);
1577
        }
1578
    }
1579
1580
1581
1582
    /**
1583
     * Preprocess image before rezising it.
1584
     *
1585
     * @return $this
1586
     */
1587
    public function preResize()
1588
    {
1589
        $this->log("### Pre-process before resizing");
1590
1591
        // Rotate image
1592
        if ($this->rotateBefore) {
1593
            $this->log("Rotating image.");
1594
            $this->rotate($this->rotateBefore, $this->bgColor)
1595
                 ->reCalculateDimensions();
1596
        }
1597
1598
        // Auto-rotate image
1599
        if ($this->autoRotate) {
1600
            $this->log("Auto rotating image.");
1601
            $this->rotateExif()
1602
                 ->reCalculateDimensions();
1603
        }
1604
1605
        // Scale the original image before starting
1606
        if (isset($this->scale)) {
1607
            $this->log("Scale by {$this->scale}%");
1608
            $newWidth  = $this->width * $this->scale / 100;
1609
            $newHeight = $this->height * $this->scale / 100;
1610
            $img = $this->CreateImageKeepTransparency($newWidth, $newHeight);
1611
            imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height);
1612
            $this->image = $img;
1613
            $this->width = $newWidth;
1614
            $this->height = $newHeight;
1615
        }
1616
1617
        return $this;
1618
    }
1619
1620
1621
1622
    /**
1623
     * Resize or resample the image while resizing.
1624
     *
1625
     * @param int $strategy as CImage::RESIZE or CImage::RESAMPLE
1626
     *
1627
     * @return $this
1628
     */
1629
     public function setCopyResizeStrategy($strategy)
1630
     {
1631
         $this->copyStrategy = $strategy;
1632
         return $this;
1633
     }
1634
1635
1636
1637
    /**
1638
     * Resize and or crop the image.
1639
     *
1640
     * @return void
1641
     */
1642
    public function imageCopyResampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
1643
    {
1644
        if($this->copyStrategy == self::RESIZE) {
1645
            $this->log("Copy by resize");
1646
            imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1647
        } else {
1648
            $this->log("Copy by resample");
1649
            imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1650
        }
1651
    }
1652
1653
1654
1655
    /**
1656
     * Resize and or crop the image.
1657
     *
1658
     * @return $this
1659
     */
1660
    public function resize()
1661
    {
1662
1663
        $this->log("### Starting to Resize()");
1664
        $this->log("Upscale = '$this->upscale'");
1665
1666
        // Only use a specified area of the image, $this->offset is defining the area to use
1667
        if (isset($this->offset)) {
1668
1669
            $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}");
1670
            $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']);
1671
            imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']);
1672
            $this->image = $img;
1673
            $this->width = $this->offset['width'];
1674
            $this->height = $this->offset['height'];
1675
        }
1676
1677
        if ($this->crop) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->crop of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1678
1679
            // Do as crop, take only part of image
1680
            $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}");
1681
            $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']);
1682
            imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']);
1683
            $this->image = $img;
1684
            $this->width = $this->crop['width'];
1685
            $this->height = $this->crop['height'];
1686
        }
1687
1688
        if (!$this->upscale) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1689
            // Consider rewriting the no-upscale code to fit within this if-statement,
1690
            // likely to be more readable code.
1691
            // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch
1692
        }
1693
1694
        if ($this->cropToFit) {
1695
1696
            // Resize by crop to fit
1697
            $this->log("Resizing using strategy - Crop to fit");
1698
1699
            if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) {
1700
                $this->log("Resizing - smaller image, do not upscale.");
1701
1702
                $cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1703
                $cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1704
1705
                $posX = 0;
1706
                $posY = 0;
1707
1708
                if ($this->newWidth > $this->width) {
1709
                    $posX = round(($this->newWidth - $this->width) / 2);
1710
                }
1711
1712
                if ($this->newHeight > $this->height) {
1713
                    $posY = round(($this->newHeight - $this->height) / 2);
1714
                }
1715
1716
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1717
                imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1718
            } else {
1719
                $cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1720
                $cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1721
                $imgPreCrop   = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight);
1722
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1723
                $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height);
1724
                imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight);
1725
            }
1726
1727
            $this->image = $imageResized;
1728
            $this->width = $this->newWidth;
1729
            $this->height = $this->newHeight;
1730
1731
        } elseif ($this->fillToFit) {
1732
1733
            // Resize by fill to fit
1734
            $this->log("Resizing using strategy - Fill to fit");
1735
1736
            $posX = 0;
1737
            $posY = 0;
1738
1739
            $ratioOrig = $this->width / $this->height;
1740
            $ratioNew  = $this->newWidth / $this->newHeight;
1741
1742
            // Check ratio for landscape or portrait
1743
            if ($ratioOrig < $ratioNew) {
1744
                $posX = round(($this->newWidth - $this->fillWidth) / 2);
1745
            } else {
1746
                $posY = round(($this->newHeight - $this->fillHeight) / 2);
1747
            }
1748
1749
            if (!$this->upscale
1750
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
1751
            ) {
1752
1753
                $this->log("Resizing - smaller image, do not upscale.");
1754
                $posX = round(($this->fillWidth - $this->width) / 2);
1755
                $posY = round(($this->fillHeight - $this->height) / 2);
1756
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1757
                imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1758
1759
            } else {
1760
                $imgPreFill   = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight);
1761
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1762
                $this->imageCopyResampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height);
1763
                imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1764
            }
1765
1766
            $this->image = $imageResized;
1767
            $this->width = $this->newWidth;
1768
            $this->height = $this->newHeight;
1769
1770
        } elseif (!($this->newWidth == $this->width && $this->newHeight == $this->height)) {
1771
1772
            // Resize it
1773
            $this->log("Resizing, new height and/or width");
1774
1775
            if (!$this->upscale
1776
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
1777
            ) {
1778
                $this->log("Resizing - smaller image, do not upscale.");
1779
1780
                if (!$this->keepRatio) {
1781
                    $this->log("Resizing - stretch to fit selected.");
1782
1783
                    $posX = 0;
1784
                    $posY = 0;
1785
                    $cropX = 0;
1786
                    $cropY = 0;
1787
1788
                    if ($this->newWidth > $this->width && $this->newHeight > $this->height) {
1789
                        $posX = round(($this->newWidth - $this->width) / 2);
1790
                        $posY = round(($this->newHeight - $this->height) / 2);
1791
                    } elseif ($this->newWidth > $this->width) {
1792
                        $posX = round(($this->newWidth - $this->width) / 2);
1793
                        $cropY = round(($this->height - $this->newHeight) / 2);
1794
                    } elseif ($this->newHeight > $this->height) {
1795
                        $posY = round(($this->newHeight - $this->height) / 2);
1796
                        $cropX = round(($this->width - $this->newWidth) / 2);
1797
                    }
1798
1799
                    //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY.");
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1800
                    $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1801
                    imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1802
                    $this->image = $imageResized;
1803
                    $this->width = $this->newWidth;
1804
                    $this->height = $this->newHeight;
1805
                }
1806
            } else {
1807
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1808
                $this->imageCopyResampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height);
1809
                $this->image = $imageResized;
1810
                $this->width = $this->newWidth;
1811
                $this->height = $this->newHeight;
1812
            }
1813
        }
1814
1815
        return $this;
1816
    }
1817
1818
1819
1820
    /**
1821
     * Postprocess image after rezising image.
1822
     *
1823
     * @return $this
1824
     */
1825
    public function postResize()
1826
    {
1827
        $this->log("### Post-process after resizing");
1828
1829
        // Rotate image
1830
        if ($this->rotateAfter) {
1831
            $this->log("Rotating image.");
1832
            $this->rotate($this->rotateAfter, $this->bgColor);
1833
        }
1834
1835
        // Apply filters
1836
        if (isset($this->filters) && is_array($this->filters)) {
1837
1838
            foreach ($this->filters as $filter) {
1839
                $this->log("Applying filter {$filter['type']}.");
1840
1841
                switch ($filter['argc']) {
1842
1843
                    case 0:
1844
                        imagefilter($this->image, $filter['type']);
1845
                        break;
1846
1847
                    case 1:
1848
                        imagefilter($this->image, $filter['type'], $filter['arg1']);
1849
                        break;
1850
1851
                    case 2:
1852
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']);
1853
                        break;
1854
1855
                    case 3:
1856
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']);
1857
                        break;
1858
1859
                    case 4:
1860
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']);
1861
                        break;
1862
                }
1863
            }
1864
        }
1865
1866
        // Convert to palette image
1867
        if ($this->palette) {
1868
            $this->log("Converting to palette image.");
1869
            $this->trueColorToPalette();
1870
        }
1871
1872
        // Blur the image
1873
        if ($this->blur) {
1874
            $this->log("Blur.");
1875
            $this->blurImage();
1876
        }
1877
1878
        // Emboss the image
1879
        if ($this->emboss) {
1880
            $this->log("Emboss.");
1881
            $this->embossImage();
1882
        }
1883
1884
        // Sharpen the image
1885
        if ($this->sharpen) {
1886
            $this->log("Sharpen.");
1887
            $this->sharpenImage();
1888
        }
1889
1890
        // Custom convolution
1891
        if ($this->convolve) {
1892
            //$this->log("Convolve: " . $this->convolve);
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1893
            $this->imageConvolution();
1894
        }
1895
1896
        return $this;
1897
    }
1898
1899
1900
1901
    /**
1902
     * Rotate image using angle.
1903
     *
1904
     * @param float $angle        to rotate image.
1905
     * @param int   $anglebgColor to fill image with if needed.
0 ignored issues
show
Documentation introduced by
There is no parameter named $anglebgColor. Did you maybe mean $bgColor?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1906
     *
1907
     * @return $this
1908
     */
1909
    public function rotate($angle, $bgColor)
0 ignored issues
show
Unused Code introduced by
The parameter $bgColor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1910
    {
1911
        $this->log("Rotate image " . $angle . " degrees with filler color.");
1912
1913
        $color = $this->getBackgroundColor();
1914
        $this->image = imagerotate($this->image, $angle, $color);
1915
1916
        $this->width  = imagesx($this->image);
1917
        $this->height = imagesy($this->image);
1918
1919
        $this->log("New image dimension width x height: " . $this->width . " x " . $this->height);
1920
1921
        return $this;
1922
    }
1923
1924
1925
1926
    /**
1927
     * Rotate image using information in EXIF.
1928
     *
1929
     * @return $this
1930
     */
1931
    public function rotateExif()
1932
    {
1933
        if (!in_array($this->fileType, array(IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM))) {
1934
            $this->log("Autorotate ignored, EXIF not supported by this filetype.");
1935
            return $this;
1936
        }
1937
1938
        $exif = exif_read_data($this->pathToImage);
1939
1940
        if (!empty($exif['Orientation'])) {
1941
            switch ($exif['Orientation']) {
1942
                case 3:
1943
                    $this->log("Autorotate 180.");
1944
                    $this->rotate(180, $this->bgColor);
1945
                    break;
1946
1947
                case 6:
1948
                    $this->log("Autorotate -90.");
1949
                    $this->rotate(-90, $this->bgColor);
1950
                    break;
1951
1952
                case 8:
1953
                    $this->log("Autorotate 90.");
1954
                    $this->rotate(90, $this->bgColor);
1955
                    break;
1956
1957
                default:
1958
                    $this->log("Autorotate ignored, unknown value as orientation.");
1959
            }
1960
        } else {
1961
            $this->log("Autorotate ignored, no orientation in EXIF.");
1962
        }
1963
1964
        return $this;
1965
    }
1966
1967
1968
1969
    /**
1970
     * Convert true color image to palette image, keeping alpha.
1971
     * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library
1972
     *
1973
     * @return void
1974
     */
1975
    public function trueColorToPalette()
1976
    {
1977
        $img = imagecreatetruecolor($this->width, $this->height);
1978
        $bga = imagecolorallocatealpha($img, 0, 0, 0, 127);
1979
        imagecolortransparent($img, $bga);
1980
        imagefill($img, 0, 0, $bga);
1981
        imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height);
1982
        imagetruecolortopalette($img, false, 255);
1983
        imagesavealpha($img, true);
1984
1985
        if (imageistruecolor($this->image)) {
1986
            $this->log("Matching colors with true color image.");
1987
            imagecolormatch($this->image, $img);
1988
        }
1989
1990
        $this->image = $img;
1991
    }
1992
1993
1994
1995
    /**
1996
     * Sharpen image using image convolution.
1997
     *
1998
     * @return $this
1999
     */
2000
    public function sharpenImage()
2001
    {
2002
        $this->imageConvolution('sharpen');
2003
        return $this;
2004
    }
2005
2006
2007
2008
    /**
2009
     * Emboss image using image convolution.
2010
     *
2011
     * @return $this
2012
     */
2013
    public function embossImage()
2014
    {
2015
        $this->imageConvolution('emboss');
2016
        return $this;
2017
    }
2018
2019
2020
2021
    /**
2022
     * Blur image using image convolution.
2023
     *
2024
     * @return $this
2025
     */
2026
    public function blurImage()
2027
    {
2028
        $this->imageConvolution('blur');
2029
        return $this;
2030
    }
2031
2032
2033
2034
    /**
2035
     * Create convolve expression and return arguments for image convolution.
2036
     *
2037
     * @param string $expression constant string which evaluates to a list of
2038
     *                           11 numbers separated by komma or such a list.
2039
     *
2040
     * @return array as $matrix (3x3), $divisor and $offset
2041
     */
2042
    public function createConvolveArguments($expression)
2043
    {
2044
        // Check of matching constant
2045
        if (isset($this->convolves[$expression])) {
2046
            $expression = $this->convolves[$expression];
2047
        }
2048
2049
        $part = explode(',', $expression);
2050
        $this->log("Creating convolution expressen: $expression");
2051
2052
        // Expect list of 11 numbers, split by , and build up arguments
2053
        if (count($part) != 11) {
2054
            throw new Exception(
2055
                "Missmatch in argument convolve. Expected comma-separated string with
2056
                11 float values. Got $expression."
2057
            );
2058
        }
2059
2060
        array_walk($part, function ($item, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2061
            if (!is_numeric($item)) {
2062
                throw new Exception("Argument to convolve expression should be float but is not.");
2063
            }
2064
        });
2065
2066
        return array(
2067
            array(
2068
                array($part[0], $part[1], $part[2]),
2069
                array($part[3], $part[4], $part[5]),
2070
                array($part[6], $part[7], $part[8]),
2071
            ),
2072
            $part[9],
2073
            $part[10],
2074
        );
2075
    }
2076
2077
2078
2079
    /**
2080
     * Add custom expressions (or overwrite existing) for image convolution.
2081
     *
2082
     * @param array $options Key value array with strings to be converted
2083
     *                       to convolution expressions.
2084
     *
2085
     * @return $this
2086
     */
2087
    public function addConvolveExpressions($options)
2088
    {
2089
        $this->convolves = array_merge($this->convolves, $options);
2090
        return $this;
2091
    }
2092
2093
2094
2095
    /**
2096
     * Image convolution.
2097
     *
2098
     * @param string $options A string with 11 float separated by comma.
2099
     *
2100
     * @return $this
2101
     */
2102
    public function imageConvolution($options = null)
2103
    {
2104
        // Use incoming options or use $this.
2105
        $options = $options ? $options : $this->convolve;
2106
2107
        // Treat incoming as string, split by +
2108
        $this->log("Convolution with '$options'");
2109
        $options = explode(":", $options);
2110
2111
        // Check each option if it matches constant value
2112
        foreach ($options as $option) {
2113
            list($matrix, $divisor, $offset) = $this->createConvolveArguments($option);
2114
            imageconvolution($this->image, $matrix, $divisor, $offset);
2115
        }
2116
2117
        return $this;
2118
    }
2119
2120
2121
2122
    /**
2123
     * Set default background color between 000000-FFFFFF or if using
2124
     * alpha 00000000-FFFFFF7F.
2125
     *
2126
     * @param string $color as hex value.
2127
     *
2128
     * @return $this
2129
    */
2130
    public function setDefaultBackgroundColor($color)
2131
    {
2132
        $this->log("Setting default background color to '$color'.");
2133
2134
        if (!(strlen($color) == 6 || strlen($color) == 8)) {
2135
            throw new Exception(
2136
                "Background color needs a hex value of 6 or 8
2137
                digits. 000000-FFFFFF or 00000000-FFFFFF7F.
2138
                Current value was: '$color'."
2139
            );
2140
        }
2141
2142
        $red    = hexdec(substr($color, 0, 2));
2143
        $green  = hexdec(substr($color, 2, 2));
2144
        $blue   = hexdec(substr($color, 4, 2));
2145
2146
        $alpha = (strlen($color) == 8)
2147
            ? hexdec(substr($color, 6, 2))
2148
            : null;
2149
2150
        if (($red < 0 || $red > 255)
2151
            || ($green < 0 || $green > 255)
2152
            || ($blue < 0 || $blue > 255)
2153
            || ($alpha < 0 || $alpha > 127)
2154
        ) {
2155
            throw new Exception(
2156
                "Background color out of range. Red, green blue
2157
                should be 00-FF and alpha should be 00-7F.
2158
                Current value was: '$color'."
2159
            );
2160
        }
2161
2162
        $this->bgColor = strtolower($color);
2163
        $this->bgColorDefault = array(
2164
            'red'   => $red,
2165
            'green' => $green,
2166
            'blue'  => $blue,
2167
            'alpha' => $alpha
2168
        );
2169
2170
        return $this;
2171
    }
2172
2173
2174
2175
    /**
2176
     * Get the background color.
2177
     *
2178
     * @param resource $img the image to work with or null if using $this->image.
2179
     *
2180
     * @return color value or null if no background color is set.
2181 2
    */
2182
    private function getBackgroundColor($img = null)
2183 2
    {
2184
        $img = isset($img) ? $img : $this->image;
2185 2
2186
        if ($this->bgColorDefault) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bgColorDefault of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2187 2
2188 2
            $red   = $this->bgColorDefault['red'];
2189 2
            $green = $this->bgColorDefault['green'];
2190 2
            $blue  = $this->bgColorDefault['blue'];
2191
            $alpha = $this->bgColorDefault['alpha'];
2192 2
2193
            if ($alpha) {
2194
                $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha);
2195 2
            } else {
2196
                $color = imagecolorallocate($img, $red, $green, $blue);
2197
            }
2198 2
2199
            return $color;
2200
2201
        } else {
2202
            return 0;
2203
        }
2204
    }
2205
2206
2207
2208
    /**
2209
     * Create a image and keep transparency for png and gifs.
2210
     *
2211
     * @param int $width of the new image.
2212
     * @param int $height of the new image.
2213
     *
2214
     * @return image resource.
2215 2
    */
2216
    private function createImageKeepTransparency($width, $height)
2217 2
    {
2218 2
        $this->log("Creating a new working image width={$width}px, height={$height}px.");
2219 2
        $img = imagecreatetruecolor($width, $height);
2220 2
        imagealphablending($img, false);
2221
        imagesavealpha($img, true);
2222 2
2223 2
        $index = $this->image
2224 2
            ? imagecolortransparent($this->image)
2225
            : -1;
2226 2
2227
        if ($index != -1) {
2228
2229
            imagealphablending($img, true);
2230
            $transparent = imagecolorsforindex($this->image, $index);
2231
            $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']);
2232
            imagefill($img, 0, 0, $color);
2233
            $index = imagecolortransparent($img, $color);
2234
            $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index");
2235 2
2236
        } elseif ($this->bgColorDefault) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bgColorDefault of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2237 2
2238 2
            $color = $this->getBackgroundColor($img);
2239 2
            imagefill($img, 0, 0, $color);
2240 2
            $this->Log("Filling image with background color.");
2241
        }
2242 2
2243
        return $img;
2244
    }
2245
2246
2247
2248
    /**
2249
     * Set optimizing  and post-processing options.
2250
     *
2251
     * @param array $options with config for postprocessing with external tools.
2252
     *
2253
     * @return $this
2254
     */
2255
    public function setPostProcessingOptions($options)
2256
    {
2257
        if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) {
2258
            $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd'];
2259
        } else {
2260
            $this->jpegOptimizeCmd = null;
2261
        }
2262
2263
        if (isset($options['png_filter']) && $options['png_filter']) {
2264
            $this->pngFilterCmd = $options['png_filter_cmd'];
2265
        } else {
2266
            $this->pngFilterCmd = null;
2267
        }
2268
2269
        if (isset($options['png_deflate']) && $options['png_deflate']) {
2270
            $this->pngDeflateCmd = $options['png_deflate_cmd'];
2271
        } else {
2272
            $this->pngDeflateCmd = null;
2273
        }
2274
2275
        return $this;
2276
    }
2277
2278
2279
2280
    /**
2281
     * Find out the type (file extension) for the image to be saved.
2282
     *
2283
     * @return string as image extension.
2284 2
     */
2285
    protected function getTargetImageExtension()
2286
    {
2287 2
        // switch on mimetype
2288
        if (isset($this->extension)) {
2289
            return strtolower($this->extension);
2290 2
        } else {
2291
            return substr(image_type_to_extension($this->fileType), 1);
2292
        }
2293
    }
2294
2295
2296
2297
    /**
2298
     * Save image.
2299
     *
2300
     * @param string  $src       as target filename.
2301
     * @param string  $base      as base directory where to store images.
2302
     * @param boolean $overwrite or not, default to always overwrite file.
2303
     *
2304
     * @return $this or false if no folder is set.
2305 2
     */
2306
    public function save($src = null, $base = null, $overwrite = true)
2307 2
    {
2308
        if (isset($src)) {
2309
            $this->setTarget($src, $base);
2310
        }
2311 2
2312
        if ($overwrite === false && is_file($this->cacheFileName)) {
2313
            $this->Log("Not overwriting file since its already exists and \$overwrite if false.");
2314
            return;
2315
        }
2316 2
2317
        is_writable($this->saveFolder)
2318
            or $this->raiseError('Target directory is not writable.');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
2319 2
2320 2
        $type = $this->getTargetImageExtension();
2321
        $this->Log("Saving image as " . $type);
2322
        switch($type) {
2323 2
2324 2
            case 'jpeg':
2325
            case 'jpg':
2326
                $this->Log("Saving image as JPEG to cache using quality = {$this->quality}.");
2327
                imagejpeg($this->image, $this->cacheFileName, $this->quality);
2328
2329
                // Use JPEG optimize if defined
2330
                if ($this->jpegOptimizeCmd) {
2331
                    if ($this->verbose) {
2332
                        clearstatcache();
2333
                        $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes.");
2334
                    }
2335
                    $res = array();
2336
                    $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName";
2337
                    exec($cmd, $res);
2338
                    $this->log($cmd);
2339
                    $this->log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2340
                }
2341
                break;
2342 2
2343
            case 'gif':
2344
                $this->Log("Saving image as GIF to cache.");
2345
                imagegif($this->image, $this->cacheFileName);
2346
                break;
2347 2
2348 2
            case 'png':
2349 2
            default:
2350
                $this->Log("Saving image as PNG to cache using compression = {$this->compress}.");
2351
2352 2
                // Turn off alpha blending and set alpha flag
2353 2
                imagealphablending($this->image, false);
2354 2
                imagesavealpha($this->image, true);
2355
                imagepng($this->image, $this->cacheFileName, $this->compress);
2356
2357 2
                // Use external program to filter PNG, if defined
2358
                if ($this->pngFilterCmd) {
2359
                    if ($this->verbose) {
2360
                        clearstatcache();
2361
                        $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes.");
2362
                    }
2363
                    $res = array();
2364
                    $cmd = $this->pngFilterCmd . " $this->cacheFileName";
2365
                    exec($cmd, $res);
2366
                    $this->Log($cmd);
2367
                    $this->Log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2368
                }
2369
2370 2
                // Use external program to deflate PNG, if defined
2371
                if ($this->pngDeflateCmd) {
2372
                    if ($this->verbose) {
2373
                        clearstatcache();
2374
                        $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes.");
2375
                    }
2376
                    $res = array();
2377
                    $cmd = $this->pngDeflateCmd . " $this->cacheFileName";
2378
                    exec($cmd, $res);
2379
                    $this->Log($cmd);
2380
                    $this->Log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2381 2
                }
2382 2
                break;
2383
        }
2384 2
2385
        if ($this->verbose) {
2386
            clearstatcache();
2387
            $this->log("Saved image to cache.");
2388
            $this->log(" Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
2389
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
2390
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
2391
            $this->log(" Number of colors in image = " . $this->ColorsTotal($this->image));
2392
            $index = imagecolortransparent($this->image);
2393
            $this->log(" Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
2394
        }
2395 2
2396
        return $this;
2397
    }
2398
2399
2400
2401
    /**
2402
     * Convert image from one colorpsace/color profile to sRGB without
2403
     * color profile.
2404
     *
2405
     * @param string  $src      of image.
2406
     * @param string  $dir      as base directory where images are.
2407
     * @param string  $cache    as base directory where to store images.
2408
     * @param string  $iccFile  filename of colorprofile.
2409
     * @param boolean $useCache or not, default to always use cache.
2410
     *
2411
     * @return string | boolean false if no conversion else the converted
2412
     *                          filename.
2413
     */
2414
    public function convert2sRGBColorSpace($src, $dir, $cache, $iccFile, $useCache = true)
2415
    {
2416
        if ($this->verbose) {
2417
            $this->log("# Converting image to sRGB colorspace.");
2418
        }
2419
2420
        if (!class_exists("Imagick")) {
2421
            $this->log(" Ignoring since Imagemagick is not installed.");
2422
            return false;
2423
        }
2424
2425
        // Prepare
2426
        $this->setSaveFolder($cache)
2427
             ->setSource($src, $dir)
2428
             ->generateFilename(null, false, 'srgb_');
2429
2430
        // Check if the cached version is accurate.
2431
        if ($useCache && is_readable($this->cacheFileName)) {
2432
            $fileTime  = filemtime($this->pathToImage);
2433
            $cacheTime = filemtime($this->cacheFileName);
2434
2435
            if ($fileTime <= $cacheTime) {
2436
                $this->log(" Using cached version: " . $this->cacheFileName);
2437
                return $this->cacheFileName;
2438
            }
2439
        }
2440
2441
        // Only covert if cachedir is writable
2442
        if (is_writable($this->saveFolder)) {
2443
            // Load file and check if conversion is needed
2444
            $image      = new Imagick($this->pathToImage);
2445
            $colorspace = $image->getImageColorspace();
2446
            $this->log(" Current colorspace: " . $colorspace);
2447
2448
            $profiles      = $image->getImageProfiles('*', false);
2449
            $hasICCProfile = (array_search('icc', $profiles) !== false);
2450
            $this->log(" Has ICC color profile: " . ($hasICCProfile ? "YES" : "NO"));
2451
2452
            if ($colorspace != Imagick::COLORSPACE_SRGB || $hasICCProfile) {
2453
                $this->log(" Converting to sRGB.");
2454
2455
                $sRGBicc = file_get_contents($iccFile);
2456
                $image->profileImage('icc', $sRGBicc);
2457
2458
                $image->transformImageColorspace(Imagick::COLORSPACE_SRGB);
0 ignored issues
show
Bug introduced by
The method transformImageColorspace() does not exist on Imagick. Did you maybe mean transformimage()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2459
                $image->writeImage($this->cacheFileName);
2460
                return $this->cacheFileName;
2461
            }
2462
        }
2463
2464
        return false;
2465
    }
2466
2467
2468
2469
    /**
2470
     * Create a hard link, as an alias, to the cached file.
2471
     *
2472
     * @param string $alias where to store the link,
2473
     *                      filename without extension.
2474
     *
2475
     * @return $this
2476
     */
2477
    public function linkToCacheFile($alias)
2478
    {
2479
        if ($alias === null) {
2480
            $this->log("Ignore creating alias.");
2481
            return $this;
2482
        }
2483
2484
        if (is_readable($alias)) {
2485
            unlink($alias);
0 ignored issues
show
Security File Manipulation introduced by
$alias can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2486
        }
2487
2488
        $res = link($this->cacheFileName, $alias);
2489
2490
        if ($res) {
2491
            $this->log("Created an alias as: $alias");
2492
        } else {
2493
            $this->log("Failed to create the alias: $alias");
2494
        }
2495
2496
        return $this;
2497
    }
2498
2499
2500
2501
    /**
2502
     * Add HTTP header for putputting together with image.
2503
     *
2504
     * @param string $type  the header type such as "Cache-Control"
2505
     * @param string $value the value to use
2506
     *
2507
     * @return void
2508
     */
2509
    public function addHTTPHeader($type, $value)
2510
    {
2511
        $this->HTTPHeader[$type] = $value;
2512
    }
2513
2514
2515
2516
    /**
2517
     * Output image to browser using caching.
2518
     *
2519
     * @param string $file   to read and output, default is to
2520
     *                       use $this->cacheFileName
2521
     * @param string $format set to json to output file as json
2522
     *                       object with details
2523
     *
2524
     * @return void
2525
     */
2526
    public function output($file = null, $format = null)
0 ignored issues
show
Coding Style introduced by
output uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2527
    {
2528
        if (is_null($file)) {
2529
            $file = $this->cacheFileName;
2530
        }
2531
2532
        if (is_null($format)) {
2533
            $format = $this->outputFormat;
2534
        }
2535
2536
        $this->log("Output format is: $format");
2537
2538
        if (!$this->verbose && $format == 'json') {
2539
            header('Content-type: application/json');
2540
            echo $this->json($file);
2541
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2542
        } elseif ($format == 'ascii') {
2543
            header('Content-type: text/plain');
2544
            echo $this->ascii($file);
0 ignored issues
show
Security Cross-Site Scripting introduced by
$this->ascii($file) can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2545
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2546
        }
2547
2548
        $this->log("Outputting image: $file");
2549
2550
        // Get image modification time
2551
        clearstatcache();
2552
        $lastModified = filemtime($file);
2553
        $gmdate = gmdate("D, d M Y H:i:s", $lastModified);
2554
2555
        if (!$this->verbose) {
2556
            header('Last-Modified: ' . $gmdate . " GMT");
2557
        }
2558
2559
        foreach($this->HTTPHeader as $key => $val) {
2560
            header("$key: $val");
2561
        }
2562
2563
        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) {
2564
2565
            if ($this->verbose) {
2566
                $this->log("304 not modified");
2567
                $this->verboseOutput();
2568
                exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2569
            }
2570
2571
            header("HTTP/1.0 304 Not Modified");
2572
2573
        } else {
2574
2575
            // Get details on image
2576
            $info = getimagesize($file);
2577
            !empty($info) or $this->raiseError("The file doesn't seem to be an image.");
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
2578
            $mime = $info['mime'];
2579
            $size = filesize($file);
2580
2581
            if ($this->verbose) {
2582
                $this->log("Last-Modified: " . $gmdate . " GMT");
2583
                $this->log("Content-type: " . $mime);
2584
                $this->log("Content-length: " . $size);
2585
                $this->verboseOutput();
2586
2587
                if (is_null($this->verboseFileName)) {
2588
                    exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2589
                }
2590
            }
2591
2592
            header("Content-type: $mime");
2593
            header("Content-length: $size");
2594
            readfile($file);
0 ignored issues
show
Security File Exposure introduced by
$file can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2595
        }
2596
2597
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method output() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2598
    }
2599
2600
2601
2602
    /**
2603
     * Create a JSON object from the image details.
2604
     *
2605
     * @param string $file the file to output.
2606
     *
2607
     * @return string json-encoded representation of the image.
2608
     */
2609
    public function json($file = null)
0 ignored issues
show
Coding Style introduced by
json uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2610
    {
2611
        $file = $file ? $file : $this->cacheFileName;
2612
2613
        $details = array();
2614
2615
        clearstatcache();
2616
2617
        $details['src']       = $this->imageSrc;
2618
        $lastModified         = filemtime($this->pathToImage);
2619
        $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2620
2621
        $details['cache']       = basename($this->cacheFileName);
2622
        $lastModified           = filemtime($this->cacheFileName);
2623
        $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2624
2625
        $this->load($file);
2626
2627 7
        $details['filename']    = basename($file);
2628
        $details['mimeType']    = image_type_to_mime_type($this->fileType);
2629 7
        $details['width']       = $this->width;
2630
        $details['height']      = $this->height;
2631
        $details['aspectRatio'] = round($this->width / $this->height, 3);
2632
        $details['size']        = filesize($file);
2633 7
        $details['colors'] = $this->colorsTotal($this->image);
2634
        $details['includedFiles'] = count(get_included_files());
2635
        $details['memoryPeek'] = round(memory_get_peak_usage()/1024/1024, 3) . " MB" ;
2636
        $details['memoryCurrent'] = round(memory_get_usage()/1024/1024, 3) . " MB";
2637
        $details['memoryLimit'] = ini_get('memory_limit');
2638
2639
        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
2640
            $details['loadTime'] = (string) round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']), 3) . "s";
2641
        }
2642
2643
        if ($details['mimeType'] == 'image/png') {
2644
            $details['pngType'] = $this->getPngTypeAsString(null, $file);
2645
        }
2646
2647
        $options = null;
2648
        if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) {
2649
            $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES;
2650
        }
2651
2652
        return json_encode($details, $options);
2653
    }
2654
2655
2656
2657
    /**
2658
     * Set options for creating ascii version of image.
2659
     *
2660
     * @param array $options empty to use default or set options to change.
2661
     *
2662
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2663
     */
2664
    public function setAsciiOptions($options = array())
2665
    {
2666
        $this->asciiOptions = $options;
2667
    }
2668
2669
2670
2671
    /**
2672
     * Create an ASCII version from the image details.
2673
     *
2674
     * @param string $file the file to output.
2675
     *
2676
     * @return string ASCII representation of the image.
2677
     */
2678
    public function ascii($file = null)
2679
    {
2680
        $file = $file ? $file : $this->cacheFileName;
2681
2682
        $asciiArt = new CAsciiArt();
2683
        $asciiArt->setOptions($this->asciiOptions);
2684
        return $asciiArt->createFromFile($file);
2685
    }
2686
2687
2688
2689
    /**
2690
     * Log an event if verbose mode.
2691
     *
2692
     * @param string $message to log.
2693
     *
2694
     * @return this
2695
     */
2696
    public function log($message)
2697
    {
2698
        if ($this->verbose) {
2699
            $this->log[] = $message;
2700
        }
2701
2702
        return $this;
2703
    }
2704
2705
2706
2707
    /**
2708
     * Do verbose output to a file.
2709
     *
2710
     * @param string $fileName where to write the verbose output.
2711
     *
2712
     * @return void
2713
     */
2714
    public function setVerboseToFile($fileName)
2715
    {
2716
        $this->log("Setting verbose output to file.");
2717
        $this->verboseFileName = $fileName;
2718
    }
2719
2720
2721
2722
    /**
2723
     * Do verbose output and print out the log and the actual images.
2724
     *
2725
     * @return void
2726
     */
2727
    private function verboseOutput()
2728
    {
2729
        $log = null;
2730
        $this->log("As JSON: \n" . $this->json());
2731
        $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M");
2732
        $this->log("Memory limit: " . ini_get('memory_limit'));
2733
2734
        $included = get_included_files();
2735
        $this->log("Included files: " . count($included));
2736
2737
        foreach ($this->log as $val) {
2738
            if (is_array($val)) {
2739
                foreach ($val as $val1) {
2740
                    $log .= htmlentities($val1) . '<br/>';
2741
                }
2742
            } else {
2743
                $log .= htmlentities($val) . '<br/>';
2744
            }
2745
        }
2746
2747
        if (!is_null($this->verboseFileName)) {
2748
            file_put_contents(
2749
                $this->verboseFileName,
2750
                str_replace("<br/>", "\n", $log)
0 ignored issues
show
Security File Manipulation introduced by
str_replace('<br/>', ' ', $log) can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2751
            );
2752
        } else {
2753
            echo <<<EOD
2754
<h1>CImage Verbose Output</h1>
2755
<pre>{$log}</pre>
2756
EOD;
2757
        }
2758
    }
2759
2760
2761
2762
    /**
2763
     * Raise error, enables to implement a selection of error methods.
2764
     *
2765
     * @param string $message the error message to display.
2766
     *
2767
     * @return void
2768
     * @throws Exception
2769
     */
2770
    private function raiseError($message)
2771
    {
2772
        throw new Exception($message);
2773
    }
2774
}
2775