Completed
Push — master ( 840a3e...82646e )
by Song
02:35
created

Form::deleteFiles()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 2
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin;
4
5
use Closure;
6
use Encore\Admin\Exception\Handler;
7
use Encore\Admin\Form\Builder;
8
use Encore\Admin\Form\Concerns\HasFields;
9
use Encore\Admin\Form\Field;
10
use Encore\Admin\Form\Concerns\HasHooks;
11
use Encore\Admin\Form\Layout\Layout;
12
use Encore\Admin\Form\Row;
13
use Encore\Admin\Form\Tab;
14
use Encore\Admin\Traits\ShouldSnakeAttributes;
15
use Illuminate\Contracts\Support\Arrayable;
16
use Illuminate\Contracts\Support\Renderable;
17
use Illuminate\Database\Eloquent\Model;
18
use Illuminate\Database\Eloquent\Relations;
19
use Illuminate\Database\Eloquent\SoftDeletes;
20
use Illuminate\Http\Request;
21
use Illuminate\Support\Arr;
22
use Illuminate\Support\Facades\DB;
23
use Illuminate\Support\MessageBag;
24
use Illuminate\Support\Str;
25
use Illuminate\Validation\Validator;
26
use Spatie\EloquentSortable\Sortable;
27
use Symfony\Component\HttpFoundation\Response;
28
29
/**
30
 * Class Form.
31
 */
32
class Form implements Renderable
33
{
34
    use HasHooks,
35
        HasFields,
36
        ShouldSnakeAttributes;
37
    /**
38
     * Remove flag in `has many` form.
39
     */
40
    const REMOVE_FLAG_NAME = '_remove_';
41
42
    /**
43
     * Eloquent model of the form.
44
     *
45
     * @var Model
46
     */
47
    protected $model;
48
49
    /**
50
     * @var \Illuminate\Validation\Validator
51
     */
52
    protected $validator;
53
54
    /**
55
     * @var Builder
56
     */
57
    protected $builder;
58
59
    /**
60
     * Data for save to current model from input.
61
     *
62
     * @var array
63
     */
64
    protected $updates = [];
65
66
    /**
67
     * Data for save to model's relations from input.
68
     *
69
     * @var array
70
     */
71
    protected $relations = [];
72
73
    /**
74
     * Input data.
75
     *
76
     * @var array
77
     */
78
    protected $inputs = [];
79
80
    /**
81
     * @var Layout
82
     */
83
    protected $layout;
84
85
    /**
86
     * Ignored saving fields.
87
     *
88
     * @var array
89
     */
90
    protected $ignored = [];
91
92
    /**
93
     * Collected field assets.
94
     *
95
     * @var array
96
     */
97
    protected static $collectedAssets = [];
98
99
    /**
100
     * @var Form\Tab
101
     */
102
    protected $tab = null;
103
104
    /**
105
     * Field rows in form.
106
     *
107
     * @var array
108
     */
109
    public $rows = [];
110
111
    /**
112
     * @var bool
113
     */
114
    protected $isSoftDeletes = false;
115
116
    /**
117
     * Create a new form instance.
118
     *
119
     * @param $model
120
     * @param \Closure $callback
121
     */
122
    public function __construct($model, Closure $callback = null)
123
    {
124
        $this->model = $model;
125
126
        $this->builder = new Builder($this);
127
128
        $this->initLayout();
129
130
        if ($callback instanceof Closure) {
131
            $callback($this);
132
        }
133
134
        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model), true);
135
136
        $this->callInitCallbacks();
137
    }
138
139
    /**
140
     * @param Field $field
141
     *
142
     * @return $this
143
     */
144
    public function pushField(Field $field): self
145
    {
146
        $field->setForm($this);
147
148
        $width = $this->builder->getWidth();
149
        $field->setWidth($width['field'], $width['label']);
150
151
        $this->builder->fields()->push($field);
152
        $this->layout->addField($field);
153
154
        return $this;
155
    }
156
157
    /**
158
     * @return Model
159
     */
160
    public function model(): Model
161
    {
162
        return $this->model;
163
    }
164
165
    /**
166
     * @return Builder
167
     */
168
    public function builder(): Builder
169
    {
170
        return $this->builder;
171
    }
172
173
    /**
174
     * Generate a edit form.
175
     *
176
     * @param $id
177
     *
178
     * @return $this
179
     */
180
    public function edit($id): self
181
    {
182
        $this->builder->setMode(Builder::MODE_EDIT);
183
        $this->builder->setResourceId($id);
184
185
        $this->setFieldValue($id);
186
187
        return $this;
188
    }
189
190
    /**
191
     * Use tab to split form.
192
     *
193
     * @param string $title
194
     * @param Closure $content
195
     * @param bool $active
196
     *
197
     * @return $this
198
     */
199
    public function tab($title, Closure $content, bool $active = false): self
