Completed
Push — master ( 6de3cf...c386e0 )
by Song
03:18
created

Form::fieldset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 12
loc 12
rs 9.8666
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\Field;
9
use Encore\Admin\Form\HasHooks;
10
use Encore\Admin\Form\Row;
11
use Encore\Admin\Form\Tab;
12
use Illuminate\Contracts\Support\Renderable;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Database\Eloquent\Relations;
15
use Illuminate\Database\Eloquent\SoftDeletes;
16
use Illuminate\Http\Request;
17
use Illuminate\Support\Arr;
18
use Illuminate\Support\Facades\DB;
19
use Illuminate\Support\MessageBag;
20
use Illuminate\Support\Str;
21
use Illuminate\Validation\Validator;
22
use Spatie\EloquentSortable\Sortable;
23
use Symfony\Component\HttpFoundation\Response;
24
25
/**
26
 * Class Form.
27
 *
28
 * @method Field\Text           text($column, $label = '')
29
 * @method Field\Checkbox       checkbox($column, $label = '')
30
 * @method Field\Radio          radio($column, $label = '')
31
 * @method Field\Select         select($column, $label = '')
32
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
33
 * @method Field\Textarea       textarea($column, $label = '')
34
 * @method Field\Hidden         hidden($column, $label = '')
35
 * @method Field\Id             id($column, $label = '')
36
 * @method Field\Ip             ip($column, $label = '')
37
 * @method Field\Url            url($column, $label = '')
38
 * @method Field\Color          color($column, $label = '')
39
 * @method Field\Email          email($column, $label = '')
40
 * @method Field\Mobile         mobile($column, $label = '')
41
 * @method Field\Slider         slider($column, $label = '')
42
 * @method Field\File           file($column, $label = '')
43
 * @method Field\Image          image($column, $label = '')
44
 * @method Field\Date           date($column, $label = '')
45
 * @method Field\Datetime       datetime($column, $label = '')
46
 * @method Field\Time           time($column, $label = '')
47
 * @method Field\Year           year($column, $label = '')
48
 * @method Field\Month          month($column, $label = '')
49
 * @method Field\DateRange      dateRange($start, $end, $label = '')
50
 * @method Field\DateTimeRange  datetimeRange($start, $end, $label = '')
51
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
52
 * @method Field\Number         number($column, $label = '')
53
 * @method Field\Currency       currency($column, $label = '')
54
 * @method Field\HasMany        hasMany($relationName, $label = '', $callback)
55
 * @method Field\SwitchField    switch($column, $label = '')
56
 * @method Field\Display        display($column, $label = '')
57
 * @method Field\Rate           rate($column, $label = '')
58
 * @method Field\Divider        divider($title = '')
59
 * @method Field\Password       password($column, $label = '')
60
 * @method Field\Decimal        decimal($column, $label = '')
61
 * @method Field\Html           html($html, $label = '')
62
 * @method Field\Tags           tags($column, $label = '')
63
 * @method Field\Icon           icon($column, $label = '')
64
 * @method Field\Embeds         embeds($column, $label = '')
65
 * @method Field\MultipleImage  multipleImage($column, $label = '')
66
 * @method Field\MultipleFile   multipleFile($column, $label = '')
67
 * @method Field\Captcha        captcha($column, $label = '')
68
 * @method Field\Listbox        listbox($column, $label = '')
69
 * @method Field\Table          table($column, $label, $builder)
70
 * @method Field\Timezone       timezone($column, $label = '')
71
 * @method Field\KeyValue       keyValue($column, $label = '')
72
 * @method Field\ListField      list($column, $label = '')
73
 */
