Completed
Push — master ( 7c2dd0...ac3296 )
by Alex
01:28
created

UploadImageBehavior::getThumbUploadUrl()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 11.375

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 10
cts 16
cp 0.625
rs 8.1635
c 0
b 0
f 0
cc 8
nc 7
nop 2
crap 11.375
1
<?php
2
3
namespace mohorev\file;
4
5
use Imagine\Image\ManipulatorInterface;
6
use Yii;
7
use yii\base\InvalidArgumentException;
8
use yii\base\InvalidConfigException;
9
use yii\base\NotSupportedException;
10
use yii\db\BaseActiveRecord;
11
use yii\helpers\ArrayHelper;
12
use yii\helpers\FileHelper;
13
use yii\imagine\Image;
14
15
/**
16
 * UploadImageBehavior automatically uploads image, creates thumbnails and fills
17
 * the specified attribute with a value of the name of the uploaded image.
18
 *
19
 * To use UploadImageBehavior, insert the following code to your ActiveRecord class:
20
 *
21
 * ```php
22
 * use mohorev\file\UploadImageBehavior;
23
 *
24
 * function behaviors()
25
 * {
26
 *     return [
27
 *         [
28
 *             'class' => UploadImageBehavior::class,
29
 *             'attribute' => 'file',
30
 *             'scenarios' => ['insert', 'update'],
31
 *             'placeholder' => '@app/modules/user/assets/images/userpic.jpg',
32
 *             'path' => '@webroot/upload/{id}/images',
33
 *             'url' => '@web/upload/{id}/images',
34
 *             'thumbPath' => '@webroot/upload/{id}/images/thumb',
35
 *             'thumbUrl' => '@web/upload/{id}/images/thumb',
36
 *             'createThumbsOnSave' => false,
37
 *             'createThumbsOnRequest' => true,
38
 *             'thumbs' => [
39
 *                   'thumb' => ['width' => 400, 'quality' => 90],
40
 *                   'preview' => ['width' => 200, 'height' => 200],
41
 *              ],
42
 *         ],
43
 *     ];
44
 * }
45
 * ```
46
 *
47
 * @author Alexander Mohorev <[email protected]>
48
 * @author Alexey Samoylov <[email protected]>
49
 */
