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

UploadField::generateUniqueName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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