Completed
Push — 079 ( 1dc04e...c009f4 )
by Mikael
06:56 queued 03:18
created

CImage::downloadRemoteSource()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 22
rs 9.2
cc 3
eloc 13
nc 3
nop 1
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
    public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null)
435
    {
436
        $this->setSource($imageSrc, $imageFolder);
437
        $this->setTarget($saveFolder, $saveName);
438
    }
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
    public function setSaveFolder($path)
468
    {
469
        $this->saveFolder = $path;
470
        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
    public function createDummyImage($width = null, $height = null)
500
    {
501
        $this->newWidth  = $this->newWidth  ?: $width  ?: 100;
502
        $this->newHeight = $this->newHeight ?: $height ?: 100;
503
504
        $this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
505
506
        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  $cache   path to cache dir.
516
     * @param string  $pattern to use to detect if its a remote file.
517
     *
518
     * @return $this
519
     */
520
    public function setRemoteDownload($allow, $cache, $pattern = null)
521
    {
522
        $this->allowRemote = $allow;
523
        $this->remoteCache = $cache;
0 ignored issues
show
Bug introduced by
The property remoteCache 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...
524
        $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern;
525
526
        $this->log(
527
            "Set remote download to: "
528
            . ($this->allowRemote ? "true" : "false")
529
            . " using pattern "
530
            . $this->remotePattern
531
        );
532
533
        return $this;
534
    }
535
536
537
538
    /**
539
     * Check if the image resource is a remote file or not.
540
     *
541
     * @param string $src check if src is remote.
542
     *
543
     * @return boolean true if $src is a remote file, else false.
544
     */
545
    public function isRemoteSource($src)
546
    {
547
        $remote = preg_match($this->remotePattern, $src);
548
        $this->log("Detected remote image: " . ($remote ? "true" : "false"));
549
        return !!$remote;
550
    }
551
552
553
554
    /**
555
     * Set whitelist for valid hostnames from where remote source can be
556
     * downloaded.
557
     *
558
     * @param array $whitelist with regexp hostnames to allow download from.
559
     *
560
     * @return $this
561
     */
562
    public function setRemoteHostWhitelist($whitelist = null)
563
    {
564
        $this->remoteHostWhitelist = $whitelist;
565
        $this->log(
566
            "Setting remote host whitelist to: "
567
            . (is_null($whitelist) ? "null" : print_r($whitelist, 1))
568
        );
569
        return $this;
570
    }
571
572
573
574
    /**
575
     * Check if the hostname for the remote image, is on a whitelist,
576
     * if the whitelist is defined.
577
     *
578
     * @param string $src the remote source.
579
     *
580
     * @return boolean true if hostname on $src is in the whitelist, else false.
581
     */
582
    public function isRemoteSourceOnWhitelist($src)
583
    {
584
        if (is_null($this->remoteHostWhitelist)) {
585
            $this->log("Remote host on whitelist not configured - allowing.");
586
            return true;
587
        }
588
589
        $whitelist = new CWhitelist();
590
        $hostname = parse_url($src, PHP_URL_HOST);
591
        $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 590 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...
592
593
        $this->log(
594
            "Remote host is on whitelist: "
595
            . ($allow ? "true" : "false")
596
        );
597
        return $allow;
598
    }
599
600
601
602
    /**
603
     * Check if file extension is valid as a file extension.
604
     *
605
     * @param string $extension of image file.
606
     *
607
     * @return $this
608
     */
609
    private function checkFileExtension($extension)
610
    {
611
        $valid = array('jpg', 'jpeg', 'png', 'gif');
612
613
        in_array(strtolower($extension), $valid)
614
            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...
615
616
        return $this;
617
    }
618
619
620
621
    /**
622
     * Normalize the file extension.
623
     *
624
     * @param string $extension of image file or skip to use internal.
625
     *
626
     * @return string $extension as a normalized file extension.
627
     */
628
    private function normalizeFileExtension($extension = null)
629
    {
630
        $extension = strtolower($extension ? $extension : $this->extension);
631
632
        if ($extension == 'jpeg') {
633
                $extension = 'jpg';
634
            }
635
636
        return $extension;
637
    }
638
639
640
641
    /**
642
     * Download a remote image and return path to its local copy.
643
     *
644
     * @param string $src remote path to image.
645
     *
646
     * @return string as path to downloaded remote source.
647
     */
648
    public function downloadRemoteSource($src)
649
    {
650
        if (!$this->isRemoteSourceOnWhitelist($src)) {
651
            throw new Exception("Hostname is not on whitelist for remote sources.");
652
        }
653
654
        $remote = new CRemoteImage();
655
656
        if (!is_writable($this->remoteCache)) {
657
            $this->log("The remote cache is not writable.");
658
        }
659
660
        $remote->setCache($this->remoteCache);
661
        $remote->useCache($this->useCache);
662
        $src = $remote->download($src);
663
664
        $this->log("Remote HTTP status: " . $remote->getStatus());
665
        $this->log("Remote item is in local cache: $src");
666
        $this->log("Remote details on cache:" . print_r($remote->getDetails(), true));
667
668
        return $src;
669
    }
670
671
672
673
    /**
674
     * Set source file to use as image source.
675
     *
676
     * @param string $src of image.
677
     * @param string $dir as optional base directory where images are.
678
     *
679
     * @return $this
680
     */
681
    public function setSource($src, $dir = null)
682
    {
683
        if (!isset($src)) {
684
            $this->imageSrc = null;
685
            $this->pathToImage = null;
686
            return $this;
687
        }
688
689
        if ($this->allowRemote && $this->isRemoteSource($src)) {
690
            $src = $this->downloadRemoteSource($src);
691
            $dir = null;
692
        }
693
694
        if (!isset($dir)) {
695
            $dir = dirname($src);
696
            $src = basename($src);
697
        }
698
699
        $this->imageSrc     = ltrim($src, '/');
700
        $imageFolder        = rtrim($dir, '/');
701
        $this->pathToImage  = $imageFolder . '/' . $this->imageSrc;
702
703
        return $this;
704
    }
705
706
707
708
    /**
709
     * Set target file.
710
     *
711
     * @param string $src of target image.
712
     * @param string $dir as optional base directory where images are stored.
713
     *                    Uses $this->saveFolder if null.
714
     *
715
     * @return $this
716
     */
717
    public function setTarget($src = null, $dir = null)
718
    {
719
        if (!isset($src)) {
720
            $this->cacheFileName = null;
721
            return $this;
722
        }
723
724
        if (isset($dir)) {
725
            $this->saveFolder = rtrim($dir, '/');
726
        }
727
728
        $this->cacheFileName  = $this->saveFolder . '/' . $src;
729
730
        // Sanitize filename
731
        $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
732
        $this->log("The cache file name is: " . $this->cacheFileName);
733
734
        return $this;
735
    }
736
737
738
739
    /**
740
     * Get filename of target file.
741
     *
742
     * @return Boolean|String as filename of target or false if not set.
743
     */
744
    public function getTarget()
745
    {
746
        return $this->cacheFileName;
747
    }
748
749
750
751
    /**
752
     * Set options to use when processing image.
753
     *
754
     * @param array $args used when processing image.
755
     *
756
     * @return $this
757
     */
758
    public function setOptions($args)
759
    {
760
        $this->log("Set new options for processing image.");
761
762
        $defaults = array(
763
            // Options for calculate dimensions
764
            'newWidth'    => null,
765
            'newHeight'   => null,
766
            'aspectRatio' => null,
767
            'keepRatio'   => true,
768
            'cropToFit'   => false,
769
            'fillToFit'   => null,
770
            '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...
771
            'area'        => null, //'0,0,0,0',
772
            'upscale'     => self::UPSCALE_DEFAULT,
773
774
            // Options for caching or using original
775
            'useCache'    => true,
776
            'useOriginal' => true,
777
778
            // Pre-processing, before resizing is done
779
            'scale'        => null,
780
            'rotateBefore' => null,
781
            'autoRotate'  => false,
782
783
            // General options
784
            'bgColor'     => null,
785
786
            // Post-processing, after resizing is done
787
            'palette'     => null,
788
            'filters'     => null,
789
            'sharpen'     => null,
790
            'emboss'      => null,
791
            'blur'        => null,
792
            'convolve'       => null,
793
            'rotateAfter' => null,
794
795
            // Output format
796
            'outputFormat' => null,
797
            'dpr'          => 1,
798
        );
799
800
        // Convert crop settings from string to array
801
        if (isset($args['crop']) && !is_array($args['crop'])) {
802
            $pices = explode(',', $args['crop']);
803
            $args['crop'] = array(
804
                'width'   => $pices[0],
805
                'height'  => $pices[1],
806
                'start_x' => $pices[2],
807
                'start_y' => $pices[3],
808
            );
809
        }
810
811
        // Convert area settings from string to array
812
        if (isset($args['area']) && !is_array($args['area'])) {
813
                $pices = explode(',', $args['area']);
814
                $args['area'] = array(
815
                    'top'    => $pices[0],
816
                    'right'  => $pices[1],
817
                    'bottom' => $pices[2],
818
                    'left'   => $pices[3],
819
                );
820
        }
821
822
        // Convert filter settings from array of string to array of array
823
        if (isset($args['filters']) && is_array($args['filters'])) {
824
            foreach ($args['filters'] as $key => $filterStr) {
825
                $parts = explode(',', $filterStr);
826
                $filter = $this->mapFilter($parts[0]);
827
                $filter['str'] = $filterStr;
828
                for ($i=1; $i<=$filter['argc']; $i++) {
829
                    if (isset($parts[$i])) {
830
                        $filter["arg{$i}"] = $parts[$i];
831
                    } else {
832
                        throw new Exception(
833
                            'Missing arg to filter, review how many arguments are needed at
834
                            http://php.net/manual/en/function.imagefilter.php'
835
                        );
836
                    }
837
                }
838
                $args['filters'][$key] = $filter;
839
            }
840
        }
841
842
        // Merge default arguments with incoming and set properties.
843
        //$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...
844
        $args = array_merge($defaults, $args);
845
        foreach ($defaults as $key => $val) {
846
            $this->{$key} = $args[$key];
847
        }
848
849
        if ($this->bgColor) {
850
            $this->setDefaultBackgroundColor($this->bgColor);
851
        }
852
853
        // Save original values to enable re-calculating
854
        $this->newWidthOrig  = $this->newWidth;
855
        $this->newHeightOrig = $this->newHeight;
856
        $this->cropOrig      = $this->crop;
857
858
        return $this;
859
    }
860
861
862
863
    /**
864
     * Map filter name to PHP filter and id.
865
     *
866
     * @param string $name the name of the filter.
867
     *
868
     * @return array with filter settings
869
     * @throws Exception
870
     */
871
    private function mapFilter($name)
872
    {
873
        $map = array(
874
            'negate'          => array('id'=>0,  'argc'=>0, 'type'=>IMG_FILTER_NEGATE),
875
            'grayscale'       => array('id'=>1,  'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE),
876
            'brightness'      => array('id'=>2,  'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS),
877
            'contrast'        => array('id'=>3,  'argc'=>1, 'type'=>IMG_FILTER_CONTRAST),
878
            'colorize'        => array('id'=>4,  'argc'=>4, 'type'=>IMG_FILTER_COLORIZE),
879
            'edgedetect'      => array('id'=>5,  'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT),
880
            'emboss'          => array('id'=>6,  'argc'=>0, 'type'=>IMG_FILTER_EMBOSS),
881
            'gaussian_blur'   => array('id'=>7,  'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR),
882
            'selective_blur'  => array('id'=>8,  'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR),
883
            'mean_removal'    => array('id'=>9,  'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL),
884
            'smooth'          => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH),
885
            'pixelate'        => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE),
886
        );
887
888
        if (isset($map[$name])) {
889
            return $map[$name];
890
        } else {
891
            throw new Exception('No such filter.');
892
        }
893
    }
894
895
896
897
    /**
898
     * Load image details from original image file.
899
     *
900
     * @param string $file the file to load or null to use $this->pathToImage.
901
     *
902
     * @return $this
903
     * @throws Exception
904
     */
905
    public function loadImageDetails($file = null)
906
    {
907
        $file = $file ? $file : $this->pathToImage;
908
909
        is_readable($file)
910
            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...
911
912
        // Get details on image
913
        $info = list($this->width, $this->height, $this->fileType, $this->attr) = getimagesize($file);
914
        if (empty($info)) {
915
            throw new Exception("The file doesn't seem to be a valid image.");
916
        }
917
918
        if ($this->verbose) {
919
            $this->log("Loading image details for: {$file}");
920
            $this->log(" Image width x height (type): {$this->width} x {$this->height} ({$this->fileType}).");
921
            $this->log(" Image filesize: " . filesize($file) . " bytes.");
922
            $this->log(" Image mimetype: " . image_type_to_mime_type($this->fileType));
923
        }
924
925
        return $this;
926
    }
927
928
929
930
    /**
931
     * Init new width and height and do some sanity checks on constraints, before any
932
     * processing can be done.
933
     *
934
     * @return $this
935
     * @throws Exception
936
     */
937
    public function initDimensions()
938
    {
939
        $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
940
941
        // width as %
942
        if ($this->newWidth[strlen($this->newWidth)-1] == '%') {
943
            $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100;
944
            $this->log("Setting new width based on % to {$this->newWidth}");
945
        }
946
947
        // height as %
948
        if ($this->newHeight[strlen($this->newHeight)-1] == '%') {
949
            $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100;
950
            $this->log("Setting new height based on % to {$this->newHeight}");
951
        }
952
953
        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...
954
955
        // width & height from aspect ratio
956
        if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) {
957
            if ($this->aspectRatio >= 1) {
958
                $this->newWidth   = $this->width;
959
                $this->newHeight  = $this->width / $this->aspectRatio;
960
                $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}");
961
962
            } else {
963
                $this->newHeight  = $this->height;
964
                $this->newWidth   = $this->height * $this->aspectRatio;
965
                $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}");
966
            }
967
968
        } elseif ($this->aspectRatio && is_null($this->newWidth)) {
969
            $this->newWidth   = $this->newHeight * $this->aspectRatio;
970
            $this->log("Setting new width based on aspect ratio to {$this->newWidth}");
971
972
        } elseif ($this->aspectRatio && is_null($this->newHeight)) {
973
            $this->newHeight  = $this->newWidth / $this->aspectRatio;
974
            $this->log("Setting new height based on aspect ratio to {$this->newHeight}");
975
        }
976
977
        // Change width & height based on dpr
978
        if ($this->dpr != 1) {
979
            if (!is_null($this->newWidth)) {
980
                $this->newWidth  = round($this->newWidth * $this->dpr);
981
                $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}");
982
            }
983
            if (!is_null($this->newHeight)) {
984
                $this->newHeight = round($this->newHeight * $this->dpr);
985
                $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}");
986
            }
987
        }
988
989
        // Check values to be within domain
990
        is_null($this->newWidth)
991
            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...
992
            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...
993
994
        is_null($this->newHeight)
995
            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...
996
            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...
997
998
        $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
999
1000
        return $this;
1001
    }
1002
1003
1004
1005
    /**
1006
     * Calculate new width and height of image, based on settings.
1007
     *
1008
     * @return $this
1009
     */
1010
    public function calculateNewWidthAndHeight()
1011
    {
1012
        // Crop, use cropped width and height as base for calulations
1013
        $this->log("Calculate new width and height.");
1014
        $this->log("Original width x height is {$this->width} x {$this->height}.");
1015
        $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
1016
1017
        // Check if there is an area to crop off
1018
        if (isset($this->area)) {
1019
            $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...
1020
            $this->offset['right']  = round($this->area['right'] / 100 * $this->width);
1021
            $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height);
1022
            $this->offset['left']   = round($this->area['left'] / 100 * $this->width);
1023
            $this->offset['width']  = $this->width - $this->offset['left'] - $this->offset['right'];
1024
            $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom'];
1025
            $this->width  = $this->offset['width'];
1026
            $this->height = $this->offset['height'];
1027
            $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']}%.");
1028
            $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.");
1029
        }
1030
1031
        $width  = $this->width;
1032
        $height = $this->height;
1033
1034
        // Check if crop is set
1035
        if ($this->crop) {
1036
            $width  = $this->crop['width']  = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width'];
1037
            $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height'];
1038
1039
            if ($this->crop['start_x'] == 'left') {
1040
                $this->crop['start_x'] = 0;
1041
            } elseif ($this->crop['start_x'] == 'right') {
1042
                $this->crop['start_x'] = $this->width - $width;
1043
            } elseif ($this->crop['start_x'] == 'center') {
1044
                $this->crop['start_x'] = round($this->width / 2) - round($width / 2);
1045
            }
1046
1047
            if ($this->crop['start_y'] == 'top') {
1048
                $this->crop['start_y'] = 0;
1049
            } elseif ($this->crop['start_y'] == 'bottom') {
1050
                $this->crop['start_y'] = $this->height - $height;
1051
            } elseif ($this->crop['start_y'] == 'center') {
1052
                $this->crop['start_y'] = round($this->height / 2) - round($height / 2);
1053
            }
1054
1055
            $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.");
1056
        }
1057
1058
        // Calculate new width and height if keeping aspect-ratio.
1059
        if ($this->keepRatio) {
1060
1061
            $this->log("Keep aspect ratio.");
1062
1063
            // Crop-to-fit and both new width and height are set.
1064
            if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) {
1065
1066
                // Use newWidth and newHeigh as width/height, image should fit in box.
1067
                $this->log("Use newWidth and newHeigh as width/height, image should fit in box.");
1068
1069
            } elseif (isset($this->newWidth) && isset($this->newHeight)) {
1070
1071
                // Both new width and height are set.
1072
                // Use newWidth and newHeigh as max width/height, image should not be larger.
1073
                $ratioWidth  = $width  / $this->newWidth;
1074
                $ratioHeight = $height / $this->newHeight;
1075
                $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight;
1076
                $this->newWidth  = round($width  / $ratio);
1077
                $this->newHeight = round($height / $ratio);
1078
                $this->log("New width and height was set.");
1079
1080
            } elseif (isset($this->newWidth)) {
1081
1082
                // Use new width as max-width
1083
                $factor = (float)$this->newWidth / (float)$width;
1084
                $this->newHeight = round($factor * $height);
1085
                $this->log("New width was set.");
1086
1087
            } elseif (isset($this->newHeight)) {
1088
1089
                // Use new height as max-hight
1090
                $factor = (float)$this->newHeight / (float)$height;
1091
                $this->newWidth = round($factor * $width);
1092
                $this->log("New height was set.");
1093
1094
            }
1095
1096
            // Get image dimensions for pre-resize image.
1097
            if ($this->cropToFit || $this->fillToFit) {
1098
1099
                // Get relations of original & target image
1100
                $ratioWidth  = $width  / $this->newWidth;
1101
                $ratioHeight = $height / $this->newHeight;
1102
1103
                if ($this->cropToFit) {
1104
1105
                    // Use newWidth and newHeigh as defined width/height,
1106
                    // image should fit the area.
1107
                    $this->log("Crop to fit.");
1108
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight;
1109
                    $this->cropWidth  = round($width  / $ratio);
1110
                    $this->cropHeight = round($height / $ratio);
1111
                    $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio).");
1112
1113
                } elseif ($this->fillToFit) {
1114
1115
                    // Use newWidth and newHeigh as defined width/height,
1116
                    // image should fit the area.
1117
                    $this->log("Fill to fit.");
1118
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth;
1119
                    $this->fillWidth  = round($width  / $ratio);
1120
                    $this->fillHeight = round($height / $ratio);
1121
                    $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio).");
1122
                }
