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 (#54)
by
unknown
06:02
created

UploadBehavior::resolvePath()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
cc 6
nc 3
nop 1
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|callable|array Base path or path alias to the directory in which to save files,
58
     * or callable for setting up your custom path generation logic.
59
     * If $path is callable, callback signature should be as follow and return a string:
60
     *
61
     * ```php
62
     * function (\yii\db\ActiveRecord $model)
63
     * {
64
     *     // do something...
65
     *     return $string;
66
     * }
67
     * ```
68
     * If this property is set up as array, it should be, for example, like as follow ['\app\models\UserProfile', 'buildAvatarPath'],
69
     * where first element is class name, while second is its static method that should be called for path generation.
70
     *
71
     * Example:
72
     * ```php
73
     * public static function buildAvatarPath(\yii\db\ActiveRecord $model)
74
     * {
75
     *      $basePath = '@webroot/upload/avatars/';
76
     *      $suffix = implode('/', array_slice(str_split(md5($model->id), 2), 0, 2));
77
     *      return $basePath . $suffix;
78
     * }
79
     * ```
80
     */
81
    public $path;
82
    /**
83
     * @var string|callable|array Base URL or path alias for this file,
84
     * or callable for setting up your custom URL generation logic.
85
     * If $url is callable, callback signature should be as follow and return a string:
86
     *
87
     * ```php
88
     * function (\yii\db\ActiveRecord $model)
89
     * {
90
     *     // do something...
91
     *     return $string;
92
     * }
93
     * ```
94
     * If this property is set up as array, it should be, for example, like as follow ['\app\models\UserProfile', 'buildAvatarUrl'],
95
     * where first element is class name, while second is its static method that should be called for URL generation.
96
     *
97
     * Example:
98
     * ```php
99
     * public static function buildAvatarUrl(\yii\db\ActiveRecord $model)
100
     * {
101
     *      $baseUrl = '@web/upload/avatars/';
102
     *      $suffix = implode('/', array_slice(str_split(md5($model->id), 2), 0, 2));
103
     *      return $baseUrl . $suffix;
104
     * }
105
     * ```
106
     */
107
    public $url;
108
    /**
109
     * @var bool Getting file instance by name
110
     */
111
    public $instanceByName = false;
112
    /**
113
     * @var boolean|callable generate a new unique name for the file
114
     * set true or anonymous function takes the old filename and returns a new name.
115
     * @see self::generateFileName()
116
     */
117
    public $generateNewName = true;
118
    /**
119
     * @var boolean If `true` current attribute file will be deleted
120
     */
121
    public $unlinkOnSave = true;
122
    /**
123
     * @var boolean If `true` current attribute file will be deleted after model deletion.
124
     */
125
    public $unlinkOnDelete = true;
126
    /**
127
     * @var boolean $deleteTempFile whether to delete the temporary file after saving.
128
     */
129
    public $deleteTempFile = true;
130
131
    /**
132
     * @var UploadedFile the uploaded file instance.
133
     */
134
    private $_file;
135
136
137
    /**
138
     * @inheritdoc
139
     */
140
    public function init()
141
    {
142
        parent::init();
143
144
        if ($this->attribute === null) {
145
            throw new InvalidConfigException('The "attribute" property must be set.');
146
        }
147
        if ($this->path === null) {
148
            throw new InvalidConfigException('The "path" property must be set.');
149
        }
150
        if ($this->url === null) {
151
            throw new InvalidConfigException('The "url" property must be set.');
152
        }
153
    }
154
155
    /**
156
     * @inheritdoc
157
     */
158
    public function events()
159
    {
160
        return [
161
            BaseActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
162
            BaseActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
163
            BaseActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
164
            BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
165
            BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
166
            BaseActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
167
        ];
168
    }
169
170
    /**
171
     * This method is invoked before validation starts.
172
     */
173
    public function beforeValidate()
174
    {
175
        /** @var BaseActiveRecord $model */
176
        $model = $this->owner;
177
        if (in_array($model->scenario, $this->scenarios)) {
178
            if (($file = $model->getAttribute($this->attribute)) instanceof UploadedFile) {
179
                $this->_file = $file;
180
            } else {
181
                if ($this->instanceByName === true) {
182
                    $this->_file = UploadedFile::getInstanceByName($this->attribute);
183
                } else {
184
                    $this->_file = UploadedFile::getInstance($model, $this->attribute);
185
                }
186
            }
187
            if ($this->_file instanceof UploadedFile) {
188
                $this->_file->name = $this->getFileName($this->_file);
189
                $model->setAttribute($this->attribute, $this->_file);
190
            }
191
        }
192
    }
193
194
    /**
195
     * This method is called at the beginning of inserting or updating a record.
196
     */
197
    public function beforeSave()
198
    {
199
        /** @var BaseActiveRecord $model */
200
        $model = $this->owner;
201
        if (in_array($model->scenario, $this->scenarios)) {
202
            if ($this->_file instanceof UploadedFile) {
203
                if (!$model->getIsNewRecord() && $model->isAttributeChanged($this->attribute)) {
204
                    if ($this->unlinkOnSave === true) {
205
                        $this->delete($this->attribute, true);
206
                    }
207
                }
208
                $model->setAttribute($this->attribute, $this->_file->name);
209
            } else {
210
                // Protect attribute
211
                unset($model->{$this->attribute});
212
            }
213
        } else {
214
            if (!$model->getIsNewRecord() && $model->isAttributeChanged($this->attribute)) {
215
                if ($this->unlinkOnSave === true) {
216
                    $this->delete($this->attribute, true);
217
                }
218
            }
219
        }
220
    }
221
222
    /**
223
     * This method is called at the end of inserting or updating a record.
224
     * @throws \yii\base\Exception
225
     */
226
    public function afterSave()
227
    {
228
        if ($this->_file instanceof UploadedFile) {
229
            $path = $this->getUploadPath($this->attribute);
230
            if (is_string($path) && FileHelper::createDirectory(dirname($path))) {
231
                $this->save($this->_file, $path);
232
                $this->afterUpload();
233
            } else {
234
                throw new InvalidArgumentException(
235
                    "Directory specified in 'path' attribute doesn't exist or cannot be created."
236
                );
237
            }
238
        }
239
    }
240
241
    /**
242
     * This method is invoked after deleting a record.
243
     */
244
    public function afterDelete()
245
    {
246
        $attribute = $this->attribute;
247
        if ($this->unlinkOnDelete && $attribute) {
248
            $this->delete($attribute);
249
        }
250
    }
251
252
    /**
253
     * Returns file path for the attribute.
254
     * @param string $attribute
255
     * @param boolean $old
256
     * @return string|null the file path.
257
     * @throws \yii\base\InvalidConfigException
258
     */
259
    public function getUploadPath($attribute, $old = false)
260
    {
261
        /** @var BaseActiveRecord $model */
262
        $model = $this->owner;
263
        $path = $this->resolvePath($this->path);
264
        $fileName = ($old === true) ? $model->getOldAttribute($attribute) : $model->$attribute;
265
266
        return $fileName ? Yii::getAlias($path . '/' . $fileName) : null;
267
    }
268
269
    /**
270
     * Returns file url for the attribute.
271
     * @param string $attribute
272
     * @return string|null
273
     * @throws \yii\base\InvalidConfigException
274
     */
275
    public function getUploadUrl($attribute)
276
    {
277
        /** @var BaseActiveRecord $model */
278
        $model = $this->owner;
279
        $url = $this->resolvePath($this->url);
280
        $fileName = $model->getOldAttribute($attribute);
281
282
        return $fileName ? Yii::getAlias($url . '/' . $fileName) : null;
283
    }
284
285
    /**
286
     * Returns the UploadedFile instance.
287
     * @return UploadedFile
288
     */
289
    protected function getUploadedFile()
290
    {
291
        return $this->_file;
292
    }
293
294
    /**
295
     * Replaces all placeholders in path variable with corresponding values.
296
     */
297
    protected function resolvePath($path)
298
    {
299
        /** @var BaseActiveRecord $model */
300
        $model = $this->owner;
301
        if (is_string($path)) {
302
            return preg_replace_callback('/{([^}]+)}/', function ($matches) use ($model) {
303
                $name = $matches[1];
304
                $attribute = ArrayHelper::getValue($model, $name);
305
                if (is_string($attribute) || is_numeric($attribute)) {
306
                    return $attribute;
307
                } else {
308
                    return $matches[0];
309
                }
310
            }, $path);
311
        } elseif (is_callable($path) || is_array($path)) {
312
            return call_user_func($path, $model);
313
        } else {
314
            throw new InvalidArgumentException(
315
                '$path argument must be a string, array or callable: ' . gettype($path) . ' given.'
316
            );
317
        }
318
    }
319
320
    /**
321
     * Saves the uploaded file.
322
     * @param UploadedFile $file the uploaded file instance
323
     * @param string $path the file path used to save the uploaded file
324
     * @return boolean true whether the file is saved successfully
325
     */
326
    protected function save($file, $path)
327
    {
328
        return $file->saveAs($path, $this->deleteTempFile);
329
    }
330
331
    /**
332
     * Deletes old file.
333
     * @param string $attribute
334
     * @param boolean $old
335
     */
336
    protected function delete($attribute, $old = false)
337
    {
338
        $path = $this->getUploadPath($attribute, $old);
339
        if (is_file($path)) {
340
            unlink($path);
341
        }
342
    }
343
344
    /**
345
     * @param UploadedFile $file
346
     * @return string
347
     */
348
    protected function getFileName($file)
349
    {
350
        if ($this->generateNewName) {
351
            return $this->generateNewName instanceof Closure
352
                ? call_user_func($this->generateNewName, $file)
353
                : $this->generateFileName($file);
354
        } else {
355
            return $this->sanitize($file->name);
356
        }
357
    }
358
359
    /**
360
     * Replaces characters in strings that are illegal/unsafe for filename.
361
     *
362
     * #my*  unsaf<e>&file:name?".png
363
     *
364
     * @param string $filename the source filename to be "sanitized"
365
     * @return boolean string the sanitized filename
366
     */
367
    public static function sanitize($filename)
368
    {
369
        return str_replace([' ', '"', '\'', '&', '/', '\\', '?', '#'], '-', $filename);
370
    }
371
372
    /**
373
     * Generates random filename.
374
     * @param UploadedFile $file
375
     * @return string
376
     */
377
    protected function generateFileName($file)
378
    {
379
        return uniqid() . '.' . $file->extension;
380
    }
381
382
    /**
383
     * This method is invoked after uploading a file.
384
     * The default implementation raises the [[EVENT_AFTER_UPLOAD]] event.
385
     * You may override this method to do postprocessing after the file is uploaded.
386
     * Make sure you call the parent implementation so that the event is raised properly.
387
     */
388
    protected function afterUpload()
389
    {
390
        $this->owner->trigger(self::EVENT_AFTER_UPLOAD);
391
    }
392
}
393