Completed
Push — master ( 076f7e...a7d859 )
by Song
02:39
created

UploadField::retainable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Form;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\Storage;
8
use Illuminate\Support\Facades\URL;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Encore\Admin\Form\Field\URL.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
9
use Symfony\Component\HttpFoundation\File\UploadedFile;
10
11
trait UploadField
12
{
13
    /**
14
     * Upload directory.
15
     *
16
     * @var string
17
     */
18
    protected $directory = '';
19
20
    /**
21
     * File name.
22
     *
23
     * @var null
24
     */
25
    protected $name = null;
26
27
    /**
28
     * Storage instance.
29
     *
30
     * @var \Illuminate\Filesystem\Filesystem
31
     */
32
    protected $storage = '';
33
34
    /**
35
     * If use unique name to store upload file.
36
     *
37
     * @var bool
38
     */
39
    protected $useUniqueName = false;
40
41
    /**
42
     * If use sequence name to store upload file.
43
     *
44
     * @var bool
45
     */
46
    protected $useSequenceName = false;
47
48
    /**
49
     * Retain file when delete record from DB.
50
     *
51
     * @var bool
52
     */
53
    protected $retainable = false;
54
55
    /**
56
     * @var bool
57
     */
58
    protected $downloadable = true;
59
60
    /**
61
     * Configuration for setting up file actions for newly selected file thumbnails in the preview window.
62
     *
63
     * @var array
64
     */
65
    protected $fileActionSettings = [
66
        'showRemove' => false,
67
        'showDrag'   => false,
68
    ];
69
70
    /**
71
     * Controls the storage permission. Could be 'private' or 'public'.
72
     *
73
     * @var string
74
     */
75
    protected $storagePermission;
76
77
    /**
78
     * @var array
79
     */
80
    protected $fileTypes = [
81
        'image'  => '/^(gif|png|jpe?g|svg|webp)$/i',
82
        'html'   => '/^(htm|html)$/i',
83
        'office' => '/^(docx?|xlsx?|pptx?|pps|potx?)$/i',
84
        'gdocs'  => '/^(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i',
85
        'text'   => '/^(txt|md|csv|nfo|ini|json|php|js|css|ts|sql)$/i',
86
        'video'  => '/^(og?|mp4|webm|mp?g|mov|3gp)$/i',
87
        'audio'  => '/^(og?|mp3|mp?g|wav)$/i',
88
        'pdf'    => '/^(pdf)$/i',
89
        'flash'  => '/^(swf)$/i',
90
    ];
91
92
    /**
93
     * @var string
94
     */
95
    protected $pathColumn;
96
97
    /**
98
     * Initialize the storage instance.
99
     *
100
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
101
     */
102
    protected function initStorage()
103
    {
104
        $this->disk(config('admin.upload.disk'));
105
    }
106
107
    /**
108
     * Set default options form image field.
109
     *
110
     * @return void
111
     */
112
    protected function setupDefaultOptions()
113
    {
114
        $defaults = [
115
            'overwriteInitial'     => false,
116
            'initialPreviewAsData' => true,
117
            'msgPlaceholder'       => trans('admin.choose_file'),
118
            'browseLabel'          => trans('admin.browse'),
119
            'cancelLabel'          => trans('admin.cancel'),
120
            'showRemove'           => false,
121
            'showUpload'           => false,
122
            'showCancel'           => false,
123
            'dropZoneEnabled'      => false,
124
            'deleteExtraData'      => [
125
                $this->formatName($this->column) => static::FILE_DELETE_FLAG,
0 ignored issues
show
Bug introduced by
The property column does not seem to exist. Did you mean pathColumn?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Bug introduced by
It seems like formatName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
126
                static::FILE_DELETE_FLAG         => '',
127
                '_token'                         => csrf_token(),
128
                '_method'                        => 'PUT',
129
            ],
130
        ];
131
132
        if ($this->form instanceof Form) {
133
            $defaults['deleteUrl'] = $this->form->resource().'/'.$this->form->model()->getKey();
0 ignored issues
show
Bug introduced by
The property form does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
134
        }
135
136
        $defaults = array_merge($defaults, ['fileActionSettings' => $this->fileActionSettings]);
137
138
        $this->options($defaults);
139
    }
140
141
    /**
142
     * Set preview options form image field.
143
     *
144
     * @return void
145
     */
146
    protected function setupPreviewOptions()
147
    {
148
        $initialPreviewConfig = $this->initialPreviewConfig();
0 ignored issues
show
Bug introduced by
It seems like initialPreviewConfig() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
149
150
        $this->options(compact('initialPreviewConfig'));
151
    }
152
153
    /**
154
     * @return array|bool
155
     */
156
    protected function guessPreviewType($file)
157
    {
158
        $filetype = 'other';
159
        $ext = strtok(strtolower(pathinfo($file, PATHINFO_EXTENSION)), '?');
160
161
        foreach ($this->fileTypes as $type => $pattern) {
162
            if (preg_match($pattern, $ext) === 1) {
163
                $filetype = $type;
164
                break;
165
            }
166
        }
167
168
        $extra = ['type' => $filetype];
169
170
        if ($filetype == 'video') {
171
            $extra['filetype'] = "video/{$ext}";
172
        }
173
174
        if ($filetype == 'audio') {
175
            $extra['filetype'] = "audio/{$ext}";
176
        }
177
178
        if ($this->downloadable) {
179
            $extra['downloadUrl'] = $this->objectUrl($file);
180
        }
181
182
        return $extra;
183
    }
184
185
    /**
186
     * Indicates if the underlying field is downloadable.
187
     *
188
     * @param bool $downloadable
189
     *
190
     * @return $this
191
     */
192
    public function downloadable($downloadable = true)
193
    {
194
        $this->downloadable = $downloadable;
195
196
        return $this;
197
    }
198
199
    /**
200
     * Allow use to remove file.
201
     *
202
     * @return $this
203
     */
204
    public function removable()
205
    {
206
        $this->fileActionSettings['showRemove'] = true;
207
208
        return $this;
209
    }
210
211
    /**
212
     * Indicates if the underlying field is retainable.
213
     *
214
     * @return $this
215
     */
216
    public function retainable($retainable = true)
217
    {
218
        $this->retainable = $retainable;
219
220
        return $this;
221
    }
222
223
    /**
224
     * Set options for file-upload plugin.
225
     *
226
     * @param array $options
227
     *
228
     * @return $this
229
     */
230
    public function options($options = [])
231
    {
232
        $this->options = array_merge($options, $this->options);
0 ignored issues
show
Bug introduced by
The property options does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
233
234
        return $this;
235
    }
236
237
    /**
238
     * Set disk for storage.
239
     *
240
     * @param string $disk Disks defined in `config/filesystems.php`.
241
     *
242
     * @throws \Exception
243
     *
244
     * @return $this
245
     */
246
    public function disk($disk)
247
    {
248
        try {
249
            $this->storage = Storage::disk($disk);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Illuminate\Support\Facades\Storage::disk($disk) of type object<Illuminate\Contra...\Filesystem\Filesystem> is incompatible with the declared type object<Illuminate\Filesystem\Filesystem> of property $storage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
250
        } catch (\Exception $exception) {
251
            if (!array_key_exists($disk, config('filesystems.disks'))) {
252
                admin_error(
253
                    'Config error.',
254
                    "Disk [$disk] not configured, please add a disk config in `config/filesystems.php`."
255
                );
256
257
                return $this;
258
            }
259
260
            throw $exception;
261
        }
262
263
        return $this;
264
    }
265
266
    /**
267
     * Specify the directory and name for upload file.
268
     *
269
     * @param string      $directory
270
     * @param null|string $name
271
     *
272
     * @return $this
273
     */
274
    public function move($directory, $name = null)
275
    {
276
        $this->dir($directory);
277
278
        $this->name($name);
0 ignored issues
show
Bug introduced by
It seems like $name defined by parameter $name on line 274 can also be of type null; however, Encore\Admin\Form\Field\UploadField::name() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
279
280
        return $this;
281
    }
282
283
    /**
284
     * Specify the directory upload file.
285
     *
286
     * @param string $dir
287
     *
288
     * @return $this
289
     */
290
    public function dir($dir)
291
    {
292
        if ($dir) {
293
            $this->directory = $dir;
294
        }
295
296
        return $this;
297
    }
298
299
    /**
300
     * Set name of store name.
301
     *
302
     * @param string|callable $name
303
     *
304
     * @return $this
305
     */
306
    public function name($name)
307
    {
308
        if ($name) {
309
            $this->name = $name;
310
        }
311
312
        return $this;
313
    }
314
315
    /**
316
     * Use unique name for store upload file.
317
     *
318
     * @return $this
319
     */
320
    public function uniqueName()
321
    {
322
        $this->useUniqueName = true;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Use sequence name for store upload file.
329
     *
330
     * @return $this
331
     */
332
    public function sequenceName()
333
    {
334
        $this->useSequenceName = true;
335
336
        return $this;
337
    }
338
339
    /**
340
     * Get store name of upload file.
341
     *
342
     * @param UploadedFile $file
343
     *
344
     * @return string
345
     */
346
    protected function getStoreName(UploadedFile $file)
347
    {
348
        if ($this->useUniqueName) {
349
            return $this->generateUniqueName($file);
350
        }
351
352
        if ($this->useSequenceName) {
353
            return $this->generateSequenceName($file);
354
        }
355
356
        if ($this->name instanceof \Closure) {
357
            return $this->name->call($this, $file);
358
        }
359
360
        if (is_string($this->name)) {
361
            return $this->name;
362
        }
363
364
        return $file->getClientOriginalName();
365
    }
366
367
    /**
368
     * Get directory for store file.
369
     *
370
     * @return mixed|string
371
     */
372
    public function getDirectory()
373
    {
374
        if ($this->directory instanceof \Closure) {
375
            return call_user_func($this->directory, $this->form);
376
        }
377
378
        return $this->directory ?: $this->defaultDirectory();
0 ignored issues
show
Bug introduced by
It seems like defaultDirectory() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
379
    }
380
381
    /**
382
     * Set path column in has-many related model.
383
     *
384
     * @param string $column
385
     * @return $this
386
     */
387
    public function pathColumn($column = 'path')
388
    {
389
        $this->pathColumn = $column;
390
391
        return $this;
392
    }
393
394
    /**
395
     * Upload file and delete original file.
396
     *
397
     * @param UploadedFile $file
398
     *
399
     * @return mixed
400
     */
401
    protected function upload(UploadedFile $file)
402
    {
403
        $this->renameIfExists($file);
404
405
        if (!is_null($this->storagePermission)) {
406
            return $this->storage->putFileAs($this->getDirectory(), $file, $this->name, $this->storagePermission);
407
        }
408
409
        return $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
410
    }
411
412
    /**
413
     * If name already exists, rename it.
414
     *
415
     * @param $file
416
     *
417
     * @return void
418
     */
419
    public function renameIfExists(UploadedFile $file)
420
    {
421
        if ($this->storage->exists("{$this->getDirectory()}/$this->name")) {
422
            $this->name = $this->generateUniqueName($file);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->generateUniqueName($file) of type string is incompatible with the declared type null of property $name.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
423
        }
424
    }
425
426
    /**
427
     * Get file visit url.
428
     *
429
     * @param $path
430
     *
431
     * @return string
432
     */
433
    public function objectUrl($path)
434
    {
435
        if ($this->pathColumn && is_array($path)) {
436
            $path = Arr::get($path, $this->pathColumn);
437
        }
438
439
        if (URL::isValidUrl($path)) {
440
            return $path;
441
        }
442
443
        if ($this->storage) {
444
            return $this->storage->url($path);
445
        }
446
447
        return Storage::disk(config('admin.upload.disk'))->url($path);
448
    }
449
450
    /**
451
     * Generate a unique name for uploaded file.
452
     *
453
     * @param UploadedFile $file
454
     *
455
     * @return string
456
     */
457
    protected function generateUniqueName(UploadedFile $file)
458
    {
459
        return md5(uniqid()).'.'.$file->getClientOriginalExtension();
460
    }
461
462
    /**
463
     * Generate a sequence name for uploaded file.
464
     *
465
     * @param UploadedFile $file
466
     *
467
     * @return string
468
     */
469
    protected function generateSequenceName(UploadedFile $file)
470
    {
471
        $index = 1;
472
        $extension = $file->getClientOriginalExtension();
473
        $original = $file->getClientOriginalName();
474
        $new = sprintf('%s_%s.%s', $original, $index, $extension);
475
476
        while ($this->storage->exists("{$this->getDirectory()}/$new")) {
477
            $index++;
478
            $new = sprintf('%s_%s.%s', $original, $index, $extension);
479
        }
480
481
        return $new;
482
    }
483
484
    /**
485
     * Destroy original files.
486
     *
487
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
488
     */
489
    public function destroy()
490
    {
491
        if ($this->retainable) {
492
            return;
493
        }
494
495
        if (method_exists($this, 'destroyThumbnail')) {
496
            $this->destroyThumbnail();
0 ignored issues
show
Bug introduced by
The method destroyThumbnail() does not exist on Encore\Admin\Form\Field\UploadField. Did you maybe mean destroy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
497
        }
498
499
        if ($this->storage->exists($this->original)) {
500
            $this->storage->delete($this->original);
0 ignored issues
show
Bug introduced by
The property original does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
501
        }
502
    }
503
504
    /**
505
     * Set file permission when stored into storage.
506
     *
507
     * @param string $permission
508
     *
509
     * @return $this
510
     */
511
    public function storagePermission($permission)
512
    {
513
        $this->storagePermission = $permission;
514
515
        return $this;
516
    }
517
}
518