Completed
Push — master ( a31b88...59e60c )
by Song
02:41
created

Form::confirm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
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
765
                    $related = $this->model->$name;
766
767
                    // if related is empty
768
                    if (is_null($related)) {
769
                        $related = $relation->getRelated();
770
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
771
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
772
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
773
                    }
774
775
                    foreach ($prepared[$name] as $column => $value) {
776
                        $related->setAttribute($column, $value);
777
                    }
778
779
                    $related->save();
780
                    break;
781
                case $relation instanceof Relations\BelongsTo:
782
                case $relation instanceof Relations\MorphTo:
783
784
                    $parent = $this->model->$name;
785
786
                    // if related is empty
787
                    if (is_null($parent)) {
788
                        $parent = $relation->getRelated();
789
                    }
790
791
                    foreach ($prepared[$name] as $column => $value) {
792
                        $parent->setAttribute($column, $value);
793
                    }
794
795
                    $parent->save();
796
797
                    // When in creating, associate two models
798
                    $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName';
799
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
800
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
801
802
                        $this->model->save();
803
                    }
804
805
                    break;
806
                case $relation instanceof Relations\MorphOne:
807
                    $related = $this->model->$name;
808
                    if ($related === null) {
809
                        $related = $relation->make();
810
                    }
811
                    foreach ($prepared[$name] as $column => $value) {
812
                        $related->setAttribute($column, $value);
813
                    }
814
                    $related->save();
815
                    break;
816
                case $relation instanceof Relations\HasMany:
817
                case $relation instanceof Relations\MorphMany:
818
819
                    foreach ($prepared[$name] as $related) {
820
                        /** @var Relations\Relation $relation */
821
                        $relation = $this->model()->$name();
822
823
                        $keyName = $relation->getRelated()->getKeyName();
824
825
                        $instance = $relation->findOrNew(Arr::get($related, $keyName));
826
827
                        if (Arr::get($related, static::REMOVE_FLAG_NAME) == 1) {
828
                            $instance->delete();
829
830
                            continue;
831
                        }
832
833
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
834
835
                        $instance->fill($related);
836
837
                        $instance->save();
838
                    }
839
840
                    break;
841
            }
842
        }
843
    }
844
845
    /**
846
     * Prepare input data for update.
847
     *
848
     * @param array $updates
849
     * @param bool  $oneToOneRelation If column is one-to-one relation.
850
     *
851
     * @return array
852
     */
853
    protected function prepareUpdate(array $updates, $oneToOneRelation = false): array
854
    {
855
        $prepared = [];
856
857
        /** @var Field $field */
858
        foreach ($this->builder->fields() as $field) {
859
            $columns = $field->column();
860
861
            // If column not in input array data, then continue.
862
            if (!Arr::has($updates, $columns)) {
863
                continue;
864
            }
865
866
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
867
                continue;
868
            }
869
870
            $value = $this->getDataByColumn($updates, $columns);
871
872
            $value = $field->prepare($value);
873
874 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...
875
                foreach ($columns as $name => $column) {
876
                    Arr::set($prepared, $column, $value[$name]);
877
                }
878
            } elseif (is_string($columns)) {
879
                Arr::set($prepared, $columns, $value);
880
            }
881
        }
882
883
        return $prepared;
884
    }
885
886
    /**
887
     * @param string|array $columns
888
     * @param bool         $containsDot
889
     *
890
     * @return bool
891
     */
892
    protected function isInvalidColumn($columns, $containsDot = false): bool
893
    {
894
        foreach ((array) $columns as $column) {
895
            if ((!$containsDot && Str::contains($column, '.')) ||
896
                ($containsDot && !Str::contains($column, '.'))) {
897
                return true;
898
            }
899
        }
900
901
        return false;
902
    }
903
904
    /**
905
     * Prepare input data for insert.
906
     *
907
     * @param $inserts
908
     *
909
     * @return array
910
     */
911
    protected function prepareInsert($inserts): array
912
    {
913
        if ($this->isHasOneRelation($inserts)) {
914
            $inserts = Arr::dot($inserts);
915
        }
916
917
        foreach ($inserts as $column => $value) {
918
            if (($field = $this->getFieldByColumn($column)) === null) {
919
                unset($inserts[$column]);
920
                continue;
921
            }
922
923
            $inserts[$column] = $field->prepare($value);
924
        }
925
926
        $prepared = [];
927
928
        foreach ($inserts as $key => $value) {
929
            Arr::set($prepared, $key, $value);
930
        }
931
932
        return $prepared;
933
    }
934
935
    /**
936
     * Is input data is has-one relation.
937
     *
938
     * @param array $inserts
939
     *
940
     * @return bool
941
     */
942
    protected function isHasOneRelation($inserts): bool
