ConvertImage   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 541
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 202
c 2
b 0
f 0
dl 0
loc 541
rs 3.12
wmc 66

18 Methods

Rating   Name   Duplication   Size   Complexity  
A checkFolder() 0 25 5
A __construct() 0 12 1
A getNewName() 0 24 4
A setQuality() 0 9 2
A deletedOriginal() 0 24 6
A getFile() 0 37 5
A setName() 0 5 1
A resize() 0 6 1
A createWebP() 0 5 1
A setFolder() 0 5 1
A deleteAfter() 0 5 1
F convert() 0 103 19
A getName() 0 26 5
A clearLink() 0 10 4
A forceOverwrite() 0 5 1
A setFormat() 0 5 1
A isExternal() 0 11 2
B webpImage() 0 34 6

How to fix   Complexity   

Complex Class

Complex classes like ConvertImage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConvertImage, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Daaner\ConvertImage;
4
5
use Daaner\ConvertImage\Contracts\ConvertImageInterface;
6
use Exception;
7
use Illuminate\Support\Facades\Http;
8
use Illuminate\Support\Facades\Log;
9
use Illuminate\Support\Str;
10
use Intervention\Image\Facades\Image;
11
12
class ConvertImage implements ConvertImageInterface
13
{
14
15
    protected $dev;
16
17
18
    protected int $width;
19
    protected int $height;
20
    protected ?string $folderName = '';
21
    protected ?string $imageName = null;
22
    protected string $format;
23
    protected int $quality;
24
    protected bool $webP;
25
    protected bool $overwrite;
26
27
    /**
28
     * Удаление оригинала после успешной конвертации
29
     */
30
    protected bool $deleteAfterConvert;
31
32
    /**
33
     * Является внешней ссылкой
34
     */
35
    protected bool $isHTTP;
36
37
38
    /**
39
     * Constructor main settings.
40
     */
41
    public function __construct()
42
    {
43
        $this->dev = config('convert.dev');
44
45
        $this->width = config('convert.width');
46
        $this->height = config('convert.height');
47
        $this->deleteAfterConvert = config('convert.delete_after_convert');
48
49
        $this->format = config('convert.format', 'jpg');
50
        $this->quality = config('convert.quality', 80);
51
        $this->webP = config('convert.create_webp', false);
52
        $this->overwrite = config('convert.overwrite', false);
53
    }
54
55
56
    /**
57
     * Обрабатываем изображение
58
     *
59
     * @param string $image
60
     * @return string Return converted image or old image
61
     */
62
    public function convert(string $image): string
63
    {
64
        $this->isExternal($image);
65
66
        /**
67
         * Если внешний линк, возвращаем что давали
68
         */
69
        if (!config('convert.convert_external_url') && $this->isHTTP) {
70
            Log::info('ConvertImage. Not allowed converted external file (config): ' . $image);
71
            return $image;
72
        }
73
74
        $image = $this->clearLink($image);
75
        $newImagePath = $this->checkFolder($this->folderName);
76
        $imageName = $this->getName($image);
77
78
79
80
        $file = $this->getFile($image);
81
82
        /**
83
         * Файл не получен, возвращаем что давали
84
         */
85
        if (!$file) {
86
            return $image;
87
        }
88
89
90
        $link = $this->getNewName($newImagePath, $imageName);
91
        $image_url = Str::replace('//', '/', public_path() . '/' . $link);
92
93
94
        $bgTransparentColor = config('convert.bg_color', '#ffffff');
95
        $resize = config('convert.resize', false);
96
97
        $status = false;
98
99
        try {
100
            $img = Image::make($file);
101
            $jpg = Image::canvas($img->width(), $img->height(), $bgTransparentColor);
102
            $jpg->insert($img);
103
104
            if ($resize) {
105
                $jpg->resize($this->width, $this->height, function ($constraint) {
106
                    if (config('convert.aspect_ratio'))
107
                    {
108
                        $constraint->aspectRatio();
109
                    }
110
111
                    if (config('convert.upsize'))
112
                    {
113
                        $constraint->upsize();
114
                    }
115
                });
116
            }
117
118
            $status = $jpg->save($image_url, $this->quality, $this->format);
119
120
        } catch (Exception $e) {
121
            Log::info('ConvertImage. Cannot save converted image: ' . $link);
122
            Log::info($e->getMessage());
123
            $image_url = $image;
124
        }
125
126
127
        /** create WebP */
128
        $webP = false;
129
        if ($this->webP && $status) {
130
131
            try {
132
                $webP = $this->webpImage($image_url);
133
            } catch (Exception $e) {
134
                Log::info('ConvertImage. Cannot create webP image: ' . $link);
135
                Log::info($e->getMessage());
136
            }
137
138
        }
139
140
        $deletedOriginal = $this->deletedOriginal($image, $link, (bool) $status);
141
142
143
        if ($this->dev) {
144
145
            $logData = "\n" . 'original: ' . $image;
146
            $logData .= "\n" . 'status: ' . ($status ? 'true' : 'false');
147
            $logData .= "\n" . 'converted (return): ' . $link;
148
            $logData .= "\n" . 'overwrite file: ' . ($this->overwrite ? 'true' : 'false');
149
            $logData .= "\n" . 'deleted original file: ' . ($deletedOriginal ? 'true' : 'false');
150
            $logData .= "\n" . 'width: ' . $this->width;
151
            $logData .= "\n" . 'height: ' . $this->height;
152
            $logData .= "\n" . 'format: ' . $this->format;
153
            $logData .= "\n" . 'quality: ' . $this->quality;
154
155
            $logData .= "\n" . 'isHTTP: ' . ($this->isHTTP ? 'true' : 'false');
156
            $logData .= "\n" . 'deleteAfterConvert: ' . ($this->deleteAfterConvert ? 'true' : 'false');
157
158
            $logData .= "\n" . 'create webP: ' . ($webP ? 'true' : 'false');
159
160
            Log::info('ConvertImage. Dev mode ->' . $logData);
161
        }
162
163
164
        return $status ? $link : $image_url;
165
    }
166
167
168
    /**
169
     * Изменение директории сохранения относительно config('convert.dir')
170
     *
171
     * @param string $name
172
     * @return ConvertImage
173
     */
174
    public function setFolder(string $name): ConvertImage
175
    {
176
        $this->folderName = Str::replace('//', '/', $name);
177
178
        return $this;
179
    }
180
181
182
    /**
183
     * Установка размеров результата
184
     *
185
     * @param int $width
186
     * @param int $height
187
     * @return ConvertImage
188
     */
189
    public function resize(int $width, int $height): ConvertImage
190
    {
191
        $this->width = $width;
192
        $this->height = $height;
193
194
        return $this;
195
    }
196
197
198
    /**
199
     * Удаление оригинала после успешной конвертации.
200
     * Если изображение по внешней ссылки - удаления не произойдет
201
     *
202
     * @param bool $marker
203
     * @return ConvertImage
204
     */
205
    public function deleteAfter(bool $marker): ConvertImage
206
    {
207
        $this->deleteAfterConvert = $marker;
208
209
        return $this;
210
    }
211
212
213
    /**
214
     * Устанавливаем имя для файла
215
     *
216
     * @param string $name
217
     * @return $this
218
     */
219
    public function setName(string $name): ConvertImage
220
    {
221
        $this->imageName = Str::slug($name, '-');
222
223
        return $this;
224
    }
225
226
227
    /**
228
     * Изменение формата изображения (JPG, PNG), отличного от дефолтного в конфиге
229
     *
230
     * @param string $format
231
     * @return ConvertImage
232
     */
233
    public function setFormat(string $format): ConvertImage
234
    {
235
        $this->format = $format;
236
237
        return $this;
238
    }
239
240
241
    /**
242
     * Изменение качества изображения, отличного от дефолтного в конфиге
243
     *
244
     * @param int $quality
245
     * @return ConvertImage
246
     */
247
    public function setQuality(int $quality): ConvertImage
248
    {
249
        $this->quality = $quality;
250
251
        if ($this->quality > 100) {
252
            $this->quality = 100;
253
        }
254
255
        return $this;
256
    }
257
258
259
    /**
260
     * Создание WebP, отличного от дефолтного в конфиге
261
     *
262
     * @param bool $webp
263
     * @return ConvertImage
264
     */
265
    public function createWebP(bool $webp): ConvertImage
266
    {
267
        $this->webP = $webp;
268
269
        return $this;
270
    }
271
272
273
    /**
274
     * Создание WebP, отличного от дефолтного в конфиге
275
     *
276
     * @param bool $overwrite
277
     * @return ConvertImage
278
     */
279
    public function forceOverwrite(bool $overwrite): ConvertImage
280
    {
281
        $this->overwrite = $overwrite;
282
283
        return $this;
284
    }
285
286
287
288
289
    /**
290
     * Получаем новое имя файла.
291
     * Если имя не указано - получаем хеш имени или имя файла без расширения
292
     *
293
     * @param string $image
294
     * @return string
295
     */
296
    protected function getName(string $image): string
297
    {
298
299
        if ($this->imageName) {
300
            $newName = $this->imageName;
301
        } else {
302
303
            try {
304
                $name = strval(pathinfo($image, PATHINFO_FILENAME));
305
            } catch (Exception $e) {
306
                $name = rand(100,999) . '-' . Str::replace('.', '', strval(microtime(true)));
307
308
                if ($this->dev) {
309
                    Log::info('ConvertImage. Error get image name: ' . $image);
310
                    Log::info($e->getMessage());
311
                }
312
            }
313
314
            if (config('convert.save_original_name')) {
315
                $newName = $name;
316
            } else {
317
                $newName = md5($name);
318
            }
319
        }
320
321
        return $newName;
322
    }
323
324
325
    /**
326
     * Удаляем оригинал файла
327
     *
328
     * @param string $source
329
     * @param string $link
330
     * @param bool $status
331
     * @return bool
332
     */
333
    protected function deletedOriginal(string $source, string $link, bool $status): bool
334
    {
335
        if ($status && $this->deleteAfterConvert) {
336
            // Сохранили файл на место оригинала
337
            if ($link == $source)
338
            {
339
                $this->deleteAfterConvert = false;
340
                return false;
341
            }
342
343
            try {
344
                unlink($source);
345
                return true;
346
            } catch (Exception $e) {
347
                try {
348
                    unlink(public_path() . $source);
349
                    return true;
350
                } catch (Exception $e) {
351
                    Log::info('ConvertImage. Error DELETE original image:' . $source);
352
                }
353
            }
354
        }
355
356
        return false;
357
    }
358
359
360
    /**
361
     * Конвертируем в webP
362
     *
363
     * @param string $source
364
     * @return bool
365
     */
366
    protected function webpImage(string $source): bool
367
    {
368
        if (!extension_loaded('gd'))
369
        {
370
            Log::info('ConvertImage. PHP extension GD missed, image not converted: ' . $source);
371
            return false;
372
        }
373
374
        $dir = strval(pathinfo($source, PATHINFO_DIRNAME));
375
        $name = strval(pathinfo($source, PATHINFO_FILENAME)) . '.' . $this->format;
376
        $destination = $dir . DIRECTORY_SEPARATOR . $name . '.webp';
377
        $info = getimagesize($source);
378
        $isAlpha = false;
379
380
        if ($info['mime'] == 'image/jpeg')
381
            $image = imagecreatefromjpeg($source);
382
        elseif ($isAlpha = $info['mime'] == 'image/gif') {
383
            $image = imagecreatefromgif($source);
384
        } elseif ($isAlpha = $info['mime'] == 'image/png') {
385
            $image = imagecreatefrompng($source);
386
        } else {
387
            Log::info('ConvertImage. Not supported mime for convert webP: ' . $source);
388
            return false;
389
        }
390
391
        if ($isAlpha) {
392
            imagepalettetotruecolor($image);
393
            imagealphablending($image, true);
394
            imagesavealpha($image, true);
395
        }
396
397
        imagewebp($image, $destination, $this->quality);
398
399
        return true;
400
    }
401
402
403
    /**
404
     * Очищаем параметры URL, если указано в конфиге.
405
     * Но не чистим, если изображение из внешнего источника.
406
     *
407
     * @param string $link
408
     * @return string
409
     */
410
    protected function clearLink(string $link): string
411
    {
412
        if (config('convert.clear_url_parameter') && !$this->isHTTP) {
413
            // Проверка с отсечением
414
            if (stristr($link, '?', true)) {
415
                $link = stristr($link, '?', true);
416
            }
417
        }
418
419
        return $link;
420
    }
421
422
423
    /**
424
     * Проверяем, является ли ссылка внешней.
425
     * Внешняя ссылка не будет удаляться
426
     *
427
     * @param string $link
428
     * @return ConvertImage
429
     */
430
    protected function isExternal(string $link): ConvertImage
431
    {
432
        $this->isHTTP = true;
433
434
        if (stripos($link, 'http', 0) === false) {
435
            $this->isHTTP = false;
436
        } else {
437
            $this->deleteAfterConvert = false;
438
        }
439
440
        return $this;
441
    }
442
443
444
    /**
445
     * Проверка на наличие папки
446
     * или папку создаем
447
     *
448
     * @param string|null $folder
449
     * @return string
450
     */
451
    protected function checkFolder(?string $folder): string
452
    {
453
454
        $imagePath = config('convert.dir');
455
456
        if ($folder) {
457
            $imagePath .= '/' . $folder;
458
        }
459
460
        // Замена двух слешей на один
461
        $imagePath = Str::replace('//', '/', $imagePath);
462
463
        if (!is_dir(public_path() . $imagePath)) {
464
            try {
465
                mkdir(public_path() . $imagePath, config('convert.dir_permission'), config('convert.dir_recursive'));
466
                if ($this->dev) {
467
                    Log::info('ConvertImage. Create folder for convert image: ' . $imagePath);
468
                }
469
            } catch (Exception $e) {
470
                Log::critical('ConvertImage. ERROR: Can not create folder: ' . $imagePath);
471
                $imagePath = config('convert.dir_temp');
472
            }
473
        }
474
475
        return $imagePath;
476
    }
477
478
479
    /**
480
     * @param string $link
481
     * @return false|string
482
     */
483
    protected function getFile(string $link)
484
    {
485
        $file = false;
486
487
        if ($this->isHTTP) {
488
            try {
489
                $getFile = Http::timeout(config('convert.http_response_timeout', 3))
490
                    ->withOptions([
491
                        'verify' => config('convert.http_option_verify'),
492
                    ])
493
                    ->retry(config('convert.http_retry_max_time', 2), config('convert.http_retry_delay', 100))
494
                    ->get($link);
495
496
                if ($getFile->successful()) {
497
                    $file = $getFile->body();
498
                } else {
499
                    Log::info('ConvertImage. Image HTTP read error: ' . $link);
500
                    return false;
501
                }
502
            } catch (Exception $e) {
503
                Log::info('ConvertImage. Error HTTP get image: ' . $link);
504
                Log::info($e->getMessage());
505
                return false;
506
            }
507
        } else {
508
            $imageLink = Str::replace('//', '/', public_path() . '/' . $link);
509
510
            try {
511
                $file = file_get_contents($imageLink);
512
            } catch (Exception $e) {
513
                Log::info('ConvertImage. Image read (local) error: ' . $link);
514
                Log::info($e->getMessage());
515
                return false;
516
            }
517
        }
518
519
        return $file;
520
521
    }
522
523
524
    /**
525
     * @param string $path
526
     * @param string $name
527
     * @return string
528
     */
529
    protected function getNewName(string $path, string $name): string
530
    {
531
        $link = $path . '/' . $name . '.' . $this->format;
532
533
        if (file_exists(public_path() . '/' . $link))
534
        {
535
            if ($this->overwrite)
536
            {
537
                // файл есть, разрешена перезапись и права на перезапись есть
538
                if(is_writable(public_path() . '/' . $link))
539
                {
540
                    return $link;
541
                }
542
            }
543
544
            // Файл есть, перезапись не разрешена или нет прав.
545
            // Меняем имя файла
546
            Log::info('ConvertImage. Change name for overwrite: ' . $link);
547
            $newName = $name . '-' . Str::replace('.', '', strval(microtime(true)));
548
            $link = $path . '/' . $newName . '.' . $this->format;
549
        }
550
551
552
        return $link;
553
    }
554
}
555