1123
            }
1124
        }
1125
1126
        // Crop, ensure to set new width and height
1127
        if ($this->crop) {
1128
            $this->log("Crop.");
1129
            $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']);
1130
            $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']);
1131
        }
1132
1133
        // Fill to fit, ensure to set new width and height
1134
        /*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...
1135
            $this->log("FillToFit.");
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
        // No new height or width is set, use existing measures.
1141
        $this->newWidth  = round(isset($this->newWidth) ? $this->newWidth : $this->width);
1142
        $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height);
1143
        $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}.");
1144
1145
        return $this;
1146
    }
1147
1148
1149
1150
    /**
1151
     * Re-calculate image dimensions when original image dimension has changed.
1152
     *
1153
     * @return $this
1154
     */
1155
    public function reCalculateDimensions()
1156
    {
1157
        $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight);
1158
1159
        $this->newWidth  = $this->newWidthOrig;
1160
        $this->newHeight = $this->newHeightOrig;
1161
        $this->crop      = $this->cropOrig;
1162
1163
        $this->initDimensions()
1164
             ->calculateNewWidthAndHeight();
1165
1166
        return $this;
1167
    }
1168
1169
1170
1171
    /**
1172
     * Set extension for filename to save as.
1173
     *
1174
     * @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...
1175
     *
1176
     * @return $this
1177
     */
