Completed
Pull Request — master (#4)
by
unknown
13:50
created

FileBehavior::markLinked()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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 rkit\filemanager\models\FileUploadSession;
12
use Yii;
13
use yii\base\Behavior;
14
use yii\base\Exception;
15
use yii\db\ActiveRecord;
16
use yii\helpers\ArrayHelper;
17
18
class FileBehavior extends Behavior
19
{
20
    /**
21
     * @var array
22
     */
23
    public $attributes = [];
24
25
    /**
26
     * @var ActiveQuery
27
     */
28
    private $relation;
29
30
    /**
31
     * @var FileBind
32
     */
33
    private $fileBind;
34
35
    /**
36
     * @var array
37
     */
38
    protected static $classPathMap = [];
39
40
    /**
41
     * @var string name of application component that represents `user`
42
     */
43
    public $userComponent = 'user';
44
45
    /**
46
     * @since 5.6.0
47
     * @var bool
48
     */
49
    protected $markedLinked = false;
50
51
    /**
52
     * @internal
53 31
     */
54
    public function init()
55 31
    {
56
        parent::init();
57 31
58
        $this->fileBind = new FileBind();
59 31
60 31
        Yii::$app->fileManager->registerTranslations();
61
    }
62
63
    /**
64
     * @inheritdoc
65
     * @internal
66 31
     */
67
    public function events()
68
    {
69 31
        return [
70 31
            ActiveRecord::EVENT_AFTER_INSERT  => 'afterSave',
71 31
            ActiveRecord::EVENT_AFTER_UPDATE  => 'afterSave',
72 31
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
73 31
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
74
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
75
        ];
76
    }
77
78
    /**
79
     * @internal
80
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
81
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
82 2
     */
83
    public function beforeSave($insert)
0 ignored issues
show
Unused Code introduced by
The parameter $insert is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84 2
    {
85 2
        foreach ($this->attributes as $attribute => $options) {
86 2
            $oldValue = $this->owner->isNewRecord ? null : $this->owner->getOldAttribute($attribute);
87
            $isAttributeChanged = $oldValue === null ? true : $this->owner->isAttributeChanged($attribute);
88 2
89 2
            $this->attributes[$attribute]['isAttributeChanged'] = $isAttributeChanged;
90
            $this->attributes[$attribute]['oldValue'] = $oldValue;
91 2
        }
92
    }
93
94
    /**
95
     * @internal
96 2
     */
97
    public function afterSave()
98 2
    {
99 2
        foreach ($this->attributes as $attribute => $options)
100
        {
101 2
            $disableAutobind = $this->fileOption($attribute, 'disableAutobind');
102 2
            if ($disableAutobind) {
103 2
                continue;
104
            }
105
106 2
            $files = $this->owner->{$attribute};
107
108
            $isAttributeNotChanged = $options['isAttributeChanged'] === false || $files === null;
109
            if ($isAttributeNotChanged) {
110 2
                continue;
111 1
            }
112
113
            if (is_numeric($files)) {
114 2
                $files = [$files];
115 1
            }
116 1
117
            if (is_array($files)) {
118
                $files = array_filter($files);
119 1
            }
120 1
121 1
            if ($files === [] || $files === '') {
122
                $this->fileBind->delete($this->owner, $attribute, $this->files($attribute));
123
                continue;
124 1
            }
125
126 1
            $maxFiles = ArrayHelper::getValue($this->fileRules($attribute, true), 'maxFiles');
127
            if (is_array($files) && $maxFiles !== null) {
128 1
                $files = array_slice($files, 0, $maxFiles, true);
129
            }
130 1
131
            $files = $this->fileBind->bind($this->owner, $attribute, $files);
132
133 2
            $this->clearState($attribute, $files);
134
135
            if (is_array($files)) {
136
                $files = array_shift($files);
137
                $this->setValue($attribute, $files, $options['oldValue']);
138
            }
139
        }
140
    }
141
142
    /**
143
     * @internal
144
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
145
     */
146 26
    public function beforeDelete()
147
    {
148 26
        foreach ($this->attributes as $attribute => $options) {
149 26
            $disableAutobind = $this->fileOption($attribute, 'disableAutobind');
150
            if (!$disableAutobind) {
151
                $this->fileBind->delete($this->owner, $attribute, $this->files($attribute));
152
            }
153
        }
154 1
    }
155
156 1
    protected function getUser()
157 1
    {
158
        if (!$this->userComponent || !isset(Yii::$app->{$this->userComponent})) {
159
            return false;
160
        }
161
        return Yii::$app->{$this->userComponent};
162
    }
163
164
    public function clearState($attribute, $files)
165
    {
166
        if (!$this->getUser()) {
167
            return [];
168
        }
169
        if (!is_array($files)) {
170
            $files = [$files];
171
        }
172
        $query = [
173
            'created_user_id' => $this->getUser()->id,
174
            'target_model_class' => static::getClass(get_class($this->owner)),
175
            'target_model_id' => $this->owner->getPrimaryKey(),
176
            'target_model_attribute' => $attribute,
177
        ];
178 25
        if ($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
179
            $fileIDs = ArrayHelper::getColumn($files, 'id');
180 25
            $query['file_id'] = $fileIDs;
181 25
        }
182
        FileUploadSession::deleteAll($query);
183
        $query['target_model_id'] = null;
184
        FileUploadSession::deleteAll($query);  // for cases of uploads when original model was a new record at the moment of uploads
185
        return;
186
    }
187
188
    private function setState($attribute, $file)
189
    {
190
        $rec = new FileUploadSession();
191
        $rec->created_user_id = $this->getUser()->id;
192
        $rec->file_id = $file->getPrimaryKey();
193
        $rec->target_model_attribute = $attribute; // TODO: write model/object id?
194
        $rec->target_model_id = (!$this->owner->isNewRecord ? $this->owner->getPrimaryKey() : null);
195
        $rec->target_model_class = static::getClass(get_class($this->owner));
196
        $rec->save(false);
197
    }
198
199
    /**
200
     * for models with single upload only
201
     * @param $attribute
202
     * @param $file
203
     * @param $defaultValue
204
     */
205
    private function setValue($attribute, $file, $defaultValue)
206
    {
207
        $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
208
        $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
209
210
        if ($saveFilePath || $saveFileId) {
211
            if (!$file) {
212
                $value = $defaultValue;
213
            } elseif ($saveFilePath) {
214
                $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
215
                $value = Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $handlerTemplatePath($file);
216
            } elseif ($saveFileId) {
217
                $value = $file->getPrimaryKey();
218
            }
219
            $this->owner->{$attribute} = $value;
0 ignored issues
show
Bug introduced by
The variable $value 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...
220
            $this->owner->updateAttributes([$attribute => $value]);
221
        }
222
    }
223
224
    /**
225
     * Generate a thumb
226
     *
227
     * @param string $attribute The attribute name
228
     * @param string $preset The preset name
229
     * @param string $path The file path
230
     * @return string The thumb path
231
     */
232
    private function generateThumb($attribute, $preset, $path)
233
    {
234
        $thumbPath = pathinfo($path, PATHINFO_FILENAME);
235
        $thumbPath = str_replace($thumbPath, $preset . '_' . $thumbPath, $path);
236
        $realPath = $this->fileStorage($attribute)->path;
237
238
        if (!file_exists($realPath . $thumbPath) && file_exists($realPath . $path)) {
239
            $handlerPreset = $this->fileOption($attribute, 'preset.'.$preset);
240
            $handlerPreset($realPath, $path, $thumbPath);
241
        }
242
243
        return $thumbPath;
244
    }
245
246
    /**
247
     * Generate file path by template
248
     *
249
     * @param string $attribute The attribute name
250
     * @param ActiveRecord $file The file model
251
     * @return string The file path
252
     */
253
    private function templatePath($attribute, $file = null)
254
    {
255
        $value = $this->owner->{$attribute};
256
257
        $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
258
        $isFilledPath = $saveFilePath && !empty($value);
259
260
        $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
261
        $isFilledId = $saveFileId && is_numeric($value) && $value;
262
263
        if (($isFilledPath || $isFilledId) && $file === null) {
264
            $file = $this->file($attribute);
265
        }
266
267
        if ($file !== null) {
268
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
269
            return $handlerTemplatePath($file);
270 2
        }
271
        return $value;
272 2
    }
273 2
274
    /**
275 2
     * Get relation
276
     *
277
     * @param string $attribute The attribute name
278
     * @return \ActiveQuery
279
     */
280
    public function fileRelation($attribute)
281
    {
282
        if ($this->relation === null) {
283
            $this->relation = $this->owner->getRelation($this->fileOption($attribute, 'relation'));
284
        }
285
        return $this->relation;
286 30
    }
287
288 30
    /**
289
     * Get file option
290
     *
291
     * @param string $attribute The attribute name
292
     * @param string $option Option name
293
     * @param mixed $defaultValue Default value
294
     * @return mixed
295
     */
296
    public function fileOption($attribute, $option, $defaultValue = null)
297 26
    {
298
        return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
299 26
    }
300
301
    /**
302
     * Get file storage
303
     *
304
     * @param string $attribute The attribute name
305
     * @return \Flysystem
306
     */
307
    public function fileStorage($attribute)
308
    {
309
        return Yii::$app->get($this->fileOption($attribute, 'storage'));
310
    }
311
312
    /**
313
     * Get file path
314
     *
315
     * @param string $attribute The attribute name
316
     * @param ActiveRecord $file Use this file model
317
     * @return string The file path
318
     */
319
    public function filePath($attribute, $file = null)
320
    {
321
        $path = $this->templatePath($attribute, $file);
322
        /** @var Filesystem $fs */
323
        $fs = $this->fileStorage($attribute);
324
        return $fs->getAdapter()->getPathPrefix() . $path;
325
    }
326
327
    /**
328
     * Get file url
329
     *
330
     * @param string $attribute The attribute name
331
     * @param ActiveRecord $file Use this file model
332
     * @return string The file url
333
     */
334
    public function fileUrl($attribute, $file = null)
335
    {
336
        $path = $this->templatePath($attribute, $file);
337
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $path;
338
    }
339
340
    /**
341
     * Get extra fields of file
342
     *
343
     * @param string $attribute The attribute name
344
     * @return array
345
     */
346
    public function fileExtraFields($attribute)
347
    {
348
        if ($this->fileOption($attribute, 'disableAutobind')) {
349 2
            return [];
350
        }
351 2
        $fields = $this->fileBind->relations($this->owner, $attribute);
352
        if (!$this->fileOption($attribute, 'multiple')) {
353
            return array_shift($fields);
354
        }
355
        return $fields;
356
    }
357
358
    /**
359
     * Get files
360 1
     *
361
     * @param string $attribute The attribute name
362 1
     * @return \ActiveRecord[] The file models
363
     */
364
    public function files($attribute)
365
    {
366
        if ($this->fileOption($attribute, 'disableAutobind')) {
367
            throw new Exception('Accessing `files()` is not allowed when auto-bind is disabled, see `FileBehavior::$disableAutobind`');
368
        }
369
        return $this->fileBind->files($this->owner, $attribute);
370
    }
371
372 29
    /**
373
     * Get the file
374 29
     *
375 29
     * @param string $attribute The attribute name
376 28
     * @return \ActiveRecord The file model
377 28
     */
378
    public function file($attribute)
379 29
    {
380
        if ($this->fileOption($attribute, 'disableAutobind')) {
381
            throw new Exception('Accessing `file()` is not allowed when auto-bind is disabled, see `FileBehavior::$disableAutobind`');
382
        }
383
        return $this->fileBind->file($this->owner, $attribute);
384
    }
385
386
    /**
387
     * Get rules
388
     *
389
     * @param string $attribute The attribute name
390
     * @param bool $onlyCoreValidators Only core validators
391
     * @return array
392
     */
393
    public function fileRules($attribute, $onlyCoreValidators = false)
394
    {
395
        $rules = $this->fileOption($attribute, 'rules', []);
396
        if ($onlyCoreValidators && isset($rules['imageSize'])) {
397
            $rules = array_merge($rules, $rules['imageSize']);
398
            unset($rules['imageSize']);
399
        }
400
        return $rules;
401
    }
402
403
    /**
404
     * Get file state
405
     *
406
     * @param string $attribute The attribute name
407
     * @return array
408
     */
409
    public function fileState($attribute)
410
    {
411
        if (!$this->getUser()) {
412
            return [];
413
        }
414
        $query = FileUploadSession::find()->where([
415
            'created_user_id' => $this->getUser()->id,
416
            'target_model_class' => static::getClass(get_class($this->owner)),
417
            'target_model_attribute' => $attribute,
418
        ]);
419
        $query->andWhere(['or',
420
            ['target_model_id' => $this->owner->getPrimaryKey()],
421
            ['target_model_id' => null] // for cases of uploads when original model was a new record at the moment of uploads
422
        ]);
423
        $data = $query->all();
424
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
425
            return ArrayHelper::getColumn($data, ['file_id']);
0 ignored issues
show
Documentation introduced by
array('file_id') is of type array<integer,string,{"0":"string"}>, but the function expects a string|object<Closure>.

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...
426
        } else {
427
            return [];
428
        }
429
    }
430
431
    /**
432
     * Get the presets of the file for apply after upload
433
     *
434
     * @param string $attribute The attribute name
435
     * @return array
436
     */
437
    public function filePresetAfterUpload($attribute)
438
    {
439
        $preset = $this->fileOption($attribute, 'applyPresetAfterUpload', []);
440
        if (is_string($preset) && $preset === '*') {
441
            return array_keys($this->fileOption($attribute, 'preset', []));
442
        }
443
        return $preset;
444
    }
445
446
    /**
447
     * Create a thumb and return url
448
     *
449
     * @param string $attribute The attribute name
450
     * @param string $preset The preset name
451
     * @param ActiveRecord $file Use this file model
452
     * @return string The file url
453
     */
454
    public function thumbUrl($attribute, $preset, $file = null)
455
    {
456
        $path = $this->templatePath($attribute, $file);
457
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
458
459
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $thumbPath;
460
    }
461
462
    /**
463
     * Create a thumb and return full path
464
     *
465 25
     * @param string $attribute The attribute name
466
     * @param string $preset The preset name
467 25
     * @param ActiveRecord $file Use this file model
468 25
     * @return string The file path
469 25
     */
470 25
    public function thumbPath($attribute, $preset, $file = null)
471 25
    {
472 25
        $path = $this->templatePath($attribute, $file);
473 25
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
474 25
475 25
        return $this->fileStorage($attribute)->path . $thumbPath;
476
    }
477
478
    /**
479
     * Create a file
480
     *
481
     * @param string $attribute The attribute name
482
     * @param string $path The file path
483
     * @param string $name The file name
484
     * @return \ActiveRecord The file model
485
     */
486
    public function createFile($attribute, $path, $name)
487
    {
488
        $handlerCreateFile = $this->fileOption($attribute, 'createFile');
489
        $file = $handlerCreateFile($path, $name);
490
        if ($file) {
491
            $storage = $this->fileStorage($attribute);
492
            $contents = file_get_contents($path);
493
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
494
            if ($storage->write($handlerTemplatePath($file), $contents, [
495
                // set correct mime type:
496
                'mimetype' => yii\helpers\FileHelper::getMimeTypeByExtension($name),
497
            ])) {
498
                $disableAutobind = $this->fileOption($attribute, 'disableAutobind');
499
                if (!$this->markedLinked && !$disableAutobind) {
500
                    $this->setState($attribute, $file);
501
                }
502
                $this->owner->{$attribute} = $file->id;
503
                return $file;
504
            }
505
        } // @codeCoverageIgnore
506
        return false; // @codeCoverageIgnore
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by rkit\filemanager\behavio...ileBehavior::createFile of type ActiveRecord.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
507
    }
508
509
    /**
510
     * Create a file from remote URL
511
     *
512
     * @author Sergii Gamaiunov <[email protected]>
513
     *
514
     * @param string $attribute The attribute name
515
     * @param \igogo5yo\uploadfromurl\UploadFromUrl $remoteFile
516
     * @param string $name The file name
517
     * @return \ActiveRecord The file model
518
     */
519
    public function createRemoteFile($attribute, $remoteFile, $name)
520
    {
521
        $url = $remoteFile->url;
522
        $handlerCreateFile = $this->fileOption($attribute, 'createRemoteFile');
523
        $file = $handlerCreateFile($remoteFile, $name);
524
        if ($file) {
525
            $storage = $this->fileStorage($attribute);
526
            $stream = fopen($url, 'r');
527
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
528
            if ($storage->putStream($handlerTemplatePath($file), $stream)) {
529
                if (is_resource($stream)) { // some adapters close resources on their own
530
                    fclose($stream);
531
                }
532
                if ($this->getUser()) {
533
                    if (!$this->markedLinked) {
534
                        $this->setState($attribute, $file);
535
                    }
536
                }
537
                $this->owner->{$attribute} = $file->id;
538
                return $file;
539
            }
540
        } // @codeCoverageIgnore
541
        return false; // @codeCoverageIgnore
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by rkit\filemanager\behavio...avior::createRemoteFile of type ActiveRecord.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
542
    }
543
544
    /**
545
     * Add class alias to be able to upload files for different versions of a model to a single API endpoint
546
     *
547
     * Example:
548
     * ```
549
     * class OldCar extends Car
550
     * {
551
     *      public function init()
552
     *      {
553
     *          parent::init();
554
     *          $this->car_type = 'old;
555
     *          FileBehavior::addClassAlias(get_class($this), Car::className());
556
     *      }
557
     *
558
     *      public function formName() {
559
     *          return 'Car';
560
     *      }
561
     * }
562
     * ```
563
     * @param $source
564
     * @param $mapTo
565
     */
566
    public static function addClassAlias($source, $mapTo) {
567
        static::$classPathMap[$source] = $mapTo;
568
    }
569
570
    protected static function getClass($source) {
571
        return isset(static::$classPathMap[$source])
572
            ? static::$classPathMap[$source]
573
            : $source;
574
    }
575
576
    /**
577
     * Mark current upload session as already linked (e.g. file is linked during `createFile`) to avoid duplicate links
578
     * @return $this
579
     * @since 5.6.0
580
     */
581
    public function markLinked() {
582
        $this->markedLinked = true;
583
        return $this;
584
    }
585
}
586