Completed
Push — master ( 9ee3be...a39ae4 )
by Damian
20s
created

GDBackend::checkAvailableMemory()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
nc 5
nop 1
dl 0
loc 19
rs 9.2
c 1
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Assets;
4
5
use SilverStripe\Assets\Storage\AssetContainer;
6
use SilverStripe\Assets\Storage\AssetStore;
7
use SilverStripe\Core\Cache;
8
use SilverStripe\Core\Object;
9
use SilverStripe\Core\Flushable;
10
use InvalidArgumentException;
11
use Zend_Cache;
12
use Zend_Cache_Core;
13
14
/**
15
 * A wrapper class for GD-based images, with lots of manipulation functions.
16
 */
17
class GDBackend extends Object implements Image_Backend, Flushable
18
{
19
20
    /**
21
     * GD Resource
22
     *
23
     * @var resource
24
     */
25
    protected $gd;
26
27
    /**
28
     * @var Zend_Cache_Core
29
     */
30
    protected $cache;
31
32
    /**
33
     * @var int
34
     */
35
    protected $width;
36
37
38
    /**
39
     * @var int
40
     */
41
    protected $height;
42
43
    /**
44
     * @var int
45
     */
46
    protected $quality;
47
48
    /**
49
     *
50
     * @var int
51
     */
52
    protected $interlace;
53
54
    /**
55
     * @config
56
     * @var integer
57
     */
58
    private static $default_quality = 75;
59
60
    /**
61
     * @config
62
     * @var integer
63
     */
64
    private static $image_interlace = 0;
65
66
    public function __construct(AssetContainer $assetContainer = null)
67
    {
68
        parent::__construct();
69
        $this->cache = Cache::factory('GDBackend_Manipulations');
70
71
        if ($assetContainer) {
72
            $this->loadFromContainer($assetContainer);
73
        }
74
    }
75
76
    public function __destruct()
77
    {
78
        if ($resource = $this->getImageResource()) {
79
            imagedestroy($resource);
80
        }
81
    }
82
83
    public function loadFrom($path)
84
    {
85
        // If we're working with image resampling, things could take a while.  Bump up the time-limit
86
        increase_time_limit_to(300);
87
        $this->resetResource();
88
89
        // Skip if path is unavailable
90
        if (!file_exists($path)) {
91
            return;
92
        }
93
        $mtime = filemtime($path);
94
95
        // Skip if load failed before
96
        if ($this->failedResample($path, $mtime)) {
97
            return;
98
        }
99
100
        // We use getimagesize instead of extension checking, because sometimes extensions are wrong.
101
        $meta = getimagesize($path);
102
        if ($meta === false || !$this->checkAvailableMemory($meta)) {
103
            $this->markFailed($path, $mtime);
104
            return;
105
        }
106
107
        $gd = null;
108
        switch ($meta[2]) {
109
            case 1:
110
                if (function_exists('imagecreatefromgif')) {
111
                    $gd = imagecreatefromgif($path);
112
                }
113
                break;
114
            case 2:
115
                if (function_exists('imagecreatefromjpeg')) {
116
                    $gd = imagecreatefromjpeg($path);
117
                }
118
                break;
119
            case 3:
120
                if (function_exists('imagecreatefrompng')) {
121
                    $gd = imagecreatefrompng($path);
122
                    if ($gd) {
123
                        imagesavealpha($gd, true); // save alphablending setting (important)
124
                    }
125
                }
126
                break;
127
        }
128
129
        // image failed
130
        if ($gd === false) {
131
            $this->markFailed($path, $mtime);
132
            return;
133
        }
134
135
        // Save
136
        $this->setImageResource($gd);
137
    }
138
139
    public function loadFromContainer(AssetContainer $assetContainer)
140
    {
141
        // If we're working with image resampling, things could take a while.  Bump up the time-limit
142
        increase_time_limit_to(300);
143
        $this->resetResource();
144
145
        // Skip non-existant files
146
        if (!$assetContainer->exists()) {
147
            return;
148
        }
149
150
        // Skip if failed before, or image is too large
151
        $filename = $assetContainer->getFilename();
152
        $hash = $assetContainer->getHash();
153
        $variant = $assetContainer->getVariant();
154
        if ($this->failedResample($filename, $hash, $variant)) {
155
            return;
156
        }
157
158
        $content = $assetContainer->getString();
159
160
        // We use getimagesizefromstring instead of extension checking, because sometimes extensions are wrong.
161
        $meta = getimagesizefromstring($content);
162
        if ($meta === false || !$this->checkAvailableMemory($meta)) {
163
            $this->markFailed($filename, $hash, $variant);
164
            return;
165
        }
166
167
        // Mark as potentially failed prior to creation, resetting this on success
168
        $image = imagecreatefromstring($content);
169
        if ($image === false) {
170
            $this->markFailed($filename, $hash, $variant);
171
            return;
172
        }
173
174
        imagealphablending($image, false);
175
        imagesavealpha($image, true); // save alphablending setting (important)
176
        $this->setImageResource($image);
177
    }
178
179
    /**
180
     * Clear GD resource
181
     */
182
    protected function resetResource()
183
    {
184
        // Set defaults and clear resource
185
        $this->setImageResource(null);
186
        $this->quality = $this->config()->default_quality;
0 ignored issues
show
Documentation introduced by
The property default_quality does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
187
        $this->interlace = $this->config()->image_interlace;
0 ignored issues
show
Documentation introduced by
The property image_interlace does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
188
    }
189
190
    /**
191
     * Assign or clear GD resource
192
     *
193
     * @param resource|null $resource
194
     */
195
    public function setImageResource($resource)
196
    {
197
        $this->gd = $resource;
198
        $this->width = $resource ? imagesx($resource) : 0;
199
        $this->height = $resource ? imagesy($resource) : 0;
200
    }
201
202
    /**
203
     * Get the currently assigned GD resource
204
     *
205
     * @return resource
206
     */
207
    public function getImageResource()
208
    {
209
        return $this->gd;
210
    }
211
212
    /**
213
     * Check if this image has previously crashed GD when attempting to open it - if it's opened
214
     * successfully, the manipulation's cache key is removed.
215
     *
216
     * @param string $arg,... Any number of args that identify this image
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,.... 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...
217
     * @return bool True if failed
218
     */
219
    public function failedResample($arg = null)
0 ignored issues
show
Unused Code introduced by
The parameter $arg 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...
220
    {
221
        $key = sha1(implode('|', func_get_args()));
222
        return (bool)$this->cache->load($key);
223
    }
224
225
    /**
226
     * Check if we've got enough memory available for resampling this image. This check is rough,
227
     * so it will not catch all images that are too large - it also won't work accurately on large,
228
     * animated GIFs as bits per pixel can't be calculated for an animated GIF with a global color
229
     * table.
230
     *
231
     * @param array $imageInfo Value from getimagesize() or getimagesizefromstring()
232
     * @return boolean
233
     */
234
    protected function checkAvailableMemory($imageInfo)
235
    {
236
        $limit = translate_memstring(ini_get('memory_limit'));
237
        if ($limit < 0) {
238
            return true; // memory_limit == -1
239
        }
240
241
        // bits per channel (rounded up, default to 1)
242
        $bits = isset($imageInfo['bits']) ? ($imageInfo['bits'] + 7) / 8 : 1;
243
244
        // channels (default 4 rgba)
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
245
        $channels = isset($imageInfo['channels']) ? $imageInfo['channels'] : 4;
246
        $bytesPerPixel = $bits * $channels;
247
248
        // width * height * bytes per pixel
249
        $memoryRequired = $imageInfo[0] * $imageInfo[1] * $bytesPerPixel;
250
251
        return $memoryRequired + memory_get_usage() < $limit;
252
    }
253
254
    /**
255
     * Mark a file as failed
256
     *
257
     * @param string $arg,... Any number of args that identify this image
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,.... 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...
258
     */
259
    protected function markFailed($arg = null)
0 ignored issues
show
Unused Code introduced by
The parameter $arg 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...
260
    {
261
        $key = sha1(implode('|', func_get_args()));
262
        $this->cache->save('1', $key);
263
    }
264
265
    /**
266
     * Mark a file as succeeded
267
     *
268
     * @param string $arg,... Any number of args that identify this image
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,.... 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...
269
     */
270
    protected function markSucceeded($arg = null)
0 ignored issues
show
Unused Code introduced by
The parameter $arg 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...
271
    {
272
        $key = sha1(implode('|', func_get_args()));
273
        $this->cache->save('0', $key);
274
    }
275
276
277
    public function setQuality($quality)
278
    {
279
        $this->quality = $quality;
280
    }
281
282
    public function croppedResize($width, $height)
283
    {
284
        if (!$this->gd) {
285
            return null;
286
        }
287
288
        $width = round($width);
289
        $height = round($height);
290
291
        // Check that a resize is actually necessary.
292
        if ($width == $this->width && $height == $this->height) {
293
            return $this;
294
        }
295
296
        $newGD = imagecreatetruecolor($width, $height);
297
298
        // Preserves transparency between images
299
        imagealphablending($newGD, false);
300
        imagesavealpha($newGD, true);
301
302
        $destAR = $width / $height;
303
        if ($this->width > 0 && $this->height > 0) {
304
            // We can't divide by zero theres something wrong.
305
306
            $srcAR = $this->width / $this->height;
307
308
            // Destination narrower than the source
309
            if ($destAR < $srcAR) {
310
                $srcY = 0;
311
                $srcHeight = $this->height;
312
313
                $srcWidth = round($this->height * $destAR);
314
                $srcX = round(($this->width - $srcWidth) / 2);
315
316
            // Destination shorter than the source
317
            } else {
318
                $srcX = 0;
319
                $srcWidth = $this->width;
320
321
                $srcHeight = round($this->width / $destAR);
322
                $srcY = round(($this->height - $srcHeight) / 2);
323
            }
324
325
            imagecopyresampled($newGD, $this->gd, 0, 0, $srcX, $srcY, $width, $height, $srcWidth, $srcHeight);
326
        }
327
        $output = clone $this;
328
        $output->setImageResource($newGD);
329
        return $output;
330
    }
331
332
    /**
333
     * Resizes the image to fit within the given region.
334
     * Behaves similarly to paddedResize but without the padding.
335
     * @todo This method isn't very efficent
336
     *
337
     * @param int $width
338
     * @param int $height
339
     * @return static
340
     */
341
    public function fittedResize($width, $height)
342
    {
343
        $gd = $this->resizeByHeight($height);
344
        if ($gd->width > $width) {
345
            $gd = $gd->resizeByWidth($width);
346
        }
347
        return $gd;
348
    }
349
350
    /**
351
     * @param int $width
352
     * @param int $height
353
     * @return static
354
     */
355
    public function resize($width, $height)
356
    {
357
        if (!$this->gd) {
358
            return null;
359
        }
360
361
        if ($width < 0 || $height < 0) {
362
            throw new InvalidArgumentException("Image resizing dimensions cannot be negative");
363
        }
364
        if (!$width && !$height) {
365
            throw new InvalidArgumentException("No dimensions given when resizing image");
366
        }
367
        if (!$width) {
368
            throw new InvalidArgumentException("Width not given when resizing image");
369
        }
370
        if (!$height) {
371
            throw new InvalidArgumentException("Height not given when resizing image");
372
        }
373
374
        //use whole numbers, ensuring that size is at least 1x1
375
        $width = max(1, round($width));
376
        $height = max(1, round($height));
377
378
        // Check that a resize is actually necessary.
379
        if ($width == $this->width && $height == $this->height) {
380
            return $this;
381
        }
382
383
384
        $newGD = imagecreatetruecolor($width, $height);
385
386
        // Preserves transparency between images
387
        imagealphablending($newGD, false);
388
        imagesavealpha($newGD, true);
389
390
        imagecopyresampled($newGD, $this->gd, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
391
392
        $output = clone $this;
393
        $output->setImageResource($newGD);
394
        return $output;
395
    }
396
397
    /**
398
     * Rotates image by given angle.
399
     *
400
     * @param float $angle Angle in degrees
401
     * @return static
402
     */
403
    public function rotate($angle)
404
    {
405
        if (!$this->gd) {
406
            return null;
407
        }
408
409
        if (function_exists("imagerotate")) {
410
            $newGD = imagerotate($this->gd, $angle, 0);
411
        } else {
412
            //imagerotate is not included in PHP included in Ubuntu
413
            $newGD = $this->rotatePixelByPixel($angle);
414
        }
415
        $output = clone $this;
416
        $output->setImageResource($newGD);
417
        return $output;
418
    }
419
420
    /**
421
     * Rotates image by given angle. It's slow because makes it pixel by pixel rather than
422
     * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu)
423
     *
424
     * @param float $angle Angle in degrees
425
     * @return static
426
     */
427
    public function rotatePixelByPixel($angle)
428
    {
429
        if (!$this->gd) {
430
            return null;
431
        }
432
        $sourceWidth = imagesx($this->gd);
433
        $sourceHeight = imagesy($this->gd);
434
        if ($angle == 180) {
435
            $destWidth = $sourceWidth;
436
            $destHeight = $sourceHeight;
437
        } else {
438
            $destWidth = $sourceHeight;
439
            $destHeight = $sourceWidth;
440
        }
441
        $rotate=imagecreatetruecolor($destWidth, $destHeight);
442
        imagealphablending($rotate, false);
443
        for ($x = 0; $x < ($sourceWidth); $x++) {
444
            for ($y = 0; $y < ($sourceHeight); $y++) {
445
                $color = imagecolorat($this->gd, $x, $y);
446
                switch ($angle) {
447
                    case 90:
448
                        imagesetpixel($rotate, $y, $destHeight - $x - 1, $color);
449
                        break;
450
                    case 180:
451
                        imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color);
452
                        break;
453
                    case 270:
454
                        imagesetpixel($rotate, $destWidth - $y - 1, $x, $color);
455
                        break;
456
                    default:
457
                        $rotate = $this->gd;
458
                };
459
            }
460
        }
461
        return $rotate;
462
    }
463
464
465
    /**
466
     * Crop's part of image.
467
     *
468
     * @param int $top y position of left upper corner of crop rectangle
469
     * @param int $left x position of left upper corner of crop rectangle
470
     * @param int $width rectangle width
471
     * @param int $height rectangle height
472
     * @return static
473
     */
474
    public function crop($top, $left, $width, $height)
475
    {
476
        if (!$this->gd) {
477
            return null;
478
        }
479
480
        $newGD = imagecreatetruecolor($width, $height);
481
482
        // Preserve alpha channel between images
483
        imagealphablending($newGD, false);
484
        imagesavealpha($newGD, true);
485
486
        imagecopyresampled($newGD, $this->gd, 0, 0, $left, $top, $width, $height, $width, $height);
487
488
        $output = clone $this;
489
        $output->setImageResource($newGD);
490
        return $output;
491
    }
492
493
    /**
494
     * Width of image.
495
     *
496
     * @return int
497
     */
498
    public function getWidth()
499
    {
500
        return $this->width;
501
    }
502
503
    /**
504
     * Height of image.
505
     *
506
     * @return int
507
     */
508
    public function getHeight()
509
    {
510
        return $this->height;
511
    }
512
513
    public function resizeByWidth($width)
514
    {
515
        $heightScale = $width / $this->width;
516
        return $this->resize($width, $heightScale * $this->height);
517
    }
518
519
    /**
520
     * @param int $height
521
     * @return static
522
     */
523
    public function resizeByHeight($height)
524
    {
525
        $scale = $height / $this->height;
526
        return $this->resize($scale * $this->width, $height);
527
    }
528
529
    public function resizeRatio($maxWidth, $maxHeight, $useAsMinimum = false)
530
    {
531
        $widthRatio = $maxWidth / $this->width;
532
        $heightRatio = $maxHeight / $this->height;
533
534
        if ($widthRatio < $heightRatio) {
535
            return $useAsMinimum
536
                ? $this->resizeByHeight($maxHeight)
537
                : $this->resizeByWidth($maxWidth);
538
        } else {
539
            return $useAsMinimum
540
                ? $this->resizeByWidth($maxWidth)
541
                : $this->resizeByHeight($maxHeight);
542
        }
543
    }
544
545
    public function paddedResize($width, $height, $backgroundColor = "FFFFFF")
546
    {
547
        if (!$this->gd) {
548
            return null;
549
        }
550
        $width = round($width);
551
        $height = round($height);
552
553
        // Check that a resize is actually necessary.
554
        if ($width == $this->width && $height == $this->height) {
555
            return $this;
556
        }
557
558
        $newGD = imagecreatetruecolor($width, $height);
559
560
        // Preserves transparency between images
561
        imagealphablending($newGD, false);
562
        imagesavealpha($newGD, true);
563
564
        $bg = $this->colourWeb2GD($newGD, $backgroundColor);
565
        imagefilledrectangle($newGD, 0, 0, $width, $height, $bg);
566
567
        $destAR = $width / $height;
568
        if ($this->width > 0 && $this->height > 0) {
569
            // We can't divide by zero theres something wrong.
570
571
            $srcAR = $this->width / $this->height;
572
573
            // Destination narrower than the source
574
            if ($destAR > $srcAR) {
575
                $destY = 0;
576
                $destHeight = $height;
577
578
                $destWidth = round($height * $srcAR);
579
                $destX = round(($width - $destWidth) / 2);
580
581
            // Destination shorter than the source
582
            } else {
583
                $destX = 0;
584
                $destWidth = $width;
585
586
                $destHeight = round($width / $srcAR);
587
                $destY = round(($height - $destHeight) / 2);
588
            }
589
590
            imagecopyresampled(
591
                $newGD,
592
                $this->gd,
593
                $destX,
594
                $destY,
595
                0,
596
                0,
597
                $destWidth,
598
                $destHeight,
599
                $this->width,
600
                $this->height
601
            );
602
        }
603
        $output = clone $this;
604
        $output->setImageResource($newGD);
605
        return $output;
606
    }
607
608
    /**
609
     * Make the image greyscale.
610
     * Default color weights are based on standard BT.601 (those used in PAL, NTSC and many software packages, also see
611
     * https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems )
612
     *
613
     * @param int $R red weight, defaults to 299
614
     * @param int $G green weight, defaults to 587
615
     * @param int $B blue weight, defaults to 114
616
     * @param int $brightness brightness in percentage, defaults to 100
617
     * @return GDBackend
618
     */
619
    public function greyscale($R = 299, $G = 587, $B = 114, $brightness = 100)
620
    {
621
        if (!$this->gd) {
622
            return null;
623
        }
624
625
        $width = $this->width;
626
        $height = $this->height;
627
        $newGD = imagecreatetruecolor($this->width, $this->height);
628
629
        // Preserves transparency between images
630
        imagealphablending($newGD, false);
631
        imagesavealpha($newGD, true);
632
633
        $rt = $R + $G + $B;
634
        // if $rt is 0, bad parameters are provided, so result will be a black image
635
        $rr = $rt ? $R/$rt : 0;
636
        $gr = $rt ? $G/$rt : 0;
637
        $br = $rt ? $B/$rt : 0;
638
        // iterate over all pixels and make them grey
639
        for ($dy = 0; $dy < $height; $dy++) {
640
            for ($dx = 0; $dx < $width; $dx++) {
641
                $pxrgb = imagecolorat($this->gd, $dx, $dy);
642
                $heightgb = imagecolorsforindex($this->gd, $pxrgb);
643
                $newcol = ($rr*$heightgb['red']) + ($br*$heightgb['blue']) + ($gr*$heightgb['green']);
644
                $newcol = min(255, $newcol*$brightness/100);
645
                $setcol = imagecolorallocatealpha($newGD, $newcol, $newcol, $newcol, $heightgb['alpha']);
646
                imagesetpixel($newGD, $dx, $dy, $setcol);
647
            }
648
        }
649
650
        $output = clone $this;
651
        $output->setImageResource($newGD);
652
        return $output;
653
    }
654
655
    public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = array())
