Completed
Push — master ( a13290...ae4efa )
by Song
30s
created

src/Form/Field/MultipleFile.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Form;
6
use Encore\Admin\Form\Field;
7
use Illuminate\Support\Arr;
8
use Symfony\Component\HttpFoundation\File\UploadedFile;
9
10
class MultipleFile extends Field
11
{
12
    use UploadField;
13
14
    /**
15
     * Css.
16
     *
17
     * @var array
18
     */
19
    protected static $css = [
20
        '/vendor/laravel-admin/bootstrap-fileinput/css/fileinput.min.css?v=4.5.2',
21
    ];
22
23
    /**
24
     * Js.
25
     *
26
     * @var array
27
     */
28
    protected static $js = [
29
        '/vendor/laravel-admin/bootstrap-fileinput/js/plugins/canvas-to-blob.min.js',
30
        '/vendor/laravel-admin/bootstrap-fileinput/js/fileinput.min.js?v=4.5.2',
31
        '/vendor/laravel-admin/bootstrap-fileinput/js/plugins/sortable.min.js?v=4.5.2',
32
    ];
33
34
    /**
35
     * Create a new File instance.
36
     *
37
     * @param string $column
38
     * @param array  $arguments
39
     */
40
    public function __construct($column, $arguments = [])
41
    {
42
        $this->initStorage();
43
44
        parent::__construct($column, $arguments);
45
    }
46
47
    /**
48
     * Default directory for file to upload.
49
     *
50
     * @return mixed
51
     */
52
    public function defaultDirectory()
53
    {
54
        return config('admin.upload.directory.file');
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function getValidator(array $input)
61
    {
62
        if (request()->has(static::FILE_DELETE_FLAG)) {
63
            return false;
64
        }
65
66
        if ($this->validator) {
67
            return $this->validator->call($this, $input);
68
        }
69
70
        $attributes = [];
71
72
        if (!$fieldRules = $this->getRules()) {
73
            return false;
74
        }
75
76
        $attributes[$this->column] = $this->label;
77
78
        list($rules, $input) = $this->hydrateFiles(Arr::get($input, $this->column, []));
79
80
        return \validator($input, $rules, $this->getValidationMessages(), $attributes);
81
    }
82
83
    /**
84
     * Hydrate the files array.
85
     *
86
     * @param array $value
87
     *
88
     * @return array
89
     */
90
    protected function hydrateFiles(array $value)
91
    {
92
        if (empty($value)) {
93
            return [[$this->column => $this->getRules()], []];
94
        }
95
96
        $rules = $input = [];
97
98
        foreach ($value as $key => $file) {
99
            $rules[$this->column . $key] = $this->getRules();
100
            $input[$this->column . $key] = $file;
101
        }
102
103
        return [$rules, $input];
104
    }
105
106
    /**
107
     * Sort files.
108
     *
109
     * @param string $order
110
     *
111
     * @return array
112
     */
113
    protected function sortFiles($order)
114
    {
115
        $order = explode(',', $order);
116
117
        $new = [];
118
        $original = $this->original();
119
120
        foreach ($order as $item) {
121
            $new[] = Arr::get($original, $item);
122
        }
123
124
        return $new;
125
    }
126
127
    /**
128
     * Prepare for saving.
129
     *
130
     * @param UploadedFile|array $files
131
     *
132
     * @return mixed|string
133
     */
134
    public function prepare($files)
135
    {
136
        if (request()->has(static::FILE_DELETE_FLAG)) {
137
            if ($this->pathColumn) {
138
                return $this->destroyFromHasMany(request(static::FILE_DELETE_FLAG));
139
            }
140
141
            return $this->destroy(request(static::FILE_DELETE_FLAG));
142
        }
143
144
        if (is_string($files) && request()->has(static::FILE_SORT_FLAG)) {
145
            return $this->sortFiles($files);
146
        }
147
148
        $targets = array_map([$this, 'prepareForeach'], $files);
149
150
        // for create or update
151
        if ($this->pathColumn) {
152
            $targets = array_map(function ($target) {
153
                return [$this->pathColumn => $target];
154
            }, $targets);
155
        }
156
157
        return array_merge($this->original(), $targets);
158
    }
159
160
    /**
161
     * @return array|mixed
162
     */
163
    public function original()
164
    {
165
        if (empty($this->original)) {
166
            return [];
167
        }
168
169
        return $this->original;
170
    }
171
172
    /**
173
     * Prepare for each file.
174
     *
175
     * @param UploadedFile $file
176
     *
177
     * @return mixed|string
178
     */
179
    protected function prepareForeach(UploadedFile $file = null)
180
    {
181
        $this->name = $this->getStoreName($file);
182
183
        return tap($this->upload($file), function () {
184
            $this->name = null;
185
        });
186
    }
187
188
    /**
189
     * Preview html for file-upload plugin.
190
     *
191
     * @return array
192
     */
193
    protected function preview()
194
    {
195
        $files = $this->value ?: [];
196
197
        return array_values(array_map([$this, 'objectUrl'], $files));
198
    }
199
200
    /**
201
     * Initialize the caption.
202
     *
203
     * @param array $caption
204
     *
205
     * @return string
206
     */
207
    protected function initialCaption($caption)
208
    {
209
        if (empty($caption)) {
210
            return '';
211
        }
212
213
        $caption = array_map('basename', $caption);
214
215
        return implode(',', $caption);
216
    }
217
218
    /**
219
     * @return array
220
     */
221
    protected function initialPreviewConfig()
222
    {
223
        $files = $this->value ?: [];
224
225
        $config = [];
226
227
        foreach ($files as $index => $file) {
228
            if (is_array($file) && $this->pathColumn) {
229
                $index = Arr::get($file, $this->getRelatedKeyName(), $index);
230
                $file = Arr::get($file, $this->pathColumn);
231
            }
232
233
            $preview = array_merge([
234
                'caption' => basename($file),
235
                'key'     => $index,
236
            ], $this->guessPreviewType($file));
237
238
            $config[] = $preview;
239
        }
240
241
        return $config;
242
    }
243
244
    /**
245
     * Get related model key name.
246
     *
247
     * @return string
248
     */
249
    protected function getRelatedKeyName()
250
    {
251
        if (is_null($this->form)) {
252
            return;
253
        }
254
255
        return $this->form->model()->{$this->column}()->getRelated()->getKeyName();
0 ignored issues
show
The method model does only exist in Encore\Admin\Form, but not in Encore\Admin\Widgets\Form.

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...
256
    }
257
258
    /**
259
     * Allow to sort files.
260
     *
261
     * @return $this
262
     */
263
    public function sortable()
264
    {
265
        $this->fileActionSettings['showDrag'] = true;
266
267
        return $this;
268
    }
269
270
    /**
271
     * @param string $options
272
     */
273
    protected function setupScripts($options)
274
    {
275
        $this->script = <<<EOT
276
$("input{$this->getElementClassSelector()}").fileinput({$options});
277
EOT;
278
279 View Code Duplication
        if ($this->fileActionSettings['showRemove']) {
280
            $text = [
281
                'title'   => trans('admin.delete_confirm'),
282
                'confirm' => trans('admin.confirm'),
283
                'cancel'  => trans('admin.cancel'),
284
            ];
285
286
            $this->script .= <<<EOT
287
$("input{$this->getElementClassSelector()}").on('filebeforedelete', function() {
288
289
    return new Promise(function(resolve, reject) {
290
291
        var remove = resolve;
292
293
        swal({
294
            title: "{$text['title']}",
295
            type: "warning",
296
            showCancelButton: true,
297
            confirmButtonColor: "#DD6B55",
298
            confirmButtonText: "{$text['confirm']}",
299
            showLoaderOnConfirm: true,
300
            cancelButtonText: "{$text['cancel']}",
301
            preConfirm: function() {
302
                return new Promise(function(resolve) {
303
                    resolve(remove());
304
                });
305
            }
306
        });
307
    });
308
});
309
EOT;
310
        }
311
312
        if ($this->fileActionSettings['showDrag']) {
313
            $this->addVariables([
314
                'sortable'  => true,
315
                'sort_flag' => static::FILE_SORT_FLAG,
316
            ]);
317
318
            $this->script .= <<<EOT
319
$("input{$this->getElementClassSelector()}").on('filesorted', function(event, params) {
320
321
    var order = [];
322
323
    params.stack.forEach(function (item) {
324
        order.push(item.key);
325
    });
326
327
    $("input{$this->getElementClassSelector()}_sort").val(order);
328
});
329
EOT;
330
        }
331
    }
332
333
    /**
334
     * Render file upload field.
335
     *
336
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
337
     */
338
    public function render()
339
    {
340
        $this->attribute('multiple', true);
341
342
        $this->setupDefaultOptions();
343
344
        if (!empty($this->value)) {
345
            $this->options(['initialPreview' => $this->preview()]);
346
            $this->setupPreviewOptions();
347
        }
348
349
        $options = json_encode($this->options);
350
351
        $this->setupScripts($options);
352
353
        return parent::render();
354
    }
355
356
    /**
357
     * Destroy original files.
358
     *
359
     * @param string $key
360
     *
361
     * @return array
362
     */
363
    public function destroy($key)
364
    {
365
        $files = $this->original ?: [];
366
367
        $path = Arr::get($files, $key);
368
369
        if (!$this->retainable && $this->storage->exists($path)) {
370
            /* If this field class is using ImageField trait i.e MultipleImage field, 
371
            we loop through the thumbnails to delete them as well. */
372
373
            if (isset($this->thumbnails) && method_exists($this, 'destroyThumbnailFile')) {
374
                foreach ($this->thumbnails as $name => $_) {
375
                    $this->destroyThumbnailFile($path, $name);
376
                }
377
            }
378
            $this->storage->delete($path);
379
        }
380
381
        unset($files[$key]);
382
383
        return $files;
384
    }
385
386
    /**
387
     * Destroy original files from hasmany related model.
388
     *
389
     * @param int $key
390
     *
391
     * @return array
392
     */
393
    public function destroyFromHasMany($key)
394
    {
395
        $files = collect($this->original ?: [])->keyBy($this->getRelatedKeyName())->toArray();
396
397
        $path = Arr::get($files, "{$key}.{$this->pathColumn}");
398
399
        if (!$this->retainable && $this->storage->exists($path)) {
400
            $this->storage->delete($path);
401
        }
402
403
        $files[$key][Form::REMOVE_FLAG_NAME] = 1;
404
405
        return $files;
406
    }
407
}
408