Completed
Push — master ( 3b1f1a...f15f2a )
by Igor
02:55
created

FileBehavior::saveFileIdInAttribute()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 2
cts 2
cp 1
rs 9.2
cc 4
eloc 7
nc 3
nop 3
crap 4
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\db\Query;
15
use yii\helpers\ArrayHelper;
16
use yii\base\InvalidParamException;
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
    public function init()
30
    {
31
        parent::init();
32
33
        Yii::$app->fileManager->registerTranslations();
34 49
    }
35
36 49
    /**
37
     * @inheritdoc
38 49
     * @internal
39 49
     */
40 49
    public function events()
41
    {
42
        return [
43
            ActiveRecord::EVENT_AFTER_INSERT  => 'afterSave',
44
            ActiveRecord::EVENT_AFTER_UPDATE  => 'afterSave',
45
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
46 49
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
47
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
48 49
        ];
49 49
    }
50
51
    /**
52
     * @internal
53
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
54
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
55 49
     */
56
    public function beforeSave($insert)
57
    {
58 49
        foreach ($this->attributes as $attribute => $options) {
59 49
            $oldValue = $this->owner->isNewRecord ? null : $this->owner->getOldAttribute($attribute);
60 49
            $isAttributeChanged = $oldValue === null ? true : $this->owner->isAttributeChanged($attribute);
61 49
62 49
            $this->attributes[$attribute]['isAttributeChanged'] = $isAttributeChanged;
63 49
            $this->attributes[$attribute]['oldValue'] = $oldValue;
64
        }
65
    }
66
67
    /**
68
     * @internal
69
     */
70
    public function afterSave()
71 49
    {
72
        foreach ($this->attributes as $attribute => $options) {
73 49
            $files = $this->owner->{$attribute};
74 23
            $files = is_numeric($files) && $files ? [$files] : $files;
75 23
76
            $isAttributeNotChanged = $options['isAttributeChanged'] === false || $files === null;
77 23
            if ($isAttributeNotChanged) {
78 23
                continue;
79 23
            }
80 23
81
            if ($files === [] || $files === '') {
82
                $this->deleteFiles($attribute);
0 ignored issues
show
Bug introduced by
The call to deleteFiles() misses a required argument $files.

This check looks for function calls that miss required arguments.

Loading history...
83
                continue;
84
            }
85 23
86
            $files = $this->bindFiles($attribute, $files);
87 23
88 23
            if ($this->getFileOption($attribute, 'saveFilePathInAttribute')) {
89
                $this->saveFilePathInAttribute($attribute, array_shift($files), $options['oldValue']);
90 23
            } elseif ($this->getFileOption($attribute, 'saveFileIdInAttribute')) {
91 23
                $this->saveFileIdInAttribute($attribute, array_shift($files), $options['oldValue']);
92
            }
93
        }
94 23
    }
95 23
96 23
    /**
97
     * @internal
98 23
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
99 2
     */
100 2
    public function beforeDelete()
101
    {
102
        foreach ($this->attributes as $attribute => $options) {
103 23
            $this->deleteFiles($attribute, $this->owner->files);
104 23
        }
105 23
    }
106
107
    /**
108
     * Delete current files
109
     *
110
     * @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...
111 1
     * @param rkit\filemanager\models\File[] $exceptFiles
0 ignored issues
show
Bug introduced by
There is no parameter named $exceptFiles. 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...
112
     */
113 1
    private function deleteFiles($attribute, $files)
114 1
    {
115 1
        $storage = $this->getFileStorage($attribute);
116 1
        $realPath = $this->getFileOption($attribute, 'basePath');
117 1
        $templatePath = $this->getFileOption($attribute, 'templatePath');
118 1
119
        foreach ($files as $file) {
120
            if ($file->delete()) {
121
                $storage->deleteDir($realPath . $templatePath($file));
122
            }
123
        }
124
    }
125
126
    private function bindFiles($attribute, $files)