200
    {
201
        $this->setTab()->append($title, $content, $active);
202
203
        return $this;
204
    }
205
206
    /**
207
     * Get Tab instance.
208
     *
209
     * @return Tab
210
     */
211
    public function getTab()
212
    {
213
        return $this->tab;
214
    }
215
216
    /**
217
     * Set Tab instance.
218
     *
219
     * @return Tab
220
     */
221
    public function setTab(): Tab
222
    {
223
        if ($this->tab === null) {
224
            $this->tab = new Tab($this);
225
        }
226
227
        return $this->tab;
228
    }
229
230
    /**
231
     * Destroy data entity and remove files.
232
     *
233
     * @param $id
234
     *
235
     * @return mixed
236
     */
237
    public function destroy($id)
238
    {
239
        try {
240
            if (($ret = $this->callDeleting($id)) instanceof Response) {
241
                return $ret;
242
            }
243
244
            collect(explode(',', $id))->filter()->each(function ($id) {
245
                $builder = $this->model()->newQuery();
246
247
                if ($this->isSoftDeletes) {
248
                    $builder = $builder->withTrashed();
249
                }
250
251
                $model = $builder->with($this->getRelations())->findOrFail($id);
252
253
                if ($this->isSoftDeletes && $model->trashed()) {
254
                    $this->deleteFiles($model, true);
255
                    $model->forceDelete();
256
257
                    return;
258
                }
259
260
                $this->deleteFiles($model);
261
                $model->delete();
262
            });
263
264
            if (($ret = $this->callDeleted()) instanceof Response) {
265
                return $ret;
266
            }
267
268
            $response = [
269
                'status'  => true,
270
                'message' => trans('admin.delete_succeeded'),
271
            ];
272
        } catch (\Exception $exception) {
273
            $response = [
274
                'status'  => false,
275
                'message' => $exception->getMessage() ?: trans('admin.delete_failed'),
276
            ];
277
        }
278
279
        return response()->json($response);
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
280
    }
281
282
    /**
283
     * Remove files in record.
284
     *
285
     * @param Model $model
286
     * @param bool $forceDelete
287
     */
288
    protected function deleteFiles(Model $model, $forceDelete = false)
289
    {
290
        // If it's a soft delete, the files in the data will not be deleted.
291
        if (!$forceDelete && $this->isSoftDeletes) {
292
            return;
293
        }
294
295
        $data = $model->toArray();
296
297
        $this->builder->fields()->filter(function ($field) {
298
            return $field instanceof Field\File;
299
        })->each(function (Field\File $file) use ($data) {
300
            $file->setOriginal($data);
301
302
            $file->destroy();
303
        });
304
    }
305
306
    /**
307
     * Store a new record.
308
     *
309
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
310
     */
311
    public function store()
312
    {
313
        $data = \request()->all();
314
315
        // Handle validation errors.
316
        if ($validationMessages = $this->validationMessages($data)) {
317
            return $this->responseValidationError($validationMessages);
318
        }
319
320
        if (($response = $this->prepare($data)) instanceof Response) {
321
            return $response;
322
        }
323
324 View Code Duplication
        DB::transaction(function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
325
            $inserts = $this->prepareInsert($this->updates);
326
327
            foreach ($inserts as $column => $value) {
328
                $this->model->setAttribute($column, $value);
329
            }
330
331
            $this->model->save();
332
333
            $this->updateRelation($this->relations);
334
        });
335
336
        if (($response = $this->callSaved()) instanceof Response) {
337
            return $response;
338
        }
339
340
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
341
            return $response;
342
        }
343
344
        return $this->redirectAfterStore();
345
    }
346
347
    /**
348
     * @param MessageBag $message
349
     *
350
     * @return $this|\Illuminate\Http\JsonResponse
351
     */
352
    protected function responseValidationError(MessageBag $message)
353
    {
354
        if (\request()->ajax() && !\request()->pjax()) {
355
            return response()->json([
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
356
                'status'     => false,
357
                'validation' => $message,
358
                'message'    => $message->first(),
359
            ]);
360
        }
361
362
        return back()->withInput()->withErrors($message);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return back()->withInput()->withErrors($message); (Illuminate\Http\RedirectResponse) is incompatible with the return type documented by Encore\Admin\Form::responseValidationError of type Encore\Admin\Form|Illuminate\Http\JsonResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
363
    }
364
365
    /**
366
     * Get ajax response.
367
     *
368
     * @param string $message
369
     *
370
     * @return bool|\Illuminate\Http\JsonResponse
371
     */
372
    protected function ajaxResponse($message)
373
    {
374
        $request = Request::capture();
375
376
        // ajax but not pjax
377
        if ($request->ajax() && !$request->pjax()) {
378
            return response()->json([
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
379
                'status'    => true,
380
                'message'   => $message,
381
                'display'  => $this->applayFieldDisplay()
382
            ]);
383
        }
384
385
        return false;
386
    }
