Completed
Push — master ( f06f95...cfadd2 )
by Song
03:17
created

File::buildPreviewItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Form\Field;
6
use Illuminate\Support\Facades\Input;
7
use Illuminate\Support\Facades\Storage;
8
use Illuminate\Support\Facades\Validator;
9
use Illuminate\Support\Str;
10
use Symfony\Component\HttpFoundation\File\UploadedFile;
11
12
class File extends Field
13
{
14
    const ACTION_KEEP = 0;
15
    const ACTION_REMOVE = 1;
16
17
    /**
18
     * Upload directory.
19
     *
20
     * @var string
21
     */
22
    protected $directory = '';
23
24
    /**
25
     * File name.
26
     *
27
     * @var null
28
     */
29
    protected $name = null;
30
31
    /**
32
     * Options for file-upload plugin.
33
     *
34
     * @var array
35
     */
36
    protected $options = [];
37
38
    /**
39
     * Storage instance.
40
     *
41
     * @var string
42
     */
43
    protected $storage = '';
44
45
    /**
46
     * Css.
47
     *
48
     * @var array
49
     */
50
    protected static $css = [
51
        '/packages/admin/bootstrap-fileinput/css/fileinput.min.css',
52
    ];
53
54
    /**
55
     * Js.
56
     *
57
     * @var array
58
     */
59
    protected static $js = [
60
        '/packages/admin/bootstrap-fileinput/js/plugins/canvas-to-blob.min.js',
61
        '/packages/admin/bootstrap-fileinput/js/fileinput.min.js',
62
    ];
63
64
    /**
65
     * If use unique name to store upload file.
66
     *
67
     * @var bool
68
     */
69
    protected $useUniqueName = false;
70
71
    /**
72
     * Use multiple upload.
73
     *
74
     * @var bool
75
     */
76
    protected $multiple = false;
77
78
    /**
79
     * Create a new File instance.
80
     *
81
     * @param string $column
82
     * @param array  $arguments
83
     */
84
    public function __construct($column, $arguments = [])
85
    {
86
        $this->initOptions();
87
        $this->initStorage();
88
89
        parent::__construct($column, $arguments);
90
    }
91
92
    /**
93
     * Initialize the storage instance.
94
     *
95
     * @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...
96
     */
97
    protected function initStorage()
98
    {
99
        $this->storage = Storage::disk(config('admin.upload.disk'));
100
    }
101
102
    /**
103
     * Initialize file-upload plugin.
104
     *
105
     * @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...
106
     */
107
    protected function initOptions()
108
    {
109
        $this->options = [
110
            'overwriteInitial'  => true,
111
            'showUpload'        => false,
112
            'language'          => config('app.locale'),
113
        ];
114
    }
115
116
    /**
117
     * Set options for file-upload plugin.
118
     *
119
     * @param array $options
120
     * @return $this
121
     */
122
    public function options($options = [])
123
    {
124
        $this->options = array_merge($this->options, $options);
125
126
        return $this;
127
    }
128
129
    /**
130
     * Set field as mulitple upload.
131
     *
132
     * @return $this
133
     */
134
    public function multiple()
135
    {
136
        $this->attribute('multiple', true);
137
138
        $this->multiple = true;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Default store path for file upload.
145
     *
146
     * @return mixed
147
     */
148
    public function defaultStorePath()
149
    {
150
        return config('admin.upload.directory.file');
151
    }
152
153
    /**
154
     * Specify the directory and name for uplaod file.
155
     *
156
     * @param string      $directory
157
     * @param null|string $name
158
     * @return $this
159
     */
160
    public function move($directory, $name = null)
161
    {
162
        $this->directory = $directory;
163
164
        $this->name = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name can also be of type string. However, the property $name is declared as type null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
165
166
        return $this;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function validate(array $input)
173
    {
174
        if (!$fieldRules = $this->getRules()) {
175
            return false;
176
        }
177
178
        if (!array_has($input, $this->column)) {
179
            return false;
180
        }
181
182
        $value = array_get($input, $this->column);
183
184
        if ($this->multiple) {
185
            list($rules, $data) = $this->hydrateFiles($value);
186
        } else {
187
            $data = [$this->column => $value];
188
            $rules = [$this->column => $this->getRules()];
189
        }
190
191
        return Validator::make($data, $rules);
192
    }
193
194
    /**
195
     * Hydrate the files array.
196
     *
197
     * @param array $value
198
     * @return array
199
     */
200
    protected function hydrateFiles(array $value)
201
    {
202
        $data = $rules = [];
203
204
        foreach ($value as $key => $file) {
205
            $rules[$this->column.$key] = $this->getRules();
206
            $data[$this->column.$key] = $file;
207
        }
208
209
        return [$rules, $data];
210
    }
211
212
    /**
213
     * Set name of store name.
214
     *
215
     * @param string|callable $name
216
     *
217
     * @return $this
218
     */
219
    public function name($name)
220
    {
221
        $this->name = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name of type callable 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...
222
223
        return $this;
224
    }
225
226
    /**
227
     * Use unique name for store upload file.
228
     *
229
     * @return $this
230
     */
231
    public function uniqueName()
232
    {
233
        $this->useUniqueName = true;
234
235
        return $this;
236
    }
237
238
    /**
239
     * Prepare for saving.
240
     *
241
     * @param UploadedFile|array $files
242
     *
243
     * @return mixed|string
244
     */
245
    public function prepare($files)
246
    {
247
        if (is_null($files)) {
248
            if ($this->isDeleteRequest()) {
249
                return '';
250
            }
251
252
            return $this->original;
253
        }
254
255
        if ($this->multiple || is_array($files)) {
256
            $targets = array_map([$this, 'prepareForSingle'], $files);
257
258
            return json_encode($targets);
259
        }
260
261
        return $this->prepareForSingle($files);
262
    }
263
264
    /**
265
     * Prepare for single file.
266
     *
267
     * @param UploadedFile $file
268
     *
269
     * @return mixed|string
270
     */
271
    protected function prepareForSingle(UploadedFile $file = null)
272
    {
273
        $this->directory = $this->directory ?: $this->defaultStorePath();
274
275
        $this->name = $this->getStoreName($file);
0 ignored issues
show
Bug introduced by
It seems like $file defined by parameter $file on line 271 can be null; however, Encore\Admin\Form\Field\File::getStoreName() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Documentation Bug introduced by
It seems like $this->getStoreName($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...
276
277
        return $this->uploadAndDeleteOriginal($file);
0 ignored issues
show
Bug introduced by
It seems like $file defined by parameter $file on line 271 can be null; however, Encore\Admin\Form\Field\...loadAndDeleteOriginal() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
278
    }
279
280
    /**
281
     * Get store name of upload file.
282
     *
283
     * @param UploadedFile $file
284
     *
285
     * @return string
286
     */
287
    protected function getStoreName(UploadedFile $file)
288
    {
289
        if ($this->useUniqueName) {
290
            return $this->generateUniqueName($file);
291
        }
292
293
        if (is_callable($this->name)) {
294
            $callback = $this->name->bindTo($this);
295
296
            return call_user_func($callback, $file);
297
        }
298
299
        if (is_string($this->name)) {
300
            return $this->name;
301
        }
302
303
        return $file->getClientOriginalName();
304
    }
305
306
    /**
307
     * Upload file and delete original file.
308
     *
309
     * @param UploadedFile $file
310
     *
311
     * @return mixed
312
     */
313
    protected function uploadAndDeleteOriginal(UploadedFile $file)
314
    {
315
        $this->renameIfExists($file);
316
317
        $target = $this->directory.'/'.$this->name;
318
319
        $this->storage->put($target, file_get_contents($file->getRealPath()));
0 ignored issues
show
Bug introduced by
The method put cannot be called on $this->storage (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
320
321
        $this->destroy();
322
323
        return $target;
324
    }
325
326
    /**
327
     * Preview html for file-upload plugin.
328
     *
329
     * @return array
330
     */
331 View Code Duplication
    protected function preview()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332
    {
333
        $files = json_decode($this->value, true);
334
335
        if (!is_array($files)) {
336
            $files = [$this->value];
337
        }
338
339
        return array_map([$this, 'buildPreviewItem'], $files);
340
    }
341
342
    /**
343
     * Preview html for file-upload plugin.
344
     *
345
     * @return string
346
     */
347
    protected function buildPreviewItem($file)
348
    {
349
        $fileName = basename($file);
350
351
        return <<<EOT
352
<div class="file-preview-other-frame">
353
   <div class="file-preview-other">
354
   <span class="file-icon-4x"><i class="fa fa-file"></i></span>
355
</div>
356
   </div>
357
   <div class="file-preview-other-footer"><div class="file-thumbnail-footer">
358
    <div class="file-footer-caption">{$fileName}</div>
359
</div></div>
360
EOT;
361
    }
362
363
    /**
364
     * Get file visit url.
365
     *
366
     * @param $path
367
     * @return string
368
     */
369
    public function objectUrl($path)
370
    {
371
        if (Str::startsWith($path, ['http://', 'https://'])) {
372
            return $path;
373
        }
374
375
        return rtrim(config('admin.upload.host'), '/').'/'.trim($path, '/');
376
    }
377
378
    /**
379
     * Initialize the caption.
380
     *
381
     * @param string $caption
382
     * @return string
383
     */
384
    protected function initialCaption($caption)
385
    {
386
        if (empty($caption)) {
387
            return '';
388
        }
389
390
        if ($this->multiple) {
391
            $caption = json_decode($caption, true);
392
        } else {
393
            $caption = [$caption];
394
        }
395
396
        $caption = array_map('basename', $caption);
397
398
        return implode(',', $caption);
399
    }
400
401
    /**
402
     * Render file upload field.
403
     *
404
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
405
     */
406
    public function render()
407
    {
408
        $this->options['initialCaption'] = $this->initialCaption($this->value);
409
410
        if (!empty($this->value)) {
411
            $this->options['initialPreview'] = $this->preview();
412
        }
413
414
        $options = json_encode($this->options);
415
416
        $this->script = <<<EOT
417
418
$("#{$this->id}").fileinput({$options});
419
420
$("#{$this->id}").on('filecleared', function(event) {
421
    $("#{$this->id}_action").val(1);
422
});
423
424
EOT;
425
426
        return parent::render()->with(['multiple' => $this->multiple]);
0 ignored issues
show
Bug introduced by
The method with does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
427
    }
428
429
    /**
430
     * If is delete request then delete original image.
431
     *
432
     * @return bool
433
     */
434
    public function isDeleteRequest()
435
    {
436
        $action = Input::get($this->id.'_action');
437
438
        if ($action == static::ACTION_REMOVE) {
439
            $this->destroy();
440
441
            return true;
442
        }
443
444
        return false;
445
    }
446
447
    /**
448
     * Generate a unique name for uploaded file.
449
     *
450
     * @param UploadedFile $file
451
     *
452
     * @return string
453
     */
454
    protected function generateUniqueName(UploadedFile $file)
455
    {
456
        return md5(uniqid()).'.'.$file->guessExtension();
457
    }
458
459
    /**
460
     * If name already exists, rename it.
461
     *
462
     * @param $file
463
     *
464
     * @return void
465
     */
466
    public function renameIfExists(UploadedFile $file)
467
    {
468
        if ($this->storage->exists("$this->directory/$this->name")) {
0 ignored issues
show
Bug introduced by
The method exists cannot be called on $this->storage (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
469
            $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...
470
        }
471
    }
472
473
    /**
474
     * Destroy original files.
475
     *
476
     * @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...
477
     */
478 View Code Duplication
    public function destroy()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
    {
480
        $files = json_decode($this->original, true);
481
482
        if (!is_array($files)) {
483
            $files = [$this->original];
484
        }
485
486
        array_map([$this, 'destroyItem'], $files);
487
    }
488
489
    /**
490
     * Destroy single original file.
491
     *
492
     * @param string $item
493
     */
494
    protected function destroyItem($item)
495
    {
496
        if ($this->storage->exists($item)) {
0 ignored issues
show
Bug introduced by
The method exists cannot be called on $this->storage (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
497
            $this->storage->delete($item);
0 ignored issues
show
Bug introduced by
The method delete cannot be called on $this->storage (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
498
        }
499
    }
500
}
501