127
    {
128
        $relation = $this->owner->getRelation('files');
129
        $linkTable = $relation->via->from[0];
130
        $fileModel = $relation->modelClass;
131 23
132
        // link field of model in link table
133 23
        $viaField = key($relation->via->link);
134 3
        // link field of file in link table
135 3
        $linkField = current($relation->link);
136
        // primary key of file table
137 20
        $linkPrimaryField = key($relation->link);
138
139 20
        $ownerId = $this->owner->getPrimaryKey();
140 14
        $extraFields = $this->getFileOption($attribute, 'extraFields');
141 20
        $onUpdateFile = $this->getFileOption($attribute, 'onUpdateFile');
142 6
143 6
        $newFiles = ArrayHelper::index($fileModel::findAll($files), $linkPrimaryField);
144
        $newLinks = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
145 20
146 20
        $currentFiles = ArrayHelper::index($this->getFiles($attribute), $linkPrimaryField);
147 20
        $currentFilesIds = ArrayHelper::getColumn($currentFiles, $linkPrimaryField);
148 20
        $currentLinks = (new Query())
149
            ->select('*')
150
            ->from($linkTable)
151
            ->where([$viaField => $ownerId, $linkField => $currentFilesIds])
152
            ->all();
153
154
        $resultFiles = [];
155
156
        $insertRows = [];
157 14
        foreach ($newLinks as $attribute => $items) {
158
            foreach ($items as $fileId) {
159 14
                if (isset($newFiles[$fileId])) {
160 12
                    $file = $newFiles[$fileId];
161 4
                    $link = [$viaField => $ownerId, $linkField => $fileId];
162 2
                    $extra = $extraFields($this->owner, $attribute, $file, $link);
163
                    $insertRows[] = array_merge([$ownerId, $fileId], array_values($extra));
164
                    $resultFiles[$file->getPrimaryKey()] = $file;
165 2
                }
166
            }
167
        }
168
169
        $updateRows = [];
170
        foreach ($currentLinks as $link) {
171
            if (isset($newFiles[$link[$linkField]])) {
172
                $file = $newFiles[$link[$linkField]];
173
                $updateRows[$file->getPrimaryKey()] = $extraFields($this->owner, $attribute, $file, $link);
174
                $resultFiles[$file->getPrimaryKey()] = $file;
175 6
            }
176
        }
177 6
178 4
        $this->deleteFiles($attribute, array_diff_key($newFiles, $resultFiles));
179 3
180 1
        if (count($insertRows)) {
181
            $columns = [$viaField, $linkField];
182
            $columns = array_merge($columns, array_keys($extra));
0 ignored issues
show
Bug introduced by
The variable $extra does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
183 2
184
            Yii::$app->getDb()->createCommand()
185
                ->batchInsert($linkTable, $columns, $insertRows)
186
                ->execute();
187
        }
188
189
        if (count($updateRows)) {
190
            foreach ($updateRows as $fileId => $row) {
191
                Yii::$app->getDb()->createCommand()
192 25
                    ->update($linkTable, $row, [$viaField => $ownerId, $linkField => $fileId])
193
                    ->execute();
194 25
            }
195 6
        }
196
197 19
        if ($onUpdateFile) {
198
            foreach ($resultFiles as $file) {
199
                $onUpdateFile($file);
200
            }
201
        }
202
203
        return $resultFiles;
204
    }
205
206 36
    /**
207
     * Get the path of the file
208 36
     *
209
     * @param mixed $file
210
     * @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...
211
     * @return string
212
     */
213
    private function saveFilePathInAttribute($attribute, $file, $defaultValue)
214
    {
215
        if (is_object($file)) {
216
            $templatePath = $this->getFileOption($attribute, 'templatePath');
217 36
            $path = $templatePath($file);
218
            $this->owner->{$attribute} = $path;
219 36
            $this->owner->updateAttributes([$attribute => $path]);
220
        } elseif ($file === false && $defaultValue !== null) {
221
            return $defaultValue;
222
        }
223
        return '';
224
    }
