Completed
Pull Request — master (#2938)
by zi
02:19
created

UploadField::move()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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