943
    {
944
        $first = current($inserts);
945
946
        if (!is_array($first)) {
947
            return false;
948
        }
949
950
        if (is_array(current($first))) {
951
            return false;
952
        }
953
954
        return Arr::isAssoc($first);
955
    }
956
957
    /**
958
     * Ignore fields to save.
959
     *
960
     * @param string|array $fields
961
     *
962
     * @return $this
963
     */
964
    public function ignore($fields): self
965
    {
966
        $this->ignored = array_merge($this->ignored, (array) $fields);
967
968
        return $this;
969
    }
970
971
    /**
972
     * @param array        $data
973
     * @param string|array $columns
974
     *
975
     * @return array|mixed
976
     */
977 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...
978
    {
979
        if (is_string($columns)) {
980
            return Arr::get($data, $columns);
981
        }
982
983
        if (is_array($columns)) {
984
            $value = [];
985
            foreach ($columns as $name => $column) {
986
                if (!Arr::has($data, $column)) {
987
                    continue;
988
                }
989
                $value[$name] = Arr::get($data, $column);
990
            }
991
992
            return $value;
993
        }
994
    }
995
996
    /**
997
     * Find field object by column.
998
     *
999
     * @param $column
1000
     *
1001
     * @return mixed
1002
     */
1003
    protected function getFieldByColumn($column)
1004
    {
1005
        return $this->builder->fields()->first(
1006
            function (Field $field) use ($column) {
1007
                if (is_array($field->column())) {
1008
                    return in_array($column, $field->column());
1009
                }
1010
1011
                return $field->column() == $column;
1012
            }
1013
        );
1014
    }
1015
1016
    /**
1017
     * Set original data for each field.
1018
     *
1019
     * @return void
1020
     */
1021
    protected function setFieldOriginalValue()
1022
    {
1023
        $values = $this->model->toArray();
1024
1025
        $this->builder->fields()->each(function (Field $field) use ($values) {
1026
            $field->setOriginal($values);
1027
        });
1028
    }
1029
1030
    /**
1031
     * Set all fields value in form.
1032
     *
1033
     * @param $id
1034
     *
1035
     * @return void
1036
     */
1037
    protected function setFieldValue($id)
1038
    {
1039
        $relations = $this->getRelations();
1040
1041
        $builder = $this->model();
1042
1043
        if ($this->isSoftDeletes) {
1044
            $builder = $builder->withTrashed();
1045
        }
1046
1047
        $this->model = $builder->with($relations)->findOrFail($id);
1048
1049
        $this->callEditing();
1050
1051
        $data = $this->model->toArray();
1052
1053
        $this->builder->fields()->each(function (Field $field) use ($data) {
1054
            if (!in_array($field->column(), $this->ignored, true)) {
1055
                $field->fill($data);
1056
            }
1057
        });
1058
    }
1059
1060
    /**
1061
     * Add a fieldset to form.
1062
     *
1063
     * @param string  $title
1064
     * @param Closure $setCallback
1065
     *
1066
     * @return Field\Fieldset
1067
     */
1068 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...
1069
    {
1070
        $fieldset = new Field\Fieldset();
1071
1072
        $this->html($fieldset->start($title))->plain();
1073
1074
        $setCallback($this);
1075
1076
        $this->html($fieldset->end())->plain();
1077
1078
        return $fieldset;
1079
    }
1080
1081
    /**
1082
     * Get validation messages.
1083
     *
1084
     * @param array $input
1085
     *
1086
     * @return MessageBag|bool
1087
     */
1088
    public function validationMessages($input)
1089
    {
1090
        $failedValidators = [];
1091
1092
        /** @var Field $field */
1093 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...
1094
            if (!$validator = $field->getValidator($input)) {
1095
                continue;
1096
            }
1097
1098
            if (($validator instanceof Validator) && !$validator->passes()) {
1099
                $failedValidators[] = $validator;
1100
            }
1101
        }
1102
1103
        $message = $this->mergeValidationMessages($failedValidators);
1104
1105
        return $message->any() ? $message : false;
1106
    }
1107
1108
    /**
1109
     * Merge validation messages from input validators.
1110
     *
1111
     * @param \Illuminate\Validation\Validator[] $validators
1112
     *
1113
     * @return MessageBag
1114
     */
1115
    protected function mergeValidationMessages($validators): MessageBag
1116
    {
1117
        $messageBag = new MessageBag();
1118
1119
        foreach ($validators as $validator) {
1120
            $messageBag = $messageBag->merge($validator->messages());
1121
        }
1122
1123
        return $messageBag;
1124
    }
1125
1126
    /**
1127
     * Get all relations of model from callable.
1128
     *
1129
     * @return array
1130
     */
