Completed
Push — master ( 6d9f7a...3b1f1a )
by Igor
05:47
created

FileBehavior::afterSave()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 22
cts 22
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 18
nc 11
nop 0
crap 8
1
<?php
2
3
/**
4
 * @link https://github.com/rkit/filemanager-yii2
5
 * @copyright Copyright (c) 2015 Igor Romanov
6
 * @license [MIT](http://opensource.org/licenses/MIT)
7
 */
8
9
namespace rkit\filemanager\behaviors;
10
11
use Yii;
12
use yii\base\Behavior;
13
use yii\db\ActiveRecord;
14
use yii\helpers\ArrayHelper;
15
use yii\base\InvalidParamException;
16
use rkit\filemanager\models\File;
17
use rkit\filemanager\helpers\FormatValidation;
18
19
class FileBehavior extends Behavior
20
{
21
    /**
22
     * @var array
23
     */
24
    public $attributes = [];
25
26
    /**
27
     * @internal
28
     */
29 44
    public function init()
30
    {
31 44
        parent::init();
32
33 44
        Yii::$app->fileManager->registerTranslations();
34 44
    }
35
36
    /**
37
     * @inheritdoc
38
     * @internal
39
     */
40 44
    public function events()
41
    {
42
        return [
43 44
            ActiveRecord::EVENT_AFTER_INSERT  => 'afterSave',
44 44
            ActiveRecord::EVENT_AFTER_UPDATE  => 'afterSave',
45 44
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
46 44
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
47 44
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
48 44
        ];
49
    }
50
51
    /**
52
     * @internal
53
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
54
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
55
     */
56 15
    public function beforeSave($insert)
57
    {
58 15
        foreach ($this->attributes as $attribute => $options) {
59 15
            $oldValue = $this->owner->isNewRecord ? null : $this->owner->getOldAttribute($attribute);
60 15
            $isAttributeChanged = $oldValue === null ? true : $this->owner->isAttributeChanged($attribute);
61
62 15
            $this->attributes[$attribute]['isAttributeChanged'] = $isAttributeChanged;
63 15
            $this->attributes[$attribute]['oldValue'] = $oldValue;
64 15
        }
65 15
    }
66
67
    /**
68
     * @internal
69
     */
70 15
    public function afterSave()
71
    {
72 15
        foreach ($this->attributes as $attribute => $options) {
73 15
            $fileId = $this->owner->{$attribute};
74 15
            $isAttributeNotChanged = $options['isAttributeChanged'] === false || $fileId === null;
75
76 15
            if ($isAttributeNotChanged) {
77 15
                continue;
78
            }
79
80 14
            $ownerId = $this->owner->primaryKey;
81 14
            $ownerType = $this->getFileOwnerType($attribute);
82
83 14
            if ($fileId === [] || $fileId === '') {
84 3
                $this->deleteFiles($attribute, $ownerId, $ownerType);
85 3
                continue;
86
            }
87
88 13
            if ($this->getFileOption($attribute, 'multiple')) {
89 4
                $this->bindMultiple($attribute, $ownerId, $ownerType, $fileId);
90 4
                return;
91
            }
92
93 9
            $file = $this->bindSingle($attribute, $ownerId, $ownerType, $fileId);
94 9
            if ($this->getFileOption($attribute, 'saveFilePath')) {
95 9
                $this->owner->updateAttributes([
96 9
                    $attribute => $this->getFilePathValue($file, $options['oldValue'])
97 9
                ]);
98 9
            }
99 12
        }
100 12
    }
101
102
    /**
103
     * @internal
104
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
105
     */
106 1
    public function beforeDelete()
107
    {
108 1
        foreach ($this->attributes as $attribute => $options) {
109 1
            $this->deleteFiles($attribute, $this->owner->primaryKey, $this->getFileOwnerType($attribute));
110 1
        }
111 1
    }
112
113
    /**
114
     * Delete files
115
     *
116
     * @param string $attribute Attribute name of the model
117
     * @param int $ownerId The id of the owner
118
     * @param int $ownerType The type of the owner
119
     */
120 4
    private function deleteFiles($attribute, $ownerId, $ownerType)
121
    {
122 4
        $storage = $this->getFileStorage($attribute);
123 4
        $realPath = $this->getFileOption($attribute, 'basePath');
124
125 4
        $files = File::findAllByOwner($ownerId, $ownerType);
126 4
        foreach ($files as $file) {
127 3
            if ($file->delete()) {
128 3
                $storage->deleteDir($realPath . $file->path());
129 3
            }
130 4
        }
131 4
    }
132
133
    /**
134
     * Delete current files
135
     *
136
     * @param Storage $storage
0 ignored issues
show
Bug introduced by
There is no parameter named $storage. 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...
137
     * @param int $ownerId The id of the owner
138
     * @param int $ownerType The type of the owner
139
     * @param rkit\filemanager\models\File[] $exceptFiles
140
     */
141 12
    private function deleteCurrentFiles($attribute, $ownerId, $ownerType, $exceptFiles = [])
142
    {
143 12
        $storage = $this->getFileStorage($attribute);
144 12
        $realPath = $this->getFileOption($attribute, 'basePath');
145
146 12
        $currentFiles = File::findAllByOwner($ownerId, $ownerType);
147 12
        foreach ($currentFiles as $currFile) {
148 5
            $isExceptFiles = count($exceptFiles) && array_key_exists($currFile->id, $exceptFiles);
149 5
            if (!$isExceptFiles) {
150 3
                if ($currFile->delete()) {
151 3
                    $storage->deleteDir($realPath . $currFile->path());
152 3
                }
153 4
            }
154 12
        }
155 12
    }
156
157
    /**
158
     * Bind the file to the with owner
159
     * @internal
160
     *
161
     * @param Storage $storage The storage for the file
0 ignored issues
show
Bug introduced by
There is no parameter named $storage. 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...
162
     * @param int $ownerId The id of the owner
163
     * @param int $ownerType The type of the owner
164
     * @param int $fileId The id of the file
165
     * @return rkit\filemanager\models\File|bool
166
     */
167 9
    private function bindSingle($attribute, $ownerId, $ownerType, $fileId)
168
    {
169 9
        $file = File::findOne($fileId);
170 9
        if ($file && $file->checkAccess($ownerId, $ownerType)) {
0 ignored issues
show
Bug introduced by
The method checkAccess cannot be called on $file (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
171 8
            $file->tmp = false;
172 8
            $file->owner_id = $ownerId;
173 8
            $this->deleteCurrentFiles($attribute, $ownerId, $ownerType, [$file->id => $file]);
0 ignored issues
show
Documentation introduced by
array($file->id => $file) is of type array<?,array|boolean>, but the function expects a array<integer,object<rki...lemanager\models\File>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
174 8
            $file->updateAttributes($file->getDirtyAttributes());
0 ignored issues
show
Bug introduced by
The method getDirtyAttributes cannot be called on $file (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug introduced by
The method updateAttributes cannot be called on $file (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
175
176 8
            return $file;
177
        }
178
179 2
        return false;
180
    }
181
182
    /**
183
     * Bind files to the with owner
184
     *
185
     * @param Storage $storage The storage for the files
0 ignored issues
show
Bug introduced by
There is no parameter named $storage. 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...
186
     * @param int $ownerId The id of the owner
187
     * @param int $ownerType The type of the owner
188
     * @param array $files Array of ids
189
     * @return rkit\filemanager\models\File[]|bool
190
     * @SuppressWarnings(PHPMD.ElseExpression)
191
     */
192 4
    private function bindMultiple($attribute, $ownerId, $ownerType, $files)
193
    {
194 4
        $newFiles = ArrayHelper::index(File::findAll(array_keys($files)), 'id');
195 4
        if (count($newFiles)) {
196 3
            foreach ($newFiles as $fileId => $file) {
197 3
                if ($file->checkAccess($ownerId, $ownerType)) {
198 3
                    $file->tmp = false;
199 3
                    $file->owner_id = $ownerId;
200 3
                    $file->position = (int)array_search($file->id, array_keys($files)) + 1;
201 3
                    $file->title = ArrayHelper::getValue($files, $file->id, $file->title);
202 3
                    $file->updateAttributes($file->getDirtyAttributes());
203
204 3
                    continue;
205
                }
206 1
                unset($newFiles[$fileId]);
207 1
                continue;
208 3
            }
209 3
            $this->deleteCurrentFiles($attribute, $ownerId, $ownerType, $newFiles);
210 3
        } else {
211 1
            $this->deleteCurrentFiles($attribute, $ownerId, $ownerType);
212
        }
213 4
        return count($newFiles) ? $newFiles : false;
214
    }
215
216
    /**
217
     * Get the path of the file
218
     *
219
     * @param mixed $file
220
     * @param mixed $oldValue
0 ignored issues
show
Bug introduced by
There is no parameter named $oldValue. 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...
221
     * @return string
222
     */
223 9
    private function getFilePathValue($file, $defaultValue)
224
    {
225 9
        if (is_object($file)) {
226 8
            return $file->path();
227 2
        } elseif ($file === false && $defaultValue !== null) {
228 1
            return $defaultValue;
229
        }
230 1
        return '';
231
    }
232
233
    /**
234
     * Get the storage of the file
235
     *
236
     * @param string $attribute Attribute name of the model
237
     * @return Storage
238
     * @throws InvalidParamException
239
     */
240 28
    private function getFileStorage($attribute)
241
    {
242 28
        $storage = $this->getFileOption($attribute, 'storage');
243 28
        if ($storage) {
244 28
            return new $storage();
245
        }
246
247
        throw new InvalidParamException('The storage is not defined'); // @codeCoverageIgnore
248
    }
249
250
    /**
251
     * Generate a thumb
252
     *
253
     * @param string $attribute Attribute name of the model
254
     * @param string $preset The name of the preset
255
     * @param string $path File path
256
     * @return string
257
     */
258 18
    private function generateThumb($attribute, $preset, $path)
259
    {
260 18
        $realPath = $this->getFileOption($attribute, 'basePath');
261 18
        $thumbPath = $this->generateThumbName($path, $preset);
262
263 18
        if (!file_exists($realPath . $thumbPath)) {
264 18
            if (file_exists($realPath . $path)) {
265 18
                $thumbInit = ArrayHelper::getValue($this->attributes[$attribute]['preset'], $preset);
266 18
                if ($thumbInit) {
267 18
                    $thumbInit($realPath, $path, $thumbPath);
268 18
                }
269 18
            }
270 18
        }
271
272 18
        return $thumbPath;
273
    }
274
275
    /**
276
     * Prepare a new file
277
     *
278
     * @param string $attribute Attribute name of the model
279
     * @param string $path File path
280
     * @param string $title File title
281
     * @return File
282
     */
283 26
    private function prepareFile($attribute, $path, $title)
284
    {
285 26
        $isAllowEmptyOwnerId = $this->getFileOption($attribute, 'allowEmptyOwnerId');
286
287 26
        $tmp = true;
288 26
        if ($this->owner->primaryKey !== null || $isAllowEmptyOwnerId) {
289 3
            $tmp = false;
290 3
        }
291
292 26
        $file = new File();
293 26
        $file->tmp = $tmp;
294 26
        $file->title = $title;
295 26
        $file->owner_id = $this->owner->primaryKey;
296 26
        $file->owner_type = $this->getFileOwnerType($attribute);
297 26
        $file->fillMetaInfo($path);
298
299 26
        return $file;
300
    }
301
302
    /**
303
     * Get file option
304
     *
305
     * @param string $attribute Attribute name of the model
306
     * @param string $option Option name
307
     * @param mixed $defaultValue Default value
308
     * @return mixed
309
     */
310 41
    public function getFileOption($attribute, $option, $defaultValue = null)
311
    {
312 41
        return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
313
    }
314
315
    /**
316
     * Get file path
317
     *
318
     * @param string $attribute Attribute name of the model
319
     * @return string
320
     */
321 22
    public function getFilePath($attribute)
322
    {
323 22
        return $this->getFileOption($attribute, 'basePath') . $this->owner->{$attribute};
324
    }
325
326
    /**
327
     * Get file url
328
     *
329
     * @param string $attribute Attribute name of the model
330
     * @return string
331
     */
332 4
    public function getFileUrl($attribute)
333
    {
334 4
        return $this->getFileOption($attribute, 'baseUrl') . $this->owner->{$attribute};
335
    }
336
337
    /**
338
     * Get the type of the owner
339
     *
340
     * @param string $attribute Attribute name of the model
341
     * @return int
342
     */
343 29
    public function getFileOwnerType($attribute)
344
    {
345 29
        return Yii::$app->fileManager->getOwnerType($this->owner->tableName() . '.' . $attribute);
346
    }
347
348
    /**
349
     * Get files
350
     *
351
     * @param string $attribute Attribute name of the model
352
     * @return array
353
     */
354 4
    public function getFiles($attribute)
355
    {
356 4
        return File::findAllByOwner($this->owner->primaryKey, $this->getFileOwnerType($attribute));
357
    }
358
359
    /**
360
     * Get the file
361
     *
362
     * @param string $attribute Attribute name of the model
363
     * @return File|null
364
     */
365 8
    public function getFile($attribute)
366
    {
367 8
        return File::findOneByOwner($this->owner->primaryKey, $this->getFileOwnerType($attribute));
368
    }
369
370
    /**
371
     * Get rules
372
     *
373
     * @param string $attribute Attribute name of the model
374
     * @param bool $onlyCoreValidators Only core validators
375
     * @return array
376
     */
377 21
    public function getFileRules($attribute, $onlyCoreValidators = false)
378
    {
379 21
        $rules = $this->getFileOption($attribute, 'rules', []);
380 21
        if ($onlyCoreValidators && isset($rules['imageSize'])) {
381 16
            $rules = array_merge($rules, $rules['imageSize']);
382 16
            unset($rules['imageSize']);
383 16
        }
384 21
        return $rules;
385
    }
386
387
    /**
388
     * Get the presets of the file for apply after upload
389
     *
390
     * @param string $attribute Attribute name of the model
391
     * @return array
392
     */
393 18
    public function getFilePresetAfterUpload($attribute)
394
    {
395 18
        $preset = $this->getFileOption($attribute, 'applyPresetAfterUpload');
396 18
        if (is_string($preset) && $preset === '*') {
397 1
            return array_keys($this->getFileOption($attribute, 'preset', []));
398 17
        } elseif (is_array($preset)) {
399 13
            return $preset;
400
        }
401
402 4
        return [];
403
    }
404
405
    /**
406
     * Get a description of the validation rules in as text
407
     *
408
     * Example
409
     *
410
     * ```php
411
     * $form->field($model, $attribute)->hint($model->getFileRulesDescription($attribute)
412
     * ```
413
     *
414
     * Output
415
     *
416
     * ```
417
     * Min. size of image: 300x300px
418
     * File types: JPG, JPEG, PNG
419
     * Max. file size: 1.049 MB
420
     * ```
421
     *
422
     * @param string $attribute Attribute name of the model
423
     * @return string
424
     */
425 9
    public function getFileRulesDescription($attribute)
426
    {
427 9
        return FormatValidation::getDescription($this->getFileOption($attribute, 'rules', []));
428
    }
429
430
    /**
431
     * Generate a thumb name
432
     *
433
     * @param string $path The path of the file
434
     * @param string $prefix Prefix for name of the file
435
     * @return string
436
     */
437 18
    public function generateThumbName($path, $prefix)
438
    {
439 18
        $fileName = pathinfo($path, PATHINFO_FILENAME);
440 18
        return str_replace($fileName, $prefix . '_' . $fileName, $path);
441
    }
442
443
    /**
444
     * Create a thumb
445
     *
446
     * @param string $attribute Attribute name of the model
447
     * @param string $preset The name of the preset
448
     * @return string
449
     */
450 18
    public function thumb($attribute, $preset)
451
    {
452 18
        return $this->generateThumb($attribute, $preset, $this->owner->$attribute);
453
    }
454
455
    /**
456
     * Create a thumb and return full path
457
     *
458
     * @param string $attribute Attribute name of the model
459
     * @param string $preset The name of the preset
460
     * @return string
461
     */
462 2
    public function thumbFullPath($attribute, $preset)
463
    {
464 2
        $realPath = $this->getFileOption($attribute, 'basePath');
465 2
        $thumbPath = $this->generateThumb($attribute, $preset, $this->owner->$attribute);
466 2
        return $realPath . $thumbPath;
467
    }
468
469
    /**
470
     * Create a file
471
     *
472
     * @param string $attribute Attribute name of the model
473
     * @param string $path File path
474
     * @param string $title File title
475
     * @return rkit\filemanager\models\File
476
     */
477 27
    public function createFile($attribute, $path, $title = null)
478
    {
479 27
        if (!file_exists($path)) {
480 1
            return false;
481
        }
482
483 26
        $file = $this->prepareFile($attribute, $path, $title);
484 26
        if ($file->save()) {
485 26
            $storage = $this->getFileStorage($attribute);
486 26
            $realPath = $this->getFileOption($attribute, 'basePath');
487 26
            if ($storage->saveFile($path, $realPath . $file->path())) {
488 26
                $this->owner->{$attribute} = $file->path();
489 26
                return $file;
490
            }
491
        } // @codeCoverageIgnore
492
        return false; // @codeCoverageIgnore
493
    }
494
}
495