Completed
Push — master ( 0ba9c9...c4f173 )
by jxlwqq
15s queued 11s
created

src/Form/Field/MultipleFile.php (2 issues)

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();
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 => $_) {
0 ignored issues
show
The property thumbnails 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...
375
                    $this->destroyThumbnailFile($path, $name);
0 ignored issues
show
Documentation Bug introduced by
The method destroyThumbnailFile does not exist on object<Encore\Admin\Form\Field\MultipleFile>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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