GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#49)
by Alexander
01:10
created

UploadBehavior::events()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
3
namespace mohorev\file;
4
5
use Closure;
6
use Yii;
7
use yii\base\Behavior;
8
use yii\base\InvalidArgumentException;
9
use yii\base\InvalidConfigException;
10
use yii\db\BaseActiveRecord;
11
use yii\helpers\ArrayHelper;
12
use yii\helpers\FileHelper;
13
use yii\web\UploadedFile;
14
15
/**
16
 * UploadBehavior automatically uploads file and fills the specified attribute
17
 * with a value of the name of the uploaded file.
18
 *
19
 * To use UploadBehavior, insert the following code to your ActiveRecord class:
20
 *
21
 * ```php
22
 * use mohorev\file\UploadBehavior;
23
 *
24
 * function behaviors()
25
 * {
26
 *     return [
27
 *         [
28
 *             'class' => UploadBehavior::class,
29
 *             'attribute' => 'file',
30
 *             'scenarios' => ['insert', 'update'],
31
 *             'path' => '@webroot/upload/{id}',
32
 *             'url' => '@web/upload/{id}',
33
 *         ],
34
 *     ];
35
 * }
36
 * ```
37
 *
38
 * @author Alexander Mohorev <[email protected]>
39
 * @author Alexey Samoylov <[email protected]>
40
 */