1178
    public function setSaveAsExtension($saveAs = null)
1179
    {
1180
        if (isset($saveAs)) {
1181
            $saveAs = strtolower($saveAs);
1182
            $this->checkFileExtension($saveAs);
1183
            $this->saveAs = $saveAs;
1184
            $this->extension = $saveAs;
1185
        }
1186
1187
        $this->log("Prepare to save image as: " . $this->extension);
1188
1189
        return $this;
1190
    }
1191
1192
1193
1194
    /**
1195
     * Set JPEG quality to use when saving image
1196
     *
1197
     * @param int $quality as the quality to set.
1198
     *
1199
     * @return $this
1200
     */
1201
    public function setJpegQuality($quality = null)
1202
    {
1203
        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...
1204
            $this->useQuality = true;
1205
        }
1206
1207
        $this->quality = isset($quality)
1208
            ? $quality
1209
            : self::JPEG_QUALITY_DEFAULT;
1210
1211
        (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...
1212
            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...
1213
1214
        $this->log("Setting JPEG quality to {$this->quality}.");
1215
1216
        return $this;
1217
    }
1218
1219
1220
1221
    /**
1222
     * Set PNG compressen algorithm to use when saving image
1223
     *
1224
     * @param int $compress as the algorithm to use.
1225
     *
1226
     * @return $this
1227
     */
1228
    public function setPngCompression($compress = null)
1229
    {
1230
        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...
1231
            $this->useCompress = true;
1232
        }
1233
1234
        $this->compress = isset($compress)
1235
            ? $compress
1236
            : self::PNG_COMPRESSION_DEFAULT;
1237
1238
        (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...
1239
            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...
1240
1241
        $this->log("Setting PNG compression level to {$this->compress}.");
1242
1243
        return $this;
1244
    }
1245
1246
1247
1248
    /**
1249
     * Use original image if possible, check options which affects image processing.
1250
     *
1251
     * @param boolean $useOrig default is to use original if possible, else set to false.
1252
     *
1253
     * @return $this
1254
     */
1255
    public function useOriginalIfPossible($useOrig = true)
1256
    {
1257
        if ($useOrig
1258
            && ($this->newWidth == $this->width)
1259
            && ($this->newHeight == $this->height)
1260
            && !$this->area
1261
            && !$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...
1262
            && !$this->cropToFit
1263
            && !$this->fillToFit
1264
            && !$this->filters
1265
            && !$this->sharpen
1266
            && !$this->emboss
1267
            && !$this->blur
1268
            && !$this->convolve
1269
            && !$this->palette
1270
            && !$this->useQuality
1271
            && !$this->useCompress
1272
            && !$this->saveAs
1273
            && !$this->rotateBefore
1274
            && !$this->rotateAfter
1275
            && !$this->autoRotate
1276
            && !$this->bgColor
1277
            && ($this->upscale === self::UPSCALE_DEFAULT)
1278
        ) {
1279
            $this->log("Using original image.");
1280
            $this->output($this->pathToImage);
1281
        }
1282
1283
        return $this;
1284
    }
1285
1286
1287
1288
    /**
1289
     * Generate filename to save file in cache.
1290
     *
1291
     * @param string  $base      as optional basepath for storing file.
1292
     * @param boolean $useSubdir use or skip the subdir part when creating the
1293
     *                           filename.
1294
     * @param string  $prefix    to add as part of filename
1295
     *
1296
     * @return $this
1297
     */
1298
    public function generateFilename($base = null, $useSubdir = true, $prefix = null)