387
388
    /**
389
     * @return array
390
     */
391
    protected function applayFieldDisplay()
392
    {
393
        $editable = [];
394
395
        /** @var Field $field */
396
        foreach ($this->builder()->fields() as $field) {
397
            if (! \request()->has($field->column())) {
398
                continue;
399
            }
400
401
            $newValue = $this->model->fresh()->getAttribute($field->column());
402
403
            if ($newValue instanceof Arrayable) {
404
                $newValue = $newValue->toArray();
405
            }
406
407
            if ($field instanceof Field\BelongsTo || $field instanceof Field\BelongsToMany) {
408
                $selectable = $field->getSelectable();
409
410
                if (method_exists($selectable, 'display')) {
411
                    $display = $selectable::display();
412
413
                    $editable[$field->column()] = $display->call($this->model, $newValue);
414
                }
415
            }
416
        }
417
418
        return $editable;
419
    }
420
421
    /**
422
     * Prepare input data for insert or update.
423
     *
424
     * @param array $data
425
     *
426
     * @return mixed
427
     */
428
    protected function prepare($data = [])
429
    {
430
        if (($response = $this->callSubmitted()) instanceof Response) {
431
            return $response;
432
        }
433
434
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
435
436
        if (($response = $this->callSaving()) instanceof Response) {
437
            return $response;
438
        }
439
440
        $this->relations = $this->getRelationInputs($this->inputs);
441
442
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
443
    }
444
445
    /**
446
     * Remove ignored fields from input.
447
     *
448
     * @param array $input
449
     *
450
     * @return array
451
     */
452
    protected function removeIgnoredFields($input): array
453
    {
454
        Arr::forget($input, $this->ignored);
455
456
        return $input;
457
    }
458
459
    /**
460
     * Get inputs for relations.
461
     *
462
     * @param array $inputs
463
     *
464
     * @return array
465
     */
466
    protected function getRelationInputs($inputs = []): array
467
    {
468
        $relations = [];
469
470
        foreach ($inputs as $column => $value) {
471
            if (method_exists($this->model, $column) ||
472
                method_exists($this->model, $column = Str::camel($column))) {
473
                $relation = call_user_func([$this->model, $column]);
474
475
                if ($relation instanceof Relations\Relation) {
476
                    $relations[$column] = $value;
477
                }
478
            }
479
        }
480
481
        return $relations;
482
    }
483
484
    /**
485
     * Handle update.
486
     *
487
     * @param int $id
488
     * @param null $data
489
     *
490
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
491
     */
492
    public function update($id, $data = null)
493
    {
494
        $data = ($data) ?: request()->all();
495
496
        $isEditable = $this->isEditable($data);
497
498
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
499
            return $data;
500
        }
501
502
        /* @var Model $this ->model */
503
        $builder = $this->model();
504
505
        if ($this->isSoftDeletes) {
506
            $builder = $builder->withTrashed();
507
        }
508
509
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
510
511
        $this->setFieldOriginalValue();
512
513
        // Handle validation errors.
514
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 498 can also be of type object<Illuminate\Contra...outing\ResponseFactory>; however, Encore\Admin\Form::validationMessages() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
515
            if (!$isEditable) {
516
                return back()->withInput()->withErrors($validationMessages);
517
            }
518
519
            return response()->json(['errors' => Arr::dot($validationMessages->getMessages())], 422);
0 ignored issues
show
Documentation introduced by
$validationMessages->getMessages() is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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...
520
        }
521
522
        if (($response = $this->prepare($data)) instanceof Response) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 498 can also be of type object<Illuminate\Contra...outing\ResponseFactory>; however, Encore\Admin\Form::prepare() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
523
            return $response;
524
        }
525
526 View Code Duplication
        DB::transaction(function () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
527
            $updates = $this->prepareUpdate($this->updates);
528
529
            foreach ($updates as $column => $value) {
530
                /* @var Model $this ->model */
531
                $this->model->setAttribute($column, $value);
532
            }
533
534
            $this->model->save();
535
536
            $this->updateRelation($this->relations);
537
        });
538
539
        if (($result = $this->callSaved()) instanceof Response) {
540
            return $result;
541
        }
542
543
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
544
            return $response;
545
        }
546
547
        return $this->redirectAfterUpdate($id);
548
    }
549
550
    /**
551
     * Get RedirectResponse after store.
552
     *
553
     * @return \Illuminate\Http\RedirectResponse
554
     */
555
    protected function redirectAfterStore()
556
    {
557
        $resourcesPath = $this->resource(0);
558
559
        $key = $this->model->getKey();
560
561
        return $this->redirectAfterSaving($resourcesPath, $key);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->redirectAfterSaving($resourcesPath, $key); of type Illuminate\Http\Redirect...nate\Routing\Redirector adds the type Illuminate\Routing\Redirector to the return on line 561 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
562
    }