74
class Form implements Renderable
75
{
76
    use HasHooks;
77
78
    /**
79
     * Remove flag in `has many` form.
80
     */
81
    const REMOVE_FLAG_NAME = '_remove_';
82
83
    /**
84
     * Eloquent model of the form.
85
     *
86
     * @var Model
87
     */
88
    protected $model;
89
90
    /**
91
     * @var \Illuminate\Validation\Validator
92
     */
93
    protected $validator;
94
95
    /**
96
     * @var Builder
97
     */
98
    protected $builder;
99
100
    /**
101
     * Data for save to current model from input.
102
     *
103
     * @var array
104
     */
105
    protected $updates = [];
106
107
    /**
108
     * Data for save to model's relations from input.
109
     *
110
     * @var array
111
     */
112
    protected $relations = [];
113
114
    /**
115
     * Input data.
116
     *
117
     * @var array
118
     */
119
    protected $inputs = [];
120
121
    /**
122
     * Available fields.
123
     *
124
     * @var array
125
     */
126
    public static $availableFields = [];
127
128
    /**
129
     * Form field alias.
130
     *
131
     * @var array
132
     */
133
    public static $fieldAlias = [];
134
135
    /**
136
     * Ignored saving fields.
137
     *
138
     * @var array
139
     */
140
    protected $ignored = [];
141
142
    /**
143
     * Collected field assets.
144
     *
145
     * @var array
146
     */
147
    protected static $collectedAssets = [];
148
149
    /**
150
     * @var Form\Tab
151
     */
152
    protected $tab = null;
153
154
    /**
155
     * Field rows in form.
156
     *
157
     * @var array
158
     */
159
    public $rows = [];
160
161
    /**
162
     * @var bool
163
     */
164
    protected $isSoftDeletes = false;
165
166
    /**
167
     * Initialization closure array.
168
     *
169
     * @var []Closure
170
     */
171
    protected static $initCallbacks;
172
173
    /**
174
     * Create a new form instance.
175
     *
176
     * @param $model
177
     * @param \Closure $callback
178
     */
179
    public function __construct($model, Closure $callback = null)
180
    {
181
        $this->model = $model;
182
183
        $this->builder = new Builder($this);
184
185
        if ($callback instanceof Closure) {
186
            $callback($this);
187
        }
188
189
        $this->isSoftDeletes = in_array(SoftDeletes::class, class_uses_deep($this->model));
190
191
        $this->callInitCallbacks();
192
    }
193
194
    /**
195
     * Initialize with user pre-defined default disables, etc.
196
     *
197
     * @param Closure $callback
198
     */
199
    public static function init(Closure $callback = null)
200
    {
201
        static::$initCallbacks[] = $callback;
202
    }
203
204
    /**
205
     * Call the initialization closure array in sequence.
206
     */
207
    protected function callInitCallbacks()
208
    {
209
        if (empty(static::$initCallbacks)) {
210
            return;
211
        }
212
213
        foreach (static::$initCallbacks as $callback) {
214
            call_user_func($callback, $this);
215
        }
216
    }
217
218
    /**
219
     * @param Field $field
220
     *
221
     * @return $this
222
     */
223
    public function pushField(Field $field)
224
    {
225
        $field->setForm($this);
226
227
        $this->builder->fields()->push($field);
228
229
        return $this;
230
    }
231
232
    /**
233
     * @return Model
234
     */
235
    public function model()
236
    {
237
        return $this->model;
238
    }
239
240
    /**
241
     * @return Builder
242
     */
243
    public function builder()
244
    {
245
        return $this->builder;
246
    }
247
248
    /**
249
     * Generate a edit form.
250
     *
251
     * @param $id
252
     *
253
     * @return $this
254
     */
255
    public function edit($id)
256
    {
257
        $this->builder->setMode(Builder::MODE_EDIT);
258
        $this->builder->setResourceId($id);
259
260
        $this->setFieldValue($id);
261
262
        return $this;
263
    }
264
265
    /**
266
     * Use tab to split form.
267
     *
268
     * @param string  $title
269
     * @param Closure $content
270
     *
271
     * @return $this
272
     */
273
    public function tab($title, Closure $content, $active = false)
274
    {
275
        $this->getTab()->append($title, $content, $active);
276
277
        return $this;
278
    }
279
280
    /**
281
     * Get Tab instance.
282
     *
283
     * @return Tab
284
     */
285
    public function getTab()
286
    {
287
        if (is_null($this->tab)) {
288
            $this->tab = new Tab($this);
289
        }
290
291
        return $this->tab;
292
    }
293
294
    /**
295
     * Destroy data entity and remove files.
296
     *
297
     * @param $id
298
     *
299
     * @return mixed
300
     */
301
    public function destroy($id)
302
    {
303
        try {
304
            if (($ret = $this->callDeleting()) instanceof Response) {
305
                return $ret;
306
            }
307
308
            collect(explode(',', $id))->filter()->each(function ($id) {
309
                $builder = $this->model()->newQuery();
310
311
                if ($this->isSoftDeletes) {
312
                    $builder = $builder->withTrashed();
313
                }
314
315
                $model = $builder->with($this->getRelations())->findOrFail($id);
316
317
                if ($this->isSoftDeletes && $model->trashed()) {
318
                    $this->deleteFiles($model, true);
319
                    $model->forceDelete();
320
321
                    return;
322
                }
323
324
                $this->deleteFiles($model);
325
                $model->delete();
326
            });
327
328
            if (($ret = $this->callDeleted()) instanceof Response) {
329
                return $ret;
330
            }
331
332
            $response = [
333
                'status'  => true,
334
                'message' => trans('admin.delete_succeeded'),
335
            ];
336
        } catch (\Exception $exception) {
337
            $response = [
338
                'status'  => false,
339
                'message' => $exception->getMessage() ?: trans('admin.delete_failed'),
340
            ];
341
        }
342
343
        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...
344
    }
345
346
    /**
347
     * Remove files in record.
348
     *
349
     * @param Model $model
350
     * @param bool  $forceDelete
351
     */
352
    protected function deleteFiles(Model $model, $forceDelete = false)
353
    {
354
        // If it's a soft delete, the files in the data will not be deleted.
355
        if (!$forceDelete && $this->isSoftDeletes) {
356
            return;
357
        }
358
359
        $data = $model->toArray();
360
361
        $this->builder->fields()->filter(function ($field) {
362
            return $field instanceof Field\File;
363
        })->each(function (Field\File $file) use ($data) {
364
            $file->setOriginal($data);
365
366
            $file->destroy();
367
        });
368
    }
369
370
    /**
371
     * Store a new record.
372
     *
373
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
374
     */
375
    public function store()
376
    {
377
        $data = \request()->all();
378
379
        // Handle validation errors.
380
        if ($validationMessages = $this->validationMessages($data)) {
381
            return back()->withInput()->withErrors($validationMessages);
382
        }
383
384
        if (($response = $this->prepare($data)) instanceof Response) {
385
            return $response;
386
        }
387
388 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...
389
            $inserts = $this->prepareInsert($this->updates);
390
391
            foreach ($inserts as $column => $value) {
392
                $this->model->setAttribute($column, $value);
393
            }
394
395
            $this->model->save();
396
397
            $this->updateRelation($this->relations);
398
        });