1299
    {
1300
        $filename     = basename($this->pathToImage);
1301
        $cropToFit    = $this->cropToFit    ? '_cf'                      : null;
1302
        $fillToFit    = $this->fillToFit    ? '_ff'                      : null;
1303
        $crop_x       = $this->crop_x       ? "_x{$this->crop_x}"        : null;
1304
        $crop_y       = $this->crop_y       ? "_y{$this->crop_y}"        : null;
1305
        $scale        = $this->scale        ? "_s{$this->scale}"         : null;
1306
        $bgColor      = $this->bgColor      ? "_bgc{$this->bgColor}"     : null;
1307
        $quality      = $this->quality      ? "_q{$this->quality}"       : null;
1308
        $compress     = $this->compress     ? "_co{$this->compress}"     : null;
1309
        $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null;
1310
        $rotateAfter  = $this->rotateAfter  ? "_ra{$this->rotateAfter}"  : null;
1311
1312
        $saveAs = $this->normalizeFileExtension();
1313
        $saveAs = $saveAs ? "_$saveAs" : null;
1314
1315
        $copyStrat = null;
1316
        if ($this->copyStrategy === self::RESIZE) {
1317
            $copyStrat = "_rs";
1318
        }
1319
1320
        $width  = $this->newWidth  ? '_' . $this->newWidth  : null;
1321
        $height = $this->newHeight ? '_' . $this->newHeight : null;
1322
1323
        $offset = isset($this->offset)
1324
            ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left']
1325
            : null;
1326
1327
        $crop = $this->crop
1328
            ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y']
1329
            : null;
1330
1331
        $filters = null;
1332
        if (isset($this->filters)) {
1333
            foreach ($this->filters as $filter) {
1334
                if (is_array($filter)) {
1335
                    $filters .= "_f{$filter['id']}";
1336
                    for ($i=1; $i<=$filter['argc']; $i++) {
1337
                        $filters .= "-".$filter["arg{$i}"];
1338
                    }
1339
                }
1340
            }
1341
        }
1342
1343
        $sharpen = $this->sharpen ? 's' : null;
1344
        $emboss  = $this->emboss  ? 'e' : null;
1345
        $blur    = $this->blur    ? 'b' : null;
1346
        $palette = $this->palette ? 'p' : null;
1347
1348
        $autoRotate = $this->autoRotate ? 'ar' : null;
1349
1350
        $optimize  = $this->jpegOptimize ? 'o' : null;
1351
        $optimize .= $this->pngFilter    ? 'f' : null;
1352
        $optimize .= $this->pngDeflate   ? 'd' : null;
1353
1354
        $convolve = null;
1355
        if ($this->convolve) {
1356
            $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve);
1357
        }
1358
1359
        $upscale = null;
1360
        if ($this->upscale !== self::UPSCALE_DEFAULT) {
1361
            $upscale = '_nu';
1362
        }
1363
1364
        $subdir = null;
1365
        if ($useSubdir === true) {
1366
            $subdir = str_replace('/', '-', dirname($this->imageSrc));
1367
            $subdir = ($subdir == '.') ? '_.' : $subdir;
1368
            $subdir .= '_';
1369
        }
1370
1371
        $file = $prefix . $subdir . $filename . $width . $height
1372
            . $offset . $crop . $cropToFit . $fillToFit
1373
            . $crop_x . $crop_y . $upscale
1374
            . $quality . $filters . $sharpen . $emboss . $blur . $palette
1375
            . $optimize . $compress
1376
            . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
1377
            . $convolve . $copyStrat . $saveAs;
1378
1379
        return $this->setTarget($file, $base);
1380
    }
1381
1382
1383
1384
    /**
1385
     * Use cached version of image, if possible.
1386
     *
1387
     * @param boolean $useCache is default true, set to false to avoid using cached object.
1388
     *
1389
     * @return $this
1390
     */
1391
    public function useCacheIfPossible($useCache = true)
1392
    {
1393
        if ($useCache && is_readable($this->cacheFileName)) {
1394
            $fileTime   = filemtime($this->pathToImage);
1395
            $cacheTime  = filemtime($this->cacheFileName);
1396
1397
            if ($fileTime <= $cacheTime) {
1398
                if ($this->useCache) {
1399
                    if ($this->verbose) {
1400
                        $this->log("Use cached file.");
1401
                        $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
1402
                    }
1403
                    $this->output($this->cacheFileName, $this->outputFormat);
1404
                } else {
1405
                    $this->log("Cache is valid but ignoring it by intention.");
1406
                }
1407
            } else {
1408
                $this->log("Original file is modified, ignoring cache.");
1409
            }
1410
        } else {
1411
            $this->log("Cachefile does not exists or ignoring it.");
1412
        }
1413
1414
        return $this;
1415
    }
1416
1417
1418
1419
    /**
1420
     * Load image from disk. Try to load image without verbose error message,
1421
     * if fail, load again and display error messages.
1422
     *
1423
     * @param string $src of image.
1424
     * @param string $dir as base directory where images are.
1425
     *
1426
     * @return $this
1427
     *
1428
     */
1429
    public function load($src = null, $dir = null)
1430
    {
1431
        if (isset($src)) {
1432
            $this->setSource($src, $dir);
1433
        }
1434
1435
        $this->loadImageDetails($this->pathToImage);
1436
1437
        $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...
1438
        if ($this->image === false) {
1439
            throw new Exception("Could not load image.");
1440
        }
1441
1442
        /* 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...
1443
        if (image_type_to_mime_type($this->fileType) == 'image/png') {
1444
            $type = $this->getPngType();
1445
            $hasFewColors = imagecolorstotal($this->image);
1446
1447
            if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) {
1448
                if ($this->verbose) {
1449
                    $this->log("Handle this image as a palette image.");
1450
                }
1451
                $this->palette = true;
1452
            }
1453
        }
1454
        */
1455
1456
        if ($this->verbose) {
1457
            $this->log("### Image successfully loaded from file.");
1458
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
1459
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
1460
            $this->log(" Number of colors in image = " . $this->colorsTotal($this->image));
1461
            $index = imagecolortransparent($this->image);
1462
            $this->log(" Detected transparent color = " . ($index >= 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
1463
        }
1464
1465
        return $this;
1466
    }
1467
1468
1469
1470
    /**
1471
     * Get the type of PNG image.
1472
     *
1473
     * @param string $filename to use instead of default.
1474
     *
1475
     * @return int as the type of the png-image
1476
     *
1477
     */
1478
    public function getPngType($filename = null)
1479
    {
1480
        $filename = $filename ? $filename : $this->pathToImage;
1481
1482
        $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...
1483
1484
        if ($this->verbose) {
1485
            $this->log("Checking png type of: " . $filename);
1486
            $this->log($this->getPngTypeAsString($pngType));
1487
        }
1488
1489
        return $pngType;
1490
    }
1491
1492
1493
1494
    /**
1495
     * Get the type of PNG image as a verbose string.
1496
     *
1497
     * @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...
1498
     * @param string  $filename to use instead of default.
1499
     *
1500
     * @return int as the type of the png-image
1501
     *
1502
     */
1503
    private function getPngTypeAsString($pngType = null, $filename = null)
1504
    {
1505
        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...
1506
            $pngType = $this->getPngType($filename);
1507
        }
1508
1509
        $index = imagecolortransparent($this->image);
1510
        $transparent = null;
1511
        if ($index != -1) {
1512
            $transparent = " (transparent)";
1513
        }
1514
1515
        switch ($pngType) {
1516
1517
            case self::PNG_GREYSCALE:
1518
                $text = "PNG is type 0, Greyscale$transparent";
1519
                break;
1520
1521
            case self::PNG_RGB:
1522
                $text = "PNG is type 2, RGB$transparent";
1523
                break;
1524
1525
            case self::PNG_RGB_PALETTE:
1526
                $text = "PNG is type 3, RGB with palette$transparent";
1527
                break;
1528
1529
            case self::PNG_GREYSCALE_ALPHA:
1530
                $text = "PNG is type 4, Greyscale with alpha channel";
1531
                break;
1532
1533
            case self::PNG_RGB_ALPHA:
1534
                $text = "PNG is type 6, RGB with alpha channel (PNG 32-bit)";
1535
                break;
1536
1537
            default:
1538
                $text = "PNG is UNKNOWN type, is it really a PNG image?";
1539
        }
1540
1541
        return $text;
1542
    }
1543
1544
1545
1546
1547
    /**
1548
     * Calculate number of colors in an image.
1549
     *
1550
     * @param resource $im the image.
1551
     *
1552
     * @return int
1553
     */
1554
    private function colorsTotal($im)
1555
    {
1556
        if (imageistruecolor($im)) {
1557
            $this->log("Colors as true color.");
1558
            $h = imagesy($im);
1559
            $w = imagesx($im);
1560
            $c = array();
1561
            for ($x=0; $x < $w; $x++) {
1562
                for ($y=0; $y < $h; $y++) {
1563
                    @$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...
1564
                }
1565
            }
1566
            return count($c);
1567
        } else {
1568
            $this->log("Colors as palette.");
1569
            return imagecolorstotal($im);
1570
        }
1571
    }
1572
1573
1574
1575
    /**
1576
     * Preprocess image before rezising it.
1577
     *
1578
     * @return $this
1579
     */
1580
    public function preResize()
1581
    {
1582
        $this->log("### Pre-process before resizing");
1583
1584
        // Rotate image
1585
        if ($this->rotateBefore) {
1586
            $this->log("Rotating image.");
1587
            $this->rotate($this->rotateBefore, $this->bgColor)
1588
                 ->reCalculateDimensions();
1589
        }
1590
1591
        // Auto-rotate image
1592
        if ($this->autoRotate) {
1593
            $this->log("Auto rotating image.");
1594
            $this->rotateExif()
1595
                 ->reCalculateDimensions();
1596
        }
1597
1598
        // Scale the original image before starting
1599
        if (isset($this->scale)) {
1600
            $this->log("Scale by {$this->scale}%");
1601
            $newWidth  = $this->width * $this->scale / 100;
1602
            $newHeight = $this->height * $this->scale / 100;
1603
            $img = $this->CreateImageKeepTransparency($newWidth, $newHeight);
1604
            imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height);
1605
            $this->image = $img;
1606
            $this->width = $newWidth;
1607
            $this->height = $newHeight;
1608
        }
1609
1610
        return $this;
1611
    }
