Completed
Push — master ( 98c282...51b9ff )
by Song
02:26
created

UploadField::storage_permission()   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
     * Configuration for setting up file actions for newly selected file thumbnails in the preview window.
56
     *
57
     * @var array
58
     */
59
    protected $fileActionSettings = [
60
        'showRemove' => false,
61
        'showDrag'   => false,
62
    ];
63
64
    /**
65
     * Controls the storage permission. Could be 'private' or 'public'.
66
     *
67
     * @var string
68
     */
69
    protected $storagePermission;
70
71
    /**
72
     * Initialize the storage instance.
73
     *
74
     * @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...
75
     */
76
    protected function initStorage()
77
    {
78
        $this->disk(config('admin.upload.disk'));
79
    }
80
81
    /**
82
     * Set default options form image field.
83
     *
84
     * @return void
85
     */
86
    protected function setupDefaultOptions()
87
    {
88
        $defaults = [
89
            'overwriteInitial'     => false,
90
            'initialPreviewAsData' => true,
91
            'browseLabel'          => trans('admin.browse'),
92
            'cancelLabel'          => trans('admin.cancel'),
93
            'showRemove'           => false,
94
            'showUpload'           => false,
95
            'dropZoneEnabled'      => false,
96
            'deleteExtraData'      => [
97
                $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...
98
                static::FILE_DELETE_FLAG         => '',
99
                '_token'                         => csrf_token(),
100
                '_method'                        => 'PUT',
101
            ],
102
        ];
103
104
        if ($this->form instanceof Form) {
105
            $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...
106
        }
107
108
        $defaults = array_merge($defaults, ['fileActionSettings' => $this->fileActionSettings]);
109
110
        $this->options($defaults);
111
    }
112
113
    /**
114
     * Set preview options form image field.
115
     *
116
     * @return void
117
     */
118
    protected function setupPreviewOptions()
119
    {
120
        $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...
121
122
        $this->options(compact('initialPreviewConfig'));
123
    }
124
125
    /**
126
     * Allow use to remove file.
127
     *
128
     * @return $this
129
     */
130
    public function removable()
131
    {
132
        $this->fileActionSettings['showRemove'] = true;
133
134
        return $this;
135
    }
136
137
    /**
138
     * Indicates if the underlying field is retainable.
139
     *
140
     * @return $this
141
     */
142
    public function retainable($retainable = true)
143
    {
144
        $this->retainable = $retainable;
145
146
        return $this;
147
    }
148
149
    /**
150
     * Set options for file-upload plugin.
151
     *
152
     * @param array $options
153
     *
154
     * @return $this
155
     */
156
    public function options($options = [])
157
    {
158
        $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...
159
160
        return $this;
161
    }
162
163
    /**
164
     * Set disk for storage.
165
     *
166
     * @param string $disk Disks defined in `config/filesystems.php`.
167
     *
168
     * @throws \Exception
169
     *
170
     * @return $this
171
     */
172
    public function disk($disk)
173
    {
174
        try {
175
            $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...
176
        } catch (\Exception $exception) {
177
            if (!array_key_exists($disk, config('filesystems.disks'))) {
178
                admin_error(
179
                    'Config error.',
180
                    "Disk [$disk] not configured, please add a disk config in `config/filesystems.php`."
181
                );
182
183
                return $this;
184
            }
185
186
            throw $exception;
187
        }
188
189
        return $this;
190
    }
191
192
    /**
193
     * Specify the directory and name for upload file.
194
     *
195
     * @param string      $directory
196
     * @param null|string $name
197
     *
198
     * @return $this
199
     */
200
    public function move($directory, $name = null)
201
    {
202
        $this->dir($directory);
203
204
        $this->name($name);
0 ignored issues
show
Bug introduced by
It seems like $name defined by parameter $name on line 200 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...
205
206
        return $this;
207
    }
208
209
    /**
210
     * Specify the directory upload file.
211
     *
212
     * @param string $dir
213
     *
214
     * @return $this
215
     */
216
    public function dir($dir)
217
    {
218
        if ($dir) {
219
            $this->directory = $dir;
220
        }
221
222
        return $this;
223
    }
224
225
    /**
226
     * Set name of store name.
227
     *
228
     * @param string|callable $name
229
     *
230
     * @return $this
231
     */
232
    public function name($name)