563
564
    /**
565
     * Get RedirectResponse after update.
566
     *
567
     * @param mixed $key
568
     *
569
     * @return \Illuminate\Http\RedirectResponse
570
     */
571
    protected function redirectAfterUpdate($key)
572
    {
573
        $resourcesPath = $this->resource(-1);
574
575
        return $this->redirectAfterSaving($resourcesPath, $key);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->redirectAfterSaving($resourcesPath, $key); of type Illuminate\Http\Redirect...nate\Routing\Redirector adds the type Illuminate\Routing\Redirector to the return on line 575 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
576
    }
577
578
    /**
579
     * Get RedirectResponse after data saving.
580
     *
581
     * @param string $resourcesPath
582
     * @param string $key
583
     *
584
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
585
     */
586
    protected function redirectAfterSaving($resourcesPath, $key)
587
    {
588
        if (request('after-save') == 1) {
589
            // continue editing
590
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
591
        } elseif (request('after-save') == 2) {
592
            // continue creating
593
            $url = rtrim($resourcesPath, '/').'/create';
594
        } elseif (request('after-save') == 3) {
595
            // view resource
596
            $url = rtrim($resourcesPath, '/')."/{$key}";
597
        } else {
598
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
599
        }
600
601
        admin_toastr(trans('admin.save_succeeded'));
602
603
        return redirect($url);
604
    }
605
606
    /**
607
     * Check if request is from editable.
608
     *
609
     * @param array $input
610
     *
611
     * @return bool
612
     */
613
    protected function isEditable(array $input = []): bool
614
    {
615
        return array_key_exists('_editable', $input);
616
    }
617
618
    /**
619
     * Handle updates for single column.
620
     *
621
     * @param int   $id
622
     * @param array $data
623
     *
624
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
625
     */
626
    protected function handleColumnUpdates($id, $data)
627
    {
628
        $data = $this->handleEditable($data);
629
630
        $data = $this->handleFileDelete($data);
631
632
        $data = $this->handleFileSort($data);
633
634
        if ($this->handleOrderable($id, $data)) {
635
            return response([
636
                'status'  => true,
637
                'message' => trans('admin.update_succeeded'),
638
            ]);
639
        }
640
641
        return $data;
642
    }
643
644
    /**
645
     * Handle editable update.
646
     *
647
     * @param array $input
648
     *
649
     * @return array
650
     */
651
    protected function handleEditable(array $input = []): array
652
    {
653
        if (array_key_exists('_editable', $input)) {
654
            $name = $input['name'];
655
            $value = $input['value'];
656
657
            Arr::forget($input, ['pk', 'value', 'name']);
658
            Arr::set($input, $name, $value);
659
        }
660
661
        return $input;
662
    }
663
664
    /**
665
     * @param array $input
666
     *
667
     * @return array
668
     */
669
    protected function handleFileDelete(array $input = []): array
670
    {
671
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
672
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
673
            unset($input['key']);
674
        }
675
676
        request()->replace($input);
677
678
        return $input;
679
    }
680
681
    /**
682
     * @param array $input
683
     *
684
     * @return array
685
     */
686
    protected function handleFileSort(array $input = []): array
687
    {
688
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
689
            return $input;
690
        }
691
692
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
693
694
        if (empty($sorts)) {
695
            return $input;
696
        }
697
698
        foreach ($sorts as $column => $order) {
699
            $input[$column] = $order;
700
        }
701
702
        request()->replace($input);
703
704
        return $input;
705
    }
706
707
    /**
708
     * Handle orderable update.
709
     *
710
     * @param int   $id
711
     * @param array $input
712
     *
713
     * @return bool
714
     */
715
    protected function handleOrderable($id, array $input = [])
716
    {
717
        if (array_key_exists('_orderable', $input)) {
718
            $model = $this->model->find($id);
719
720
            if ($model instanceof Sortable) {
0 ignored issues
show
Bug introduced by
The class Spatie\EloquentSortable\Sortable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
721
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
722
723
                return true;
724
            }
725
        }
726
727
        return false;
728
    }
729
730
    /**
731
     * Update relation data.
732
     *
733
     * @param array $relationsData
734
     *
735
     * @return void
736
     */
737
    protected function updateRelation($relationsData)