50
class UploadImageBehavior extends UploadBehavior
51
{
52
    /**
53
     * @var string
54
     */
55
    public $placeholder;
56
    /**
57
     * create all thumbs profiles on image upload
58
     * @var boolean
59
     */
60
    public $createThumbsOnSave = true;
61
    /**
62
     * create thumb only for profile request by getThumbUploadUrl() method
63
     * @var boolean
64
     */
65
    public $createThumbsOnRequest = false;
66
    /**
67
     * Whether delete original uploaded image after thumbs generating.
68
     * Defaults to FALSE
69
     * @var boolean
70
     */
71
    public $deleteOriginalFile = false;
72
    /**
73
     * @var array the thumbnail profiles
74
     * - `width`
75
     * - `height`
76
     * - `quality`
77
     */
78
    public $thumbs = [
79
        'thumb' => ['width' => 200, 'height' => 200, 'quality' => 90],
80
    ];
81
    /**
82
     * @var string|null
83
     */
84
    public $thumbPath;
85
    /**
86
     * @var string|null
87
     */
88
    public $thumbUrl;
89
90
91
    /**
92
     * @inheritdoc
93
     */
94 5
    public function init()
95
    {
96 5
        if (!class_exists(Image::class)) {
97
            throw new NotSupportedException("Yii2-imagine extension is required to use the UploadImageBehavior");
98
        }
99
100 5
        parent::init();
101
102 5
        if ($this->thumbPath === null) {
103 5
            $this->thumbPath = $this->path;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->path of type callable is incompatible with the declared type string|null of property $thumbPath.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
104
        }
105 5
        if ($this->thumbUrl === null) {
106 5
            $this->thumbUrl = $this->url;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->url of type callable is incompatible with the declared type string|null of property $thumbUrl.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
107
        }
108
109 5
        foreach ($this->thumbs as $config) {
110 5
            $width = ArrayHelper::getValue($config, 'width');
111 5
            $height = ArrayHelper::getValue($config, 'height');
112 5
            if ($height < 1 && $width < 1) {
113
                throw new InvalidConfigException(sprintf(
114
                    'Length of either side of thumb cannot be 0 or negative, current size ' .
115 5
                    'is %sx%s', $width, $height
116
                ));
117
            }
118
        }
119 5
    }
120
121
    /**
122
     * @inheritdoc
123
     */
124 3
    protected function afterUpload()
125
    {
126 3
        parent::afterUpload();
127 3
        if ($this->createThumbsOnSave) {
128
            $this->createThumbs();
129
        }
130 3
    }
131
132
    /**
133
     * @param string $needed_profile - profile name to create thumb
134
     * @throws \yii\base\Exception
135
     * @throws \yii\base\InvalidConfigException
136
     */
137 2
    protected function createThumbs($needed_profile = false)
138
    {
139 2
        $path = $this->getUploadPath($this->attribute);
140 2
        foreach ($this->thumbs as $profile => $config) {
141
            //skip profiles not needed now
142 2
            if ($needed_profile && $needed_profile != $profile) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $needed_profile of type false|string 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...
143 2
                continue;
144
            }
145
146 2
            $thumbPath = $this->getThumbUploadPath($this->attribute, $profile);
147 2
            if ($thumbPath !== null) {
148 2
                if (!FileHelper::createDirectory(dirname($thumbPath))) {
149
                    throw new InvalidArgumentException(
150
                        "Directory specified in 'thumbPath' attribute doesn't exist or cannot be created."
151
                    );
152
                }
153 2
                if (!is_file($thumbPath)) {
154 2
                    $this->generateImageThumb($config, $path, $thumbPath);
155
                }
156
            }
157
        }
158
159 2
        if ($this->deleteOriginalFile) {
160
            $this->deleteFile($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by $this->getUploadPath($this->attribute) on line 139 can also be of type boolean or null; however, mohorev\file\UploadBehavior::deleteFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
161
        }
162 2
    }
163
164
    /**
165
     * @param string $attribute
166
     * @param string $profile
167
     * @param boolean $old
168
     * @return string
169
     * @throws \yii\base\InvalidConfigException
170
     */
171 2
    public function getThumbUploadPath($attribute, $profile = 'thumb', $old = false)
172
    {
173
        /** @var BaseActiveRecord $model */
174 2
        $model = $this->owner;
175 2
        $path = $this->resolvePath($this->thumbPath);
176 2
        $attribute = ($old === true) ? $model->getOldAttribute($attribute) : $model->$attribute;
177 2
        $filename = $this->getThumbFileName($attribute, $profile);
178
179 2
        return $filename ? Yii::getAlias($path . '/' . $filename) : null;
180
    }
181
182
    /**
183
     * @param string $attribute
184
     * @param string $profile
185
     * @return string|null
186
     * @throws \yii\base\Exception
187
     * @throws \yii\base\InvalidConfigException
188
     */
189 3
    public function getThumbUploadUrl($attribute, $profile = 'thumb')
190
    {
191
        /** @var BaseActiveRecord $model */
192 3
        $model = $this->owner;
193
194 3
        if (!$model->getAttribute($attribute))
195
        {
196 1
            if ($this->placeholder) {
197 1
                return $this->getPlaceholderUrl($profile);
198
            }
199
            else {
200
                return null;
201
            }
202
        }
203
204 2
        $path = $this->getUploadPath($attribute, true);
205
206
        //if original file exist - generate profile thumb and generate url to thumb
207 2
        if (is_file($path) || !$this->deleteOriginalFile) {
208 2
            if ($this->createThumbsOnRequest) {
209 2
                $this->createThumbs($profile);
210
            }
211 2
            return $this->getThumbProfileUrl($attribute, $profile, $model);
212
        } //if original file is deleted generate url to thumb
213
        elseif ($this->deleteOriginalFile) {
214
            return $this->getThumbProfileUrl($attribute, $profile, $model);
215
        } elseif ($this->placeholder) {
216
            return $this->getPlaceholderUrl($profile);
217
        } else {
218
            return null;
219
        }
220
    }
221
222
    /**
223
     * @param $profile
224
     * @return string
225
     */
226 1
    protected function getPlaceholderUrl($profile)
227
    {
228 1
        list ($path, $url) = Yii::$app->assetManager->publish($this->placeholder);
229 1
        $filename = basename($path);
230 1
        $thumb = $this->getThumbFileName($filename, $profile);
231 1
        $thumbPath = dirname($path) . DIRECTORY_SEPARATOR . $thumb;
232 1
        $thumbUrl = dirname($url) . '/' . $thumb;
233
234 1
        if (!is_file($thumbPath)) {
235 1
            $this->generateImageThumb($this->thumbs[$profile], $path, $thumbPath);
236
        }
237
238 1
        return $thumbUrl;
239
    }
240
241
    /**
242
     * @inheritdoc
243
     */
244 1
    protected function delete($attribute, $old = false)
245
    {
246 1
        $profiles = array_keys($this->thumbs);
247 1
        foreach ($profiles as $profile) {
248 1
            $path = $this->getThumbUploadPath($attribute, $profile, $old);
249 1
            $this->deleteFile($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by $this->getThumbUploadPat...ribute, $profile, $old) on line 248 can also be of type boolean or null; however, mohorev\file\UploadBehavior::deleteFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
250
        }
251 1
        parent::delete($attribute, $old);
252 1
    }
253
254
    /**
255
     * @param $filename
256
     * @param string $profile
257
     * @return string
258
     */
259 3
    protected function getThumbFileName($filename, $profile = 'thumb')
260
    {
261 3
        return $profile . '-' . $filename;
262
    }
263
264
    /**
265
     * @param $config
266
     * @param $path
267
     * @param $thumbPath
268
     */
269 3
    protected function generateImageThumb($config, $path, $thumbPath)
270
    {
271 3
        $width = ArrayHelper::getValue($config, 'width');
272 3
        $height = ArrayHelper::getValue($config, 'height');
273 3
        $quality = ArrayHelper::getValue($config, 'quality', 100);
274 3
        $mode = ArrayHelper::getValue($config, 'mode', ManipulatorInterface::THUMBNAIL_INSET);
275 3
        $bg_color = ArrayHelper::getValue($config, 'bg_color', 'FFF');
276
277 3
        if (!$width || !$height) {
278 2
            $image = Image::getImagine()->open($path);
279 2
            $ratio = $image->getSize()->getWidth() / $image->getSize()->getHeight();
280 2
            if ($width) {
281 2
                $height = ceil($width / $ratio);
282
            } else {
283
                $width = ceil($height * $ratio);
284
            }
285
        }
286
287
        // Fix error "PHP GD Allowed memory size exhausted".
288 3
        ini_set('memory_limit', '512M');
289
        //for big images size
290 3
        ini_set('max_execution_time', 60);
291 3
        Image::$thumbnailBackgroundColor = $bg_color;
292 3
        Image::thumbnail($path, $width, $height, $mode)->save($thumbPath, ['quality' => $quality]);
293 3
    }
294
295
    /**
296
     * @param $attribute
297
     * @param $profile
298
     * @param BaseActiveRecord $model
299
     * @return bool|string
300
     */
301 2
    protected function getThumbProfileUrl($attribute, $profile, BaseActiveRecord $model)
302
    {
303 2
        $url = $this->resolvePath($this->thumbUrl);
304 2
        $fileName = $model->getOldAttribute($attribute);
305 2
        $thumbName = $this->getThumbFileName($fileName, $profile);
306
307 2
        return Yii::getAlias($url . '/' . $thumbName);
308
    }
309
}
310