EasyImage::getUrl()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 3
1
<?php
2
3
namespace yiicod\easyimage;
4
5
use Exception;
6
use Imagine\Image\ImagineInterface;
7
use Imagine\Image\ManipulatorInterface;
8
use Yii;
9
use yii\base\Component;
10
use yii\helpers\ArrayHelper;
11
use yii\helpers\Url;
12
use yiicod\easyimage\base\ToolInterface;
13
use yiicod\easyimage\tools\Background;
14
use yiicod\easyimage\tools\Crop;
15
use yiicod\easyimage\tools\Flip;
16
use yiicod\easyimage\tools\Resize;
17
use yiicod\easyimage\tools\Rotate;
18
use yiicod\easyimage\tools\Scale;
19
use yiicod\easyimage\tools\Thumbnail;
20
use yiicod\easyimage\tools\Watermark;
21
22
/**
23
 * Class EasyImage
24
 * Easy image extension
25
 *
26
 * @author Virchenko Maksim <[email protected]>
27
 *
28
 * @package yiicod\easyimage
29
 */
30
class EasyImage extends Component
31
{
32
    /**
33
     * GD2 driver definition for Imagine implementation using the GD library.
34
     */
35
    const DRIVER_GD2 = 'gd2';
36
    /**
37
     * imagick driver definition.
38
     */
39
    const DRIVER_IMAGICK = 'imagick';
40
    /**
41
     * gmagick driver definition.
42
     */
43
    const DRIVER_GMAGICK = 'gmagick';
44
    /**
45
     * @var string alias for webroot folder
46
     */
47
    public $webrootAlias = '@webroot';
48
    /**
49
     * @var string relative path where the cache files are kept
50
     */
51
    public $cachePath = '/assets/easyimage/';
52
    /**
53
     * @var int cache lifetime in seconds
54
     */
55
    public $cacheTime = 2592000;
56
    /**
57
     * @var array output image options
58
     */
59
    public $imageOptions = [
60
        'quality' => 100,
61
    ];
62
    /**
63
     * Additional tools to work with images
64
     * ['toolName' => 'toolClass']
65
     * Tool class must implement yiicod\easyimage\base\ToolInterface
66
     *
67
     * @var array
68
     */
69
    public $tools = [];
70
    /**
71
     * Default empty image in "base64" string
72
     *
73
     * @var string
74
     */
75
    public $emptyImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=';
76
    /**
77
     * @var array|string the driver to use. This can be either a single driver name or an array of driver names.
78
     * If the latter, the first available driver will be used.
79
     */
80
    public static $driver = [self::DRIVER_IMAGICK, self::DRIVER_GMAGICK, self::DRIVER_GD2];
81
    /**
82
     * Imagine instance
83
     *
84
     * @var ImagineInterface
85
     */
86
    protected static $imagine;
87
88
    /**
89
     * Init component
90
     */
91
    public function init()
92
    {
93
        $this->initTools();
94
95
        Yii::setAlias('yiicod', realpath(dirname(__FILE__) . '/..'));
96
    }
97
98
    /**
99
     * Initialize default tools
100
     */
101
    protected function initTools()
102
    {
103
        $this->tools = ArrayHelper::merge([
104
            'crop' => Crop::class,
105
            'flip' => Flip::class,
106
            'resize' => Resize::class,
107
            'rotate' => Rotate::class,
108
            'scale' => Scale::class,
109
            'watermark' => Watermark::class,
110
            'thumbnail' => Thumbnail::class,
111
            'background' => Background::class,
112
        ], $this->tools);
113
    }
114
115
    /**
116
     * Returns the `Imagine` object that supports various image manipulations.
117
     *
118
     * @return ImagineInterface the `Imagine` object
119
     */
120
    public static function getImagine(): ImagineInterface
121
    {
122
        if (null === self::$imagine) {
123
            self::$imagine = static::createImagine();
124
        }
125
126
        return self::$imagine;
127
    }
128
129
    /**
130
     * @param ImagineInterface $imagine the `Imagine` object
131
     */
132
    public static function setImagine(ImagineInterface $imagine)
133
    {
134
        self::$imagine = $imagine;
135
    }
136
137
    /**
138
     * Creates an `Imagine` object based on the specified [[driver]].
139
     *
140
     * @return ImagineInterface the new `Imagine` object
141
     *
142
     * @throws Exception
143
     */
144
    protected static function createImagine(): ImagineInterface
145
    {
146
        foreach ((array)static::$driver as $driver) {
147
            switch ($driver) {
148
                case self::DRIVER_IMAGICK:
149
                    if (class_exists('Imagick', false)) {
150
                        return new \Imagine\Imagick\Imagine();
151
                    }
152
                    break;
153
                case self::DRIVER_GMAGICK:
154
                    if (class_exists('Gmagick', false)) {
155
                        return new \Imagine\Gmagick\Imagine();
156
                    }
157
                    break;
158
                case self::DRIVER_GD2:
159
                    if (function_exists('gd_info')) {
160
                        return new \Imagine\Gd\Imagine();
161
                    }
162
                    break;
163
                default:
164
                    throw new Exception("Unknown driver: $driver");
165
            }
166
        }
167
        throw new Exception('Your system does not support any of these drivers: ' . implode(',', (array)static::$driver));
168
    }
169
170
    /**
171
     * Get url for cached image using available tools
172
     *
173
     * @param string $file
174
     * @param array $params
175
     * @param bool $absolute
176
     *
177
     * @return string
178
     */
179
    public function getUrl(string $file, array $params = [], $absolute = false): string
180
    {
181
        $cacheFilePath = $this->getCacheFile($file, $params);
182
183
        if ($cacheFilePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFilePath of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
184
            return $this->createUrl($cacheFilePath, $absolute);
185
        }
186
187
        return $this->getEmptyImage();
188
    }
189
190
    /**
191
     * Get path for cached image using available tools
192
     *
193
     * @param string $file
194
     * @param array $params
195
     *
196
     * @return string
197
     */
198
    public function getPath(string $file, array $params = []): string
199
    {
200
        $cacheFilePath = $this->getCacheFile($file, $params);
201
202
        if ($cacheFilePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFilePath of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
203
            return $this->createPath($cacheFilePath);
204
        }
205
206
        return $this->getEmptyImage();
207
    }
208
209
    /**
210
     * Get component instance
211
     * Added for better IDE support
212
     *
213
     * @return mixed
214
     */
215
    public static function getInstance(): EasyImage
216
    {
217
        return Yii::$app->easyimage;
218
    }
219
220
    /**
221
     * Get cahced image
222
     *
223
     * @param $file
224
     * @param array $params
225
     *
226
     * @return bool|string
227
     */
228
    protected function getCacheFile(string $file, array $params = [])
229
    {
230
        try {
231
            $cacheFilePath = $this->generateCacheFilePath($file, $params);
232
            $filePath = $this->createPath($cacheFilePath);
233
234
            if (false === file_exists($filePath) ||
235
                (time() - filemtime($filePath) > $this->cacheTime) ||
236
                filemtime($file) > filemtime($filePath)
237
            ) {
238
                $this->makeDir(substr($filePath, 0, strpos($filePath, basename($filePath))));
239
                $this->createImage($file, $params, $filePath);
240
            }
241
242
            return $cacheFilePath;
243
        } catch (Exception $e) {
244
            $this->log($e);
245
246
            return false;
247
        }
248
    }
249
250
    /**
251
     * Create url for cached image
252
     *
253
     * @param string $filePath
254
     * @param bool $absolute
255
     *
256
     * @return string
257
     */
258
    protected function createUrl(string $filePath, $absolute = false): string
259
    {
260
        return rtrim(Url::base($absolute), DIRECTORY_SEPARATOR) . $filePath;
261
    }
262
263
    /**
264
     * Create path for cached image
265
     *
266
     * @param string $filePath
267
     *
268
     * @return string
269
     */
270
    protected function createPath(string $filePath): string
271
    {
272
        return Yii::getAlias($this->webrootAlias) . $filePath;
273
    }
274
275
    /**
276
     * Generate cached image path
277
     *
278
     * @param string $file
279
     * @param array $params
280
     *
281
     * @return string
282
     */
283
    protected function generateCacheFilePath(string $file, array $params = []): string
284
    {
285
        $hash = md5($file . serialize($params));
286
287
        $cacheFolderPath = $this->cachePath . $hash[0];
288
289
        $cacheFileExt = isset($params['type']) ? $params['type'] : pathinfo($file, PATHINFO_EXTENSION);
290
        $cacheFileName = $hash . '.' . $cacheFileExt;
291
292
        $cacheFilePath = $cacheFolderPath . DIRECTORY_SEPARATOR . $cacheFileName;
293
294
        return $cacheFilePath;
295
    }
296
297
    /**
298
     * Create new image using available tools
299
     *
300
     * @param string $file
301
     * @param array $params
302
     * @param string $savePath
303
     *
304
     * @return \Imagine\Image\ManipulatorInterface
305
     */
306
    protected function createImage(string $file, array $params, string $savePath): ManipulatorInterface
307
    {
308
        $image = self::getImagine()->open($file)->copy();
309
        $options = $this->imageOptions;
310
311
        foreach ($params as $key => $value) {
312
            if (isset($this->tools[$key]) && in_array(ToolInterface::class, class_implements($this->tools[$key]))) {
313
                $toolClass = $this->tools[$key];
314
                $image = $toolClass::handle($image, $value);
315
            } else {
316
                $options[$key] = $value;
317
            }
318
        }
319
320
        return $image->save($savePath, $params);
321
    }
322
323
    /**
324
     * Make directory to save cache image
325
     *
326
     * @param string $savePath
327
     */
328
    protected function makeDir(string $savePath)
329
    {
330
        if (!file_exists($savePath)) {
331
            mkdir($savePath, 0755, true);
332
        }
333
    }
334
335
    /**
336
     * Get default empty image
337
     *
338
     * @return string
339
     */
340
    protected function getEmptyImage(): string
341
    {
342
        return $this->emptyImage;
343
    }
344
345
    /**
346
     * Log exception
347
     *
348
     * @param Exception $e
349
     */
350
    protected function log(Exception $e)
351
    {
352
        Yii::error(sprintf("%s (%s : %s)\nStack trace:\n%s", $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()), 'easyimage');
353
    }
354
}
355