738
    {
739
        foreach ($relationsData as $name => $values) {
740
            if (!method_exists($this->model, $name)) {
741
                continue;
742
            }
743
744
            $relation = $this->model->$name();
745
746
            $oneToOneRelation = $relation instanceof Relations\HasOne
747
                || $relation instanceof Relations\MorphOne
748
                || $relation instanceof Relations\BelongsTo;
749
750
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
751
752
            if (empty($prepared)) {
753
                continue;
754
            }
755
756
            switch (true) {
757
                case $relation instanceof Relations\BelongsToMany:
758
                case $relation instanceof Relations\MorphToMany:
759
                    if (isset($prepared[$name])) {
760
                        $relation->sync($prepared[$name]);
761
                    }
762
                    break;
763
                case $relation instanceof Relations\HasOne:
764
                case $relation instanceof Relations\MorphOne:
765
                    $related = $this->model->getRelationValue($name) ?: $relation->getRelated();
766
767
                    foreach ($prepared[$name] as $column => $value) {
768
                        $related->setAttribute($column, $value);
769
                    }
770
771
                    // save child
772
                    $relation->save($related);
773
                    break;
774
                case $relation instanceof Relations\BelongsTo:
775
                case $relation instanceof Relations\MorphTo:
776
                    $related = $this->model->getRelationValue($name) ?: $relation->getRelated();
777
778
                    foreach ($prepared[$name] as $column => $value) {
779
                        $related->setAttribute($column, $value);
780
                    }
781
782
                    // save parent
783
                    $related->save();
784
785
                    // save child (self)
786
                    $relation->associate($related)->save();
787
                    break;
788
                case $relation instanceof Relations\HasMany:
789
                case $relation instanceof Relations\MorphMany:
790
                    foreach ($prepared[$name] as $related) {
791
                        /** @var Relations\HasOneOrMany $relation */
792
                        $relation = $this->model->$name();
793
794
                        $keyName = $relation->getRelated()->getKeyName();
795
796
                        /** @var Model $child */
797
                        $child = $relation->findOrNew(Arr::get($related, $keyName));
798
799
                        if (Arr::get($related, static::REMOVE_FLAG_NAME) == 1) {
800
                            $child->delete();
801
                            continue;
802
                        }
803
804
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
805
806
                        foreach ($related as $colum => $value) {
807
                            $child->setAttribute($colum, $value);
808
                        }
809
810
                        $child->save();
811
                    }
812
                    break;
813
            }
814
        }
815
    }
816
817
    /**
818
     * Prepare input data for update.
819
     *
820
     * @param array $updates
821
     * @param bool  $oneToOneRelation If column is one-to-one relation.
822
     *
823
     * @return array
824
     */
825
    protected function prepareUpdate(array $updates, $oneToOneRelation = false): array
826
    {
827
        $prepared = [];
828
829
        /** @var Field $field */
830
        foreach ($this->builder->fields() as $field) {
831
            $columns = $field->column();
832
833
            // If column not in input array data, then continue.
834
            if (!Arr::has($updates, $columns)) {
835
                continue;
836
            }
837
838
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
839
                continue;
840
            }
841
842
            $value = $this->getDataByColumn($updates, $columns);
843
844
            $value = $field->prepare($value);
845
846 View Code Duplication
            if (is_array($columns)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
847
                foreach ($columns as $name => $column) {
848
                    Arr::set($prepared, $column, $value[$name]);
849
                }
850
            } elseif (is_string($columns)) {
851
                Arr::set($prepared, $columns, $value);
852
            }
853
        }
854
855
        return $prepared;
856
    }
857
858
    /**
859
     * @param string|array $columns
860
     * @param bool         $containsDot
861
     *
862
     * @return bool
863
     */
864
    protected function isInvalidColumn($columns, $containsDot = false): bool
865
    {
866
        foreach ((array) $columns as $column) {
867
            if ((!$containsDot && Str::contains($column, '.')) ||
868
                ($containsDot && !Str::contains($column, '.'))) {
869
                return true;
870
            }
871
        }
872
873
        return false;
874
    }
875
876
    /**
877
     * Prepare input data for insert.
878
     *
879
     * @param $inserts
880
     *
881
     * @return array
882
     */
883
    protected function prepareInsert($inserts): array
884
    {
885
        if ($this->isHasOneRelation($inserts)) {
886
            $inserts = Arr::dot($inserts);
887
        }
888
889
        foreach ($inserts as $column => $value) {
890
            if (($field = $this->getFieldByColumn($column)) === null) {
891
                unset($inserts[$column]);
892
                continue;
893
            }
894
895
            $inserts[$column] = $field->prepare($value);
896
        }
897
898
        $prepared = [];
899
900
        foreach ($inserts as $key => $value) {
901
            Arr::set($prepared, $key, $value);
902
        }
903
904
        return $prepared;
905
    }
906
907
    /**
908
     * Is input data is has-one relation.
909
     *
910
     * @param array $inserts
911
     *
912
     * @return bool
913
     */
914
    protected function isHasOneRelation($inserts): bool
915
    {
916
        $first = current($inserts);
917
918
        if (!is_array($first)) {
919
            return false;
920
        }
921
922
        if (is_array(current($first))) {
923
            return false;
924
        }
925
926
        return Arr::isAssoc($first);
927
    }
928
929
    /**
930
     * Ignore fields to save.
931
     *
932
     * @param string|array $fields
933
     *
934
     * @return $this
935
     */
936
    public function ignore($fields): self
