Completed
Pull Request — master (#4)
by
unknown
12:59
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 ($this->fileOption($attribute, 'disableAutobind')) {
264
            if (($isFilledPath || $isFilledId) && $file === null) {
265
                $file = $this->file($attribute);
266
            }
267
            if ($file !== null) {
268
                $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
269
                return $handlerTemplatePath($file);
270 2
            }
271
        }
272 2
        return $value;
273 2
    }
274
275 2
    /**
276
     * Get relation
277
     *
278
     * @param string $attribute The attribute name
279
     * @return \ActiveQuery
280
     */
281
    public function fileRelation($attribute)
282
    {
283
        if ($this->relation === null) {
284
            $this->relation = $this->owner->getRelation($this->fileOption($attribute, 'relation'));
285
        }
286 30
        return $this->relation;
287
    }
288 30
289
    /**
290
     * Get file option
291
     *
292
     * @param string $attribute The attribute name
293
     * @param string $option Option name
294
     * @param mixed $defaultValue Default value
295
     * @return mixed
296
     */
297 26
    public function fileOption($attribute, $option, $defaultValue = null)
298
    {
299 26
        return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
300
    }
301
302
    /**
303
     * Get file storage
304
     *
305
     * @param string $attribute The attribute name
306
     * @return \Flysystem
307
     */
308
    public function fileStorage($attribute)
309
    {
310
        return Yii::$app->get($this->fileOption($attribute, 'storage'));
311
    }
312
313
    /**
314
     * Get file path
315
     *
316
     * @param string $attribute The attribute name
317
     * @param ActiveRecord $file Use this file model
318
     * @return string The file path
319
     */
320
    public function filePath($attribute, $file = null)
321
    {
322
        $path = $this->templatePath($attribute, $file);
323
        /** @var Filesystem $fs */
324
        $fs = $this->fileStorage($attribute);
325
        return $fs->getAdapter()->getPathPrefix() . $path;
326
    }
327
328
    /**
329
     * Get file url
330
     *
331
     * @param string $attribute The attribute name
332
     * @param ActiveRecord $file Use this file model
333
     * @return string The file url
334
     */
335
    public function fileUrl($attribute, $file = null)
336
    {
337
        $path = $this->templatePath($attribute, $file);
338
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $path;
339
    }
340
341
    /**
342
     * Get extra fields of file
343
     *
344
     * @param string $attribute The attribute name
345
     * @return array
346
     */
347
    public function fileExtraFields($attribute)
348
    {
349 2
        if ($this->fileOption($attribute, 'disableAutobind')) {
350
            return [];
351 2
        }
352
        $fields = $this->fileBind->relations($this->owner, $attribute);
353
        if (!$this->fileOption($attribute, 'multiple')) {
354
            return array_shift($fields);
355
        }
356
        return $fields;
357
    }
358
359
    /**
360 1
     * Get files
361
     *
362 1
     * @param string $attribute The attribute name
363
     * @return \ActiveRecord[] The file models
364
     */
365
    public function files($attribute)
366
    {
367
        if ($this->fileOption($attribute, 'disableAutobind')) {
368
            throw new Exception('Accessing `files()` is not allowed when auto-bind is disabled, see `FileBehavior::$disableAutobind`');
369
        }
370
        return $this->fileBind->files($this->owner, $attribute);
371
    }
372 29
373
    /**
374 29
     * Get the file
375 29
     *
376 28
     * @param string $attribute The attribute name
377 28
     * @return \ActiveRecord The file model
378
     */
379 29
    public function file($attribute)
380
    {
381
        if ($this->fileOption($attribute, 'disableAutobind')) {
382
            throw new Exception('Accessing `file()` is not allowed when auto-bind is disabled, see `FileBehavior::$disableAutobind`');
383
        }
384
        return $this->fileBind->file($this->owner, $attribute);
385
    }
386
387
    /**
388
     * Get rules
389
     *
390
     * @param string $attribute The attribute name
391
     * @param bool $onlyCoreValidators Only core validators
392
     * @return array
393
     */
394
    public function fileRules($attribute, $onlyCoreValidators = false)
395
    {
396
        $rules = $this->fileOption($attribute, 'rules', []);
397
        if ($onlyCoreValidators && isset($rules['imageSize'])) {
398
            $rules = array_merge($rules, $rules['imageSize']);
399
            unset($rules['imageSize']);
400
        }
401
        return $rules;
402
    }
403
404
    /**
405
     * Get file state
406
     *
407
     * @param string $attribute The attribute name
408
     * @return array
409
     */
410
    public function fileState($attribute)
411
    {
412
        if (!$this->getUser()) {
413
            return [];
414
        }
415
        $query = FileUploadSession::find()->where([
416
            'created_user_id' => $this->getUser()->id,
417
            'target_model_class' => static::getClass(get_class($this->owner)),
418
            'target_model_attribute' => $attribute,
419
        ]);
420
        $query->andWhere(['or',
421
            ['target_model_id' => $this->owner->getPrimaryKey()],
422
            ['target_model_id' => null] // for cases of uploads when original model was a new record at the moment of uploads
423
        ]);
424
        $data = $query->all();
425
        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...
426
            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...
427
        } else {
428
            return [];
429
        }
