Completed
Push — master ( 3a6a8b...8c64c3 )
by Song
02:41
created

UploadField::downloadable()   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\Facades\Storage;
7
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...
8
use Symfony\Component\HttpFoundation\File\UploadedFile;
9
10
trait UploadField
11
{
12
    /**
13
     * Upload directory.
14
     *
15
     * @var string
16
     */
17
    protected $directory = '';
18
19
    /**
20
     * File name.
21
     *
22
     * @var null
23
     */
24
    protected $name = null;
25
26
    /**
27
     * Storage instance.
28
     *
29
     * @var \Illuminate\Filesystem\Filesystem
30
     */
31
    protected $storage = '';
32
33
    /**
34
     * If use unique name to store upload file.
35
     *
36
     * @var bool
37
     */
38
    protected $useUniqueName = false;
39
40
    /**
41
     * If use sequence name to store upload file.
42
     *
43
     * @var bool
44
     */
45
    protected $useSequenceName = false;
46
47
    /**
48
     * Retain file when delete record from DB.
49
     *
50
     * @var bool
51
     */
52
    protected $retainable = false;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $downloadable = true;
58
59
    /**
60
     * Configuration for setting up file actions for newly selected file thumbnails in the preview window.
61
     *
62
     * @var array
63
     */
64
    protected $fileActionSettings = [
65
        'showRemove' => false,
66
        'showDrag'   => false,
67
    ];
68
69
    /**
70
     * Controls the storage permission. Could be 'private' or 'public'.
71
     *
72
     * @var string
73
     */
74
    protected $storagePermission;
75
76
    /**
77
     * @var array
78
     */
79
    protected $fileTypes = [
80
        'image'  => '/^(gif|png|jpe?g|svg)$/i',
81
        'html'   => '/^(htm|html)$/i',
82
        'office' => '/^(docx?|xlsx?|pptx?|pps|potx?)$/i',
83
        'gdocs'  => '/^(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i',
84
        'text'   => '/^(txt|md|csv|nfo|ini|json|php|js|css|ts|sql)$/i',
85
        'video'  => '/^(og?|mp4|webm|mp?g|mov|3gp)$/i',
86
        'audio'  => '/^(og?|mp3|mp?g|wav)$/i',
87
        'pdf'    => '/^(pdf)$/i',
88
        'flash'  => '/^(swf)$/i',
89
    ];
90
91
    /**
92
     * Initialize the storage instance.
93
     *
94
     * @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...
95
     */
96
    protected function initStorage()
97
    {
98
        $this->disk(config('admin.upload.disk'));
99
    }
100
101
    /**
102
     * Set default options form image field.
103
     *
104
     * @return void
105
     */
106
    protected function setupDefaultOptions()
107
    {
108
        $defaults = [
109
            'overwriteInitial'     => false,
110
            'initialPreviewAsData' => true,
111
            'browseLabel'          => trans('admin.browse'),
112
            'cancelLabel'          => trans('admin.cancel'),
113
            'showRemove'           => false,
114
            'showUpload'           => false,
115
            'showCancel'           => false,
116
            'dropZoneEnabled'      => false,
117
            'deleteExtraData'      => [
118
                $this->formatName($this->column) => static::FILE_DELETE_FLAG,
0 ignored issues
show
Bug introduced by
The property column 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...
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...
119
                static::FILE_DELETE_FLAG         => '',
120
                '_token'                         => csrf_token(),
121
                '_method'                        => 'PUT',
122
            ],
123
        ];
124
125
        if ($this->form instanceof Form) {
126
            $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...
127
        }
128
129
        $defaults = array_merge($defaults, ['fileActionSettings' => $this->fileActionSettings]);
130
131
        $this->options($defaults);
132
    }
133
134
    /**
135
     * Set preview options form image field.
136
     *
137
     * @return void
138
     */
139
    protected function setupPreviewOptions()
140
    {
141
        $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...
142
143
        $this->options(compact('initialPreviewConfig'));
144
    }
145
146
    /**
147
     * @return array|bool
148
     */
149
    protected function guessPreviewType($file)
150
    {
151
        $filetype = 'other';
152
        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
153
154
        foreach ($this->fileTypes as $type => $pattern) {
155
            if (preg_match($pattern, $ext) === 1) {
156
                $filetype = $type;
157
                break;
158
            }
159
        }
160
161
        $extra = ['type' => $filetype];
162
163
        if ($filetype == 'video') {
164
            $extra['filetype'] = "video/{$ext}";
165
        }
166
167
        if ($this->downloadable) {
168
            $extra['downloadUrl'] = $this->objectUrl($file);
169
        }
170
171
        return $extra;
172
    }
173
174
    /**
175
     * Indicates if the underlying field is downloadable.
176
     *
177
     * @param bool $downloadable
178
     * @return $this
179
     */
180
    public function downloadable($downloadable = true)
181
    {
182
        $this->downloadable = $downloadable;
183
184
        return $this;
185
    }
186
187
    /**
188
     * Allow use to remove file.
189
     *
190
     * @return $this
191
     */
192
    public function removable()
193
    {
194
        $this->fileActionSettings['showRemove'] = true;
195
196
        return $this;
197
    }
198
199
    /**
200
     * Indicates if the underlying field is retainable.
201
     *
202
     * @return $this
203
     */
204
    public function retainable($retainable = true)
205
    {
206
        $this->retainable = $retainable;
207
208
        return $this;
209
    }
210
211
    /**
212
     * Set options for file-upload plugin.
213
     *
214
     * @param array $options
215
     *
216
     * @return $this
217
     */
218
    public function options($options = [])
219
    {
220
        $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...
221
222
        return $this;
223
    }
224
225
    /**
226
     * Set disk for storage.
227
     *
228
     * @param string $disk Disks defined in `config/filesystems.php`.
229
     *
230
     * @throws \Exception
231
     *
232
     * @return $this
233
     */
234
    public function disk($disk)
235
    {
236
        try {
237
            $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...
238
        } catch (\Exception $exception) {
239
            if (!array_key_exists($disk, config('filesystems.disks'))) {
240
                admin_error(
241
                    'Config error.',
242
                    "Disk [$disk] not configured, please add a disk config in `config/filesystems.php`."
243
                );
244
245
                return $this;
246
            }
247
248
            throw $exception;
249
        }
250
251
        return $this;
252
    }
253
254
    /**
255
     * Specify the directory and name for upload file.
256
     *
257
     * @param string      $directory
258
     * @param null|string $name
259
     *
260
     * @return $this
261
     */
262
    public function move($directory, $name = null)
263
    {
264
        $this->dir($directory);
265
266
        $this->name($name);
0 ignored issues
show
Bug introduced by
It seems like $name defined by parameter $name on line 262 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...
267
268
        return $this;
269
    }
270
271
    /**
272
     * Specify the directory upload file.
273
     *
274
     * @param string $dir
275
     *
276
     * @return $this
277
     */
278
    public function dir($dir)
279
    {
280
        if ($dir) {
281
            $this->directory = $dir;
282
        }
283
284
        return $this;
285
    }
286
287
    /**
288
     * Set name of store name.
289
     *
290
     * @param string|callable $name
291
     *
292
     * @return $this
293
     */
294
    public function name($name)
295
    {
296
        if ($name) {
297
            $this->name = $name;
298
        }
299
300
        return $this;
301
    }
302
303
    /**
304
     * Use unique name for store upload file.
305
     *
306
     * @return $this
307
     */
308
    public function uniqueName()
309
    {
310
        $this->useUniqueName = true;
311
312
        return $this;
313
    }
314
315
    /**
316
     * Use sequence name for store upload file.
317
     *
318
     * @return $this
319
     */
320
    public function sequenceName()
321
    {
322
        $this->useSequenceName = true;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Get store name of upload file.
329
     *
330
     * @param UploadedFile $file
331
     *
332
     * @return string
333
     */
334
    protected function getStoreName(UploadedFile $file)
335
    {
336
        if ($this->useUniqueName) {
337
            return $this->generateUniqueName($file);
338
        }
339
340
        if ($this->useSequenceName) {
341
            return $this->generateSequenceName($file);
342
        }
343
344
        if ($this->name instanceof \Closure) {
345
            return $this->name->call($this, $file);
346
        }
347
348
        if (is_string($this->name)) {
349
            return $this->name;
350
        }
351
352
        return $file->getClientOriginalName();
353
    }
354
355
    /**
356
     * Get directory for store file.
357
     *
358
     * @return mixed|string
359
     */
360
    public function getDirectory()
361
    {
362
        if ($this->directory instanceof \Closure) {
363
            return call_user_func($this->directory, $this->form);
364
        }
365
366
        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...
367
    }
368
369
    /**
370
     * Upload file and delete original file.
371
     *
372
     * @param UploadedFile $file
373
     *
374
     * @return mixed
375
     */
376
    protected function upload(UploadedFile $file)
377
    {
378
        $this->renameIfExists($file);
379
380
        if (!is_null($this->storagePermission)) {
381
            return $this->storage->putFileAs($this->getDirectory(), $file, $this->name, $this->storagePermission);
382
        }
383
384
        return $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
385
    }
386
387
    /**
388
     * If name already exists, rename it.
389
     *
390
     * @param $file
391
     *
392
     * @return void
393
     */
394
    public function renameIfExists(UploadedFile $file)
395
    {
396
        if ($this->storage->exists("{$this->getDirectory()}/$this->name")) {
397
            $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...
398
        }
399
    }
400
401
    /**
402
     * Get file visit url.
403
     *
404
     * @param $path
405
     *
406
     * @return string
407
     */
408
    public function objectUrl($path)
409
    {
410
        if (URL::isValidUrl($path)) {
411
            return $path;
412
        }
413
414
        if ($this->storage) {
415
            return $this->storage->url($path);
416
        }
417
418
        return Storage::disk(config('admin.upload.disk'))->url($path);
419
    }
420
421
    /**
422
     * Generate a unique name for uploaded file.
423
     *
424
     * @param UploadedFile $file
425
     *
426
     * @return string
427
     */
428
    protected function generateUniqueName(UploadedFile $file)
429
    {
430
        return md5(uniqid()).'.'.$file->getClientOriginalExtension();
431
    }
432
433
    /**
434
     * Generate a sequence name for uploaded file.
435
     *
436
     * @param UploadedFile $file
437
     *
438
     * @return string
439
     */
440
    protected function generateSequenceName(UploadedFile $file)
441
    {
442
        $index = 1;
443
        $extension = $file->getClientOriginalExtension();
444
        $original = $file->getClientOriginalName();
445
        $new = sprintf('%s_%s.%s', $original, $index, $extension);
446
447
        while ($this->storage->exists("{$this->getDirectory()}/$new")) {
448
            $index++;
449
            $new = sprintf('%s_%s.%s', $original, $index, $extension);
450
        }
451
452
        return $new;
453
    }
454
455
    /**
456
     * Destroy original files.
457
     *
458
     * @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...
459
     */
460
    public function destroy()
461
    {
462
        if (!$this->retainable && $this->storage->exists($this->original)) {
463
            $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...
464
        }
465
    }
466
467
    /**
468
     * Set file permission when stored into storage.
469
     *
470
     * @param string $permission
471
     *
472
     * @return $this
473
     */
474
    public function storagePermission($permission)
475
    {
476
        $this->storagePermission = $permission;
477
478
        return $this;
479
    }
480
}
481