1131
    public function getRelations(): array
1132
    {
1133
        $relations = $columns = [];
1134
1135
        /** @var Field $field */
1136
        foreach ($this->builder->fields() as $field) {
1137
            $columns[] = $field->column();
1138
        }
1139
1140
        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...
1141
            if (Str::contains($column, '.')) {
1142
                list($relation) = explode('.', $column);
1143
1144
                if (method_exists($this->model, $relation) &&
1145
                    $this->model->$relation() instanceof Relations\Relation
1146
                ) {
1147
                    $relations[] = $relation;
1148
                }
1149
            } elseif (method_exists($this->model, $column) &&
1150
                !method_exists(Model::class, $column)
1151
            ) {
1152
                $relations[] = $column;
1153
            }
1154
        }
1155
1156
        return array_unique($relations);
1157
    }
1158
1159
    /**
1160
     * Set action for form.
1161
     *
1162
     * @param string $action
1163
     *
1164
     * @return $this
1165
     */
1166
    public function setAction($action): self
1167
    {
1168
        $this->builder()->setAction($action);
1169
1170
        return $this;
1171
    }
1172
1173
    /**
1174
     * Set field and label width in current form.
1175
     *
1176
     * @param int $fieldWidth
1177
     * @param int $labelWidth
1178
     *
1179
     * @return $this
1180
     */
1181
    public function setWidth($fieldWidth = 8, $labelWidth = 2): self
1182
    {
1183
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1184
            /* @var Field $field  */
1185
            $field->setWidth($fieldWidth, $labelWidth);
1186
        });
1187
1188
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1189
1190
        return $this;
1191
    }
1192
1193
    /**
1194
     * Set view for form.
1195
     *
1196
     * @param string $view
1197
     *
1198
     * @return $this
1199
     */
1200
    public function setView($view): self
1201
    {
1202
        $this->builder()->setView($view);
1203
1204
        return $this;
1205
    }
1206
1207
    /**
1208
     * Set title for form.
1209
     *
1210
     * @param string $title
1211
     *
1212
     * @return $this
1213
     */
1214
    public function setTitle($title = ''): self
1215
    {
1216
        $this->builder()->setTitle($title);
1217
1218
        return $this;
1219
    }
1220
1221
    /**
1222
     * Set a submit confirm.
1223
     *
1224
     * @param string $message
1225
     * @return $this
1226
     */
1227
    public function confirm(string $message)
1228
    {
1229
        $this->builder()->confirm($message);
1230
1231
        return $this;
1232
    }
1233
1234
    /**
1235
     * Add a row in form.
1236
     *
1237
     * @param Closure $callback
1238
     *
1239
     * @return $this
1240
     */
1241
    public function row(Closure $callback): self
1242
    {
1243
        $this->rows[] = new Row($callback, $this);
1244
1245
        return $this;
1246
    }
1247
1248
    /**
1249
     * Tools setting for form.
1250
     *
1251
     * @param Closure $callback
1252
     */
1253
    public function tools(Closure $callback)
1254
    {
1255
        $callback->call($this, $this->builder->getTools());
1256
    }
1257
1258
    /**
1259
     * @param Closure|null $callback
1260
     *
1261
     * @return Form\Tools
1262
     */
1263 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...
1264
    {
1265
        if (func_num_args() === 0) {
1266
            return $this->builder->getTools();
1267
        }
1268
1269
        $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...
1270
    }
1271
1272
    /**
1273
     * Indicates if current form page is creating.
1274
     *
1275
     * @return bool
1276
     */
1277
    public function isCreating(): bool
1278
    {
1279
        return Str::endsWith(\request()->route()->getName(), ['.create', '.store']);
1280
    }
1281
1282
    /**
1283
     * Indicates if current form page is editing.
1284
     *
1285
     * @return bool
1286
     */
1287
    public function isEditing(): bool
1288
    {
1289
        return Str::endsWith(\request()->route()->getName(), ['.edit', '.update']);
1290
    }
1291
1292
    /**
1293
     * Disable form submit.
1294
     *
1295
     * @param bool $disable
1296
     *
1297
     * @return $this
1298
     *
1299
     * @deprecated
1300
     */
1301
    public function disableSubmit(bool $disable = true): self
1302
    {
1303
        $this->builder()->getFooter()->disableSubmit($disable);
1304
1305
        return $this;
1306
    }
1307
1308
    /**
1309
     * Disable form reset.
1310
     *
1311
     * @param bool $disable
1312
     *
1313
     * @return $this
1314
     *
1315
     * @deprecated
1316
     */
1317
    public function disableReset(bool $disable = true): self
1318
    {
1319
        $this->builder()->getFooter()->disableReset($disable);
1320
1321
        return $this;
1322
    }