937
    {
938
        $this->ignored = array_merge($this->ignored, (array) $fields);
939
940
        return $this;
941
    }
942
943
    /**
944
     * @param array        $data
945
     * @param string|array $columns
946
     *
947
     * @return array|mixed
948
     */
949 View Code Duplication
    protected function getDataByColumn($data, $columns)
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...
950
    {
951
        if (is_string($columns)) {
952
            return Arr::get($data, $columns);
953
        }
954
955
        if (is_array($columns)) {
956
            $value = [];
957
            foreach ($columns as $name => $column) {
958
                if (!Arr::has($data, $column)) {
959
                    continue;
960
                }
961
                $value[$name] = Arr::get($data, $column);
962
            }
963
964
            return $value;
965
        }
966
    }
967
968
    /**
969
     * Find field object by column.
970
     *
971
     * @param $column
972
     *
973
     * @return mixed
974
     */
975
    protected function getFieldByColumn($column)
976
    {
977
        return $this->builder->fields()->first(
978
            function (Field $field) use ($column) {
979
                if (is_array($field->column())) {
980
                    return in_array($column, $field->column());
981
                }
982
983
                return $field->column() == $column;
984
            }
985
        );
986
    }
987
988
    /**
989
     * Set original data for each field.
990
     *
991
     * @return void
992
     */
993
    protected function setFieldOriginalValue()
994
    {
995
        $values = $this->model->toArray();
996
997
        $this->builder->fields()->each(function (Field $field) use ($values) {
998
            $field->setOriginal($values);
999
        });
1000
    }
1001
1002
    /**
1003
     * Set all fields value in form.
1004
     *
1005
     * @param $id
1006
     *
1007
     * @return void
1008
     */
1009
    protected function setFieldValue($id)
1010
    {
1011
        $relations = $this->getRelations();
1012
1013
        $builder = $this->model();
1014
1015
        if ($this->isSoftDeletes) {
1016
            $builder = $builder->withTrashed();
1017
        }
1018
1019
        $this->model = $builder->with($relations)->findOrFail($id);
1020
1021
        $this->callEditing();
1022
1023
        $data = $this->model->toArray();
1024
1025
        $this->builder->fields()->each(function (Field $field) use ($data) {
1026
            if (!in_array($field->column(), $this->ignored, true)) {
1027
                $field->fill($data);
1028
            }
1029
        });
1030
    }
1031
1032
    /**
1033
     * Add a fieldset to form.
1034
     *
1035
     * @param string  $title
1036
     * @param Closure $setCallback
1037
     *
1038
     * @return Field\Fieldset
1039
     */
1040 View Code Duplication
    public function fieldset(string $title, Closure $setCallback)
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...
1041
    {
1042
        $fieldset = new Field\Fieldset();
1043
1044
        $this->html($fieldset->start($title))->plain();
1045
1046
        $setCallback($this);
1047
1048
        $this->html($fieldset->end())->plain();
1049
1050
        return $fieldset;
1051
    }
1052
1053
    /**
1054
     * Get validation messages.
1055
     *
1056
     * @param array $input
1057
     *
1058
     * @return MessageBag|bool
1059
     */
1060
    public function validationMessages($input)
1061
    {
1062
        $failedValidators = [];
1063
1064
        /** @var Field $field */
1065 View Code Duplication
        foreach ($this->builder->fields() as $field) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1066
            if (!$validator = $field->getValidator($input)) {
1067
                continue;
1068
            }
1069
1070
            if (($validator instanceof Validator) && !$validator->passes()) {
1071
                $failedValidators[] = $validator;
1072
            }
1073
        }
1074
1075
        $message = $this->mergeValidationMessages($failedValidators);
1076
1077
        return $message->any() ? $message : false;
1078
    }
1079
1080
    /**
1081
     * Merge validation messages from input validators.
1082
     *
1083
     * @param \Illuminate\Validation\Validator[] $validators
1084
     *
1085
     * @return MessageBag
1086
     */
1087
    protected function mergeValidationMessages($validators): MessageBag
1088
    {
1089
        $messageBag = new MessageBag();
1090
1091
        foreach ($validators as $validator) {
1092
            $messageBag = $messageBag->merge($validator->messages());
1093
        }
1094
1095
        return $messageBag;
1096
    }
1097
1098
    /**
1099
     * Get all relations of model from callable.
1100
     *
1101
     * @return array
1102
     */
1103
    public function getRelations(): array
1104
    {
1105
        $relations = $columns = [];
1106
1107
        /** @var Field $field */
1108
        foreach ($this->builder->fields() as $field) {
1109
            $columns[] = $field->column();
1110
        }
1111
1112
        foreach (Arr::flatten($columns) as $column) {
0 ignored issues
show
Documentation introduced by
$columns is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1113
            if (Str::contains($column, '.')) {
1114
                list($relation) = explode('.', $column);
1115
1116
                if (method_exists($this->model, $relation) &&
1117
                    $this->model->$relation() instanceof Relations\Relation
1118
                ) {
1119
                    $relations[] = $relation;
1120
                }
1121
            } elseif (method_exists($this->model, $column) &&
1122
                !method_exists(Model::class, $column)
1123
            ) {
1124
                $relations[] = $column;
1125
            }
1126
        }
1127
1128
        return array_unique($relations);
1129
    }