656
    {
657
        // Write to temporary file, taking care to maintain the extension
658
        $path = tempnam(sys_get_temp_dir(), 'gd');
659
        if ($extension = pathinfo($filename, PATHINFO_EXTENSION)) {
660
            $path .= "." . $extension;
661
        }
662
663
        $writeSuccess = $this->writeTo($path);
664
        if (!$writeSuccess) {
665
            return null;
666
        }
667
668
        $result = $assetStore->setFromLocalFile($path, $filename, $hash, $variant, $config);
669
        unlink($path);
670
671
        return $result;
672
    }
673
674
    /**
675
     * @param string $filename
676
     * @return boolean
677
     */
678
    public function writeTo($filename)
679
    {
680
        if (!$filename) {
681
            return false;
682
        }
683
684
        // The GD resource might not exist if the image is too large to be processed, see checkAvailableMemory().
685
        if (!$this->gd) {
686
            return false;
687
        }
688
689
        // Get current image data
690
        if (file_exists($filename)) {
691
            list($width, $height, $type, $attr) = getimagesize($filename);
0 ignored issues
show
Unused Code introduced by
The assignment to $width is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $height is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $attr is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
692
            unlink($filename);
693
        } else {
694
            Filesystem::makeFolder(dirname($filename));
695
        }
696
697
        // If image type isn't known, guess from extension
698
        $ext = strtolower(substr($filename, strrpos($filename, '.')+1));
699
        if (empty($type)) {
700
            switch ($ext) {
701
                case "gif":
702
                    $type = IMAGETYPE_GIF;
703
                    break;
704
                case "jpeg":
705
                case "jpg":
706
                case "jpe":
707
                    $type = IMAGETYPE_JPEG;
708
                    break;
709
                default:
710
                    $type = IMAGETYPE_PNG;
711
                    break;
712
            }
713
        }
714
715
        // If $this->interlace != 0, the output image will be interlaced.
716
        imageinterlace($this->gd, $this->interlace);
717
718
        // if the extension does not exist, the file will not be created!
719
        switch ($type) {
720
            case IMAGETYPE_GIF:
721
                imagegif($this->gd, $filename);
722
                break;
723
            case IMAGETYPE_JPEG:
724
                imagejpeg($this->gd, $filename, $this->quality);
725
                break;
726
727
            // case 3, and everything else
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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...
728
            default:
729
                // Save them as 8-bit images
730
                // imagetruecolortopalette($this->gd, false, 256);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% 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...
731
                imagepng($this->gd, $filename);
732
                break;
733
        }
734
735
        if (!file_exists($filename)) {
736
            return false;
737
        }
738
739
        @chmod($filename, 0664);
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...
740
741
        return true;
742
    }
743
744
    /**
745
     * Helper function to allocate a colour to an image
746
     *
747
     * @param resource $image
748
     * @param string $webColor
749
     * @return int
750
     */
751
    protected function colourWeb2GD($image, $webColor)
752
    {
753
        if (substr($webColor, 0, 1) == "#") {
754
            $webColor = substr($webColor, 1);
755
        }
756
        $r = hexdec(substr($webColor, 0, 2));
757
        $g = hexdec(substr($webColor, 2, 2));
758
        $b = hexdec(substr($webColor, 4, 2));
759
760
        return imagecolorallocate($image, $r, $g, $b);
761
    }
762
763
    public static function flush()
764
    {
765
        // Clear factory
766
        $cache = Cache::factory('GDBackend_Manipulations');
767
        $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
768
    }
769
}
770