1612
1613
1614
1615
    /**
1616
     * Resize or resample the image while resizing.
1617
     *
1618
     * @param int $strategy as CImage::RESIZE or CImage::RESAMPLE
1619
     *
1620
     * @return $this
1621
     */
1622
     public function setCopyResizeStrategy($strategy)
1623
     {
1624
         $this->copyStrategy = $strategy;
1625
         return $this;
1626
     }
1627
1628
1629
1630
    /**
1631
     * Resize and or crop the image.
1632
     *
1633
     * @return void
1634
     */
1635
    public function imageCopyResampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
1636
    {
1637
        if($this->copyStrategy == self::RESIZE) {
1638
            $this->log("Copy by resize");
1639
            imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1640
        } else {
1641
            $this->log("Copy by resample");
1642
            imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1643
        }
1644
    }
1645
1646
1647
1648
    /**
1649
     * Resize and or crop the image.
1650
     *
1651
     * @return $this
1652
     */
1653
    public function resize()
1654
    {
1655
1656
        $this->log("### Starting to Resize()");
1657
        $this->log("Upscale = '$this->upscale'");
1658
1659
        // Only use a specified area of the image, $this->offset is defining the area to use
1660
        if (isset($this->offset)) {
1661
1662
            $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']}");
1663
            $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']);
1664
            imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']);
1665
            $this->image = $img;
1666
            $this->width = $this->offset['width'];
1667
            $this->height = $this->offset['height'];
1668
        }
1669
1670
        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...
1671
1672
            // Do as crop, take only part of image
1673
            $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}");
1674
            $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']);
1675
            imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']);
1676
            $this->image = $img;
1677
            $this->width = $this->crop['width'];
1678
            $this->height = $this->crop['height'];
1679
        }
1680
1681
        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...
1682
            // Consider rewriting the no-upscale code to fit within this if-statement,
1683
            // likely to be more readable code.
1684
            // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch
1685
        }
1686
1687
        if ($this->cropToFit) {
1688
1689
            // Resize by crop to fit
1690
            $this->log("Resizing using strategy - Crop to fit");
1691
1692
            if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) {
1693
                $this->log("Resizing - smaller image, do not upscale.");
1694
1695
                $cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1696
                $cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1697
1698
                $posX = 0;
1699
                $posY = 0;
1700
1701
                if ($this->newWidth > $this->width) {
1702
                    $posX = round(($this->newWidth - $this->width) / 2);
1703
                }
1704
1705
                if ($this->newHeight > $this->height) {
1706
                    $posY = round(($this->newHeight - $this->height) / 2);
1707
                }
1708
1709
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1710
                imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1711
            } else {
1712
                $cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1713
                $cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1714
                $imgPreCrop   = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight);
1715
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1716
                $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height);
1717
                imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight);
1718
            }
1719
1720
            $this->image = $imageResized;
1721
            $this->width = $this->newWidth;
1722
            $this->height = $this->newHeight;
1723
1724
        } elseif ($this->fillToFit) {
1725
1726
            // Resize by fill to fit
1727
            $this->log("Resizing using strategy - Fill to fit");
1728
1729
            $posX = 0;
1730
            $posY = 0;
1731
1732
            $ratioOrig = $this->width / $this->height;
1733
            $ratioNew  = $this->newWidth / $this->newHeight;
1734
1735
            // Check ratio for landscape or portrait
1736
            if ($ratioOrig < $ratioNew) {
1737
                $posX = round(($this->newWidth - $this->fillWidth) / 2);
1738
            } else {
1739
                $posY = round(($this->newHeight - $this->fillHeight) / 2);
1740
            }
1741
1742
            if (!$this->upscale
1743
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
1744
            ) {
1745
1746
                $this->log("Resizing - smaller image, do not upscale.");
1747
                $posX = round(($this->fillWidth - $this->width) / 2);
1748
                $posY = round(($this->fillHeight - $this->height) / 2);
1749
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1750
                imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1751
1752
            } else {
1753
                $imgPreFill   = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight);
1754
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1755
                $this->imageCopyResampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height);
1756
                imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1757
            }
1758
1759
            $this->image = $imageResized;
1760
            $this->width = $this->newWidth;
1761
            $this->height = $this->newHeight;
1762
1763
        } elseif (!($this->newWidth == $this->width && $this->newHeight == $this->height)) {
1764
1765
            // Resize it
1766
            $this->log("Resizing, new height and/or width");
1767
1768
            if (!$this->upscale
1769
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
1770
            ) {
1771
                $this->log("Resizing - smaller image, do not upscale.");
1772
1773
                if (!$this->keepRatio) {
1774
                    $this->log("Resizing - stretch to fit selected.");
1775
1776
                    $posX = 0;
1777
                    $posY = 0;
1778
                    $cropX = 0;
1779
                    $cropY = 0;
1780
1781
                    if ($this->newWidth > $this->width && $this->newHeight > $this->height) {
1782
                        $posX = round(($this->newWidth - $this->width) / 2);
1783
                        $posY = round(($this->newHeight - $this->height) / 2);
1784
                    } elseif ($this->newWidth > $this->width) {
1785
                        $posX = round(($this->newWidth - $this->width) / 2);
1786
                        $cropY = round(($this->height - $this->newHeight) / 2);
1787
                    } elseif ($this->newHeight > $this->height) {
1788
                        $posY = round(($this->newHeight - $this->height) / 2);
1789
                        $cropX = round(($this->width - $this->newWidth) / 2);
1790
                    }
1791
1792
                    //$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...
1793
                    $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1794
                    imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1795
                    $this->image = $imageResized;
1796
                    $this->width = $this->newWidth;
1797
                    $this->height = $this->newHeight;
1798
                }
1799
            } else {
1800
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1801
                $this->imageCopyResampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height);
1802
                $this->image = $imageResized;
1803
                $this->width = $this->newWidth;
1804
                $this->height = $this->newHeight;
1805
            }
1806
        }
1807
1808
        return $this;
1809
    }
1810
1811
1812
1813
    /**
1814
     * Postprocess image after rezising image.
1815
     *
1816
     * @return $this
1817
     */
1818
    public function postResize()
1819
    {
1820
        $this->log("### Post-process after resizing");
1821
1822
        // Rotate image
1823
        if ($this->rotateAfter) {
1824
            $this->log("Rotating image.");
1825
            $this->rotate($this->rotateAfter, $this->bgColor);
1826
        }
1827
1828
        // Apply filters
1829
        if (isset($this->filters) && is_array($this->filters)) {
1830
1831
            foreach ($this->filters as $filter) {
1832
                $this->log("Applying filter {$filter['type']}.");
1833
1834
                switch ($filter['argc']) {
1835
1836
                    case 0:
1837
                        imagefilter($this->image, $filter['type']);
1838
                        break;
1839
1840
                    case 1:
1841
                        imagefilter($this->image, $filter['type'], $filter['arg1']);
1842
                        break;
1843
1844
                    case 2:
1845
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']);
1846
                        break;
1847
1848
                    case 3:
1849
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']);
1850
                        break;
1851
1852
                    case 4:
1853
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']);
1854
                        break;
1855
                }
1856
            }
1857
        }
1858
1859
        // Convert to palette image
1860
        if ($this->palette) {
1861
            $this->log("Converting to palette image.");
1862
            $this->trueColorToPalette();
1863
        }
1864
1865
        // Blur the image
1866
        if ($this->blur) {
1867
            $this->log("Blur.");
1868
            $this->blurImage();
1869
        }
1870
1871
        // Emboss the image
1872
        if ($this->emboss) {
1873
            $this->log("Emboss.");
1874
            $this->embossImage();
1875
        }
1876
1877
        // Sharpen the image
1878
        if ($this->sharpen) {
1879
            $this->log("Sharpen.");
1880
            $this->sharpenImage();
1881
        }
1882
1883
        // Custom convolution
1884
        if ($this->convolve) {
1885
            //$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...
1886
            $this->imageConvolution();
1887
        }
1888
1889
        return $this;
1890
    }
1891
1892
1893
1894
    /**
1895
     * Rotate image using angle.
1896
     *
1897
     * @param float $angle        to rotate image.
1898
     * @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...
1899
     *
1900
     * @return $this
1901
     */