430
    }
431
432
    /**
433
     * Get the presets of the file for apply after upload
434
     *
435
     * @param string $attribute The attribute name
436
     * @return array
437
     */
438
    public function filePresetAfterUpload($attribute)
439
    {
440
        $preset = $this->fileOption($attribute, 'applyPresetAfterUpload', []);
441
        if (is_string($preset) && $preset === '*') {
442
            return array_keys($this->fileOption($attribute, 'preset', []));
443
        }
444
        return $preset;
445
    }
446
447
    /**
448
     * Create a thumb and return url
449
     *
450
     * @param string $attribute The attribute name
451
     * @param string $preset The preset name
452
     * @param ActiveRecord $file Use this file model
453
     * @return string The file url
454
     */
455
    public function thumbUrl($attribute, $preset, $file = null)
456
    {
457
        $path = $this->templatePath($attribute, $file);
458
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
459
460
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $thumbPath;
461
    }
462
463
    /**
464
     * Create a thumb and return full path
465 25
     *
466
     * @param string $attribute The attribute name
467 25
     * @param string $preset The preset name
468 25
     * @param ActiveRecord $file Use this file model
469 25
     * @return string The file path
470 25
     */
471 25
    public function thumbPath($attribute, $preset, $file = null)
472 25
    {
473 25
        $path = $this->templatePath($attribute, $file);
474 25
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
475 25
476
        return $this->fileStorage($attribute)->path . $thumbPath;
477
    }
478
479
    /**
480
     * Create a file
481
     *
482
     * @param string $attribute The attribute name
483
     * @param string $path The file path
484
     * @param string $name The file name
485
     * @return \ActiveRecord The file model
486
     */
487
    public function createFile($attribute, $path, $name)
488
    {
489
        $handlerCreateFile = $this->fileOption($attribute, 'createFile');
490
        $file = $handlerCreateFile($path, $name);
491
        if ($file) {
492
            $storage = $this->fileStorage($attribute);
493
            $contents = file_get_contents($path);
494
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
495
            if ($storage->write($handlerTemplatePath($file), $contents, [
496
                // set correct mime type:
497
                'mimetype' => yii\helpers\FileHelper::getMimeTypeByExtension($name),
498
            ])) {
499
                $disableAutobind = $this->fileOption($attribute, 'disableAutobind');
500
                if (!$this->markedLinked && !$disableAutobind) {
501
                    $this->setState($attribute, $file);
502
                }
503
                $this->owner->{$attribute} = $file->id;
504
                return $file;
505
            }
506
        } // @codeCoverageIgnore
507
        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...
508
    }
509
510
    /**
511
     * Create a file from remote URL
512
     *
513
     * @author Sergii Gamaiunov <[email protected]>
514
     *
515
     * @param string $attribute The attribute name
516
     * @param \igogo5yo\uploadfromurl\UploadFromUrl $remoteFile
517
     * @param string $name The file name
518
     * @return \ActiveRecord The file model
519
     */
520
    public function createRemoteFile($attribute, $remoteFile, $name)
521
    {
522
        $url = $remoteFile->url;
523
        $handlerCreateFile = $this->fileOption($attribute, 'createRemoteFile');
524
        $file = $handlerCreateFile($remoteFile, $name);
525
        if ($file) {
526
            $storage = $this->fileStorage($attribute);
527
            $stream = fopen($url, 'r');
528
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
529
            if ($storage->putStream($handlerTemplatePath($file), $stream)) {
530
                if (is_resource($stream)) { // some adapters close resources on their own
531
                    fclose($stream);
532
                }
533
                if ($this->getUser()) {
534
                    if (!$this->markedLinked) {
535
                        $this->setState($attribute, $file);
536
                    }
537
                }
538
                $this->owner->{$attribute} = $file->id;
539
                return $file;
540
            }
541
        } // @codeCoverageIgnore
542
        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...
543
    }
544
545
    /**
546
     * Add class alias to be able to upload files for different versions of a model to a single API endpoint
547
     *
548
     * Example:
549
     * ```
550
     * class OldCar extends Car
551
     * {
552
     *      public function init()
553
     *      {
554
     *          parent::init();
555
     *          $this->car_type = 'old;
556
     *          FileBehavior::addClassAlias(get_class($this), Car::className());
557
     *      }
558
     *
559
     *      public function formName() {
560
     *          return 'Car';
561
     *      }
562
     * }
563
     * ```
564
     * @param $source
565
     * @param $mapTo
566
     */
567
    public static function addClassAlias($source, $mapTo) {
568
        static::$classPathMap[$source] = $mapTo;
569
    }
570
571
    protected static function getClass($source) {
572
        return isset(static::$classPathMap[$source])
573
            ? static::$classPathMap[$source]
574
            : $source;
575
    }
576
577
    /**
578
     * Mark current upload session as already linked (e.g. file is linked during `createFile`) to avoid duplicate links
579
     * @return $this
580
     * @since 5.6.0
581
     */
582
    public function markLinked() {
583
        $this->markedLinked = true;
584
        return $this;
585
    }
586
}
587