233
    {
234
        if ($name) {
235
            $this->name = $name;
236
        }
237
238
        return $this;
239
    }
240
241
    /**
242
     * Use unique name for store upload file.
243
     *
244
     * @return $this
245
     */
246
    public function uniqueName()
247
    {
248
        $this->useUniqueName = true;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Use sequence name for store upload file.
255
     *
256
     * @return $this
257
     */
258
    public function sequenceName()
259
    {
260
        $this->useSequenceName = true;
261
262
        return $this;
263
    }
264
265
    /**
266
     * Get store name of upload file.
267
     *
268
     * @param UploadedFile $file
269
     *
270
     * @return string
271
     */
272
    protected function getStoreName(UploadedFile $file)
273
    {
274
        if ($this->useUniqueName) {
275
            return $this->generateUniqueName($file);
276
        }
277
278
        if ($this->useSequenceName) {
279
            return $this->generateSequenceName($file);
280
        }
281
282
        if ($this->name instanceof \Closure) {
283
            return $this->name->call($this, $file);
284
        }
285
286
        if (is_string($this->name)) {
287
            return $this->name;
288
        }
289
290
        return $file->getClientOriginalName();
291
    }
292
293
    /**
294
     * Get directory for store file.
295
     *
296
     * @return mixed|string
297
     */
298
    public function getDirectory()
299
    {
300
        if ($this->directory instanceof \Closure) {
301
            return call_user_func($this->directory, $this->form);
302
        }
303
304
        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...
305
    }
306
307
    /**
308
     * Upload file and delete original file.
309
     *
310
     * @param UploadedFile $file
311
     *
312
     * @return mixed
313
     */
314
    protected function upload(UploadedFile $file)
315
    {
316
        $this->renameIfExists($file);
317
318
        if (!is_null($this->storagePermission)) {
319
            return $this->storage->putFileAs($this->getDirectory(), $file, $this->name, $this->storagePermission);
320
        }
321
322
        return $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
323
    }
324
325
    /**
326
     * If name already exists, rename it.
327
     *
328
     * @param $file
329
     *
330
     * @return void
331
     */
332
    public function renameIfExists(UploadedFile $file)
333
    {
334
        if ($this->storage->exists("{$this->getDirectory()}/$this->name")) {
335
            $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...
336
        }
337
    }
338
339
    /**
340
     * Get file visit url.
341
     *
342
     * @param $path
343
     *
344
     * @return string
345
     */
346
    public function objectUrl($path)
347
    {
348
        if (URL::isValidUrl($path)) {
349
            return $path;
350
        }
351
352
        if ($this->storage) {
353
            return $this->storage->url($path);
354
        }
355
356
        return Storage::disk(config('admin.upload.disk'))->url($path);
357
    }
358
359
    /**
360
     * Generate a unique name for uploaded file.
361
     *
362
     * @param UploadedFile $file
363
     *
364
     * @return string
365
     */
366
    protected function generateUniqueName(UploadedFile $file)
367
    {
368
        return md5(uniqid()).'.'.$file->getClientOriginalExtension();
369
    }
370
371
    /**
372
     * Generate a sequence name for uploaded file.
373
     *
374
     * @param UploadedFile $file
375
     *
376
     * @return string
377
     */
378
    protected function generateSequenceName(UploadedFile $file)
379
    {
380
        $index = 1;
381
        $extension = $file->getClientOriginalExtension();
382
        $original = $file->getClientOriginalName();
383
        $new = sprintf('%s_%s.%s', $original, $index, $extension);
384
385
        while ($this->storage->exists("{$this->getDirectory()}/$new")) {
386
            $index++;
387
            $new = sprintf('%s_%s.%s', $original, $index, $extension);
388
        }
389
390
        return $new;
391
    }
392
393
    /**
394
     * Destroy original files.
395
     *
396
     * @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...
397
     */
398
    public function destroy()
399
    {
400
        if (!$this->retainable && $this->storage->exists($this->original)) {
401
            $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...
402
        }
403
    }
404
405
    /**
406
     * Set file permission when stored into storage.
407
     *
408
     * @param string $permission
409
     *
410
     * @return $this
411
     */
412
    public function storagePermission($permission)
413
    {
414
        $this->storagePermission = $permission;
415
416
        return $this;
417
    }
418
}
419