1902
    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...
1903
    {
1904
        $this->log("Rotate image " . $angle . " degrees with filler color.");
1905
1906
        $color = $this->getBackgroundColor();
1907
        $this->image = imagerotate($this->image, $angle, $color);
1908
1909
        $this->width  = imagesx($this->image);
1910
        $this->height = imagesy($this->image);
1911
1912
        $this->log("New image dimension width x height: " . $this->width . " x " . $this->height);
1913
1914
        return $this;
1915
    }
1916
1917
1918
1919
    /**
1920
     * Rotate image using information in EXIF.
1921
     *
1922
     * @return $this
1923
     */
1924
    public function rotateExif()
1925
    {
1926
        if (!in_array($this->fileType, array(IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM))) {
1927
            $this->log("Autorotate ignored, EXIF not supported by this filetype.");
1928
            return $this;
1929
        }
1930
1931
        $exif = exif_read_data($this->pathToImage);
1932
1933
        if (!empty($exif['Orientation'])) {
1934
            switch ($exif['Orientation']) {
1935
                case 3:
1936
                    $this->log("Autorotate 180.");
1937
                    $this->rotate(180, $this->bgColor);
1938
                    break;
1939
1940
                case 6:
1941
                    $this->log("Autorotate -90.");
1942
                    $this->rotate(-90, $this->bgColor);
1943
                    break;
1944
1945
                case 8:
1946
                    $this->log("Autorotate 90.");
1947
                    $this->rotate(90, $this->bgColor);
1948
                    break;
1949
1950
                default:
1951
                    $this->log("Autorotate ignored, unknown value as orientation.");
1952
            }
1953
        } else {
1954
            $this->log("Autorotate ignored, no orientation in EXIF.");
1955
        }
1956
1957
        return $this;
1958
    }
1959
1960
1961
1962
    /**
1963
     * Convert true color image to palette image, keeping alpha.
1964
     * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library
1965
     *
1966
     * @return void
1967
     */
1968
    public function trueColorToPalette()
1969
    {
1970
        $img = imagecreatetruecolor($this->width, $this->height);
1971
        $bga = imagecolorallocatealpha($img, 0, 0, 0, 127);
1972
        imagecolortransparent($img, $bga);
1973
        imagefill($img, 0, 0, $bga);
1974
        imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height);
1975
        imagetruecolortopalette($img, false, 255);
1976
        imagesavealpha($img, true);
1977
1978
        if (imageistruecolor($this->image)) {
1979
            $this->log("Matching colors with true color image.");
1980
            imagecolormatch($this->image, $img);
1981
        }
1982
1983
        $this->image = $img;
1984
    }
1985
1986
1987
1988
    /**
1989
     * Sharpen image using image convolution.
1990
     *
1991
     * @return $this
1992
     */
1993
    public function sharpenImage()
1994
    {
1995
        $this->imageConvolution('sharpen');
1996
        return $this;
1997
    }
1998
1999
2000
2001
    /**
2002
     * Emboss image using image convolution.
2003
     *
2004
     * @return $this
2005
     */
2006
    public function embossImage()
2007
    {
2008
        $this->imageConvolution('emboss');
2009
        return $this;
2010
    }
2011
2012
2013
2014
    /**
2015
     * Blur image using image convolution.
2016
     *
2017
     * @return $this
2018
     */
2019
    public function blurImage()
2020
    {
2021
        $this->imageConvolution('blur');
2022
        return $this;
2023
    }
2024
2025
2026
2027
    /**
2028
     * Create convolve expression and return arguments for image convolution.
2029
     *
2030
     * @param string $expression constant string which evaluates to a list of
2031
     *                           11 numbers separated by komma or such a list.
2032
     *
2033
     * @return array as $matrix (3x3), $divisor and $offset
2034
     */
2035
    public function createConvolveArguments($expression)
2036
    {
2037
        // Check of matching constant
2038
        if (isset($this->convolves[$expression])) {
2039
            $expression = $this->convolves[$expression];
2040
        }
2041
2042
        $part = explode(',', $expression);
2043
        $this->log("Creating convolution expressen: $expression");
2044
2045
        // Expect list of 11 numbers, split by , and build up arguments
2046
        if (count($part) != 11) {
2047
            throw new Exception(
2048
                "Missmatch in argument convolve. Expected comma-separated string with
2049
                11 float values. Got $expression."
2050
            );
2051
        }
2052
2053
        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...
2054
            if (!is_numeric($item)) {
2055
                throw new Exception("Argument to convolve expression should be float but is not.");
2056
            }
2057
        });
2058
2059
        return array(
2060
            array(
2061
                array($part[0], $part[1], $part[2]),
2062
                array($part[3], $part[4], $part[5]),
2063
                array($part[6], $part[7], $part[8]),
2064
            ),
2065
            $part[9],
2066
            $part[10],
2067
        );
2068
    }
2069
2070
2071
2072
    /**
2073
     * Add custom expressions (or overwrite existing) for image convolution.
2074
     *
2075
     * @param array $options Key value array with strings to be converted
2076
     *                       to convolution expressions.
2077
     *
2078
     * @return $this
2079
     */
2080
    public function addConvolveExpressions($options)
2081
    {
2082
        $this->convolves = array_merge($this->convolves, $options);
2083
        return $this;
2084
    }
2085
2086
2087
2088
    /**
2089
     * Image convolution.
2090
     *
2091
     * @param string $options A string with 11 float separated by comma.
2092
     *
2093
     * @return $this
2094
     */
2095
    public function imageConvolution($options = null)
2096
    {
2097
        // Use incoming options or use $this.
2098
        $options = $options ? $options : $this->convolve;
2099
2100
        // Treat incoming as string, split by +
2101
        $this->log("Convolution with '$options'");
2102
        $options = explode(":", $options);
2103
2104
        // Check each option if it matches constant value
2105
        foreach ($options as $option) {
2106
            list($matrix, $divisor, $offset) = $this->createConvolveArguments($option);
2107
            imageconvolution($this->image, $matrix, $divisor, $offset);
2108
        }
2109
2110
        return $this;
2111
    }
2112
2113
2114
2115
    /**
2116
     * Set default background color between 000000-FFFFFF or if using
2117
     * alpha 00000000-FFFFFF7F.
2118
     *
2119
     * @param string $color as hex value.
2120
     *
2121
     * @return $this
2122
    */
2123
    public function setDefaultBackgroundColor($color)
2124
    {
2125
        $this->log("Setting default background color to '$color'.");
2126
2127
        if (!(strlen($color) == 6 || strlen($color) == 8)) {
2128
            throw new Exception(
2129
                "Background color needs a hex value of 6 or 8
2130
                digits. 000000-FFFFFF or 00000000-FFFFFF7F.
2131
                Current value was: '$color'."
2132
            );
2133
        }
2134
2135
        $red    = hexdec(substr($color, 0, 2));
2136
        $green  = hexdec(substr($color, 2, 2));
2137
        $blue   = hexdec(substr($color, 4, 2));
2138
2139
        $alpha = (strlen($color) == 8)
2140
            ? hexdec(substr($color, 6, 2))
2141
            : null;
2142
2143
        if (($red < 0 || $red > 255)
2144
            || ($green < 0 || $green > 255)
2145
            || ($blue < 0 || $blue > 255)
2146
            || ($alpha < 0 || $alpha > 127)
2147
        ) {
2148
            throw new Exception(
2149
                "Background color out of range. Red, green blue
2150
                should be 00-FF and alpha should be 00-7F.
2151
                Current value was: '$color'."
2152
            );
2153
        }
2154
2155
        $this->bgColor = strtolower($color);
2156
        $this->bgColorDefault = array(
2157
            'red'   => $red,
2158
            'green' => $green,
2159
            'blue'  => $blue,
2160
            'alpha' => $alpha
2161
        );
2162
2163
        return $this;
2164
    }
2165
2166
2167
2168
    /**
2169
     * Get the background color.
2170
     *
2171
     * @param resource $img the image to work with or null if using $this->image.
2172
     *
2173
     * @return color value or null if no background color is set.
2174
    */
2175
    private function getBackgroundColor($img = null)
2176
    {
2177
        $img = isset($img) ? $img : $this->image;
2178
2179
        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...
2180
2181
            $red   = $this->bgColorDefault['red'];
2182
            $green = $this->bgColorDefault['green'];
2183
            $blue  = $this->bgColorDefault['blue'];
2184
            $alpha = $this->bgColorDefault['alpha'];
2185
2186
            if ($alpha) {
2187
                $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha);
2188
            } else {
2189
                $color = imagecolorallocate($img, $red, $green, $blue);
2190
            }
2191
2192
            return $color;