1130
1131
    /**
1132
     * Set action for form.
1133
     *
1134
     * @param string $action
1135
     *
1136
     * @return $this
1137
     */
1138
    public function setAction($action): self
1139
    {
1140
        $this->builder()->setAction($action);
1141
1142
        return $this;
1143
    }
1144
1145
    /**
1146
     * Set field and label width in current form.
1147
     *
1148
     * @param int $fieldWidth
1149
     * @param int $labelWidth
1150
     *
1151
     * @return $this
1152
     */
1153
    public function setWidth($fieldWidth = 8, $labelWidth = 2): self
1154
    {
1155
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1156
            /* @var Field $field  */
1157
            $field->setWidth($fieldWidth, $labelWidth);
1158
        });
1159
1160
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1161
1162
        return $this;
1163
    }
1164
1165
    /**
1166
     * Set view for form.
1167
     *
1168
     * @param string $view
1169
     *
1170
     * @return $this
1171
     */
1172
    public function setView($view): self
1173
    {
1174
        $this->builder()->setView($view);
1175
1176
        return $this;
1177
    }
1178
1179
    /**
1180
     * Set title for form.
1181
     *
1182
     * @param string $title
1183
     *
1184
     * @return $this
1185
     */
1186
    public function setTitle($title = ''): self
1187
    {
1188
        $this->builder()->setTitle($title);
1189
1190
        return $this;
1191
    }
1192
1193
    /**
1194
     * Set a submit confirm.
1195
     *
1196
     * @param string $message
1197
     * @param string $on
1198
     * @return $this
1199
     */
1200
    public function confirm(string $message, $on = null)