399
400
        if (($response = $this->callSaved()) instanceof Response) {
401
            return $response;
402
        }
403
404
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
405
            return $response;
406
        }
407
408
        return $this->redirectAfterStore();
409
    }
410
411
    /**
412
     * Get ajax response.
413
     *
414
     * @param string $message
415
     *
416
     * @return bool|\Illuminate\Http\JsonResponse
417
     */
418
    protected function ajaxResponse($message)
419
    {
420
        $request = Request::capture();
421
422
        // ajax but not pjax
423
        if ($request->ajax() && !$request->pjax()) {
424
            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...
425
                'status'  => true,
426
                'message' => $message,
427
            ]);
428
        }
429
430
        return false;
431
    }
432
433
    /**
434
     * Prepare input data for insert or update.
435
     *
436
     * @param array $data
437
     *
438
     * @return mixed
439
     */
440
    protected function prepare($data = [])
441
    {
442
        if (($response = $this->callSubmitted()) instanceof Response) {
443
            return $response;
444
        }
445
446
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
447
448
        if (($response = $this->callSaving()) instanceof Response) {
449
            return $response;
450
        }
451
452
        $this->relations = $this->getRelationInputs($this->inputs);
453
454
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
455
    }
456
457
    /**
458
     * Remove ignored fields from input.
459
     *
460
     * @param array $input
461
     *
462
     * @return array
463
     */
464
    protected function removeIgnoredFields($input)
465
    {
466
        Arr::forget($input, $this->ignored);
467
468
        return $input;
469
    }
470
471
    /**
472
     * Get inputs for relations.
473
     *
474
     * @param array $inputs
475
     *
476
     * @return array
477
     */
478
    protected function getRelationInputs($inputs = [])
479
    {
480
        $relations = [];
481
482
        foreach ($inputs as $column => $value) {
483
            if (!method_exists($this->model, $column)) {
484
                continue;
485
            }
486
487
            $relation = call_user_func([$this->model, $column]);
488
489
            if ($relation instanceof Relations\Relation) {
490
                $relations[$column] = $value;
491
            }
492
        }
493
494
        return $relations;
495
    }
496
497
    /**
498
     * Handle update.
499
     *
500
     * @param int  $id
501
     * @param null $data
502
     *
503
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
504
     */
505
    public function update($id, $data = null)
506
    {
507
        $data = ($data) ?: request()->all();
508
509
        $isEditable = $this->isEditable($data);
510
511
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
512
            return $data;
513
        }
514
515
        /* @var Model $this->model */
516
        $builder = $this->model();
517
518
        if ($this->isSoftDeletes) {
519
            $builder = $builder->withTrashed();
520
        }
521
522
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
523
524
        $this->setFieldOriginalValue();
525
526
        // Handle validation errors.
527
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 511 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...
528
            if (!$isEditable) {
529
                return back()->withInput()->withErrors($validationMessages);
530
            }
531
532
            return response()->json(['errors' => Arr::dot($validationMessages->getMessages())], 422);
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...
533
        }
534
535
        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 511 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...
536
            return $response;
537
        }
538
539 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...
540
            $updates = $this->prepareUpdate($this->updates);
541
542
            foreach ($updates as $column => $value) {
543
                /* @var Model $this->model */
544
                $this->model->setAttribute($column, $value);
545
            }
546
547
            $this->model->save();
548
549
            $this->updateRelation($this->relations);
550
        });
551
552
        if (($result = $this->callSaved()) instanceof Response) {
553
            return $result;
554
        }
555
556
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
557
            return $response;
558
        }
559
560
        return $this->redirectAfterUpdate($id);
561
    }
562
563
    /**
564
     * Get RedirectResponse after store.
565
     *
566
     * @return \Illuminate\Http\RedirectResponse
567
     */
568
    protected function redirectAfterStore()
569
    {
570
        $resourcesPath = $this->resource(0);
571
572
        $key = $this->model->getKey();
573
574
        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 574 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
575
    }
576
577
    /**
578
     * Get RedirectResponse after update.
579
     *
580
     * @param mixed $key
581
     *
582
     * @return \Illuminate\Http\RedirectResponse
583
     */
584
    protected function redirectAfterUpdate($key)
585
    {
586
        $resourcesPath = $this->resource(-1);
587
588
        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 588 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
589
    }
590
591
    /**
592
     * Get RedirectResponse after data saving.
593
     *
594
     * @param string $resourcesPath
595
     * @param string $key
596
     *
597
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
598
     */
599
    protected function redirectAfterSaving($resourcesPath, $key)
600
    {
601
        if (request('after-save') == 1) {
602
            // continue editing
603
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
604
        } elseif (request('after-save') == 2) {
605
            // continue creating
606
            $url = rtrim($resourcesPath, '/').'/create';
607
        } elseif (request('after-save') == 3) {
608
            // view resource
609
            $url = rtrim($resourcesPath, '/')."/{$key}";
610
        } else {
611
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
612
        }
613
614
        admin_toastr(trans('admin.save_succeeded'));
615
616
        return redirect($url);
617
    }