2193
2194
        } else {
2195
            return 0;
2196
        }
2197
    }
2198
2199
2200
2201
    /**
2202
     * Create a image and keep transparency for png and gifs.
2203
     *
2204
     * @param int $width of the new image.
2205
     * @param int $height of the new image.
2206
     *
2207
     * @return image resource.
2208
    */
2209
    private function createImageKeepTransparency($width, $height)
2210
    {
2211
        $this->log("Creating a new working image width={$width}px, height={$height}px.");
2212
        $img = imagecreatetruecolor($width, $height);
2213
        imagealphablending($img, false);
2214
        imagesavealpha($img, true);
2215
2216
        $index = $this->image
2217
            ? imagecolortransparent($this->image)
2218
            : -1;
2219
2220
        if ($index != -1) {
2221
2222
            imagealphablending($img, true);
2223
            $transparent = imagecolorsforindex($this->image, $index);
2224
            $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']);
2225
            imagefill($img, 0, 0, $color);
2226
            $index = imagecolortransparent($img, $color);
2227
            $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index");
2228
2229
        } 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...
2230
2231
            $color = $this->getBackgroundColor($img);
2232
            imagefill($img, 0, 0, $color);
2233
            $this->Log("Filling image with background color.");
2234
        }
2235
2236
        return $img;
2237
    }
2238
2239
2240
2241
    /**
2242
     * Set optimizing  and post-processing options.
2243
     *
2244
     * @param array $options with config for postprocessing with external tools.
2245
     *
2246
     * @return $this
2247
     */
2248
    public function setPostProcessingOptions($options)
2249
    {
2250
        if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) {
2251
            $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd'];
2252
        } else {
2253
            $this->jpegOptimizeCmd = null;
2254
        }
2255
2256
        if (isset($options['png_filter']) && $options['png_filter']) {
2257
            $this->pngFilterCmd = $options['png_filter_cmd'];
2258
        } else {
2259
            $this->pngFilterCmd = null;
2260
        }
2261
2262
        if (isset($options['png_deflate']) && $options['png_deflate']) {
2263
            $this->pngDeflateCmd = $options['png_deflate_cmd'];
2264
        } else {
2265
            $this->pngDeflateCmd = null;
2266
        }
2267
2268
        return $this;
2269
    }
2270
2271
2272
2273
    /**
2274
     * Find out the type (file extension) for the image to be saved.
2275
     *
2276
     * @return string as image extension.
2277
     */
2278
    protected function getTargetImageExtension()
2279
    {
2280
        // switch on mimetype
2281
        if (isset($this->extension)) {
2282
            return strtolower($this->extension);
2283
        } else {
2284
            return substr(image_type_to_extension($this->fileType), 1);
2285
        }
2286
    }
2287
2288
2289
2290
    /**
2291
     * Save image.
2292
     *
2293
     * @param string  $src       as target filename.
2294
     * @param string  $base      as base directory where to store images.
2295
     * @param boolean $overwrite or not, default to always overwrite file.
2296
     *
2297
     * @return $this or false if no folder is set.
2298
     */
2299
    public function save($src = null, $base = null, $overwrite = true)
2300
    {
2301
        if (isset($src)) {
2302
            $this->setTarget($src, $base);
2303
        }
2304
2305
        if ($overwrite === false && is_file($this->cacheFileName)) {
2306
            $this->Log("Not overwriting file since its already exists and \$overwrite if false.");
2307
            return;
2308
        }
2309
2310
        is_writable($this->saveFolder)
2311
            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...
2312
2313
        $type = $this->getTargetImageExtension();
2314
        $this->Log("Saving image as " . $type);
2315
        switch($type) {
2316
2317
            case 'jpeg':
2318
            case 'jpg':
2319
                $this->Log("Saving image as JPEG to cache using quality = {$this->quality}.");
2320
                imagejpeg($this->image, $this->cacheFileName, $this->quality);
2321
2322
                // Use JPEG optimize if defined
2323
                if ($this->jpegOptimizeCmd) {
2324
                    if ($this->verbose) {
2325
                        clearstatcache();
2326
                        $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes.");
2327
                    }
2328
                    $res = array();
2329
                    $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName";
2330
                    exec($cmd, $res);
2331
                    $this->log($cmd);
2332
                    $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...
2333
                }
2334
                break;
2335
2336
            case 'gif':
2337
                $this->Log("Saving image as GIF to cache.");
2338
                imagegif($this->image, $this->cacheFileName);
2339
                break;
2340
2341
            case 'png':
2342
            default:
2343
                $this->Log("Saving image as PNG to cache using compression = {$this->compress}.");
2344
2345
                // Turn off alpha blending and set alpha flag
2346
                imagealphablending($this->image, false);
2347
                imagesavealpha($this->image, true);
2348
                imagepng($this->image, $this->cacheFileName, $this->compress);
2349
2350
                // Use external program to filter PNG, if defined
2351
                if ($this->pngFilterCmd) {
2352
                    if ($this->verbose) {
2353
                        clearstatcache();
2354
                        $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes.");
2355
                    }
2356
                    $res = array();
2357
                    $cmd = $this->pngFilterCmd . " $this->cacheFileName";
2358
                    exec($cmd, $res);
2359
                    $this->Log($cmd);
2360
                    $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...
2361
                }
2362
2363
                // Use external program to deflate PNG, if defined
2364
                if ($this->pngDeflateCmd) {
2365
                    if ($this->verbose) {
2366
                        clearstatcache();
2367
                        $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes.");
2368
                    }
2369
                    $res = array();
2370
                    $cmd = $this->pngDeflateCmd . " $this->cacheFileName";
2371
                    exec($cmd, $res);
2372
                    $this->Log($cmd);
2373
                    $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...
2374
                }
2375
                break;
2376
        }
2377
2378
        if ($this->verbose) {
2379
            clearstatcache();
2380
            $this->log("Saved image to cache.");
2381
            $this->log(" Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
2382
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
2383
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
2384
            $this->log(" Number of colors in image = " . $this->ColorsTotal($this->image));
2385
            $index = imagecolortransparent($this->image);
2386
            $this->log(" Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
2387
        }
2388
2389
        return $this;
2390
    }
2391
2392
2393
2394
    /**
2395
     * Convert image from one colorpsace/color profile to sRGB without
2396
     * color profile.
2397
     *
2398
     * @param string  $src      of image.
2399
     * @param string  $dir      as base directory where images are.
2400
     * @param string  $cache    as base directory where to store images.
2401
     * @param string  $iccFile  filename of colorprofile.
2402
     * @param boolean $useCache or not, default to always use cache.
2403
     *
2404
     * @return string | boolean false if no conversion else the converted
2405
     *                          filename.
2406
     */
2407
    public function convert2sRGBColorSpace($src, $dir, $cache, $iccFile, $useCache = true)
2408
    {
2409
        if ($this->verbose) {
2410
            $this->log("# Converting image to sRGB colorspace.");
2411
        }
2412
2413
        if (!class_exists("Imagick")) {
2414
            $this->log(" Ignoring since Imagemagick is not installed.");
2415
            return false;
2416
        }
2417
2418
        // Prepare
2419
        $this->setSaveFolder($cache)
2420
             ->setSource($src, $dir)
2421
             ->generateFilename(null, false, 'srgb_');
2422
2423
        // Check if the cached version is accurate.
2424
        if ($useCache && is_readable($this->cacheFileName)) {
2425
            $fileTime  = filemtime($this->pathToImage);
2426
            $cacheTime = filemtime($this->cacheFileName);
2427
2428
            if ($fileTime <= $cacheTime) {
2429
                $this->log(" Using cached version: " . $this->cacheFileName);
2430
                return $this->cacheFileName;
2431
            }
2432
        }
2433
2434
        // Only covert if cachedir is writable
2435
        if (is_writable($this->saveFolder)) {
2436
            // Load file and check if conversion is needed
2437
            $image      = new Imagick($this->pathToImage);
2438
            $colorspace = $image->getImageColorspace();
2439
            $this->log(" Current colorspace: " . $colorspace);
2440
2441
            $profiles      = $image->getImageProfiles('*', false);
2442
            $hasICCProfile = (array_search('icc', $profiles) !== false);
2443
            $this->log(" Has ICC color profile: " . ($hasICCProfile ? "YES" : "NO"));
2444
2445
            if ($colorspace != Imagick::COLORSPACE_SRGB || $hasICCProfile) {
2446
                $this->log(" Converting to sRGB.");
2447
2448
                $sRGBicc = file_get_contents($iccFile);
2449
                $image->profileImage('icc', $sRGBicc);
2450
2451
                $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...
2452
                $image->writeImage($this->cacheFileName);
2453
                return $this->cacheFileName;
2454
            }
2455
        }
2456
2457
        return false;
2458
    }
2459
2460
2461
2462
    /**
2463
     * Create a hard link, as an alias, to the cached file.
2464
     *
2465
     * @param string $alias where to store the link,
2466
     *                      filename without extension.
2467
     *
2468
     * @return $this
2469
     */
2470
    public function linkToCacheFile($alias)
2471
    {
2472
        if ($alias === null) {
2473
            $this->log("Ignore creating alias.");
2474
            return $this;
2475
        }
2476
2477
        if (is_readable($alias)) {
2478
            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...
2479
        }
2480
2481
        $res = link($this->cacheFileName, $alias);
2482
2483
        if ($res) {
2484
            $this->log("Created an alias as: $alias");
2485
        } else {
2486
            $this->log("Failed to create the alias: $alias");
2487
        }
2488
2489
        return $this;
2490
    }
2491
2492
2493
2494
    /**
2495
     * Add HTTP header for putputting together with image.
2496
     *
2497
     * @param string $type  the header type such as "Cache-Control"
2498
     * @param string $value the value to use
2499
     *
2500
     * @return void
2501
     */
2502
    public function addHTTPHeader($type, $value)
2503
    {
2504
        $this->HTTPHeader[$type] = $value;
2505
    }
2506
2507
2508
2509
    /**
2510
     * Output image to browser using caching.
2511
     *
2512
     * @param string $file   to read and output, default is to
2513
     *                       use $this->cacheFileName
2514
     * @param string $format set to json to output file as json
2515
     *                       object with details
2516
     *
2517
     * @return void
2518
     */
2519
    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...
2520
    {
2521
        if (is_null($file)) {
2522
            $file = $this->cacheFileName;
2523
        }
2524
2525
        if (is_null($format)) {
2526
            $format = $this->outputFormat;
2527
        }
2528
2529
        $this->log("Output format is: $format");
2530
2531
        if (!$this->verbose && $format == 'json') {
2532
            header('Content-type: application/json');
2533
            echo $this->json($file);
2534
            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...
2535
        } elseif ($format == 'ascii') {
2536
            header('Content-type: text/plain');
2537
            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...
2538
            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...
2539
        }
2540
2541
        $this->log("Outputting image: $file");
2542
2543
        // Get image modification time
2544
        clearstatcache();
2545
        $lastModified = filemtime($file);
2546
        $gmdate = gmdate("D, d M Y H:i:s", $lastModified);
2547
2548
        if (!$this->verbose) {
2549
            header('Last-Modified: ' . $gmdate . " GMT");
2550
        }
2551
2552
        foreach($this->HTTPHeader as $key => $val) {
2553
            header("$key: $val");
2554
        }
2555
2556
        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) {
2557
2558
            if ($this->verbose) {
2559
                $this->log("304 not modified");
2560
                $this->verboseOutput();
2561
                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...
2562
            }
2563
2564
            header("HTTP/1.0 304 Not Modified");
2565
2566
        } else {
2567
2568
            // Get details on image
2569
            $info = getimagesize($file);
2570
            !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...
2571
            $mime = $info['mime'];
2572
            $size = filesize($file);
2573
2574
            if ($this->verbose) {
2575
                $this->log("Last-Modified: " . $gmdate . " GMT");
2576
                $this->log("Content-type: " . $mime);
2577
                $this->log("Content-length: " . $size);
2578
                $this->verboseOutput();
2579
2580
                if (is_null($this->verboseFileName)) {
2581
                    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...
2582
                }
2583
            }
2584
2585
            header("Content-type: $mime");
2586
            header("Content-length: $size");
2587
            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...
2588
        }
2589
2590
        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...
2591
    }
2592
2593
2594
2595
    /**
2596
     * Create a JSON object from the image details.
2597
     *
2598
     * @param string $file the file to output.
2599
     *
2600
     * @return string json-encoded representation of the image.
2601
     */
2602
    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...
2603
    {
2604
        $file = $file ? $file : $this->cacheFileName;
2605
2606
        $details = array();
2607
2608
        clearstatcache();
2609
2610
        $details['src']       = $this->imageSrc;
2611
        $lastModified         = filemtime($this->pathToImage);
2612
        $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2613
2614
        $details['cache']       = basename($this->cacheFileName);
2615
        $lastModified           = filemtime($this->cacheFileName);
2616
        $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2617
2618
        $this->load($file);
2619
2620
        $details['filename']    = basename($file);
2621
        $details['mimeType']    = image_type_to_mime_type($this->fileType);
2622
        $details['width']       = $this->width;
2623
        $details['height']      = $this->height;
2624
        $details['aspectRatio'] = round($this->width / $this->height, 3);
2625
        $details['size']        = filesize($file);
2626
        $details['colors'] = $this->colorsTotal($this->image);
2627
        $details['includedFiles'] = count(get_included_files());
2628
        $details['memoryPeek'] = round(memory_get_peak_usage()/1024/1024, 3) . " MB" ;
2629
        $details['memoryCurrent'] = round(memory_get_usage()/1024/1024, 3) . " MB";
2630
        $details['memoryLimit'] = ini_get('memory_limit');
2631
2632
        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
2633
            $details['loadTime'] = (string) round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']), 3) . "s";
2634
        }
2635
2636
        if ($details['mimeType'] == 'image/png') {
2637
            $details['pngType'] = $this->getPngTypeAsString(null, $file);
2638
        }
2639
2640
        $options = null;
2641
        if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) {
2642
            $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES;
2643
        }