225
226
    /**
227
     * Get the path of the file
228 3
     *
229
     * @param mixed $file
230 3
     * @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...
231 3
     * @return string
232 3
     */
233 3
    private function saveFileIdInAttribute($attribute, $file, $defaultValue)
234
    {
235 3
        if (is_object($file)) {
236
            $this->owner->{$attribute} = $file->getPrimaryKey();
237
            $this->owner->updateAttributes([$attribute => $file->getPrimaryKey()]);
238
        } elseif ($file === false && $defaultValue !== null) {
239
            return $defaultValue;
240
        }
241
        return '';
242
    }
243
244 1
    /**
245
     * Generate a thumb
246 1
     *
247 1
     * @param string $attribute Attribute name of the model
248 1
     * @param string $preset The name of the preset
249
     * @param string $path File path
250
     * @return string
251
     */
252
    private function generateThumb($attribute, $preset, $path)
253
    {
254
        $realPath = $this->getFileOption($attribute, 'basePath');
255
        $thumbPath = $this->generateThumbName($path, $preset);
256
257 23
        if (!file_exists($realPath . $thumbPath)) {
258
            if (file_exists($realPath . $path)) {
259 23
                $thumbInit = ArrayHelper::getValue($this->attributes[$attribute]['preset'], $preset);
260
                if ($thumbInit) {
261
                    $thumbInit($realPath, $path, $thumbPath);
262
                }
263
            }
264
        }
265
266
        return $thumbPath;
267
    }
268 35
269
    /**
270 35
     * Get file option
271
     *
272
     * @param string $attribute Attribute name of the model
273
     * @param string $option Option name
274
     * @param mixed $defaultValue Default value
275
     * @return mixed
276
     */
277
    public function getFileOption($attribute, $option, $defaultValue = null)
278
    {
279
        return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
280 28
    }
281
282 28
    /**
283 28
     * Get the storage of the file
284 24
     *
285 24
     * @param string $attribute Attribute name of the model
286 24
     * @return Storage
287 28
     * @throws InvalidParamException
288
     */
289
    public function getFileStorage($attribute)
290
    {
291
        $storage = $this->getFileOption($attribute, 'storage');
292
        if ($storage) {
293
            return new $storage();
294
        }
295
        throw new InvalidParamException('The storage is not defined'); // @codeCoverageIgnore
296 6
    }
297
298 6
    /**
299
     * Get file path
300
     *
301
     * @param string $attribute Attribute name of the model
302
     * @return string
303
     */
304
    public function getTemplatePath($attribute, $file = null)
305
    {
306
        $needFile = empty($this->owner->{$attribute}) || is_numeric($this->owner->{$attribute});
307 25
        if ($needFile && $file === null) {
308
            $file = $this->getFile($attribute);
309 25
        }
310 25
        if ($file !== null) {
311 6
            $templatePath = $this->getFileOption($attribute, 'templatePath');
312 19
            return $templatePath($file);
313 16
        }
314
        return $this->owner->{$attribute};
315
    }
316 3
317
    /**
318
     * Get file path
319
     *
320
     * @param string $attribute Attribute name of the model
321
     * @return string
322
     */
323
    public function getFilePath($attribute, $file = null)
324
    {
325
        $path = $this->getTemplatePath($attribute, $file);
326 35
        return $this->getFileOption($attribute, 'basePath') . $path;
327
    }
328 35
329 35
    /**
330 35
     * Get file url
331
     *
332
     * @param string $attribute Attribute name of the model
333
     * @return string
334
     */
335
    public function getFileUrl($attribute, $file = null)
336
    {
337
        $path = $this->getTemplatePath($attribute, $file);
338
        return $this->getFileOption($attribute, 'baseUrl') . $path;
339
    }
340
341
    /**
342
     * Get files
343 25
     *
344
     * @param string $attribute Attribute name of the model
345 25
     * @return array
346 25
     */