618
619
    /**
620
     * Check if request is from editable.
621
     *
622
     * @param array $input
623
     *
624
     * @return bool
625
     */
626
    protected function isEditable(array $input = [])
627
    {
628
        return array_key_exists('_editable', $input);
629
    }
630
631
    /**
632
     * Handle updates for single column.
633
     *
634
     * @param int   $id
635
     * @param array $data
636
     *
637
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
638
     */
639
    protected function handleColumnUpdates($id, $data)
640
    {
641
        $data = $this->handleEditable($data);
642
643
        $data = $this->handleFileDelete($data);
644
645
        $data = $this->handleFileSort($data);
646
647
        if ($this->handleOrderable($id, $data)) {
648
            return response([
649
                'status'  => true,
650
                'message' => trans('admin.update_succeeded'),
651
            ]);
652
        }
653
654
        return $data;
655
    }
656
657
    /**
658
     * Handle editable update.
659
     *
660
     * @param array $input
661
     *
662
     * @return array
663
     */
664
    protected function handleEditable(array $input = [])
665
    {
666
        if (array_key_exists('_editable', $input)) {
667
            $name = $input['name'];
668
            $value = $input['value'];
669
670
            Arr::forget($input, ['pk', 'value', 'name']);
671
            Arr::set($input, $name, $value);
672
        }
673
674
        return $input;
675
    }
676
677
    /**
678
     * @param array $input
679
     *
680
     * @return array
681
     */
682
    protected function handleFileDelete(array $input = [])
683
    {
684
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
685
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
686
            unset($input['key']);
687
        }
688
689
        request()->replace($input);
690
691
        return $input;
692
    }
693
694
    /**
695
     * @param array $input
696
     *
697
     * @return array
698
     */
699
    protected function handleFileSort(array $input = [])
700
    {
701
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
702
            return $input;
703
        }
704
705
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
706
707
        if (empty($sorts)) {
708
            return $input;
709
        }
710
711
        foreach ($sorts as $column => $order) {
712
            $input[$column] = $order;
713
        }
714
715
        request()->replace($input);
716
717
        return $input;
718
    }
719
720
    /**
721
     * Handle orderable update.
722
     *
723
     * @param int   $id
724
     * @param array $input
725
     *
726
     * @return bool
727
     */
728
    protected function handleOrderable($id, array $input = [])
729
    {
730
        if (array_key_exists('_orderable', $input)) {
731
            $model = $this->model->find($id);
732
733
            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...
734
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
735
736
                return true;
737
            }
738
        }
739
740
        return false;
741
    }
742
743
    /**
744
     * Update relation data.
745
     *
746
     * @param array $relationsData
747
     *
748
     * @return void
749
     */
750
    protected function updateRelation($relationsData)
751
    {
752
        foreach ($relationsData as $name => $values) {
753
            if (!method_exists($this->model, $name)) {
754
                continue;
755
            }
756
757
            $relation = $this->model->$name();
758
759
            $oneToOneRelation = $relation instanceof Relations\HasOne
760
                || $relation instanceof Relations\MorphOne
761
                || $relation instanceof Relations\BelongsTo;
762
763
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
764
765
            if (empty($prepared)) {
766
                continue;
767
            }
768
769
            switch (true) {
770
                case $relation instanceof Relations\BelongsToMany:
771
                case $relation instanceof Relations\MorphToMany:
772
                    if (isset($prepared[$name])) {
773
                        $relation->sync($prepared[$name]);
774
                    }
775
                    break;
776
                case $relation instanceof Relations\HasOne:
777
778
                    $related = $this->model->$name;
779
780
                    // if related is empty
781
                    if (is_null($related)) {
782
                        $related = $relation->getRelated();
783
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
784
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
785
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
786
                    }
787
788
                    foreach ($prepared[$name] as $column => $value) {
789
                        $related->setAttribute($column, $value);
790
                    }
791
792
                    $related->save();
793
                    break;
794
                case $relation instanceof Relations\BelongsTo:
795
                case $relation instanceof Relations\MorphTo:
796
797
                    $parent = $this->model->$name;
798
799
                    // if related is empty
800
                    if (is_null($parent)) {
801
                        $parent = $relation->getRelated();
802
                    }
803
804
                    foreach ($prepared[$name] as $column => $value) {
805
                        $parent->setAttribute($column, $value);
806
                    }
807
808
                    $parent->save();
809
810
                    // When in creating, associate two models
811
                    $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName';
812
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
813
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
814
815
                        $this->model->save();
816
                    }
817
818
                    break;
819
                case $relation instanceof Relations\MorphOne:
820
                    $related = $this->model->$name;
821
                    if (is_null($related)) {
822
                        $related = $relation->make();
823
                    }
824
                    foreach ($prepared[$name] as $column => $value) {
825
                        $related->setAttribute($column, $value);
826
                    }
827
                    $related->save();
828
                    break;
829
                case $relation instanceof Relations\HasMany:
830
                case $relation instanceof Relations\MorphMany:
831
832
                    foreach ($prepared[$name] as $related) {
833
                        /** @var Relations\Relation $relation */
834
                        $relation = $this->model()->$name();
835
836
                        $keyName = $relation->getRelated()->getKeyName();
837
838
                        $instance = $relation->findOrNew(Arr::get($related, $keyName));
839
840
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
841
                            $instance->delete();
842
843
                            continue;
844
                        }
845
846
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
847
848
                        $instance->fill($related);
849
850
                        $instance->save();
851
                    }
852
853
                    break;
854
            }