2644
2645
        return json_encode($details, $options);
2646
    }
2647
2648
2649
2650
    /**
2651
     * Set options for creating ascii version of image.
2652
     *
2653
     * @param array $options empty to use default or set options to change.
2654
     *
2655
     * @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...
2656
     */
2657
    public function setAsciiOptions($options = array())
2658
    {
2659
        $this->asciiOptions = $options;
2660
    }
2661
2662
2663
2664
    /**
2665
     * Create an ASCII version from the image details.
2666
     *
2667
     * @param string $file the file to output.
2668
     *
2669
     * @return string ASCII representation of the image.
2670
     */
2671
    public function ascii($file = null)
2672
    {
2673
        $file = $file ? $file : $this->cacheFileName;
2674
2675
        $asciiArt = new CAsciiArt();
2676
        $asciiArt->setOptions($this->asciiOptions);
2677
        return $asciiArt->createFromFile($file);
2678
    }
2679
2680
2681
2682
    /**
2683
     * Log an event if verbose mode.
2684
     *
2685
     * @param string $message to log.
2686
     *
2687
     * @return this
2688
     */
2689
    public function log($message)
2690
    {
2691
        if ($this->verbose) {
2692
            $this->log[] = $message;
2693
        }
2694
2695
        return $this;
2696
    }
2697
2698
2699
2700
    /**
2701
     * Do verbose output to a file.
2702
     *
2703
     * @param string $fileName where to write the verbose output.
2704
     *
2705
     * @return void
2706
     */
2707
    public function setVerboseToFile($fileName)
2708
    {
2709
        $this->log("Setting verbose output to file.");
2710
        $this->verboseFileName = $fileName;
2711
    }
2712
2713
2714
2715
    /**
2716
     * Do verbose output and print out the log and the actual images.
2717
     *
2718
     * @return void
2719
     */
2720
    private function verboseOutput()
2721
    {
2722
        $log = null;
2723
        $this->log("As JSON: \n" . $this->json());
2724
        $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M");
2725
        $this->log("Memory limit: " . ini_get('memory_limit'));
2726
2727
        $included = get_included_files();
2728
        $this->log("Included files: " . count($included));
2729
2730
        foreach ($this->log as $val) {
2731
            if (is_array($val)) {
2732
                foreach ($val as $val1) {
2733
                    $log .= htmlentities($val1) . '<br/>';
2734
                }
2735
            } else {
2736
                $log .= htmlentities($val) . '<br/>';
2737
            }
2738
        }
2739
2740
        if (!is_null($this->verboseFileName)) {
2741
            file_put_contents(
2742
                $this->verboseFileName,
2743
                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...
2744
            );
2745
        } else {
2746
            echo <<<EOD
2747
<h1>CImage Verbose Output</h1>
2748
<pre>{$log}</pre>
2749
EOD;
2750
        }
2751
    }
2752
2753
2754
2755
    /**
2756
     * Raise error, enables to implement a selection of error methods.
2757
     *
2758
     * @param string $message the error message to display.
2759
     *
2760
     * @return void
2761
     * @throws Exception
2762
     */
2763
    private function raiseError($message)
2764
    {
2765
        throw new Exception($message);
2766
    }
2767
}
2768