Passed
Push — master ( 38e935...742673 )
by Robson
01:26
created

Cropper::make()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 10
b 0
f 0
nc 3
nop 3
dl 0
loc 15
rs 10
1
<?php
2
3
namespace CoffeeCode\Cropper;
4
5
use Exception;
6
use WebPConvert\Convert\Exceptions\ConversionFailedException;
7
use WebPConvert\WebPConvert;
8
9
/**
10
 * Class CoffeeCode Cropper
11
 *
12
 * @author Robson V. Leite <https://github.com/robsonvleite>
13
 * @package CoffeeCode\Cropper
14
 */
15
class Cropper
16
{
17
    /** @var string */
18
    private $cachePath;
19
20
    /** @var string */
21
    private $imagePath;
22
23
    /** @var string */
24
    private $imageName;
25
26
    /** @var string */
27
    private $imageMime;
28
29
    /** @var int */
30
    private $quality;
31
32
    /** @var int */
33
    private $compressor;
34
35
    /**@var bool */
36
    private $webP;
37
38
    /**
39
     * Allow jpg and png to thumb and cache generate
40
     * @var array allowed media types
41
     */
42
    private static $allowedExt = ['image/jpeg', "image/png"];
43
44
    /**
45
     * Cropper constructor.
46
     *
47
     * @param string $cachePath
48
     * @param int $quality
49
     * @param int $compressor
50
     * @param bool $webP
51
     * @throws Exception
52
     */
53
    public function __construct(string $cachePath, int $quality = 75, int $compressor = 5, bool $webP = true)
54
    {
55
        $this->cachePath = $cachePath;
56
        $this->quality = $quality;
57
        $this->compressor = $compressor;
58
        $this->webP = $webP;
59
60
        if (!file_exists($this->cachePath) || !is_dir($this->cachePath)) {
61
            if (!mkdir($this->cachePath, 0755, true)) {
62
                throw new Exception("Could not create cache folder");
63
            }
64
        }
65
    }
66
67
    /**
68
     * Make an thumb image
69
     *
70
     * @param string $imagePath
71
     * @param int $width
72
     * @param int|null $height
73
     * @return null|string
74
     */
75
    public function make(string $imagePath, int $width, int $height = null): ?string
76
    {
77
        if (!file_exists($imagePath)) {
78
            return "Image not found";
79
        }
80
81
        $this->imagePath = $imagePath;
82
        $this->imageName = $this->name($this->imagePath, $width, $height);
83
        $this->imageMime = mime_content_type($this->imagePath);
84
85
        if (!in_array($this->imageMime, self::$allowedExt)) {
86
            return "Not a valid JPG or PNG image";
87
        }
88
89
        return $this->image($width, $height);
90
    }
91
92
    /**
93
     * @param int $width
94
     * @param int|null $height
95
     * @return string|null
96
     */
97
    private function image(int $width, int $height = null): ?string
98
    {
99
        $imageWebP = "{$this->cachePath}/{$this->imageName}.webp";
100
        $imageExt = "{$this->cachePath}/{$this->imageName}." . pathinfo($this->imagePath)['extension'];
101
102
        if ($this->webP && file_exists($imageWebP) && is_file($imageWebP)) {
103
            return $imageWebP;
104
        }
105
106
        if (file_exists($imageExt) && is_file($imageExt)) {
107
            return $imageExt;
108
        }
109
110
        return $this->imageCache($width, $height);
111
    }
112
113
    /**
114
     * @param string $name
115
     * @param int $width
116
     * @param int $height
117
     * @return string
118
     */
119
    protected function name(string $name, int $width = null, int $height = null): string
120
    {
121
        $filterName = filter_var(mb_strtolower(pathinfo($name)["filename"]), FILTER_SANITIZE_STRIPPED);
122
        $formats = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜüÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿRr"!@#$%&*()_-+={[}]/?;:.,\\\'<>°ºª';
123
        $replace = 'aaaaaaaceeeeiiiidnoooooouuuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyRr                                 ';
124
        $trimName = trim(strtr(utf8_decode($filterName), utf8_decode($formats), $replace));
125
        $name = str_replace(["-----", "----", "---", "--"], "-", str_replace(" ", "-", $trimName));
126
127
        $hash = $this->hash($this->imagePath);
128
        $widthName = ($width ? "-{$width}" : "");
129
        $heightName = ($height ? "x{$height}" : "");
130
131
        return "{$name}{$widthName}{$heightName}-{$hash}";
132
    }
133
134
    /**
135
     * @param string $path
136
     * @return string
137
     */
138
    protected function hash(string $path): string
139
    {
140
        return hash("crc32", pathinfo($path)['basename']);
141
    }
142
143
    /**
144
     * Clear cache
145
     *
146
     * @param string|null $imagePath
147
     * @example $t->flush("images/image.jpg"); clear image name and variations size
148
     * @example $t->flush(); clear all image cache folder
149
     */
150
    public function flush(string $imagePath = null): void
151
    {
152
        foreach (scandir($this->cachePath) as $file) {
153
            $file = "{$this->cachePath}/{$file}";
154
            if ($imagePath && strpos($file, $this->hash($imagePath))) {
155
                $this->imageDestroy($file);
156
            } elseif (!$imagePath) {
157
                $this->imageDestroy($file);
158
            }
159
        }
160
    }
161
162
    /**
163
     * @param int $width
164
     * @param int|null $height
165
     * @return null|string
166
     */
167
    private function imageCache(int $width, int $height = null): ?string
168
    {
169
        list($src_w, $src_h) = getimagesize($this->imagePath);
170
        $height = ($height ?? ($width * $src_h) / $src_w);
171
172
        $src_x = 0;
173
        $src_y = 0;
174
175
        $cmp_x = $src_w / $width;
176
        $cmp_y = $src_h / $height;
177
178
        if ($cmp_x > $cmp_y) {
179
            $src_w = round($src_w / $cmp_x * $cmp_y);
180
            $src_x = round(($src_w - ($src_w / $cmp_x * $cmp_y))); //2
181
        } elseif ($cmp_y > $cmp_x) {
182
            $src_h = round($src_h / $cmp_y * $cmp_x);
183
            $src_y = round(($src_h - ($src_h / $cmp_y * $cmp_x))); //2
184
        }
185
186
        $src_x = (int)$src_x;
187
        $src_h = (int)$src_h;
188
        $src_y = (int)$src_y;
189
        $src_y = (int)$src_y;
190
191
        if ($this->imageMime == "image/jpeg") {
192
            return $this->fromJpg($width, $height, $src_x, $src_y, $src_w, $src_h);
193
        }
194
195
        if ($this->imageMime == "image/png") {
196
            return $this->fromPng($width, $height, $src_x, $src_y, $src_w, $src_h);
197
        }
198
199
        return null;
200
    }
201
202
    /**
203
     * @param string $imagePatch
204
     */
205
    private function imageDestroy(string $imagePatch): void
206
    {
207
        if (file_exists($imagePatch) && is_file($imagePatch)) {
208
            unlink($imagePatch);
209
        }
210
    }
211
212
    /**
213
     * @param int $width
214
     * @param int $height
215
     * @param int $src_x
216
     * @param int $src_y
217
     * @param int $src_w
218
     * @param int $src_h
219
     * @return string
220
     */
221
    private function fromJpg(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string
222
    {
223
        $thumb = imagecreatetruecolor($width, $height);
224
        $source = imagecreatefromjpeg($this->imagePath);
225
226
        imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type false; however, parameter $src_image of imagecopyresampled() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

226
        imagecopyresampled($thumb, /** @scrutinizer ignore-type */ $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
Loading history...
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $dst_image of imagecopyresampled() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

226
        imagecopyresampled(/** @scrutinizer ignore-type */ $thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
Loading history...
227
        imagejpeg($thumb, "{$this->cachePath}/{$this->imageName}.jpg", $this->quality);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagejpeg() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

227
        imagejpeg(/** @scrutinizer ignore-type */ $thumb, "{$this->cachePath}/{$this->imageName}.jpg", $this->quality);
Loading history...
228
229
        imagedestroy($thumb);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagedestroy() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

229
        imagedestroy(/** @scrutinizer ignore-type */ $thumb);
Loading history...
230
        imagedestroy($source);
231
232
        if ($this->webP) {
233
            return $this->toWebP("{$this->cachePath}/{$this->imageName}.jpg");
234
        }
235
236
        return "{$this->cachePath}/{$this->imageName}.jpg";
237
    }
238
239
    /**
240
     * @param int $width
241
     * @param int $height
242
     * @param int $src_x
243
     * @param int $src_y
244
     * @param int $src_w
245
     * @param int $src_h
246
     * @return string
247
     */
248
    private function fromPng(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string
249
    {
250
        $thumb = imagecreatetruecolor($width, $height);
251
        $source = imagecreatefrompng($this->imagePath);
252
253
        imagealphablending($thumb, false);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagealphablending() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

253
        imagealphablending(/** @scrutinizer ignore-type */ $thumb, false);
Loading history...
254
        imagesavealpha($thumb, true);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagesavealpha() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

254
        imagesavealpha(/** @scrutinizer ignore-type */ $thumb, true);
Loading history...
255
        imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $dst_image of imagecopyresampled() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

255
        imagecopyresampled(/** @scrutinizer ignore-type */ $thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
Loading history...
Bug introduced by
It seems like $source can also be of type false; however, parameter $src_image of imagecopyresampled() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

255
        imagecopyresampled($thumb, /** @scrutinizer ignore-type */ $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
Loading history...
256
        imagepng($thumb, "{$this->cachePath}/{$this->imageName}.png", $this->compressor);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagepng() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

256
        imagepng(/** @scrutinizer ignore-type */ $thumb, "{$this->cachePath}/{$this->imageName}.png", $this->compressor);
Loading history...
257
258
        imagedestroy($thumb);
0 ignored issues
show
Bug introduced by
It seems like $thumb can also be of type false; however, parameter $image of imagedestroy() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

258
        imagedestroy(/** @scrutinizer ignore-type */ $thumb);
Loading history...
259
        imagedestroy($source);
260
261
        if ($this->webP) {
262
            return $this->toWebP("{$this->cachePath}/{$this->imageName}.png");
263
        }
264
265
        return "{$this->cachePath}/{$this->imageName}.png";
266
    }
267
268
    /**
269
     * @param string $image
270
     * @param bool $unlinkImage
271
     * @return string
272
     */
273
    public function toWebP(string $image, $unlinkImage = true): string
274
    {
275
        try {
276
            $webPConverted = pathinfo($image)["dirname"] . "/" . pathinfo($image)["filename"] . ".webp";
277
            WebPConvert::convert($image, $webPConverted, ["default-quality" => $this->quality]);
278
279
            if ($unlinkImage) {
280
                unlink($image);
281
            }
282
283
            return $webPConverted;
284
        } catch (ConversionFailedException $exception) {
285
            return $image;
286
        }
287
    }
288
}
289