855
        }
856
    }
857
858
    /**
859
     * Prepare input data for update.
860
     *
861
     * @param array $updates
862
     * @param bool  $oneToOneRelation If column is one-to-one relation.
863
     *
864
     * @return array
865
     */
866
    protected function prepareUpdate(array $updates, $oneToOneRelation = false)
867
    {
868
        $prepared = [];
869
870
        /** @var Field $field */
871
        foreach ($this->builder->fields() as $field) {
872
            $columns = $field->column();
873
874
            // If column not in input array data, then continue.
875
            if (!Arr::has($updates, $columns)) {
876
                continue;
877
            }
878
879
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
880
                continue;
881
            }
882
883
            $value = $this->getDataByColumn($updates, $columns);
884
885
            $value = $field->prepare($value);
886
887 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...
888
                foreach ($columns as $name => $column) {
889
                    Arr::set($prepared, $column, $value[$name]);
890
                }
891
            } elseif (is_string($columns)) {
892
                Arr::set($prepared, $columns, $value);
893
            }
894
        }
895
896
        return $prepared;
897
    }
898
899
    /**
900
     * @param string|array $columns
901
     * @param bool         $containsDot
902
     *
903
     * @return bool
904
     */
905
    protected function isInvalidColumn($columns, $containsDot = false)
906
    {
907
        foreach ((array) $columns as $column) {
908
            if ((!$containsDot && Str::contains($column, '.')) ||
909
                ($containsDot && !Str::contains($column, '.'))) {
910
                return true;
911
            }
912
        }
913
914
        return false;
915
    }
916
917
    /**
918
     * Prepare input data for insert.
919
     *
920
     * @param $inserts
921
     *
922
     * @return array
923
     */
924
    protected function prepareInsert($inserts)
925
    {
926
        if ($this->isHasOneRelation($inserts)) {
927
            $inserts = Arr::dot($inserts);
928
        }
929
930
        foreach ($inserts as $column => $value) {
931
            if (is_null($field = $this->getFieldByColumn($column))) {
932
                unset($inserts[$column]);
933
                continue;
934
            }
935
936
            $inserts[$column] = $field->prepare($value);
937
        }
938
939
        $prepared = [];
940
941
        foreach ($inserts as $key => $value) {
942
            Arr::set($prepared, $key, $value);
943
        }
944
945
        return $prepared;
946
    }
947
948
    /**
949
     * Is input data is has-one relation.
950
     *
951
     * @param array $inserts
952
     *
953
     * @return bool
954
     */
955
    protected function isHasOneRelation($inserts)
956
    {
957
        $first = current($inserts);
958
959
        if (!is_array($first)) {
960
            return false;
961
        }
962
963
        if (is_array(current($first))) {
964
            return false;
965
        }
966
967
        return Arr::isAssoc($first);
968
    }
969
970
    /**
971
     * Ignore fields to save.
972
     *
973
     * @param string|array $fields
974
     *
975
     * @return $this
976
     */
977
    public function ignore($fields)
978
    {
979
        $this->ignored = array_merge($this->ignored, (array) $fields);
980
981
        return $this;
982
    }
983
984
    /**
985
     * @param array        $data
986
     * @param string|array $columns
987
     *
988
     * @return array|mixed
989
     */
990 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...
991
    {
992
        if (is_string($columns)) {
993
            return Arr::get($data, $columns);
994
        }
995
996
        if (is_array($columns)) {
997
            $value = [];
998
            foreach ($columns as $name => $column) {
999
                if (!Arr::has($data, $column)) {
1000
                    continue;
1001
                }
1002
                $value[$name] = Arr::get($data, $column);
1003
            }
1004
1005
            return $value;
1006
        }
1007
    }
1008
1009
    /**
1010
     * Find field object by column.
1011
     *
1012
     * @param $column
1013
     *
1014
     * @return mixed
1015
     */
1016
    protected function getFieldByColumn($column)
1017
    {
1018
        return $this->builder->fields()->first(
1019
            function (Field $field) use ($column) {
1020
                if (is_array($field->column())) {
1021
                    return in_array($column, $field->column());
1022
                }
1023
1024
                return $field->column() == $column;
1025
            }
1026
        );
1027
    }
1028
1029
    /**
1030
     * Set original data for each field.
1031
     *
1032
     * @return void
1033
     */
1034
    protected function setFieldOriginalValue()
1035
    {
1036
//        static::doNotSnakeAttributes($this->model);
1037
1038
        $values = $this->model->toArray();
1039
1040
        $this->builder->fields()->each(function (Field $field) use ($values) {
1041
            $field->setOriginal($values);
1042
        });
1043
    }
1044
1045
    /**
1046
     * Set all fields value in form.
1047
     *
1048
     * @param $id
1049
     *
1050
     * @return void
1051
     */
1052
    protected function setFieldValue($id)