1323
1324
    /**
1325
     * Disable View Checkbox on footer.
1326
     *
1327
     * @param bool $disable
1328
     *
1329
     * @return $this
1330
     */
1331
    public function disableViewCheck(bool $disable = true): self
1332
    {
1333
        $this->builder()->getFooter()->disableViewCheck($disable);
1334
1335
        return $this;
1336
    }
1337
1338
    /**
1339
     * Disable Editing Checkbox on footer.
1340
     *
1341
     * @param bool $disable
1342
     *
1343
     * @return $this
1344
     */
1345
    public function disableEditingCheck(bool $disable = true): self
1346
    {
1347
        $this->builder()->getFooter()->disableEditingCheck($disable);
1348
1349
        return $this;
1350
    }
1351
1352
    /**
1353
     * Disable Creating Checkbox on footer.
1354
     *
1355
     * @param bool $disable
1356
     *
1357
     * @return $this
1358
     */
1359
    public function disableCreatingCheck(bool $disable = true): self
1360
    {
1361
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1362
1363
        return $this;
1364
    }
1365
1366
    /**
1367
     * Footer setting for form.
1368
     *
1369
     * @param Closure $callback
1370
     *
1371
     * @return \Encore\Admin\Form\Footer
1372
     */
1373 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...
1374
    {
1375
        if (func_num_args() === 0) {
1376
            return $this->builder()->getFooter();
1377
        }
1378
1379
        $callback($this->builder()->getFooter());
1380
    }
1381
1382
    /**
1383
     * Get current resource route url.
1384
     *
1385
     * @param int $slice
1386
     *
1387
     * @return string
1388
     */
1389
    public function resource($slice = -2): string
1390
    {
1391
        $segments = explode('/', trim(\request()->getUri(), '/'));
1392
1393
        if ($slice !== 0) {
1394
            $segments = array_slice($segments, 0, $slice);
1395
        }
1396
1397
        return implode('/', $segments);
1398
    }
1399
1400
    /**
1401
     * Render the form contents.
1402
     *
1403
     * @return string
1404
     */
1405
    public function render()
1406
    {
1407
        try {
1408
            return $this->builder->render();
1409
        } catch (\Exception $e) {
1410
            return Handler::renderException($e);
1411
        }
1412
    }
1413
1414
    /**
1415
     * Get or set input data.
1416
     *
1417
     * @param string $key
1418
     * @param null   $value
1419
     *
1420
     * @return array|mixed
1421
     */
1422
    public function input($key, $value = null)
1423
    {
1424
        if ($value === null) {
1425
            return Arr::get($this->inputs, $key);
1426
        }
1427
1428
        return Arr::set($this->inputs, $key, $value);
1429
    }
1430
1431
    /**
1432
     * Add a new layout column.
1433
     *
1434
     * @param int      $width
1435
     * @param \Closure $closure
1436
     *
1437
     * @return $this
1438
     */
1439 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...
1440
    {
1441
        $width = $width < 1 ? round(12 * $width) : $width;
1442
1443
        $this->layout->column($width, $closure);
1444
1445
        return $this;
1446
    }
1447
1448
    /**
1449
     * Initialize filter layout.
1450
     */
1451
    protected function initLayout()
1452
    {
1453
        $this->layout = new Layout($this);
1454
    }
1455
1456
    /**
1457
     * Getter.
1458
     *
1459
     * @param string $name
1460
     *
1461
     * @return array|mixed
1462
     */
1463
    public function __get($name)
1464
    {
1465
        return $this->input($name);
1466
    }
1467
1468
    /**
1469
     * Setter.
1470
     *
1471
     * @param string $name
1472
     * @param mixed  $value
1473
     *
1474
     * @return array
1475
     */
1476
    public function __set($name, $value)
1477
    {
1478
        return Arr::set($this->inputs, $name, $value);
1479
    }
1480
1481
    /**
1482
     * Generate a Field object and add to form builder if Field exists.
1483
     *
1484
     * @param string $method
1485
     * @param array  $arguments
1486
     *
1487
     * @return Field
1488
     */
1489
    public function __call($method, $arguments)
1490
    {
1491
        if ($className = static::findFieldClass($method)) {
1492
            $column = Arr::get($arguments, 0, ''); //[0];
1493
1494
            $element = new $className($column, array_slice($arguments, 1));
1495
1496
            $this->pushField($element);
1497
1498
            return $element;
1499
        }
1500
1501
        admin_error('Error', "Field type [$method] does not exist.");
1502
1503
        return new Field\Nullable();
1504
    }
1505
1506
    /**
1507
     * @return Layout
1508
     */
1509
    public function getLayout(): Layout
1510
    {
1511
        return $this->layout;
1512
    }
1513
}
1514