347
    public function getFiles($attribute)
348
    {
349
        $relationQuery = $this->getFileOption($attribute, 'relationQuery');
350
        return $this->owner->getFiles($relationQuery)->all();
351
    }
352
353
    /**
354
     * Get the file
355
     *
356
     * @param string $attribute Attribute name of the model
357
     * @return File|null
358 25
     */
359
    public function getFile($attribute)
360 25
    {
361 25
        $relationQuery = $this->getFileOption($attribute, 'relationQuery');
362 25
        return $this->owner->getFiles($relationQuery)->one();
363
    }
364 25
365 25
    /**
366 25
     * Get rules
367 25
     *
368 25
     * @param string $attribute Attribute name of the model
369 25
     * @param bool $onlyCoreValidators Only core validators
370 25
     * @return array
371 25
     */
372
    public function getFileRules($attribute, $onlyCoreValidators = false)
373 25
    {
374
        $rules = $this->getFileOption($attribute, 'rules', []);
375
        if ($onlyCoreValidators && isset($rules['imageSize'])) {
376
            $rules = array_merge($rules, $rules['imageSize']);
377
            unset($rules['imageSize']);
378
        }
379
        return $rules;
380
    }
381
382
    /**
383
     * Get the presets of the file for apply after upload
384
     *
385 35
     * @param string $attribute Attribute name of the model
386
     * @return array
387 35
     */
388 35
    public function getFilePresetAfterUpload($attribute)
389 35
    {
390 35
        $preset = $this->getFileOption($attribute, 'applyPresetAfterUpload');
391 35
        if (is_string($preset) && $preset === '*') {
392 35
            return array_keys($this->getFileOption($attribute, 'preset', []));
393 35
        } elseif (is_array($preset)) {
394
            return $preset;
395 35
        }
396 34
397 34
        return [];
398
    }
399
400
    /**
401
     * Get a description of the validation rules in as text
402
     *
403
     * Example
404
     *
405
     * ```php
406
     * $form->field($model, $attribute)->hint($model->getFileRulesDescription($attribute)
407
     * ```
408
     *
409
     * Output
410
     *
411
     * ```
412
     * Min. size of image: 300x300px
413
     * File types: JPG, JPEG, PNG
414
     * Max. file size: 1.049 MB
415
     * ```
416
     *
417
     * @param string $attribute Attribute name of the model
418
     * @return string
419
     */
420
    public function getFileRulesDescription($attribute)
421
    {
422
        return FormatValidation::getDescription($this->getFileOption($attribute, 'rules', []));
423 9
    }
424
425 9
    /**
426
     * Generate a thumb name
427
     *
428
     * @param string $path The path of the file
429
     * @param string $prefix Prefix for name of the file
430
     * @return string
431
     */
432
    public function generateThumbName($path, $prefix)
433
    {
434
        $fileName = pathinfo($path, PATHINFO_FILENAME);
435
        return str_replace($fileName, $prefix . '_' . $fileName, $path);
436
    }
437
438
    /**
439
     * Create a thumb
440
     *
441
     * @param string $attribute Attribute name of the model
442
     * @param string $preset The name of the preset
443
     * @return string
444
     */
445
    public function thumb($attribute, $preset, $file = null)
446
    {
447
        $path = $this->getTemplatePath($attribute, $file);
448
        return $this->generateThumb($attribute, $preset, $path);
449
    }
450
451
    /**
452
     * Create a thumb and return full path
453
     *
454
     * @param string $attribute Attribute name of the model
455
     * @param string $preset The name of the preset
456
     * @return string
457
     */
458
    public function thumbFullPath($attribute, $preset, $file = null)
459
    {
460
        $path = $this->getTemplatePath($attribute, $file);
461
        $realPath = $this->getFileOption($attribute, 'basePath');
462
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
463
        return $realPath . $thumbPath;
464
    }
465
}
466