1053
    {
1054
        $relations = $this->getRelations();
1055
1056
        $builder = $this->model();
1057
1058
        if ($this->isSoftDeletes) {
1059
            $builder = $builder->withTrashed();
1060
        }
1061
1062
        $this->model = $builder->with($relations)->findOrFail($id);
1063
1064
        $this->callEditing();
1065
1066
//        static::doNotSnakeAttributes($this->model);
1067
1068
        $data = $this->model->toArray();
1069
1070
        $this->builder->fields()->each(function (Field $field) use ($data) {
1071
            if (!in_array($field->column(), $this->ignored)) {
1072
                $field->fill($data);
1073
            }
1074
        });
1075
    }
1076
1077
    /**
1078
     * Add a fieldset to form.
1079
     *
1080
     * @param string $title
1081
     * @param Closure $setCallback
1082
     *
1083
     * @return Field\Fieldset
1084
     */
1085 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...
1086
    {
1087
        $fieldset = new Field\Fieldset();
1088
1089
        $this->html($fieldset->start($title))->plain();
1090
1091
        $setCallback($this);
1092
1093
        $this->html($fieldset->end())->plain();
1094
1095
        return $fieldset;
1096
    }
1097
1098
    /**
1099
     * Don't snake case attributes.
1100
     *
1101
     * @param Model $model
1102
     *
1103
     * @return void
1104
     */
1105
    protected static function doNotSnakeAttributes(Model $model)
1106
    {
1107
        $class = get_class($model);
1108
1109
        $class::$snakeAttributes = false;
1110
    }
1111
1112
    /**
1113
     * Get validation messages.
1114
     *
1115
     * @param array $input
1116
     *
1117
     * @return MessageBag|bool
1118
     */
1119
    public function validationMessages($input)
1120
    {
1121
        $failedValidators = [];
1122
1123
        /** @var Field $field */
1124 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...
1125
            if (!$validator = $field->getValidator($input)) {
1126
                continue;
1127
            }
1128
1129
            if (($validator instanceof Validator) && !$validator->passes()) {
1130
                $failedValidators[] = $validator;
1131
            }
1132
        }
1133
1134
        $message = $this->mergeValidationMessages($failedValidators);
1135
1136
        return $message->any() ? $message : false;
1137
    }
1138
1139
    /**
1140
     * Merge validation messages from input validators.
1141
     *
1142
     * @param \Illuminate\Validation\Validator[] $validators
1143
     *
1144
     * @return MessageBag
1145
     */
1146
    protected function mergeValidationMessages($validators)
1147
    {
1148
        $messageBag = new MessageBag();
1149
1150
        foreach ($validators as $validator) {
1151
            $messageBag = $messageBag->merge($validator->messages());
1152
        }
1153
1154
        return $messageBag;
1155
    }
1156
1157
    /**
1158
     * Get all relations of model from callable.
1159
     *
1160
     * @return array
1161
     */
1162
    public function getRelations()
1163
    {
1164
        $relations = $columns = [];
1165
1166
        /** @var Field $field */
1167
        foreach ($this->builder->fields() as $field) {
1168
            $columns[] = $field->column();
1169
        }
1170
1171
        foreach (Arr::flatten($columns) as $column) {
1172
            if (Str::contains($column, '.')) {
1173
                list($relation) = explode('.', $column);
1174
1175
                if (method_exists($this->model, $relation) &&
1176
                    $this->model->$relation() instanceof Relations\Relation
1177
                ) {
1178
                    $relations[] = $relation;
1179
                }
1180
            } elseif (method_exists($this->model, $column) &&
1181
                !method_exists(Model::class, $column)
1182
            ) {
1183
                $relations[] = $column;
1184
            }
1185
        }
1186
1187
        return array_unique($relations);
1188
    }
1189
1190
    /**
1191
     * Set action for form.
1192
     *
1193
     * @param string $action
1194
     *
1195
     * @return $this
1196
     */
1197
    public function setAction($action)
1198
    {
1199
        $this->builder()->setAction($action);
1200
1201
        return $this;
1202
    }
1203
1204
    /**
1205
     * Set field and label width in current form.
1206
     *
1207
     * @param int $fieldWidth
1208
     * @param int $labelWidth
1209
     *
1210
     * @return $this
1211
     */
1212
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
1213
    {
1214
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1215
            /* @var Field $field  */
1216
            $field->setWidth($fieldWidth, $labelWidth);
1217
        });
1218
1219
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1220
1221
        return $this;
1222
    }
1223
1224
    /**
1225
     * Set view for form.
1226
     *
1227
     * @param string $view
1228
     *
1229
     * @return $this
1230
     */
1231
    public function setView($view)
1232
    {
1233
        $this->builder()->setView($view);
1234
1235
        return $this;
1236
    }
1237
1238
    /**
1239
     * Set title for form.
1240
     *
1241
     * @param string $title
1242
     *
1243
     * @return $this
1244
     */
1245
    public function setTitle($title = '')
1246
    {
1247
        $this->builder()->setTitle($title);
1248
1249
        return $this;
1250
    }
1251
1252
    /**
1253
     * Add a row in form.
1254
     *
1255
     * @param Closure $callback
1256
     *
1257
     * @return $this
1258
     */
1259
    public function row(Closure $callback)