41
class UploadBehavior extends Behavior
42
{
43
    /**
44
     * @event Event an event that is triggered after a file is uploaded.
45
     */
46
    const EVENT_AFTER_UPLOAD = 'afterUpload';
47
48
    /**
49
     * @var string the attribute which holds the attachment.
50
     */
51
    public $attribute;
52
    /**
53
     * @var array the scenarios in which the behavior will be triggered
54
     */
55
    public $scenarios = [];
56
    /**
57
     * @var string the base path or path alias to the directory in which to save files.
58
     */
59
    public $path;
60
    /**
61
     * @var string the base URL or path alias for this file
62
     */
63
    public $url;
64
    /**
65
     * @var bool Getting file instance by name
66
     */
67
    public $instanceByName = false;
68
    /**
69
     * @var boolean|callable generate a new unique name for the file
70
     * set true or anonymous function takes the old filename and returns a new name.
71
     * @see self::generateFileName()
72
     */
73
    public $generateNewName = true;
74
    /**
75
     * @var boolean If `true` current attribute file will be deleted
76
     */
77
    public $unlinkOnSave = true;
78
    /**
79
     * @var boolean If `true` current attribute file will be deleted after model deletion.
80
     */
81
    public $unlinkOnDelete = true;
82
    /**
83
     * @var boolean $deleteTempFile whether to delete the temporary file after saving.
84
     */
85
    public $deleteTempFile = true;
86
    /**
87
     * @var boolean $deleteEmptyDir whether to delete the empty directory after model deletion.
88
     */
89
    public $deleteEmptyDir = false;
90
91
    /**
92
     * @var UploadedFile the uploaded file instance.
93
     */
94
    private $_file;
95
96
97
    /**
98
     * @inheritdoc
99
     */
100
    public function init()
101
    {
102
        parent::init();
103
104
        if ($this->attribute === null) {
105
            throw new InvalidConfigException('The "attribute" property must be set.');
106
        }
107
        if ($this->path === null) {
108
            throw new InvalidConfigException('The "path" property must be set.');
109
        }
110
        if ($this->url === null) {
111
            throw new InvalidConfigException('The "url" property must be set.');
112
        }
113
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118
    public function events()
119
    {
120
        return [
121
            BaseActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
122
            BaseActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
123
            BaseActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
124
            BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
125
            BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
126
            BaseActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
127
        ];
128
    }
129
130
    /**
131
     * This method is invoked before validation starts.
132
     */
133
    public function beforeValidate()
134
    {
135
        /** @var BaseActiveRecord $model */
136
        $model = $this->owner;
137
        if (in_array($model->scenario, $this->scenarios)) {
138
            if (($file = $model->getAttribute($this->attribute)) instanceof UploadedFile) {
139
                $this->_file = $file;
140
            } else {
141
                if ($this->instanceByName === true) {
142
                    $this->_file = UploadedFile::getInstanceByName($this->attribute);
143
                } else {
144
                    $this->_file = UploadedFile::getInstance($model, $this->attribute);
145
                }
146
            }
147
            if ($this->_file instanceof UploadedFile) {
148
                $this->_file->name = $this->getFileName($this->_file);
149
                $model->setAttribute($this->attribute, $this->_file);
150
            }
151
        }
152
    }
153
154
    /**
155
     * This method is called at the beginning of inserting or updating a record.
156
     */
157
    public function beforeSave()
158
    {
159
        /** @var BaseActiveRecord $model */
160
        $model = $this->owner;
161
        if (in_array($model->scenario, $this->scenarios)) {
162
            if ($this->_file instanceof UploadedFile) {
163
                if (!$model->getIsNewRecord() && $model->isAttributeChanged($this->attribute)) {
164
                    if ($this->unlinkOnSave === true) {
165
                        $this->delete($this->attribute, true);
166
                    }
167
                }
168
                $model->setAttribute($this->attribute, $this->_file->name);
169
            } else {
170
                // Protect attribute
171
                unset($model->{$this->attribute});
172
            }
173
        } else {
174
            if (!$model->getIsNewRecord() && $model->isAttributeChanged($this->attribute)) {
175
                if ($this->unlinkOnSave === true) {
176
                    $this->delete($this->attribute, true);
177
                }
178
            }
179
        }
180
    }
181
182
    /**
183
     * This method is called at the end of inserting or updating a record.
184
     * @throws \yii\base\InvalidArgumentException
185
     */
186
    public function afterSave()
187
    {
188
        if ($this->_file instanceof UploadedFile) {
189
            $path = $this->getUploadPath($this->attribute);
190
            if (is_string($path) && FileHelper::createDirectory(dirname($path))) {
191
                $this->save($this->_file, $path);
192
                $this->afterUpload();
193
            } else {
194
                throw new InvalidArgumentException("Directory specified in 'path' attribute doesn't exist or cannot be created.");
195
            }
196
        }
197
    }
198
199
    /**
200
     * This method is invoked after deleting a record.
201
     */
202
    public function afterDelete()
203
    {
204
        $attribute = $this->attribute;
205
        if ($this->unlinkOnDelete && $attribute) {
206
            $this->delete($attribute);
207
        }
208
    }
209
210
    /**
211
     * Returns file path for the attribute.
212
     * @param string $attribute
213
     * @param boolean $old
214
     * @return string|null the file path.
215
     */
216
    public function getUploadPath($attribute, $old = false)
217
    {
218
        /** @var BaseActiveRecord $model */
219
        $model = $this->owner;
220
        $path = $this->resolvePath($this->path);
221
        $fileName = ($old === true) ? $model->getOldAttribute($attribute) : $model->$attribute;
222
223
        return $fileName ? Yii::getAlias($path . '/' . $fileName) : null;
224
    }
225
226
    /**
227
     * Returns file url for the attribute.
228
     * @param string $attribute
229
     * @return string|null
230
     */
231
    public function getUploadUrl($attribute)
232
    {
233
        /** @var BaseActiveRecord $model */
234
        $model = $this->owner;
235
        $url = $this->resolvePath($this->url);
236
        $fileName = $model->getOldAttribute($attribute);
237
238
        return $fileName ? Yii::getAlias($url . '/' . $fileName) : null;
239
    }
240
241
    /**
242
     * Returns the UploadedFile instance.
243
     * @return UploadedFile
244
     */
245
    protected function getUploadedFile()
246
    {
247
        return $this->_file;
248
    }
249
250
    /**
251
     * Replaces all placeholders in path variable with corresponding values.
252
     */
253
    protected function resolvePath($path)
254
    {
255
        /** @var BaseActiveRecord $model */
256
        $model = $this->owner;
257
        return preg_replace_callback('/{([^}]+)}/', function ($matches) use ($model) {
258
            $name = $matches[1];
259
            $attribute = ArrayHelper::getValue($model, $name);
260
            if (is_string($attribute) || is_numeric($attribute)) {
261
                return $attribute;
262
            } else {
263
                return $matches[0];
264
            }
265
        }, $path);
266
    }
267
268
    /**
269
     * Saves the uploaded file.
270
     * @param UploadedFile $file the uploaded file instance
271
     * @param string $path the file path used to save the uploaded file
272
     * @return boolean true whether the file is saved successfully
273
     */
274
    protected function save($file, $path)
275
    {
276
        return $file->saveAs($path, $this->deleteTempFile);
277
    }
278
279
    /**
280
     * Deletes old file.
281
     * @param string $attribute
282
     * @param boolean $old
283
     */
284
    protected function delete($attribute, $old = false)
285
    {
286
        $path = $this->getUploadPath($attribute, $old);
287
288
        if (is_file($path)) {
289
            unlink($path);
290
        }
291
292
        if ($this->deleteEmptyDir) {
293
            $dir = dirname($path);
294
295
            if (is_dir($dir) && count(scandir($dir)) == 2) {
296
                rmdir($dir);
297
            }
298
        }
299
    }
300
301
    /**
302
     * @param UploadedFile $file
303
     * @return string
304
     */
305
    protected function getFileName($file)
306
    {
307
        if ($this->generateNewName) {
308
            return $this->generateNewName instanceof Closure
309
                ? call_user_func($this->generateNewName, $file)
310
                : $this->generateFileName($file);
311
        } else {
312
            return $this->sanitize($file->name);
313
        }
314
    }
315
316
    /**
317
     * Replaces characters in strings that are illegal/unsafe for filename.
318
     *
319
     * #my*  unsaf<e>&file:name?".png
320
     *
321
     * @param string $filename the source filename to be "sanitized"
322
     * @return boolean string the sanitized filename
323
     */
324
    public static function sanitize($filename)
325
    {
326
        return str_replace([' ', '"', '\'', '&', '/', '\\', '?', '#'], '-', $filename);
327
    }
328
329
    /**
330
     * Generates random filename.
331
     * @param UploadedFile $file
332
     * @return string
333
     */
334
    protected function generateFileName($file)
335
    {
336
        return uniqid() . '.' . $file->extension;
337
    }
338
339
    /**
340
     * This method is invoked after uploading a file.
341
     * The default implementation raises the [[EVENT_AFTER_UPLOAD]] event.
342
     * You may override this method to do postprocessing after the file is uploaded.
343
     * Make sure you call the parent implementation so that the event is raised properly.
344
     */
345
    protected function afterUpload()
346
    {
347
        $this->owner->trigger(self::EVENT_AFTER_UPLOAD);
348
    }
349
}
350