1201
    {
1202
        if ($on && !in_array($on, ['create', 'edit'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $on of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1203
            throw new \InvalidArgumentException("The second paramater `\$on` must be one of ['create', 'edit']");
1204
        }
1205
1206
        if ($on == 'create' && !$this->isCreating()) {
1207
            return;
1208
        }
1209
1210
        if ($on == 'edit' && !$this->isEditing()) {
1211
            return;
1212
        }
1213
1214
        $this->builder()->confirm($message);
1215
1216
        return $this;
1217
    }
1218
1219
    /**
1220
     * Add a row in form.
1221
     *
1222
     * @param Closure $callback
1223
     *
1224
     * @return $this
1225
     */
1226
    public function row(Closure $callback): self
1227
    {
1228
        $this->rows[] = new Row($callback, $this);
1229
1230
        return $this;
1231
    }
1232
1233
    /**
1234
     * Tools setting for form.
1235
     *
1236
     * @param Closure $callback
1237
     */
1238
    public function tools(Closure $callback)
1239
    {
1240
        $callback->call($this, $this->builder->getTools());
1241
    }
1242
1243
    /**
1244
     * @param Closure|null $callback
1245
     *
1246
     * @return Form\Tools
1247
     */
1248 View Code Duplication
    public function header(Closure $callback = null)
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...
1249
    {
1250
        if (func_num_args() === 0) {
1251
            return $this->builder->getTools();
1252
        }
1253
1254
        $callback->call($this, $this->builder->getTools());
0 ignored issues
show
Bug introduced by
It seems like $callback is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
1255
    }
1256
1257
    /**
1258
     * Indicates if current form page is creating.
1259
     *
1260
     * @return bool
1261
     */
1262
    public function isCreating(): bool
1263
    {
1264
        return Str::endsWith(\request()->route()->getName(), ['.create', '.store']);
1265
    }
1266
1267
    /**
1268
     * Indicates if current form page is editing.
1269
     *
1270
     * @return bool
1271
     */
1272
    public function isEditing(): bool
1273
    {
1274
        return Str::endsWith(\request()->route()->getName(), ['.edit', '.update']);
1275
    }
1276
1277
    /**
1278
     * Disable form submit.
1279
     *
1280
     * @param bool $disable
1281
     *
1282
     * @return $this
1283
     *
1284
     * @deprecated
1285
     */
1286
    public function disableSubmit(bool $disable = true): self
1287
    {
1288
        $this->builder()->getFooter()->disableSubmit($disable);
1289
1290
        return $this;
1291
    }
1292
1293
    /**
1294
     * Disable form reset.
1295
     *
1296
     * @param bool $disable
1297
     *
1298
     * @return $this
1299
     *
1300
     * @deprecated
1301
     */
1302
    public function disableReset(bool $disable = true): self
1303
    {
1304
        $this->builder()->getFooter()->disableReset($disable);
1305
1306
        return $this;
1307
    }
1308
1309
    /**
1310
     * Disable View Checkbox on footer.
1311
     *
1312
     * @param bool $disable
1313
     *
1314
     * @return $this
1315
     */
1316
    public function disableViewCheck(bool $disable = true): self
1317
    {
1318
        $this->builder()->getFooter()->disableViewCheck($disable);
1319
1320
        return $this;
1321
    }
1322
1323
    /**
1324
     * Disable Editing Checkbox on footer.
1325
     *
1326
     * @param bool $disable
1327
     *
1328
     * @return $this
1329
     */
1330
    public function disableEditingCheck(bool $disable = true): self
1331
    {
1332
        $this->builder()->getFooter()->disableEditingCheck($disable);
1333
1334
        return $this;
1335
    }
1336
1337
    /**
1338
     * Disable Creating Checkbox on footer.
1339
     *
1340
     * @param bool $disable
1341
     *
1342
     * @return $this
1343
     */
1344
    public function disableCreatingCheck(bool $disable = true): self
1345
    {
1346
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1347
1348
        return $this;
1349
    }
1350
1351
    /**
1352
     * Footer setting for form.
1353
     *
1354
     * @param Closure $callback
1355
     *
1356
     * @return \Encore\Admin\Form\Footer
1357
     */
1358 View Code Duplication
    public function footer(Closure $callback = null)
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...
1359
    {
1360
        if (func_num_args() === 0) {
1361
            return $this->builder()->getFooter();
1362
        }
1363
1364
        $callback($this->builder()->getFooter());
1365
    }
1366
1367
    /**
1368
     * Get current resource route url.
1369
     *
1370
     * @param int $slice
1371
     *
1372
     * @return string
1373
     */
1374
    public function resource($slice = -2): string
1375
    {
1376
        $segments = explode('/', trim(\request()->getUri(), '/'));
1377
1378
        if ($slice !== 0) {
1379
            $segments = array_slice($segments, 0, $slice);
1380
        }
1381
1382
        return implode('/', $segments);
1383
    }
1384
1385
    /**
1386
     * Render the form contents.
1387
     *
1388
     * @return string
1389
     */
1390
    public function render()
1391
    {
1392
        try {
1393
            return $this->builder->render();
1394
        } catch (\Exception $e) {
1395
            return Handler::renderException($e);
1396
        }
1397
    }
1398
1399
    /**
1400
     * Get or set input data.
1401
     *
1402
     * @param string $key
1403
     * @param null   $value
1404
     *
1405
     * @return array|mixed
1406
     */
1407
    public function input($key, $value = null)
1408
    {
1409
        if ($value === null) {
1410
            return Arr::get($this->inputs, $key);
1411
        }
1412
1413
        return Arr::set($this->inputs, $key, $value);
1414
    }
1415
1416
    /**
1417
     * Add a new layout column.
1418
     *
1419
     * @param int      $width
1420
     * @param \Closure $closure
1421
     *
1422
     * @return $this
1423
     */
1424 View Code Duplication
    public function column($width, \Closure $closure): self
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...
1425
    {
1426
        $width = $width < 1 ? round(12 * $width) : $width;
1427
1428
        $this->layout->column($width, $closure);
1429
1430
        return $this;
1431
    }
1432
1433
    /**
1434
     * Initialize filter layout.
1435
     */
1436
    protected function initLayout()
1437
    {
1438
        $this->layout = new Layout($this);
1439
    }
1440
1441
    /**
1442
     * Getter.
1443
     *
1444
     * @param string $name
1445
     *
1446
     * @return array|mixed
1447
     */
1448
    public function __get($name)
1449
    {
1450
        return $this->input($name);
1451
    }
1452
1453
    /**
1454
     * Setter.
1455
     *
1456
     * @param string $name
1457
     * @param mixed  $value
1458
     *
1459
     * @return array
1460
     */
1461
    public function __set($name, $value)
1462
    {
1463
        return Arr::set($this->inputs, $name, $value);
1464
    }
1465
1466
    /**
1467
     * Generate a Field object and add to form builder if Field exists.
1468
     *
1469
     * @param string $method
1470
     * @param array  $arguments
1471
     *
1472
     * @return Field
1473
     */
1474
    public function __call($method, $arguments)
1475
    {
1476
        if ($className = static::findFieldClass($method)) {
1477
            $column = Arr::get($arguments, 0, ''); //[0];
1478
1479
            $element = new $className($column, array_slice($arguments, 1));
1480
1481
            $this->pushField($element);
1482
1483
            return $element;
1484
        }
1485
1486
        admin_error('Error', "Field type [$method] does not exist.");
1487
1488
        return new Field\Nullable();
1489
    }
1490
1491
    /**
1492
     * @return Layout
1493
     */
1494
    public function getLayout(): Layout
1495
    {
1496
        return $this->layout;
1497
    }
1498
}
1499