1260
    {
1261
        $this->rows[] = new Row($callback, $this);
1262
1263
        return $this;
1264
    }
1265
1266
    /**
1267
     * Tools setting for form.
1268
     *
1269
     * @param Closure $callback
1270
     */
1271
    public function tools(Closure $callback)
1272
    {
1273
        $callback->call($this, $this->builder->getTools());
1274
    }
1275
1276
    /**
1277
     * @param Closure|null $callback
1278
     *
1279
     * @return Form\Tools
1280
     */
1281
    public function header(Closure $callback = null)
1282
    {
1283
        if (func_num_args() == 0) {
1284
            return $this->builder->getTools();
1285
        }
1286
1287
        $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...
1288
    }
1289
1290
    /**
1291
     * Disable form submit.
1292
     *
1293
     * @param bool $disable
1294
     *
1295
     * @return $this
1296
     *
1297
     * @deprecated
1298
     */
1299
    public function disableSubmit(bool $disable = true)
1300
    {
1301
        $this->builder()->getFooter()->disableSubmit($disable);
1302
1303
        return $this;
1304
    }
1305
1306
    /**
1307
     * Disable form reset.
1308
     *
1309
     * @param bool $disable
1310
     *
1311
     * @return $this
1312
     *
1313
     * @deprecated
1314
     */
1315
    public function disableReset(bool $disable = true)
1316
    {
1317
        $this->builder()->getFooter()->disableReset($disable);
1318
1319
        return $this;
1320
    }
1321
1322
    /**
1323
     * Disable View Checkbox on footer.
1324
     *
1325
     * @param bool $disable
1326
     *
1327
     * @return $this
1328
     */
1329
    public function disableViewCheck(bool $disable = true)
1330
    {
1331
        $this->builder()->getFooter()->disableViewCheck($disable);
1332
1333
        return $this;
1334
    }
1335
1336
    /**
1337
     * Disable Editing Checkbox on footer.
1338
     *
1339
     * @param bool $disable
1340
     *
1341
     * @return $this
1342
     */
1343
    public function disableEditingCheck(bool $disable = true)
1344
    {
1345
        $this->builder()->getFooter()->disableEditingCheck($disable);
1346
1347
        return $this;
1348
    }
1349
1350
    /**
1351
     * Disable Creating Checkbox on footer.
1352
     *
1353
     * @param bool $disable
1354
     *
1355
     * @return $this
1356
     */
1357
    public function disableCreatingCheck(bool $disable = true)
1358
    {
1359
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1360
1361
        return $this;
1362
    }
1363
1364
    /**
1365
     * Footer setting for form.
1366
     *
1367
     * @param Closure $callback
1368
     */
1369
    public function footer(Closure $callback = null)
1370
    {
1371
        if (func_num_args() == 0) {
1372
            return $this->builder()->getFooter();
1373
        }
1374
1375
        call_user_func($callback, $this->builder()->getFooter());
1376
    }
1377
1378
    /**
1379
     * Get current resource route url.
1380
     *
1381
     * @param int $slice
1382
     *
1383
     * @return string
1384
     */
1385
    public function resource($slice = -2)
1386
    {
1387
        $segments = explode('/', trim(app('request')->getUri(), '/'));
1388
1389
        if ($slice != 0) {
1390
            $segments = array_slice($segments, 0, $slice);
1391
        }
1392
1393
        return implode('/', $segments);
1394
    }
1395
1396
    /**
1397
     * Render the form contents.
1398
     *
1399
     * @return string
1400
     */
1401
    public function render()
1402
    {
1403
        try {
1404
            return $this->builder->render();
1405
        } catch (\Exception $e) {
1406
            return Handler::renderException($e);
1407
        }
1408
    }
1409
1410
    /**
1411
     * Get or set input data.
1412
     *
1413
     * @param string $key
1414
     * @param null   $value
1415
     *
1416
     * @return array|mixed
1417
     */
1418
    public function input($key, $value = null)
1419
    {
1420
        if (is_null($value)) {
1421
            return Arr::get($this->inputs, $key);
1422
        }
1423
1424
        return Arr::set($this->inputs, $key, $value);
1425
    }
1426
1427
    /**
1428
     * Register builtin fields.
1429
     *
1430
     * @return void
1431
     */
1432
    public static function registerBuiltinFields()
1433
    {
1434
        $map = [
1435
            'button'         => Field\Button::class,
1436
            'checkbox'       => Field\Checkbox::class,
1437
            'color'          => Field\Color::class,
1438
            'currency'       => Field\Currency::class,
1439
            'date'           => Field\Date::class,
1440
            'dateRange'      => Field\DateRange::class,
1441
            'datetime'       => Field\Datetime::class,
1442
            'dateTimeRange'  => Field\DatetimeRange::class,
1443
            'datetimeRange'  => Field\DatetimeRange::class,
1444
            'decimal'        => Field\Decimal::class,
1445
            'display'        => Field\Display::class,
1446
            'divider'        => Field\Divider::class,
1447
            'embeds'         => Field\Embeds::class,
1448
            'email'          => Field\Email::class,
1449
            'file'           => Field\File::class,
1450
            'hasMany'        => Field\HasMany::class,
1451
            'hidden'         => Field\Hidden::class,
1452
            'id'             => Field\Id::class,
1453
            'image'          => Field\Image::class,
1454
            'ip'             => Field\Ip::class,
1455
            'mobile'         => Field\Mobile::class,
1456
            'month'          => Field\Month::class,
1457
            'multipleSelect' => Field\MultipleSelect::class,
1458
            'number'         => Field\Number::class,
1459
            'password'       => Field\Password::class,
1460
            'radio'          => Field\Radio::class,
1461
            'rate'           => Field\Rate::class,
1462
            'select'         => Field\Select::class,
1463
            'slider'         => Field\Slider::class,
1464
            'switch'         => Field\SwitchField::class,
1465
            'text'           => Field\Text::class,
1466
            'textarea'       => Field\Textarea::class,
1467
            'time'           => Field\Time::class,
1468
            'timeRange'      => Field\TimeRange::class,
1469
            'url'            => Field\Url::class,
1470
            'year'           => Field\Year::class,
1471
            'html'           => Field\Html::class,
1472
            'tags'           => Field\Tags::class,
1473
            'icon'           => Field\Icon::class,
1474
            'multipleFile'   => Field\MultipleFile::class,
1475
            'multipleImage'  => Field\MultipleImage::class,
1476
            'captcha'        => Field\Captcha::class,
1477
            'listbox'        => Field\Listbox::class,
1478
            'table'          => Field\Table::class,
1479
            'timezone'       => Field\Timezone::class,
1480
            'keyValue'       => Field\KeyValue::class,
1481
            'list'           => Field\ListField::class,
1482
        ];
1483
1484
        foreach ($map as $abstract => $class) {
1485
            static::extend($abstract, $class);
1486
        }
1487
    }
1488
1489
    /**
1490
     * Register custom field.
1491
     *
1492
     * @param string $abstract
1493
     * @param string $class
1494
     *
1495
     * @return void
1496
     */
1497
    public static function extend($abstract, $class)
1498
    {
1499
        static::$availableFields[$abstract] = $class;
1500
    }
1501
1502
    /**
1503
     * Set form field alias.
1504
     *
1505
     * @param string $field
1506
     * @param string $alias
1507
     *
1508
     * @return void
1509
     */
1510
    public static function alias($field, $alias)
1511
    {
1512
        static::$fieldAlias[$alias] = $field;
1513
    }
1514
1515
    /**
1516
     * Remove registered field.
1517
     *
1518
     * @param array|string $abstract
1519
     */
1520
    public static function forget($abstract)
1521
    {
1522
        Arr::forget(static::$availableFields, $abstract);
1523
    }
1524
1525
    /**
1526
     * Find field class.
1527
     *
1528
     * @param string $method
1529
     *
1530
     * @return bool|mixed
1531
     */
1532
    public static function findFieldClass($method)
1533
    {
1534
        // If alias exists.
1535
        if (isset(static::$fieldAlias[$method])) {
1536
            $method = static::$fieldAlias[$method];
1537
        }
1538
1539
        $class = Arr::get(static::$availableFields, $method);
1540
1541
        if (class_exists($class)) {
1542
            return $class;
1543
        }
1544
1545
        return false;
1546
    }
1547
1548
    /**
1549
     * Collect assets required by registered field.
1550
     *
1551
     * @return array
1552
     */
1553
    public static function collectFieldAssets()
1554
    {
1555
        if (!empty(static::$collectedAssets)) {
1556
            return static::$collectedAssets;
1557
        }
1558
1559
        $css = collect();
1560
        $js = collect();
1561
1562
        foreach (static::$availableFields as $field) {
1563
            if (!method_exists($field, 'getAssets')) {
1564
                continue;
1565
            }
1566
1567
            $assets = call_user_func([$field, 'getAssets']);
1568
1569
            $css->push(Arr::get($assets, 'css'));
1570
            $js->push(Arr::get($assets, 'js'));
1571
        }
1572
1573
        return static::$collectedAssets = [
1574
            'css' => $css->flatten()->unique()->filter()->toArray(),
1575
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1576
        ];
1577
    }
1578
1579
    /**
1580
     * Getter.
1581
     *
1582
     * @param string $name
1583
     *
1584
     * @return array|mixed
1585
     */
1586
    public function __get($name)
1587
    {
1588
        return $this->input($name);
1589
    }
1590
1591
    /**
1592
     * Setter.
1593
     *
1594
     * @param string $name
1595
     * @param mixed  $value
1596
     *
1597
     * @return array
1598
     */
1599
    public function __set($name, $value)
1600
    {
1601
        return Arr::set($this->inputs, $name, $value);
1602
    }
1603
1604
    /**
1605
     * Generate a Field object and add to form builder if Field exists.
1606
     *
1607
     * @param string $method
1608
     * @param array  $arguments
1609
     *
1610
     * @return Field
1611
     */
1612
    public function __call($method, $arguments)
1613
    {
1614
        if ($className = static::findFieldClass($method)) {
1615
            $column = Arr::get($arguments, 0, ''); //[0];
1616
1617
            $element = new $className($column, array_slice($arguments, 1));
1618
1619
            $this->pushField($element);
1620
1621
            return $element;
1622
        }
1623
1624
        admin_error('Error', "Field type [$method] does not exist.");
1625
1626
        return new Field\Nullable();
1627
    }
1628
}
1629